一、订单支付
1.1 微信支付介绍
1.1.1 注册商户号
要使用微信支付,必须注册微信支付商户号,并且:
- 必须是企业
- 拥有正规营业执照
- 通过资质审核后,才能开通支付权限
当然,大部分人是没有的(我也没有QAQ)
- 了解微信支付流程
- 阅读微信官方接口文档
- 能与第三方支付平台对接(蓝兔支付)
1.1.2 微信支付类型
本项目选择 小程序支付
参考:微信支付产品中心
1.1.3 微信支付接入流程
1.1.4 微信小程序支付时序图
1.1.5 微信支付相关接口
JSAPI 下单
商户系统调用此接口,在微信支付服务后台生成预支付交易单
微信小程序调起支付
- 通过 JSAPI 下单接口获取
prepay_id - 使用微信提供的小程序方法调起支付

- 通过 JSAPI 下单接口获取
1.2 微信支付准备工作
1.2.1 如何保证数据安全?
微信支付有两个关键步骤:
- 系统调用微信下单接口 → 生成预支付交易单
- 支付成功后,微信后台推送支付结果给系统
这两个接口的数据安全性要求很高。
- 微信通过加密、解密、签名保证安全
- 需要提前准备:
- 微信支付平台证书
- 商户私钥文件
1.2.2 如何调用到商户系统?
- 微信后台推送支付结果,本质上是 HTTP 请求
- 必须保证系统可被访问
- 如果有公网 IP(云服务器)可直接回调
- 没有公网 IP,可用内网穿透工具(文末会介绍)
1.3 功能实现
1.3.1 微信支付配置
application.yml
wechat:
appid: ${qi.wechat.appid}
secret: ${qi.wechat.secret}
mchid: ${qi.wechat.mchid}
mch-serial-no: ${qi.wechat.mch-serial-no}
private-key-file-path: ${qi.wechat.private-key-file-path}
api-v3-key: ${qi.wechat.api-v3-key}
we-chat-pay-cert-file-path: ${qi.wechat.we-chat-pay-cert-file-path}
notify-url: ${qi.wechat.notify-url}
refund-notify-url: ${qi.wechat.refund-notify-url}
WeChatProperties.java
@Component
@ConfigurationProperties(prefix = "qi.wechat")
@Data
public class WeChatProperties {
private String appid;
private String secret;
private String mchid;
private String mchSerialNo;
private String privateKeyFilePath;
private String apiV3Key;
private String weChatPayCertFilePath;
private String notifyUrl;
private String refundNotifyUrl;
}
1.3.2 Controller 层
OrderController.java
@PutMapping("/payment")
@ApiOperation("订单支付")
public Result<OrderPaymentVO> payment(@RequestBody OrdersPaymentDTO ordersPaymentDTO) throws Exception {
log.info("订单支付:{}", ordersPaymentDTO);
OrderPaymentVO orderPaymentVO = orderService.payment(ordersPaymentDTO);
log.info("生成预支付交易单:{}", orderPaymentVO);
return Result.success(orderPaymentVO);
}
PayNotifyController.java(支付回调)
@RestController
@RequestMapping("/notify")
@Slf4j
public class PayNotifyController {
@Autowired
private OrderService orderService;
@Autowired
private WeChatProperties weChatProperties;
/**
* 支付成功回调
*
* @param request
*/
@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");//微信支付交易号
log.info("商户平台订单号:{}", outTradeNo);
log.info("微信支付交易号:{}", transactionId);
//业务处理,修改订单状态、来单提醒
orderService.paySuccess(outTradeNo);
//给微信响应
responseToWeixin(response);
}
/**
* 读取数据
*
* @param request
* @return
* @throws Exception
*/
private String readData(HttpServletRequest request) throws Exception {
BufferedReader reader = request.getReader();
StringBuilder result = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
if (result.length() > 0) {
result.append("\n");
}
result.append(line);
}
return result.toString();
}
/**
* 数据解密
*
* @param body
* @return
* @throws Exception
*/
private String decryptData(String body) throws Exception {
JSONObject resultObject = JSON.parseObject(body);
JSONObject resource = resultObject.getJSONObject("resource");
String ciphertext = resource.getString("ciphertext");
String nonce = resource.getString("nonce");
String associatedData = resource.getString("associated_data");
AesUtil aesUtil = new AesUtil(weChatProperties.getApiV3Key().getBytes(StandardCharsets.UTF_8));
//密文解密
String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
ciphertext);
return plainText;
}
/**
* 给微信响应
* @param response
*/
private void responseToWeixin(HttpServletResponse response) throws Exception{
response.setStatus(200);
HashMap<Object, Object> map = new HashMap<>();
map.put("code", "SUCCESS");
map.put("message", "SUCCESS");
response.setHeader("Content-type", ContentType.APPLICATION_JSON.toString());
response.getOutputStream().write(JSONUtils.toJSONString(map).getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
}
}
1.3.3 Service 层
OrderService.java
OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception;
void paySuccess(String outTradeNo);
1.3.4 Service 实现类
OrderServiceImpl.java
@Autowired
private UserMapper userMapper;
@Autowired
private WeChatPayUtil weChatPayUtil;
public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {
Long userId = BaseContext.getCurrentId();
User user = userMapper.getById(userId);
JSONObject jsonObject = weChatPayUtil.pay(
ordersPaymentDTO.getOrderNumber(),
new BigDecimal(0.01),
"小柒味来订单",
user.getOpenid()
);
if ("ORDERPAID".equals(jsonObject.getString("code"))) {
throw new OrderBusinessException("该订单已支付");
}
OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
vo.setPackageStr(jsonObject.getString("package"));
return vo;
}
public void paySuccess(String outTradeNo) {
Long userId = BaseContext.getCurrentId();
Orders ordersDB = orderMapper.getByNumberAndUserId(outTradeNo, userId);
Orders orders = Orders.builder()
.id(ordersDB.getId())
.status(Orders.TO_BE_CONFIRMED)
.payStatus(Orders.PAID)
.checkoutTime(LocalDateTime.now())
.build();
orderMapper.update(orders);
}
1.3.4 Mapper 层
OrderMapper.java
@Select("select * from orders where number = #{orderNumber} and user_id= #{userId}")
Orders getByNumberAndUserId(String orderNumber, Long userId);
void update(Orders orders);
OrderMapper.xml
<update id="update" parameterType="com.qi.entity.Orders">
update orders
<set>
<if test="cancelReason != null and cancelReason!='' ">
cancel_reason=#{cancelReason},
</if>
<if test="rejectionReason != null and rejectionReason!='' ">
rejection_reason=#{rejectionReason},
</if>
<if test="cancelTime != null">
cancel_time=#{cancelTime},
</if>
<if test="payStatus != null">
pay_status=#{payStatus},
</if>
<if test="payMethod != null">
pay_method=#{payMethod},
</if>
<if test="checkoutTime != null">
checkout_time=#{checkoutTime},
</if>
<if test="status != null">
status = #{status},
</if>
<if test="deliveryTime != null">
delivery_time = #{deliveryTime}
</if>
</set>
where id = #{id}
</update>
1.4 联调测试
- 去支付

- 扫码支付即可
二、知识点补充
Q1:内网穿透解决方案
为使微信后台能够访问商户系统,需要将本地服务映射到公网。
我们可以通过 cpolar 软件可以获取一个临时域名,该域名对应一个公网 IP,从而实现微信后台对商户系统的访问。
1. 下载与安装
下载地址:https://dashboard.cpolar.com/get-started
安装步骤:保持默认选项,连续点击“下一步”完成安装(此处省略演示图)。
2. 配置 authtoken
- 登录 cpolar 控制台,复制个人
authtoken:
- 在本地终端执行以下命令绑定
authtoken:
cpolar.exe authtoken <你的_authtoken>
绑定成功后,cpolar 即可将本地端口映射为公网访问地址。
- 获取临时域名,执行命令:
cpolar.exe http 7030
- 得到域名:
Q2:我没有商户号又想体验支付流程怎么办?
蓝兔支付api
关于订单支付的功能就基本实现了,下一章小柒味来 - Spring Task 定时任务
欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1701220998@qq.com