本章深入探讨微信支付流程实现,涵盖订单支付功能、内网穿透技术应用,以及如何绕过支付验证进行开发测试。通过实际代码演示,掌握支付接口集成与商户系统通信的核心技术要点。

这里由于我们没有正规的营业执照,所以我们并不能开通支付权限。所以只需要了解微信支付流程即可,同时如何访问到商户系统主要涉及到了内网穿透的知识点。 最后,我们需要把前端的部分代码改掉,以此来绕过微信支付。
我将微信支付全流程拆分成以下四个阶段,每个阶段多个步骤,以便于快速理解。
用户下单:分别为 用户进入小程序、 下单请求、 返回订单号 这三个步骤.
申请支付:分别为 申请微信支付, 调用微信下单接口、 返回预支付交易标识, 将组合数据再次签名, 返回支付参数 这五个步骤。
用户支付:分别为 用户确认支付、调起微信支付、返回支付结果、显示支付结果 这四个步骤。
支付结果同时与订单更新:分别为 推送支付结果,更新订单状态 这两个步骤。
微信支付全流程图

第一阶段:用户下单
1.用户在微信小程序中选择商品,填写订单信息(微信用户 -> 微信小程序) 2.小程序向商户系统发送下单请求(微信小程序 -> 商户系统) 3.商户系统处理订单,返回订单号等信息(商户系统 -> 微信小程序)
相关代码 下单请求代码
@PostMapping("/submit")
@ApiOperation("用户下单功能")
public Result<OrderSubmitVO> submit(@RequestBody OrdersSubmitDTO ordersSubmitDTO){
log.info("用户功能下单{}",ordersSubmitDTO);
OrderSubmitVO vo=orderService.submit(ordersSubmitDTO);
return Result.success(vo);
}返回订单号信息代码
OrderSubmitVO build = OrderSubmitVO.builder()
.id(order.getId())
.orderTime(order.getOrderTime())
.orderNumber(order.getNumber()) // 返回订单号
.orderAmount(order.getAmount())
.build();
return build;第二阶段:申请支付
4.用户点击支付,小程序请求支付参数(微信小程序 -> 商户系统)
5.商户系统调用微信统一下单接口(商户系统 -> 微信后台)
6.微信返回prepay_id(预支付交易标识)
7.使用prepay_id等参数进行二次签名(商户系统内部处理)
8.返回小程序调起支付所需参数(商户系统 -> 微信小程序)
相关代码 申请微信支付代码
// sky-server/src/main/java/com/sky/controller/user/OrderController.java
@PutMapping("/payment")
@ApiOperation("订单支付")
public Result<String> payment(@RequestBody OrdersPaymentDTO ordersPaymentDTO) throws Exception {
log.info("订单支付:{}", ordersPaymentDTO);
orderService.payment(ordersPaymentDTO);
return Result.success();
}微信下单接口
private String jsapi(String orderNum, BigDecimal total, String description, String openid) throws Exception {
JSONObject jsonObject = new JSONObject();
jsonObject.put("appid", weChatProperties.getAppid());
jsonObject.put("mchid", weChatProperties.getMchid());
jsonObject.put("description", description);
jsonObject.put("out_trade_no", orderNum);
jsonObject.put("notify_url", weChatProperties.getNotifyUrl());
JSONObject amount = new JSONObject();
amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
amount.put("currency", "CNY");
jsonObject.put("amount", amount);
JSONObject payer = new JSONObject();
payer.put("openid", openid);
jsonObject.put("payer", payer);
String body = jsonObject.toJSONString();
return post(JSAPI, body);
}预支付交易表示的数据格式
{
"prepay_id": "wx201410272009395522657a690389285100",
"code": "SUCCESS"
}组合数据签名
public JSONObject pay(String orderNum, BigDecimal total, String description, String openid) throws Exception {
// 统一下单,生成预支付交易单
String bodyAsString = jsapi(orderNum, total, description, openid);
JSONObject jsonObject = JSON.parseObject(bodyAsString);
String prepayId = jsonObject.getString("prepay_id");
if (prepayId != null) {
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
String nonceStr = RandomStringUtils.randomNumeric(32);
// 构造签名数据
ArrayList<Object> list = new ArrayList<>();
list.add(weChatProperties.getAppid());
list.add(timeStamp);
list.add(nonceStr);
list.add("prepay_id=" + prepayId);
// 二次签名
StringBuilder stringBuilder = new StringBuilder();
for (Object o : list) {
stringBuilder.append(o).append("\n");
}
String signMessage = stringBuilder.toString();
byte[] message = signMessage.getBytes();
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath()))));
signature.update(message);
String packageSign = Base64.getEncoder().encodeToString(signature.sign());
// 构造返回数据
JSONObject jo = new JSONObject();
jo.put("timeStamp", timeStamp);
jo.put("nonceStr", nonceStr);
jo.put("package", "prepay_id=" + prepayId);
jo.put("signType", "RSA");
jo.put("paySign", packageSign);
return jo;
}
return jsonObject;
}返回小程序调起支付所需的参数
参数名 | 说明 |
|---|---|
timeStamp | 时间戳 |
nonceStr | 随机字符串 |
package | 统一下单接口返回的 prepay_id 参数值 |
signType | 签名算法 |
paySign | 签名 |
第三阶段:用户支付
9.用户在支付界面确认支付(微信用户) 10.使用支付参数调起微信支付(微信小程序) 11.微信处理支付,返回结果给小程序(微信后台 -> 微信小程序) 12.小程序向用户显示支付结果(微信小程序 -> 微信用户)
第四阶段:支付结果通知与订单更新
13.微信异步推送支付结果到商户系统(微信后台 -> 商户系统) 14.根据支付结果更新订单状态(商户系统内部处理)
相关代码 支付结果通知
@RequestMapping("/paySuccess")
public void paySuccessNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 读取数据
String body = readData(request);
log.info("支付成功回调:{}", body);
// 数据解密
String plainText = decryptData(body);
log.info("解密后的文本:{}", plainText);
JSONObject jsonObject = JSON.parseObject(plainText);
String outTradeNo = jsonObject.getString("out_trade_no");//商户平台订单号
String transactionId = jsonObject.getString("transaction_id");//微信支付交易号
// 业务处理,修改订单状态、来单提醒
orderService.paySuccess(outTradeNo);
// 给微信响应
responseToWeixin(response);
}更新订单状态
public void paySuccess(String outTradeNo) {
// 根据订单号查询订单
Orders ordersDB = orderMapper.getByNumber(outTradeNo);
// 根据订单id更新订单的状态、支付方式、支付状态、结账时间
Orders orders = Orders.builder()
.id(ordersDB.getId())
.status(Orders.TO_BE_CONFIRMED) // 更新订单状态
.payStatus(Orders.PAID) // 更新支付状态
.checkoutTime(LocalDateTime.now())
.build();
orderMapper.update(orders);
// 通过websocket向客户端浏览器推送消息
Map map = new HashMap();
map.put("type", 1); // 1 来单提醒 2 客户催单
map.put("orderId", ordersDB.getId());
map.put("content", "订单号" + outTradeNo);
String jsonString = JSON.toJSONString(map);
webSocketServer.sendToAllClient(jsonString);
}内网穿透(NAT traversal)是一种技术,用于实现公网与内网之间的通信连接。 当内网中的设备无法直接从公网访问时,内网穿透技术可以通过一些手段,让公网上的设备能够穿透到内网中的设备,建立起通信连接。
业务场景 微信后台会调用到商户系统给推送支付的结果,但由于未上线的缘故,微信后台无法请求到商户系统。
解决思路 通过cpolar软件可以获得一个临时域名,而这个临时域名是一个公网ip,这样,微信后台就可以请求到商户系统了。
实现流程
在命令提示符中输入cpolar.exe http 后台服务端口后,就会获取临时域名,使用临时域名访问成功即可。

先到小程序开发工具,在pages下的index.js的第226行,将下面的框注释掉

并将下面的重定向的代码给取消注释

这样,当成功支付时,就不会再调用微信支付的API了,并直接回调
前端主要是逻辑验证,后端是严格验证。
由于前端主要是在用户浏览器/小程序中执行,因此在前端有着许多的逻辑验证,这些主要是为了减少无效请求,从而达到提升用户体验,提供及时反馈的作用。(前端验证的作用)
但同时,前端可以通过如Postman为例的接口测试工具,去进行恶意攻击或绕过验证,如伪造支付回调,调用支付接口等。
后端验证是在服务端执行,无法被用户修改,确保了数据安全性和业务逻辑正确性。
本文为苍穹外卖项目学习笔记,持续更新中…
如果我的内容对你有帮助,希望可以收获你的点赞、评论、收藏。