1. 微信支付概述

微信支付是腾讯公司推出的移动支付服务,支持多种支付场景。本文将详细介绍微信支付的集成实现,包括JSAPI支付、APP支付、H5支付、Native支付、退款处理、对账下载的完整解决方案。

1.1 支付场景

  1. JSAPI支付: 公众号/小程序内支付
  2. APP支付: 移动应用内支付
  3. H5支付: 手机浏览器支付
  4. Native支付: 扫码支付
  5. 付款码支付: 商户扫用户付款码

1.2 技术架构

1
2
3
4
5
6
7
商户系统 → 统一下单API → 微信支付系统
↓ ↓ ↓
调起支付 → 用户支付 → 支付结果
↓ ↓ ↓
支付回调 → 订单处理 → 业务完成

退款/对账

2. Maven依赖配置

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
<!-- pom.xml -->
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.wechat</groupId>
<artifactId>wechat-pay-demo</artifactId>
<version>1.0.0</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
</parent>

<properties>
<java.version>11</java.version>
</properties>

<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 微信支付SDK -->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.12</version>
</dependency>

<!-- Apache HttpClient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>

<!-- JSON处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.25</version>
</dependency>

<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- XML处理 -->
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.20</version>
</dependency>

<!-- Commons工具 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>

<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
</dependencies>
</project>

3. 配置文件

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
# application.yml
server:
port: 8080

spring:
application:
name: wechat-pay-demo

# 数据库配置
datasource:
url: jdbc:mysql://localhost:3306/wechat_pay?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver

jpa:
hibernate:
ddl-auto: update
show-sql: true

# Redis配置
redis:
host: localhost
port: 6379
database: 0

# 微信支付配置
wechat:
pay:
# 应用ID
app-id: wx1234567890abcdef

# 商户号
mch-id: 1234567890

# API密钥(V2)
api-key: your-api-key-32-characters-long

# API V3密钥
api-v3-key: your-api-v3-key-32-characters

# 商户证书序列号
merchant-serial-number: 1234567890ABCDEF

# 商户私钥路径
private-key-path: classpath:cert/apiclient_key.pem

# 商户证书路径
certificate-path: classpath:cert/apiclient_cert.pem

# 回调通知URL
notify-url: https://your-domain.com/api/wechat/pay/notify

# 退款通知URL
refund-notify-url: https://your-domain.com/api/wechat/pay/refund/notify

4. 微信支付配置类

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
package com.wechat.config;

import com.wechatpay.pay.java.core.Config;
import com.wechatpay.pay.java.core.RSAAutoCertificateConfig;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* 微信支付配置类
* @author Java实战
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "wechat.pay")
public class WeChatPayConfig {

/**
* 应用ID
*/
private String appId;

/**
* 商户号
*/
private String mchId;

/**
* API密钥(V2)
*/
private String apiKey;

/**
* API V3密钥
*/
private String apiV3Key;

/**
* 商户证书序列号
*/
private String merchantSerialNumber;

/**
* 商户私钥路径
*/
private String privateKeyPath;

/**
* 商户证书路径
*/
private String certificatePath;

/**
* 回调通知URL
*/
private String notifyUrl;

/**
* 退款通知URL
*/
private String refundNotifyUrl;

/**
* 创建微信支付配置对象
*/
@Bean
public Config config() {
// 使用自动更新证书的配置
return new RSAAutoCertificateConfig.Builder()
.merchantId(mchId)
.privateKeyFromPath(privateKeyPath)
.merchantSerialNumber(merchantSerialNumber)
.apiV3Key(apiV3Key)
.build();
}
}

