最近在做微信小程序,今天刚好做到小程序里的微信支付这块,踩过不少坑,特此写个博客记录下,希望能帮到其它人吧。
我总结了一下,小程序中的微信支付和之前其它的公众号里的微信支付有两个区别,第一就是小程序必须要Https接口,第二服务器在返回给小程序之前需要二次签名,记住要二次签名。
我发现网上的相关例子很多,但是也很乱,各种封装类,五花八门的,在这里我用的是微信官方的方法,简单,无脑,高效。
1,我们需要在Maven坐标系里导入官方封装好的方法
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
2,写一个MyConfig类继承WXPayConfig
import com.baibeiyun.robot.base.util.PropertiesUtils; import com.github.wxpay.sdk.WXPayConfig; import java.io.InputStream; /** * 配置类 * * @author liyuan * @date 2018/3/30 **/ public class MyConfig implements WXPayConfig { @Override public String getAppID() {
// 放你的appid return PropertiesUtils.getInstace("conf/webService.properties").getProperty("appId"); } @Override public String getMchID() {
// 商户号 return PropertiesUtils.getInstace("conf/webService.properties").getProperty("mchId"); } @Override public String getKey() {
// 你的商户号里的key return PropertiesUtils.getInstace("conf/webService.properties").getProperty("key"); } @Override public InputStream getCertStream() { return null; } @Override public int getHttpConnectTimeoutMs() { return 8000; } @Override public int getHttpReadTimeoutMs() { return 10000; } }
3. 在服务器端充值接口里获取到那5个关键参数传给小程序
@ApiOperation("小程序充值接口") @RequestMapping(value = "saveMoney", method = RequestMethod.POST) public String saveMoney(@ModelAttribute SaveMoney1Dto dto, HttpServletRequest request) throws Exception { if (!dto.checkParam()) { throw new MyParameterException(400, "请求参数异常"); } // 获取用户userId Integer userId = Integer.valueOf(redisService.get(dto.getUuidKey()).substring(52)); // 生成交易订单号 String orderNum = CommonUtils.getOrderNumber(); MoneyTransaction moneyTransaction = new MoneyTransaction(); moneyTransaction.setPayUserId(userId); moneyTransaction.setOrderNum(orderNum); moneyTransaction.setMoney(dto.getMoney()); // 交易时间 moneyTransaction.setGmtCreate(new Date()); // 将交易订单存入数据库 iMoneyTransactionService.insertSelective(moneyTransaction); // 给微信服务器统一下单 MyConfig config = new MyConfig(); WXPay wxpay = new WXPay(config); // 封装入参字典 Map<String, String> data = new HashMap<>(16); // 商品描述 data.put("body", "机器人服务充值"); // 商户订单号 data.put("out_trade_no", orderNum); // 随机字符串 data.put("nonce_str", WXPayUtil.generateNonceStr()); // 总金额 分 将BigDecimal乘以100再转为字符串 String totalFee = (dto.getMoney().multiply(new BigDecimal("100")).setScale(0, BigDecimal.ROUND_HALF_UP)).toString(); data.put("total_fee", totalFee); // 终端IP data.put("spbill_create_ip", request.getRemoteAddr()); // 接收微信支付异步通知回调地址 data.put("notify_url", PropertiesUtils.getInstace("conf/webService.properties").getProperty("notifyUrl")); // 交易类型 data.put("trade_type", "JSAPI"); // 获取opneid String openid = redisService.get(dto.getUuidKey()).substring(24, 52); data.put("openid", openid); // 和微信服务器交互,统一下单 Map<String, String> resp = wxpay.unifiedOrder(data); log.info("微信返回: " + resp); // 小程序再次签名 Map<String, String> data2 = new HashMap<>(16); data2.put("appId", resp.get("appid")); String timeStamp = System.currentTimeMillis()/1000 + ""; data2.put("timeStamp", timeStamp); String nonceStr = WXPayUtil.generateNonceStr(); data2.put("nonceStr", nonceStr); String packages = "prepay_id="+resp.get("prepay_id"); data2.put("package", packages); data2.put("signType", "MD5"); String sign2 = WXPayUtil.generateSignature(data2, PropertiesUtils.getInstace("conf/webService.properties").getProperty("key")); // 封装返回给前端map Map<String, String> result = new HashMap<>(16); result.put("timeStamp", timeStamp); result.put("nonceStr", nonceStr); result.put("package", packages); result.put("signType", "MD5"); result.put("paySign", sign2); log.info("返回给前端: " + result); return YuanResultUtil.successWithDataAndMsg(result, "OK"); }
在这里直接用微信的工具类,微信的方法比较高效,直接用map字典比较直观,犯不上像网上大多数人那样造一个封装类转来转去的麻烦
4 微信通知回调类
@ApiOperation("小程序充值结果(自动从微信服务器获取,前端用不到)") @RequestMapping(value = "saveMoneyResult", method = RequestMethod.POST) public String saveMoneyResult(HttpServletRequest request) throws Exception { // 获取支付结果,XML格式 String notifyData = StreamUtil.read(request.getInputStream()); log.info("支付结果: " + notifyData); MyConfig config = new MyConfig(); WXPay wxPay = new WXPay(config); // 支付结果转成map Map<String, String> notifyMap = WXPayUtil.xmlToMap(notifyData); if (wxPay.isPayResultNotifySignatureValid(notifyMap)) { // 签名正确 log.info("签名正确: " + notifyMap); // 获取订单号 String orderNum = notifyMap.get("out_trade_no"); log.info("订单号:" + orderNum); // 获取金额(分) String totalFee = notifyMap.get("total_fee"); // 转为BigDecimal BigDecimal totalFee2 = new BigDecimal(totalFee); totalFee2 = totalFee2.divide(new BigDecimal("100"), 2, BigDecimal.ROUND_HALF_UP); // 根据订单号获取交易记录 MoneyTransaction moneyTransaction = iMoneyTransactionService.getMoneyTransactionByOrderNum(orderNum); if (moneyTransaction != null && moneyTransaction.getStatus() == 0 && moneyTransaction.getMoney().equals(totalFee2)) { // 设置状态为支付成功 moneyTransaction.setStatus(1); moneyTransaction.setGmtCreate(new Date()); log.info("修改成功"); iMoneyTransactionService.updateByPrimaryKeySelective(moneyTransaction); return "<xml><return_code>SUCCESS</return_code><return_msg>OK</return_msg></xml>"; } else { log.info("修改失败"); } } else { // 签名错误 throw new BuinessException(501, "签名错误"); } return "<xml><return_code>FALL</return_code><return_msg>失败</return_msg></xml>"; }