微信支付

学习使用微信的jsapi支付,即微信内置浏览器或者小程序唤起微信支付

因为没有办法申请商户号,所以无法验证是否正确,只能先记录下代码

1.注册绑定商户号

微信支付 - 中国领先的第三方支付平台 | 微信支付提供安全快捷的支付方式 (qq.com)

商户号后台绑定小程序的AppID

2.导入依赖

1
2
3
4
5
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.8</version>
</dependency>

3.准备参数

application.yml

1
2
3
4
5
6
7
8
9
10
11
sky:
wechat:
appid: ${sky.wechat.appid}
secret: ${sky.wechat.secret}
mchid: ${sky.wechat.mchid}
mchSerialNo: ${sky.wechat.mchSerialNo}
privateKeyFilePath: ${sky.wechat.privateKeyFilePath}
apiV3Key: ${sky.wechat.apiV3Key}
weChatPayCertFilePath: ${sky.wechat.weChatPayCertFilePath}
notifyUrl: ${sky.wechat.notifyUrl}
refundNotifyUrl: ${sky.wechat.refundNotifyUrl}

application-dev.yml

1
2
3
4
5
6
7
8
9
10
11
sky:
wechat:
appid: 小程序的appid
secret: 小程序的秘钥
mchid: 商户号
mchSerialNo: 商户API证书的证书序列号
privateKeyFilePath: 商户私钥文件绝对路径
apiV3Key: 证书解密的密钥绝对路径
weChatPayCertFilePath: 平台证书绝对路径
notifyUrl: 支付成功的回调地址
refundNotifyUrl: 退款成功的回调地址

参数配置类 WeChatProperties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "sky.wechat")
@Data
public class WeChatProperties {

private String appid; //小程序的appid
private String secret; //小程序的秘钥
private String mchid; //商户号
private String mchSerialNo; //商户API证书的证书序列号
private String privateKeyFilePath; //商户私钥文件
private String apiV3Key; //证书解密的密钥
private String weChatPayCertFilePath; //平台证书
private String notifyUrl; //支付成功的回调地址
private String refundNotifyUrl; //退款成功的回调地址

}

4.小程序下单生成预支付交易单

小程序下单 - 小程序支付 | 微信支付商户文档中心 (qq.com)

利用已经封装好的工具类来进行操作:WeChatPayUtil.java

OrdersPaymentDTO

1
2
3
4
5
6
7
8
9
@Data
public class OrdersPaymentDTO implements Serializable {
//订单号
private String orderNumber;

//付款方式
private Integer payMethod;

}

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 订单支付
*
* @param ordersPaymentDTO
* @return
*/
@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);
}

Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface OrderService {
OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO);
/**
* 订单支付
* @param ordersPaymentDTO
* @return
*/
OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception;

/**
* 支付成功,修改订单状态
* @param outTradeNo
*/
void paySuccess(String outTradeNo);

}

ServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* 订单支付
*
* @param ordersPaymentDTO
* @return
*/
public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {
// 当前登录用户id
Long userId = BaseContext.getCurrentId();
User user = userMapper.selectById(userId);

//调用微信支付接口,生成预支付交易单
JSONObject jsonObject = weChatPayUtil.pay(
ordersPaymentDTO.getOrderNumber(), //商户订单号
new BigDecimal(0.01), //支付金额,单位 元
"苍穹外卖订单", //商品描述
user.getOpenid() //微信用户的openid
);

if (jsonObject.getString("code") != null
&& jsonObject.getString("code").equals("ORDERPAID")) {
throw new OrderBusinessException("该订单已支付");
}

OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
vo.setPackageStr(jsonObject.getString("package"));

return vo;
}

/**
* 支付成功,修改订单状态
*
* @param outTradeNo
*/
public void paySuccess(String outTradeNo) {

// 根据订单号查询订单
LambdaQueryWrapper<Orders> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Orders::getNumber,outTradeNo);
Orders ordersDB = orderMapper.selectOne(wrapper);

// 根据订单id更新订单的状态、支付方式、支付状态、结账时间
Orders orders = Orders.builder()
.id(ordersDB.getId())
.status(Orders.TO_BE_CONFIRMED)
.payStatus(Orders.PAID)
.checkoutTime(LocalDateTime.now())
.build();

orderMapper.updateById(orders);
}

OrderPaymentVO

1
2
3
4
5
6
7
8
9
10
11
12
13
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderPaymentVO implements Serializable {

private String nonceStr; //随机字符串
private String paySign; //签名
private String timeStamp; //时间戳
private String signType; //签名算法
private String packageStr; //统一下单接口返回的 prepay_id 参数值

}

PayNotifyController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/**
* 支付回调相关接口
*/
@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();
}
}

5.小程序调起支付

小程序调起支付 - 小程序支付 | 微信支付商户文档中心 (qq.com)

调用wx.requestPayment(OBJECT)发起微信支付

请求示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
wx.requestPayment
2(
3 {
4 "timeStamp": "1414561699",
5 "nonceStr": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS",
6 "package": "prepay_id=wx201410272009395522657a690389285100",
7 "signType": "RSA",
8 "paySign": "xxxx",
9 "success":function(res){},
10 "fail":function(res){},
11 "complete":function(res){}
12 }
13)