5. 支付订单实体

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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package com.wechat.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
* 支付订单实体
* @author Java实战
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "pay_order", indexes = {
@Index(name = "idx_order_no", columnList = "order_no"),
@Index(name = "idx_transaction_id", columnList = "transaction_id"),
@Index(name = "idx_user_id", columnList = "user_id")
})
public class PayOrder {

/**
* 主键ID
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

/**
* 商户订单号(唯一)
*/
@Column(nullable = false, unique = true, length = 64)
private String orderNo;

/**
* 微信支付订单号
*/
@Column(length = 64)
private String transactionId;

/**
* 用户ID
*/
@Column(nullable = false, length = 64)
private String userId;

/**
* OpenID(JSAPI支付必填)
*/
@Column(length = 128)
private String openId;

/**
* 商品描述
*/
@Column(nullable = false, length = 200)
private String body;

/**
* 订单金额(分)
*/
@Column(nullable = false)
private Integer totalAmount;

/**
* 支付金额(分)
*/
private Integer payAmount;

/**
* 货币类型
*/
@Column(length = 10)
private String currency = "CNY";

/**
* 支付类型:JSAPI-公众号, APP-APP支付, H5-H5支付, NATIVE-扫码支付
*/
@Column(nullable = false, length = 20)
private String payType;

/**
* 订单状态:WAIT_PAY-待支付, PAYING-支付中, PAY_SUCCESS-支付成功, PAY_FAIL-支付失败, CLOSED-已关闭
*/
@Column(nullable = false, length = 20)
private String status;

/**
* 支付时间
*/
private LocalDateTime payTime;

/**
* 创建时间
*/
@Column(nullable = false)
private LocalDateTime createTime;

/**
* 更新时间
*/
private LocalDateTime updateTime;

/**
* 过期时间
*/
private LocalDateTime expireTime;

/**
* 附加数据
*/
@Column(length = 500)
private String attach;

/**
* 备注
*/
@Column(length = 500)
private String remark;
}

6. JSAPI支付实现

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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package com.wechat.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wechat.config.WeChatPayConfig;
import com.wechat.entity.PayOrder;
import com.wechat.repository.PayOrderRepository;
import com.wechatpay.pay.java.core.Config;
import com.wechatpay.pay.java.service.payments.jsapi.JsapiService;
import com.wechatpay.pay.java.service.payments.jsapi.model.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;

