文章目录
- 背景
- 微信支付的模式
- 一、前期准备
- 1.注册服务商
- 2.服务商入驻
- 页面入驻
- 申请证书
- 重要参数说明
- 二、子商户支付流程
- 三、实现方案
- 1.引入依赖
- 2.支付配置
- 3.相关配置类
- 4.业务实现类
- 5.测试类
- 6.相关测试
- 创建支付订单
- 相应结果
- 查询订单
- 相应结果
- 微信异步回调
- 总结
背景
小程序盛行时代,一般的企业中都都会包含多个小程序,而大部分的小程序通常都需要实现支付功能,本文将针对服务商模式进行微信支付进行详细讲解。
微信支付的模式
一般企业选用是服务商或者为渠道商模式,但是成为渠道商需要相关流量支撑才能申请,本文以服务商模式进行讲解。
咨询微信支付客服关于服务商的有些问题:
1.服务商和特约只能有一个,需要注销特约商户后申请成为服务商
2.服务商不能单独收款,只能给特约商户进行收款
3.服务商可以设置分账抽成微信会自动完成分账
4.服务商下特约商户收款会直接将钱打到特约商户下
5.直连或者特约可以成为注册另一个服务商下的特约(根据风险适时调整)
一、前期准备
1.注册服务商
服务商申请需要通过已做过公司认证的公众号,登录公司的微信服务号,在【微信支付】>【服务商申请】,直接跟着官方引导一步步操作即可,具体申请流程如下:
说明:所以企业需要申请公共号,才能申请注册服务商。
2.服务商入驻
通过服务商来开发的系统来帮助商户微信支付,首先需要完成商户号在服务商号中的入驻过程。服务商注册成功后,进入微信支付平台,登录服务商,进行商户入驻。一般商户入驻有两种,具体如下:
- 页面入驻
- 调用API方式入驻
页面入驻
入驻可以看作是商户生成商户号的同时与服务商形成绑定关系。具体可以参考微信公众号中按流程指引一步步操作就行。
说明:商户入驻完成后,此商户才能用于微信支付。
申请证书
商户号入驻成功后,需要申请API证书。
说明:按照官方文档申请证书,设置密钥,设置好密钥后一定要在安全的前提下记住,之后只能重置不能查看。
重要参数说明
- appid:服务商Appid
- mchId:服务商的商户id
- mchKey:证书的序列号
- subAppId:子商户小程序Appid
- subOpenId:子商户小程序用户的openId
- subMchId:子商户的商户id
二、子商户支付流程
以下是子商户APP中调用支付的交互时序图
流程说明:
- 用户在商户APP中选择商品,选择微信支付,提交订单,如图中步骤1-3所示。
- 调用服务商提供的下单接口,服务商后台收到下单请求,会返回签好名的订单数据,用于商户APP里面调起微信支付,如图中步骤3-5所示。
- 用户确认支付,输入密码,支付完成,如图中步骤6-8所示。
- 支付完成后,微信返回商户APP,回调APP实现的回调函数,此时需要根据单号调用服务商提供的查询结果,查询后台实际支付结果,再作用户页面展示和发货操作。如图步骤9-13.
三、实现方案
服务商模式的微信支付的具体实现方案,本文采用的是Spring Boot集成weixin-java-pay来实现微信支付。
1.引入依赖
<dependency><groupId>com.github.binarywang</groupId><artifactId>weixin-java-pay</artifactId><version>4.1.0</version>
</dependency>
2.支付配置
# 微信支付配置
wx:pay:#服务商idmchId: 12#证书keyPath: apiclient_cert.p12#服务商appidappId: wxe12233#支付回调通知地址notifyUrl: https://localhost:8080/pay/resWxPay#服务商key的密钥mchKey: 123444
3.相关配置类
说明:读取微信支付的配置信息
@Component
@ConfigurationProperties(prefix = "wx.pay")
public class WxPayProperties {/*** 小程序appid*/private String appId;/*** 微信支付商户号*/private String mchId;/*** 微信支付商户密钥*/private String mchKey;/*** 服务商模式下的子商户公众账号ID*/private String subAppId;/*** 服务商模式下的子商户号*/private String subMchId;/*** apiclient_cert.p12文件的绝对路径,或者如果放在项目中,请以classpath:开头指定*/private String keyPath;/*** 支付回调*/private String notifyUrl;//省略get、set方法}
说明:将微信支付的相关参数设置到wxjava中的cofig中。
/*** 微信支付配置类*/
@Configuration
public class CustWxPayConfig
{@Autowiredprivate WxPayProperties properties;@Bean@ConditionalOnMissingBeanpublic WxPayService wxService() {WxPayConfig payConfig = new WxPayConfig();payConfig.setAppId(StringUtils.trimToNull(this.properties.getAppId()));payConfig.setMchId(StringUtils.trimToNull(this.properties.getMchId()));payConfig.setMchKey(StringUtils.trimToNull(this.properties.getMchKey()));payConfig.setSubMchId(StringUtils.trimToNull(this.properties.getSubMchId()));payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath()));payConfig.setNotifyUrl(StringUtils.trimToNull(this.properties.getNotifyUrl()));//设置微信支付参数WxPayService wxPayService = new WxPayServiceImpl();wxPayService.setConfig(payConfig);return wxPayService;}
}
4.业务实现类
@Service
@Transactional(rollbackFor =Exception.class)
public class CustWxPayServiceImpl implements CustWxPayService
{private Logger logger = LoggerFactory.getLogger(CustWxPayService.class);@Autowiredprivate WxPayService wxService; public void createWxPayOrder(WxPayObject payObject){logger.info("Create wx pay order.");//并发控制WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();request.setTradeType(payObject.getTradeType()); int payMoney = payObject.getPayMoney().multiply(new BigDecimal(100)).intValue();// totalFee单位为(分)request.setTotalFee(payMoney);//第三方订单号request.setOutTradeNo(payObject.getOrderNo());//用户OpenID(支付小程序)request.setSubOpenid(payObject.getSubOpenid());//支付小程序APPIDrequest.setSubAppId(payObject.getSubAppId()); request.setNonceStr(getRandomString(32));request.setBody(payObject.getBody()); request.setFeeType(payObject.getFeeType()); HttpServletRequest httpRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();//IP地址request.setSpbillCreateIp(IpUtil.getIpAddr(httpRequest));//配置回调地址request.setNotifyUrl(wxService.getConfig().getNotifyUrl());//收款子商户String subMchId = wxService.getConfig().getSubAppId();request.setSubMchId(subMchId);//DeviceIDrequest.setDeviceInfo(payObject.getDeviceInfo());//支付类型:request.setTradeType(payObject.getTradeType());//创建建订单WxPayMpOrderResult wxPayMpOrderResult =null;try{wxPayMpOrderResult = wxService.createOrder(request);if (wxPayMpOrderResult == null){logger.info("Create Wx Order Success. Result is null.");}else{logger.info("Create Wx Order Success. Result:{}",JSON.toJSONString(wxPayMpOrderResult));}}catch (WxPayException e){logger.error("createWxPayOrder error",e);throw new SysException("create wx order error",e); }}@Overridepublic void resWxPay(String xmlData){logger.info("Wx Pay Response, param[{}]", xmlData);WxPayOrderNotifyResult result =null;try{result = wxService.parseOrderNotifyResult(xmlData);//支付失败if ("FAIL".equalsIgnoreCase(result.getResultCode()) || "FAIL".equalsIgnoreCase(result.getReturnCode())){//记录支付失败错误日志throw new SysException(result.getErrCodeDes()); }//获取订单号String outTradeNo = result.getOutTradeNo();if (StringUtils.contains(outTradeNo, "_")){outTradeNo = outTradeNo.substring(0, outTradeNo.indexOf("_"));}//执行具体业务逻辑//记录流水//更新订单状态 }catch (WxPayException e){throw new SysException("wx pay error",e); }}@Overridepublic WxPayOrderQueryResult queryOrder(String outTradeNo){WxPayOrderQueryResult wxPayOrderQueryResult =null;try{wxPayOrderQueryResult= wxService.queryOrder(null, outTradeNo);}catch (WxPayException e){throw new SysException("query wx order error",e); }return wxPayOrderQueryResult;}public static String getRandomString(int length) {String base = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";Random random = new Random();StringBuffer sb = new StringBuffer();for (int i = 0; i < length; i++) {int number = random.nextInt(base.length());sb.append(base.charAt(number));}return sb.toString();}}
说明:实现微信创建订单、支付回调、查询订单接口。
5.测试类
@RestController
@RequestMapping("/pay/wxpay/")
public class WxPayController
{private Logger logger =LoggerFactory.getLogger(WxPayController.class);@Autowiredprivate CustWxPayService custWxPayService;/*** * @param subAppId 小程序AppId* @param subOpenid 用户在小程序的openid* @param payMoney 支付金额* @param orderNo 第三方订单号* @return*/@PostMapping("/reqWxPay")public Result reqWxPay(@RequestBody WxPayObject wxPayObject){//支付参数验证logger.info("Req Wx Pay,param:{}",JSON.toJSONString(wxPayObject));Result result =new Result();try{//创建订单custWxPayService.createWxPayOrder(wxPayObject);}catch(Exception e){logger.error("Req Wx Pay Error.", e);return Result.error("Req Wx Pay error"+e);}return Result.success(result);}/*** 异步回调* @param xmlData* @return*/@PostMapping("/resWxPay")public String resWxPay(@RequestBody String xmlData){logger.info("Wx Pay Response, Data:[{}]", xmlData);try{custWxPayService.resWxPay(xmlData);}catch(Exception e){logger.error("Wx Pay Response Process Error.", e);return WxPayNotifyResponse.fail("订单支付失败.");}logger.info("Wx Pay Response Process Success.");return WxPayNotifyResponse.success("付款成功");}@GetMapping("/queryOrder")public Result queryOrder(String outTradeNo){WxPayOrderQueryResult wxPayOrderQueryResult = custWxPayService.queryOrder(outTradeNo);logger.info(wxPayOrderQueryResult.toString());return Result.success(wxPayOrderQueryResult);}
}
6.相关测试
创建支付订单
说明:
- subAppId:子商户的小程序id
- subOpenid:小程序用户的openid
相应结果
说明:returnCode为success,则说明创建微信支付订单成功。
查询订单
订单创建成功后,需要查询订单状态。
相应结果
微信异步回调
关于微信支付回调,需要绑定外网域名,在内网需要测试的话,需要进行内网穿透,微信异步回调可能由于网络原因出现延迟,所以在业务上需要提供主动查询和异步回调结合的方式,关于具体实现将在后续的文章中进行讲解。
总结
本文服务商模式实现微信支付进行详细的讲解,实现过程还是比较复杂,订单支付中涉及到重复提交、幂等性验证、同步+异步的轮询处理等问题将在后续的文章中进行讲解。