在做完一个仿美团的应用,我将其中涉及到的一些技术点进行总结和整理,当下次遇到类似的问题就能省下不少时间,避免踩坑。
一、微信小程序支付的使用
微信支付有官方提供的SDK,但要自己处理的东西比较多。我使用的是一个别人封装过的SDK https://github.com/Wechat-Group/WxJava (同事推荐我用的)。
要如何使用呢,首先引入依赖。
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-pay-spring-boot-starter</artifactId>
<version>4.0.0</version>
</dependency>
然后去 https://github.com/Wechat-Group/WxJava 下载一个demo,将demo中的 WxPayConfiguration.java、WxPayProperties.java 文件复制到自己的项目中
也可以直接复制我在下方提供的代码,是一模一样的
WxPayConfiguration.java
package com.cmzn.meigo.wx.pay.config;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Binary Wang
*/
@Configuration
@ConditionalOnClass(WxPayService.class)
@EnableConfigurationProperties(WxPayProperties.class)
@AllArgsConstructor
public class WxPayConfiguration {
private WxPayProperties properties;
@Bean
@ConditionalOnMissingBean
public 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.setSubAppId(StringUtils.trimToNull(this.properties.getSubAppId()));
payConfig.setSubMchId(StringUtils.trimToNull(this.properties.getSubMchId()));
payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath()));
// 可以指定是否使用沙箱环境
payConfig.setUseSandboxEnv(false);
WxPayService wxPayService = new WxPayServiceImpl();
wxPayService.setConfig(payConfig);
return wxPayService;
}
}
WxPayProperties.java
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* wxpay pay properties.
*
* @author Binary Wang
*/
@Data
@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;
}
然后在application.yml中配置参数,支付用不到证书,可以注释掉,我的也不是服务商模式,不用理subAppId和subMchId
wx:
pay:
appId: #小程序APPID
mchId: #商户号ID
mchKey: #商户号秘钥
subAppId: #服务商模式下的子商户公众账号ID
subMchId: #服务商模式下的子商户号
#keyPath: classpath:/wx/apiclient_cert.p12 # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
secret: #小程序秘钥
二、唤起微信支付窗口
写一个简单的下单流程,业务逻辑代码我给他省略了
private HttpResult order(OrderRequest orderRequest, String gkId) throws WxPayException{
/*
* 业务代码省略
*/
wxPayMpOrderResult = myWxPayService.createDdzqOrder(ddzqOrder); //调用唤起窗口方法
if(wxPayMpOrderResult == null){
throw new RuntimeException("创建订单异常");
}else{
return HttpResult.ok(wxPayMpOrderResult);
}
}
唤起微信小程序支付窗口 参数可以按自己需要进行增加 https://pay.weixin.qq.com/wiki/doc/api/jsapi_sl.php?chapter=9_1 参考微信支付开发文档
public WxPayMpOrderResult createDdzqOrder(DdzqOrder ddzqOrder)throws WxPayException {
String endPayMoney = ddzqOrder.getEndPayMoney().multiply(BigDecimal.valueOf(100)).stripTrailingZeros().toPlainString(); //将金额转换为分
WxPayUnifiedOrderRequest.WxPayUnifiedOrderRequestBuilder builder = WxPayUnifiedOrderRequest.newBuilder();
WxPayUnifiedOrderRequest wxOrder = builder
.body("") //商品描述
.openid("") //用户openid
.outTradeNo(ddzqOrder.getMasterId()) //商户订单号
.spbillCreateIp("0.0.0.0") //终端IP
.totalFee(endPayMoney) // 支付收款金额 100分 注意:单位(分)
//.totalFee(1) 测试时可以设置一分钱
.timeStart(DateTimeUtils.yyyyMMddHHmmssDateTime(0)) //交易起始时间
.timeExpire(DateTimeUtils.yyyyMMddHHmmssDateTime(2)) //交易结束时间
.tradeType(WxPayConstants.TradeType.JSAPI).build();
wxOrder.setSignType("MD5");
//wxOrder.setNotifyUrl("https://**微信支付成功回调地址***/pay/ddzqOrder"); //不测试时需要放开注释
return wxPayService.createOrder(wxOrder);
}
当控制台打印出如下信息,则微信支付接口调用成功
三、微信支付成功回调
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7&index=8
意思就是支付成功后,微信会调用我们之前设置的 微信支付成功回调地址
wxOrder.setNotifyUrl("https://**微信支付成功回调地址***/pay/ddzqOrder");
如果我们不给微信返回应答,微信会不断的请求我们接受微信回调通知的接口,所以这里就涉及到并发的问题,多次请求或者重复请求。
下面是我接收订单回调的接口,注意*需要给微信支付成功回调接口放行,不放行微信那边访问不到。
@Api("微信支付")
@RestController
@RequestMapping("/pay")
@AllArgsConstructor
public class WxPayController {
//监听到店自取下单的支付成功回调
@PostMapping("/ddzqOrder")
public String ddzqOrderPay(HttpServletRequest request, HttpServletResponse response) throws WxPayException {
return myWxPayService.ddzqOrder(request);
}
}
那微信那边重复请求我们应该怎么处理呢,其实这里有个小技巧,我们可以通过数据库锁避免同一条数据被重复操作。
update st_order set pay_status = #{payStatus } where pay_status = 0
通过 where pay_status = 0 这个条件,那么这条订单数据只会被修改一次,以下就是我处理订单成功回调的方法
public String ddzqOrder(HttpServletRequest request) {
try {
String xmlResult = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding());
WxPayOrderNotifyResult payResult = wxPayService.parseOrderNotifyResult(xmlResult);
String masterOrderId = payResult.getOutTradeNo();//内部订单记录ID
String wxTransactionId = payResult.getTransactionId();//商户号订单ID
BigDecimal wxRealPayAmount = new BigDecimal(BaseWxPayResult.fenToYuan(payResult.getTotalFee()));//微信真实支付金额
StOrder stOrder = stOrderMapper.selectByEOrderId(masterOrderId);
Date nowTime = new Date();
if(stOrder.getPayStatus() == 1){
//下一个进程进来的时候,上一个进程已经修改了订单支付状态
return WxPayNotifyResponse.success("处理成功!");
}
StOrder orderRequest = new StOrder();
orderRequest.setPayStatus(1);//标记已支付
orderRequest.setMasterOrderId(masterOrderId);
orderRequest.setPaymentTime(nowTime);
orderRequest.setStoreReceiveTime(nowTime);
orderRequest.setOrderStatus(10); //等待顾客取货
stOrderMapper.updateByMasterOrderIdSelective(orderRequest);
StOrderPay stOrderPay = new StOrderPay();
stOrderPay.setMasterOrderId(masterOrderId);
stOrderPay.setWxRealPayAmount(wxRealPayAmount);
stOrderPay.setWxTransactionId(wxTransactionId);
stOrderPay.setPayWay(1); //支付方式 微信支付:1 支付宝支付:2
stOrderPayMapper.insert(stOrderPay);
/**
* 打印订单
*/
List<StPrinter> stPrinterList = stPrinterMapper.selectPrintersByStoreId(stOrder.getStoreId());
for (StPrinter stPrinter:stPrinterList) {
stPrinterService.printOrder(stOrder.getEOrderId(),stOrder.getStoreId(),stPrinter.getOkPrinterSn());
}
return WxPayNotifyResponse.success("处理成功!");
} catch (Exception e) {
e.printStackTrace();
return WxPayNotifyResponse.fail(e.getMessage());
}
}