/**
* JSAPI支付服务(公众号/小程序支付)
* @author Java实战
*/
@Slf4j
@Service
public class JsapiPayService {

@Autowired
private WeChatPayConfig weChatPayConfig;

@Autowired
private Config config;

@Autowired
private PayOrderRepository payOrderRepository;

/**
* 创建JSAPI支付订单
*/
@Transactional
public JSONObject createJsapiOrder(String userId, String openId, String body, Integer totalAmount) {
try {
// 生成商户订单号
String orderNo = generateOrderNo();

log.info("创建JSAPI支付订单: userId={}, orderNo={}, body={}, amount={}",
userId, orderNo, body, totalAmount);

// 保存订单到数据库
PayOrder payOrder = PayOrder.builder()
.orderNo(orderNo)
.userId(userId)
.openId(openId)
.body(body)
.totalAmount(totalAmount)
.payType("JSAPI")
.status("WAIT_PAY")
.currency("CNY")
.createTime(LocalDateTime.now())
.expireTime(LocalDateTime.now().plusMinutes(30))
.build();

payOrderRepository.save(payOrder);

// 调用微信统一下单接口
JsapiService jsapiService = new JsapiService.Builder().config(config).build();

PrepayRequest request = new PrepayRequest();

// 应用ID
request.setAppid(weChatPayConfig.getAppId());

// 商户号
request.setMchid(weChatPayConfig.getMchId());

// 商品描述
request.setDescription(body);

// 商户订单号
request.setOutTradeNo(orderNo);

// 回调通知URL
request.setNotifyUrl(weChatPayConfig.getNotifyUrl());

// 订单金额
Amount amount = new Amount();
amount.setTotal(totalAmount);
amount.setCurrency("CNY");
request.setAmount(amount);

// 支付者信息
Payer payer = new Payer();
payer.setOpenid(openId);
request.setPayer(payer);

// 订单过期时间(RFC 3339格式)
request.setTimeExpire(formatRFC3339(payOrder.getExpireTime()));

// 调用下单接口
PrepayResponse response = jsapiService.prepay(request);

log.info("JSAPI下单成功: orderNo={}, prepayId={}", orderNo, response.getPrepayId());

// 构建前端调起支付所需参数
JSONObject result = new JSONObject();
result.put("orderNo", orderNo);
result.put("appId", weChatPayConfig.getAppId());
result.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
result.put("nonceStr", UUID.randomUUID().toString().replace("-", ""));
result.put("package", "prepay_id=" + response.getPrepayId());
result.put("signType", "RSA");

// 生成签名(前端调起支付时需要)
String paySign = generatePaySign(result);
result.put("paySign", paySign);

return result;

} catch (Exception e) {
log.error("创建JSAPI支付订单失败: userId={}", userId, e);
throw new RuntimeException("创建支付订单失败", e);
}
}

/**
* 查询订单状态
*/
public JSONObject queryOrder(String orderNo) {
try {
log.info("查询订单状态: orderNo={}", orderNo);

JsapiService jsapiService = new JsapiService.Builder().config(config).build();

QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
request.setMchid(weChatPayConfig.getMchId());
request.setOutTradeNo(orderNo);

Transaction transaction = jsapiService.queryOrderByOutTradeNo(request);

log.info("查询订单成功: orderNo={}, status={}", orderNo, transaction.getTradeState());

// 转换为JSON返回
return JSON.parseObject(JSON.toJSONString(transaction));

} catch (Exception e) {
log.error("查询订单失败: orderNo={}", orderNo, e);
throw new RuntimeException("查询订单失败", e);
}
}

/**
* 关闭订单
*/
@Transactional
public void closeOrder(String orderNo) {
try {
log.info("关闭订单: orderNo={}", orderNo);

JsapiService jsapiService = new JsapiService.Builder().config(config).build();

CloseOrderRequest request = new CloseOrderRequest();
request.setMchid(weChatPayConfig.getMchId());
request.setOutTradeNo(orderNo);

jsapiService.closeOrder(request);

// 更新数据库订单状态
PayOrder payOrder = payOrderRepository.findByOrderNo(orderNo);
if (payOrder != null) {
payOrder.setStatus("CLOSED");
payOrder.setUpdateTime(LocalDateTime.now());
payOrderRepository.save(payOrder);
}

log.info("关闭订单成功: orderNo={}", orderNo);

} catch (Exception e) {
log.error("关闭订单失败: orderNo={}", orderNo, e);
throw new RuntimeException("关闭订单失败", e);
}
}

/**
* 生成商户订单号
*/
private String generateOrderNo() {
return "ORDER" + System.currentTimeMillis() +
String.format("%06d", (int)(Math.random() * 1000000));
}

/**
* 格式化为RFC 3339时间格式
*/
private String formatRFC3339(LocalDateTime dateTime) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'+08:00'");
return dateTime.format(formatter);
}

/**
* 生成支付签名
*/
private String generatePaySign(JSONObject params) {
// 实际实现需要使用商户私钥进行RSA签名
// 这里简化处理
return "signature";
}
}

7. APP支付实现

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
104
105
106
107
108
109
110
111
112
113
114
package com.wechat.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wechat.config.WeChatPayConfig;
import com.wechat.entity.PayOrder;
import com.wechat.repository.PayOrderRepository;
import com.wechatpay.pay.java.core.Config;
import com.wechatpay.pay.java.service.payments.app.AppService;
import com.wechatpay.pay.java.service.payments.app.model.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.UUID;

