先来看一下微信支付的流程,可见在整个支付流程中后台需要处理的事情有:
1、调用统一下单api
2、生成JSAPI页面调用的支付参数,并请求支付
3、异步通知商户支付结果
4、返回微信异步通知的处理结果
下面来实际编程实现以上的过程,这里面我们使用了第三方Sdk,best-pay-sdk
https://github.com/Pay-Group/best-pay-sdk
maven引入依赖
<dependency>
<groupId>cn.springboot</groupId>
<artifactId>best-pay-sdk</artifactId>
<version>1.3.0</version>
</dependency>
支付最核心的就是要出现微信支付的界面,那么就需要在商户页面调起支付,那么如何调起支付界面呢,根据开发文档,调起支付需要执行下面的JS,JS中需要传入相应的信息
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6
那么思路比较明确,只需要在controller层执行支付的逻辑,调用统一下单的api,并返回JSAPI页面调用的支付参数动态注入到这段JS支付模板就可以了,那么每次调用controller层的接口时,就会调起支付。
@Controller
@RequestMapping("/pay")
public class PayController {
@Autowired
private OrderService orderService;
@Autowired
private PayService payService;
@GetMapping("/create")
public ModelAndView create(@RequestParam("orderId")String orderId,
@RequestParam("returnUrl")String returnUrl,
Map<String,Object> map) {
//查询订单
OrderDTO orderDTO=orderService.findOne(orderId);
if(orderDTO==null){
throw new OrderException(ResultEnum.ORDER_NOT_EXIST);
}
//配置创建支付所需的必要信息
PayResponse payResponse=payService.create(orderDTO);
//把信息放入支付模板中
map.put("payResponse",payResponse);
//returnUrl为支付成功之后需要跳转到的页面
returnUrl="http://sell.com/#/order/"+orderId;
map.put("returnUrl",returnUrl);
return new ModelAndView("pay/create",map);
}
}
接下来需要将后台传过来的支付参数动态注入到支付模板中,这里使用了freemarker
<script>
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":"${payResponse.appId}", //公众号名称,由商户传入
"timeStamp":"${payResponse.timeStamp}", //时间戳,自1970年以来的秒数
"nonceStr":"${payResponse.nonceStr}", //随机串
"package":"${payResponse.packAge}",
"signType":"MD5", //微信签名方式:
"paySign":"${payResponse.paySign}" //微信签名
},
function(res){
/* if(res.err_msg == "get_brand_wcpay_request:ok" ){
// 使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
}
*/
location.href="${returnUrl}";
});
}
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
}else{
onBridgeReady();
}
</script>
service层中的逻辑,bestPayService.pay(payRequest)调用了统一支付的API,并返回了JSAPI页面调用的支付参数
@Service
@Slf4j
public class PayServiceImpl implements PayService {
@Autowired
private OrderService orderService;
private static final String ORDER_NAME="微信订单";
@Autowired
private BestPayServiceImpl bestPayService;
@Override
public PayResponse create(OrderDTO orderDTO) {
PayRequest payRequest=new PayRequest();
payRequest.setOpenid(orderDTO.getBuyerOpenid());
payRequest.setOrderAmount(orderDTO.getOrderAmount().doubleValue());
payRequest.setOrderId(orderDTO.getOrderId());
payRequest.setOrderName(ORDER_NAME);
payRequest.setPayTypeEnum(BestPayTypeEnum.WXPAY_MP);
log.info("【微信支付】payRequest={}", JsonUtil.toJson(payRequest));
PayResponse payResponse=bestPayService.pay(payRequest);
log.info("【微信支付】payResponse={}",JsonUtil.toJson(payResponse));
return payResponse;
}
}
配置JAVAconfig,设置商户信息
@Configuration
public class WechatPayConfig {
@Autowired
private WechatAccountConfig wechatAccountConfig;
@Bean WxPayConfig wxPayConfig(){
WxPayConfig wxPayConfig=new WxPayConfig();
wxPayConfig.setAppId(wechatAccountConfig.getMpAppId());
wxPayConfig.setAppSecret(wechatAccountConfig.getMpAppSecret());
wxPayConfig.setMchId(wechatAccountConfig.getMchId());
wxPayConfig.setMchKey(wechatAccountConfig.getMchKey());
wxPayConfig.setKeyPath(wechatAccountConfig.getKeyPath());
wxPayConfig.setNotifyUrl(wechatAccountConfig.getNotifyUrl());
return wxPayConfig;
}
@Bean
public BestPayServiceImpl bestPayService(){
BestPayServiceImpl bestPayService=new BestPayServiceImpl();
bestPayService.setWxPayConfig(wxPayConfig());
return bestPayService;
}
}
@Data
@Component
@ConfigurationProperties(prefix = "wechat")
public class WechatAccountConfig {
//公众号ID
private String mpAppId;
//公众号密钥
private String mpAppSecret;
//商户号
private String mchId;
//商户密钥
private String mchKey;
//商户证书路径
private String keyPath;
//异步回调接口
private String notifyUrl;
}
然后在yml中配置相应的信息即可
此时打开微信端调用controller层的支付接口就可以实现支付了
支付完成之后还需要处理微信异步返回的结果,接收到异步返回的结果后可以处理业务逻辑,比如修改支付状态等,随后将处理结果返回微信,如果不处理请求,微信会一直返回异步通知。
controller层
@PostMapping("/notify")
public ModelAndView notify(@RequestBody String notifyData)
{
payService.notify(notifyData);
return new ModelAndView("pay/success");
}
service层
@Override
public PayResponse notify( String notifyData)
{
PayResponse payResponse=bestPayService.asyncNotify(notifyData);
log.info("【微信异步通知】,payResonse={}",JsonUtil.toJson(payResponse));
OrderDTO orderDTO=orderService.findOne(payResponse.getOrderId());
orderService.paid(orderDTO);
return payResponse;
}
支付成功后返回的JS代码,至此支付的所有流程结束
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>
支付测试:
调用统一支付API之后返回的内容,可见调用了统一支付API后返回了之后生成JSAPI页面调用的所需要的支付参数
2020-02-25 03:39:22.518 INFO 22880 --- [nio-8889-exec-6] okhttp3.OkHttpClient : <xml><return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[wxd898fcb01713c658]]></appid>
<mch_id><![CDATA[1483469312]]></mch_id>
<nonce_str><![CDATA[g1K2MKyNdNkMkn7t]]></nonce_str>
<sign><![CDATA[61D46CFE319025CE457FC8F90B6CBADC]]></sign>
<result_code><![CDATA[SUCCESS]]></result_code>
<prepay_id><![CDATA[wx250337332209497ce17477f81982372500]]></prepay_id>
<trade_type><![CDATA[JSAPI]]></trade_type>
</xml>
2020-02-25 03:39:22.518 INFO 22880 --- [nio-8889-exec-6] okhttp3.OkHttpClient : <-- END HTTP (456-byte body)
2020-02-25 03:39:22.531 INFO 22880 --- [nio-8889-exec-6] com.pers.food.service.PayServiceImpl :
【微信支付】payResponse={
"appId": "wxd898fcb01713c658",
"timeStamp": "1582573162",
"nonceStr": "75htValspgU82402",
"packAge": "prepay_id\u003dwx250337332209497ce17477f81982372500",
"signType": "MD5",
"paySign": "855A556EBA7799B011235554A00268DB"
}
支付成功后,接收异步通知后返回的信息,可见返回了支付金额,订单号等信息
2020-02-25 03:40:24.144 INFO 22880 --- [nio-8889-exec-4] com.pers.food.service.PayServiceImpl :
【微信异步通知】,payResonse={
"orderAmount": 0.01,
"orderId": "1582573104751271126",
"outTradeNo": "4200000512202002252531868211",
"payPlatformEnum": "WX"
}