/**
* APP支付服务
* @author Java实战
*/
@Slf4j
@Service
public class AppPayService {

@Autowired
private WeChatPayConfig weChatPayConfig;

@Autowired
private Config config;

@Autowired
private PayOrderRepository payOrderRepository;

/**
* 创建APP支付订单
*/
@Transactional
public JSONObject createAppOrder(String userId, String body, Integer totalAmount) {
try {
// 生成商户订单号
String orderNo = "ORDER" + System.currentTimeMillis() +
String.format("%06d", (int)(Math.random() * 1000000));

log.info("创建APP支付订单: userId={}, orderNo={}, body={}, amount={}",
userId, orderNo, body, totalAmount);

// 保存订单到数据库
PayOrder payOrder = PayOrder.builder()
.orderNo(orderNo)
.userId(userId)
.body(body)
.totalAmount(totalAmount)
.payType("APP")
.status("WAIT_PAY")
.currency("CNY")
.createTime(LocalDateTime.now())
.expireTime(LocalDateTime.now().plusMinutes(30))
.build();

payOrderRepository.save(payOrder);

// 调用微信APP支付统一下单接口
AppService appService = new AppService.Builder().config(config).build();

PrepayRequest request = new PrepayRequest();
request.setAppid(weChatPayConfig.getAppId());
request.setMchid(weChatPayConfig.getMchId());
request.setDescription(body);
request.setOutTradeNo(orderNo);
request.setNotifyUrl(weChatPayConfig.getNotifyUrl());

// 订单金额
Amount amount = new Amount();
amount.setTotal(totalAmount);
amount.setCurrency("CNY");
request.setAmount(amount);

// 调用下单接口
PrepayResponse response = appService.prepay(request);

log.info("APP下单成功: orderNo={}, prepayId={}", orderNo, response.getPrepayId());

// 构建APP调起支付所需参数
JSONObject result = new JSONObject();
result.put("orderNo", orderNo);
result.put("appid", weChatPayConfig.getAppId());
result.put("partnerid", weChatPayConfig.getMchId());
result.put("prepayid", response.getPrepayId());
result.put("package", "Sign=WXPay");
result.put("noncestr", UUID.randomUUID().toString().replace("-", ""));
result.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));

// 生成签名
String sign = generateAppPaySign(result);
result.put("sign", sign);

return result;

} catch (Exception e) {
log.error("创建APP支付订单失败: userId={}", userId, e);
throw new RuntimeException("创建支付订单失败", e);
}
}

/**
* 生成APP支付签名
*/
private String generateAppPaySign(JSONObject params) {
// 实际实现需要使用商户私钥进行RSA签名
return "signature";
}
}

8. Native支付实现

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
package com.wechat.service;

import com.wechat.config.WeChatPayConfig;
import com.wechat.entity.PayOrder;
import com.wechat.repository.PayOrderRepository;
import com.wechatpay.pay.java.core.Config;
import com.wechatpay.pay.java.service.payments.nativepay.NativePayService;
import com.wechatpay.pay.java.service.payments.nativepay.model.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;

/**
* Native支付服务(扫码支付)
* @author Java实战
*/
@Slf4j
@Service
public class NativePayService {

@Autowired
private WeChatPayConfig weChatPayConfig;

@Autowired
private Config config;

@Autowired
private PayOrderRepository payOrderRepository;

/**
* 创建Native支付订单
*/
@Transactional
public String createNativeOrder(String userId, String body, Integer totalAmount) {
try {
// 生成商户订单号
String orderNo = "ORDER" + System.currentTimeMillis() +
String.format("%06d", (int)(Math.random() * 1000000));

log.info("创建Native支付订单: userId={}, orderNo={}, body={}, amount={}",
userId, orderNo, body, totalAmount);

// 保存订单到数据库
PayOrder payOrder = PayOrder.builder()
.orderNo(orderNo)
.userId(userId)
.body(body)
.totalAmount(totalAmount)
.payType("NATIVE")
.status("WAIT_PAY")
.currency("CNY")
.createTime(LocalDateTime.now())
.expireTime(LocalDateTime.now().plusMinutes(30))
.build();

payOrderRepository.save(payOrder);

// 调用微信Native支付统一下单接口
com.wechatpay.pay.java.service.payments.nativepay.NativePayService nativePayService =
new com.wechatpay.pay.java.service.payments.nativepay.NativePayService.Builder()
.config(config)
.build();

PrepayRequest request = new PrepayRequest();
request.setAppid(weChatPayConfig.getAppId());
request.setMchid(weChatPayConfig.getMchId());
request.setDescription(body);
request.setOutTradeNo(orderNo);
request.setNotifyUrl(weChatPayConfig.getNotifyUrl());

// 订单金额
Amount amount = new Amount();
amount.setTotal(totalAmount);
amount.setCurrency("CNY");
request.setAmount(amount);

// 调用下单接口
PrepayResponse response = nativePayService.prepay(request);

log.info("Native下单成功: orderNo={}, codeUrl={}", orderNo, response.getCodeUrl());

// 返回二维码链接(前端用于生成二维码)
return response.getCodeUrl();

} catch (Exception e) {
log.error("创建Native支付订单失败: userId={}", userId, e);
throw new RuntimeException("创建支付订单失败", e);
}
}
}

9. 支付回调处理

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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
package com.wechat.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wechat.config.WeChatPayConfig;
import com.wechat.entity.PayOrder;
import com.wechat.repository.PayOrderRepository;
import com.wechatpay.pay.java.core.Config;
import com.wechatpay.pay.java.core.notification.NotificationParser;
import com.wechatpay.pay.java.core.notification.RequestParam;
import com.wechatpay.pay.java.service.payments.model.Transaction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;

/**
* 支付回调处理服务
* @author Java实战
*/
@Slf4j
@Service
public class PayNotifyService {

@Autowired
private WeChatPayConfig weChatPayConfig;

@Autowired
private Config config;

@Autowired
private PayOrderRepository payOrderRepository;

@Autowired
private StringRedisTemplate redisTemplate;

private static final String NOTIFY_LOCK_PREFIX = "pay:notify:lock:";

/**
* 处理支付回调通知
*/
@Transactional
public String handlePayNotify(String requestBody, String signature,
String nonce, String timestamp, String serialNo) {
try {
log.info("收到支付回调通知: signature={}, nonce={}, timestamp={}",
signature, nonce, timestamp);

// 验证签名
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(serialNo)
.nonce(nonce)
.signature(signature)
.timestamp(timestamp)
.body(requestBody)
.build();

NotificationParser parser = new NotificationParser(config);
Transaction transaction = parser.parse(requestParam, Transaction.class);

String orderNo = transaction.getOutTradeNo();
String transactionId = transaction.getTransactionId();
String tradeState = transaction.getTradeState().name();

log.info("支付回调解析成功: orderNo={}, transactionId={}, tradeState={}",
orderNo, transactionId, tradeState);

// 防止重复通知(使用Redis分布式锁)
String lockKey = NOTIFY_LOCK_PREFIX + orderNo;
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 5, TimeUnit.MINUTES);

if (Boolean.FALSE.equals(locked)) {
log.warn("重复的支付回调通知: orderNo={}", orderNo);
return buildSuccessResponse();
}

try {
// 查询订单
PayOrder payOrder = payOrderRepository.findByOrderNo(orderNo);
if (payOrder == null) {
log.error("订单不存在: orderNo={}", orderNo);
return buildFailResponse("订单不存在");
}

// 检查订单状态
if ("PAY_SUCCESS".equals(payOrder.getStatus())) {
log.warn("订单已支付: orderNo={}", orderNo);
return buildSuccessResponse();
}

// 处理支付成功
if ("SUCCESS".equals(tradeState)) {
processPaySuccess(payOrder, transaction);
} else if ("REFUND".equals(tradeState)) {
processRefund(payOrder, transaction);
} else {
log.warn("未知的交易状态: orderNo={}, tradeState={}", orderNo, tradeState);
}

return buildSuccessResponse();

} finally {
// 释放锁
redisTemplate.delete(lockKey);
}

} catch (Exception e) {
log.error("处理支付回调失败", e);
return buildFailResponse("处理失败");
}
}

/**
* 处理支付成功
*/
private void processPaySuccess(PayOrder payOrder, Transaction transaction) {
try {
log.info("处理支付成功: orderNo={}, transactionId={}",
payOrder.getOrderNo(), transaction.getTransactionId());

// 更新订单状态
payOrder.setTransactionId(transaction.getTransactionId());
payOrder.setStatus("PAY_SUCCESS");
payOrder.setPayAmount(transaction.getAmount().getPayerTotal());
payOrder.setPayTime(parsePayTime(transaction.getSuccessTime()));
payOrder.setUpdateTime(LocalDateTime.now());

payOrderRepository.save(payOrder);

// 执行业务逻辑(异步)
executeBusiness(payOrder);

log.info("支付成功处理完成: orderNo={}", payOrder.getOrderNo());

} catch (Exception e) {
log.error("处理支付成功失败: orderNo={}", payOrder.getOrderNo(), e);
throw e;
}
}

/**
* 处理退款
*/
private void processRefund(PayOrder payOrder, Transaction transaction) {
log.info("处理退款: orderNo={}, transactionId={}",
payOrder.getOrderNo(), transaction.getTransactionId());

// 退款处理逻辑
// ...
}

/**
* 执行业务逻辑
*/
private void executeBusiness(PayOrder payOrder) {
// 异步执行业务逻辑
// 例如:发货、增加会员、发放优惠券等
log.info("执行业务逻辑: orderNo={}", payOrder.getOrderNo());
}

/**
* 解析支付时间
*/
private LocalDateTime parsePayTime(String payTime) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX");
return LocalDateTime.parse(payTime, formatter);
} catch (Exception e) {
log.error("解析支付时间失败: payTime={}", payTime, e);
return LocalDateTime.now();
}
}

/**
* 构建成功响应
*/
private String buildSuccessResponse() {
JSONObject response = new JSONObject();
response.put("code", "SUCCESS");
response.put("message", "成功");
return response.toJSONString();
}

/**
* 构建失败响应
*/
private String buildFailResponse(String message) {
JSONObject response = new JSONObject();
response.put("code", "FAIL");
response.put("message", message);
return response.toJSONString();
}
}

10. 退款服务

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
104
105
106
package com.wechat.service;

import com.wechat.config.WeChatPayConfig;
import com.wechat.entity.PayOrder;
import com.wechat.repository.PayOrderRepository;
import com.wechatpay.pay.java.core.Config;
import com.wechatpay.pay.java.service.refund.RefundService;
import com.wechatpay.pay.java.service.refund.model.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
* 退款服务
* @author Java实战
*/
@Slf4j
@Service
public class WeChatRefundService {

@Autowired
private WeChatPayConfig weChatPayConfig;

@Autowired
private Config config;

@Autowired
private PayOrderRepository payOrderRepository;

/**
* 申请退款
*/
@Transactional
public String refund(String orderNo, Integer refundAmount, String reason) {
try {
log.info("申请退款: orderNo={}, refundAmount={}, reason={}",
orderNo, refundAmount, reason);

// 查询订单
PayOrder payOrder = payOrderRepository.findByOrderNo(orderNo);
if (payOrder == null) {
throw new RuntimeException("订单不存在");
}

if (!"PAY_SUCCESS".equals(payOrder.getStatus())) {
throw new RuntimeException("订单状态不允许退款");
}

// 生成退款单号
String refundNo = "REFUND" + System.currentTimeMillis();

// 调用微信退款接口
RefundService refundService = new RefundService.Builder().config(config).build();

CreateRequest request = new CreateRequest();
request.setOutTradeNo(orderNo);
request.setOutRefundNo(refundNo);
request.setReason(reason);
request.setNotifyUrl(weChatPayConfig.getRefundNotifyUrl());

// 退款金额
AmountReq amount = new AmountReq();
amount.setRefund(Long.valueOf(refundAmount));
amount.setTotal(Long.valueOf(payOrder.getTotalAmount()));
amount.setCurrency("CNY");
request.setAmount(amount);

// 调用退款接口
Refund refund = refundService.create(request);

log.info("退款申请成功: orderNo={}, refundNo={}, status={}",
orderNo, refundNo, refund.getStatus());

return refundNo;

} catch (Exception e) {
log.error("申请退款失败: orderNo={}", orderNo, e);
throw new RuntimeException("申请退款失败", e);
}
}

/**
* 查询退款状态
*/
public Refund queryRefund(String refundNo) {
try {
log.info("查询退款状态: refundNo={}", refundNo);

RefundService refundService = new RefundService.Builder().config(config).build();

QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
request.setOutRefundNo(refundNo);

Refund refund = refundService.queryByOutRefundNo(request);

log.info("查询退款成功: refundNo={}, status={}", refundNo, refund.getStatus());

return refund;

} catch (Exception e) {
log.error("查询退款失败: refundNo={}", refundNo, e);
throw new RuntimeException("查询退款失败", e);
}
}
}

11. 控制器

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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
package com.wechat.controller;

import com.alibaba.fastjson.JSONObject;
import com.wechat.service.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;

/**
* 微信支付控制器
* @author Java实战
*/
@Slf4j
@RestController
@RequestMapping("/api/wechat/pay")
public class WeChatPayController {

@Autowired
private JsapiPayService jsapiPayService;

@Autowired
private AppPayService appPayService;

@Autowired
private NativePayService nativePayService;

@Autowired
private PayNotifyService payNotifyService;

@Autowired
private WeChatRefundService refundService;

/**
* JSAPI支付下单
*/
@PostMapping("/jsapi/create")
public ResponseEntity<JSONObject> createJsapiOrder(@RequestBody JSONObject params) {
try {
String userId = params.getString("userId");
String openId = params.getString("openId");
String body = params.getString("body");
Integer totalAmount = params.getInteger("totalAmount");

JSONObject result = jsapiPayService.createJsapiOrder(userId, openId, body, totalAmount);

return ResponseEntity.ok(result);

} catch (Exception e) {
log.error("JSAPI支付下单失败", e);
JSONObject error = new JSONObject();
error.put("code", "FAIL");
error.put("message", e.getMessage());
return ResponseEntity.internalServerError().body(error);
}
}

/**
* APP支付下单
*/
@PostMapping("/app/create")
public ResponseEntity<JSONObject> createAppOrder(@RequestBody JSONObject params) {
try {
String userId = params.getString("userId");
String body = params.getString("body");
Integer totalAmount = params.getInteger("totalAmount");

JSONObject result = appPayService.createAppOrder(userId, body, totalAmount);

return ResponseEntity.ok(result);

} catch (Exception e) {
log.error("APP支付下单失败", e);
JSONObject error = new JSONObject();
error.put("code", "FAIL");
error.put("message", e.getMessage());
return ResponseEntity.internalServerError().body(error);
}
}

/**
* Native支付下单
*/
@PostMapping("/native/create")
public ResponseEntity<JSONObject> createNativeOrder(@RequestBody JSONObject params) {
try {
String userId = params.getString("userId");
String body = params.getString("body");
Integer totalAmount = params.getInteger("totalAmount");

String codeUrl = nativePayService.createNativeOrder(userId, body, totalAmount);

JSONObject result = new JSONObject();
result.put("code", "SUCCESS");
result.put("codeUrl", codeUrl);

return ResponseEntity.ok(result);

} catch (Exception e) {
log.error("Native支付下单失败", e);
JSONObject error = new JSONObject();
error.put("code", "FAIL");
error.put("message", e.getMessage());
return ResponseEntity.internalServerError().body(error);
}
}

/**
* 支付回调通知
*/
@PostMapping("/notify")
public String payNotify(HttpServletRequest request) {
try {
// 读取请求体
StringBuilder requestBody = new StringBuilder();
try (BufferedReader reader = request.getReader()) {
String line;
while ((line = reader.readLine()) != null) {
requestBody.append(line);
}
}

// 获取签名相关信息
String signature = request.getHeader("Wechatpay-Signature");
String nonce = request.getHeader("Wechatpay-Nonce");
String timestamp = request.getHeader("Wechatpay-Timestamp");
String serialNo = request.getHeader("Wechatpay-Serial");

// 处理回调
return payNotifyService.handlePayNotify(
requestBody.toString(), signature, nonce, timestamp, serialNo);

} catch (Exception e) {
log.error("处理支付回调失败", e);
return "{\"code\":\"FAIL\",\"message\":\"处理失败\"}";
}
}

/**
* 查询订单
*/
@GetMapping("/query/{orderNo}")
public ResponseEntity<JSONObject> queryOrder(@PathVariable String orderNo) {
try {
JSONObject result = jsapiPayService.queryOrder(orderNo);
return ResponseEntity.ok(result);

} catch (Exception e) {
log.error("查询订单失败", e);
JSONObject error = new JSONObject();
error.put("code", "FAIL");
error.put("message", e.getMessage());
return ResponseEntity.internalServerError().body(error);
}
}

/**
* 关闭订单
*/
@PostMapping("/close/{orderNo}")
public ResponseEntity<JSONObject> closeOrder(@PathVariable String orderNo) {
try {
jsapiPayService.closeOrder(orderNo);

JSONObject result = new JSONObject();
result.put("code", "SUCCESS");
result.put("message", "订单关闭成功");

return ResponseEntity.ok(result);

} catch (Exception e) {
log.error("关闭订单失败", e);
JSONObject error = new JSONObject();
error.put("code", "FAIL");
error.put("message", e.getMessage());
return ResponseEntity.internalServerError().body(error);
}
}

/**
* 申请退款
*/
@PostMapping("/refund")
public ResponseEntity<JSONObject> refund(@RequestBody JSONObject params) {
try {
String orderNo = params.getString("orderNo");
Integer refundAmount = params.getInteger("refundAmount");
String reason = params.getString("reason");

String refundNo = refundService.refund(orderNo, refundAmount, reason);

JSONObject result = new JSONObject();
result.put("code", "SUCCESS");
result.put("refundNo", refundNo);
result.put("message", "退款申请成功");

return ResponseEntity.ok(result);

} catch (Exception e) {
log.error("申请退款失败", e);
JSONObject error = new JSONObject();
error.put("code", "FAIL");
error.put("message", e.getMessage());
return ResponseEntity.internalServerError().body(error);
}
}
}

12. Repository

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
package com.wechat.repository;

import com.wechat.entity.PayOrder;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;
import java.util.List;

/**
* 支付订单Repository
* @author Java实战
*/
@Repository
public interface PayOrderRepository extends JpaRepository<PayOrder, Long> {

/**
* 根据订单号查询
*/
PayOrder findByOrderNo(String orderNo);

/**
* 根据微信支付订单号查询
*/
PayOrder findByTransactionId(String transactionId);

/**
* 根据用户ID查询订单列表
*/
List<PayOrder> findByUserId(String userId);

/**
* 查询过期未支付订单
*/
List<PayOrder> findByStatusAndExpireTimeBefore(String status, LocalDateTime expireTime);
}

13. 总结

微信支付是电商系统的核心功能。通过本文的详细介绍,我们了解了:

  1. 多种支付方式: JSAPI、APP、H5、Native等多种支付场景
  2. 统一下单流程: 完整的下单、支付、回调流程
  3. 支付回调处理: 签名验证、防重复通知、订单状态更新
  4. 退款功能: 申请退款、查询退款状态
  5. 安全机制: 签名验证、幂等性保证、分布式锁

通过合理的微信支付集成实现,可以为用户提供便捷、安全的支付体验。


Java实战要点:

  • 使用官方SDK简化开发
  • 支付回调必须验证签名
  • 使用分布式锁防止重复通知
  • 订单状态流转要严格控制
  • 敏感信息需要加密存储

代码注解说明:

  • @Transactional: 事务管理确保数据一致性
  • PrepayRequest: 统一下单请求对象
  • Transaction: 支付结果对象
  • NotificationParser: 回调通知解析器
  • 金额单位: 微信支付金额单位为分