mirror of
https://gitee.com/binary/weixin-java-tools.git
synced 2026-01-23 05:12:03 +08:00
🆕 #3842 【微信支付】添加 wx-java-pay-multi-spring-boot-starter 模块支持多公众号关联配置
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
<module>wx-java-mp-multi-spring-boot-starter</module>
|
||||
<module>wx-java-mp-spring-boot-starter</module>
|
||||
<module>wx-java-pay-spring-boot-starter</module>
|
||||
<module>wx-java-pay-multi-spring-boot-starter</module>
|
||||
<module>wx-java-open-multi-spring-boot-starter</module>
|
||||
<module>wx-java-open-spring-boot-starter</module>
|
||||
<module>wx-java-qidian-spring-boot-starter</module>
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
# wx-java-pay-multi-spring-boot-starter
|
||||
|
||||
## 快速开始
|
||||
|
||||
本starter支持微信支付多公众号关联配置,适用于以下场景:
|
||||
- 一个服务商需要为多个公众号提供支付服务
|
||||
- 一个系统需要支持多个公众号的支付业务
|
||||
- 需要根据不同的appId动态切换支付配置
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 1. 引入依赖
|
||||
|
||||
在项目的 `pom.xml` 中添加以下依赖:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java-pay-multi-spring-boot-starter</artifactId>
|
||||
<version>${version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 2. 添加配置
|
||||
|
||||
在 `application.yml` 或 `application.properties` 中配置多个公众号的支付信息。
|
||||
|
||||
#### 配置示例(application.yml)
|
||||
|
||||
##### V2版本配置
|
||||
```yml
|
||||
wx:
|
||||
pay:
|
||||
configs:
|
||||
# 配置1 - 可以使用appId作为key
|
||||
wx1234567890abcdef:
|
||||
appId: wx1234567890abcdef
|
||||
mchId: 1234567890
|
||||
mchKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
keyPath: classpath:cert/app1/apiclient_cert.p12
|
||||
notifyUrl: https://example.com/pay/notify
|
||||
# 配置2 - 也可以使用自定义标识作为key
|
||||
config2:
|
||||
appId: wx9876543210fedcba
|
||||
mchId: 9876543210
|
||||
mchKey: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
keyPath: classpath:cert/app2/apiclient_cert.p12
|
||||
notifyUrl: https://example.com/pay/notify
|
||||
```
|
||||
|
||||
##### V3版本配置
|
||||
```yml
|
||||
wx:
|
||||
pay:
|
||||
configs:
|
||||
# 公众号1配置
|
||||
wx1234567890abcdef:
|
||||
appId: wx1234567890abcdef
|
||||
mchId: 1234567890
|
||||
apiV3Key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
certSerialNo: 62C6CEAA360BCxxxxxxxxxxxxxxx
|
||||
privateKeyPath: classpath:cert/app1/apiclient_key.pem
|
||||
privateCertPath: classpath:cert/app1/apiclient_cert.pem
|
||||
notifyUrl: https://example.com/pay/notify
|
||||
# 公众号2配置
|
||||
wx9876543210fedcba:
|
||||
appId: wx9876543210fedcba
|
||||
mchId: 9876543210
|
||||
apiV3Key: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
certSerialNo: 73D7DFBB471CDxxxxxxxxxxxxxxx
|
||||
privateKeyPath: classpath:cert/app2/apiclient_key.pem
|
||||
privateCertPath: classpath:cert/app2/apiclient_cert.pem
|
||||
notifyUrl: https://example.com/pay/notify
|
||||
```
|
||||
|
||||
##### V3服务商版本配置
|
||||
```yml
|
||||
wx:
|
||||
pay:
|
||||
configs:
|
||||
# 服务商为公众号1提供服务
|
||||
config1:
|
||||
appId: wxe97b2x9c2b3d # 服务商appId
|
||||
mchId: 16486610 # 服务商商户号
|
||||
subAppId: wx118cexxe3c07679 # 子商户公众号appId
|
||||
subMchId: 16496705 # 子商户号
|
||||
apiV3Key: Dc1DBwSc094jAKDGR5aqqb7PTHr
|
||||
privateKeyPath: classpath:cert/apiclient_key.pem
|
||||
privateCertPath: classpath:cert/apiclient_cert.pem
|
||||
# 服务商为公众号2提供服务
|
||||
config2:
|
||||
appId: wxe97b2x9c2b3d # 服务商appId(可以相同)
|
||||
mchId: 16486610 # 服务商商户号(可以相同)
|
||||
subAppId: wx228dexxf4d18890 # 子商户公众号appId(不同)
|
||||
subMchId: 16496706 # 子商户号(不同)
|
||||
apiV3Key: Dc1DBwSc094jAKDGR5aqqb7PTHr
|
||||
privateKeyPath: classpath:cert/apiclient_key.pem
|
||||
privateCertPath: classpath:cert/apiclient_cert.pem
|
||||
```
|
||||
|
||||
#### 配置示例(application.properties)
|
||||
|
||||
```properties
|
||||
# 公众号1配置
|
||||
wx.pay.configs.wx1234567890abcdef.app-id=wx1234567890abcdef
|
||||
wx.pay.configs.wx1234567890abcdef.mch-id=1234567890
|
||||
wx.pay.configs.wx1234567890abcdef.apiv3-key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
wx.pay.configs.wx1234567890abcdef.cert-serial-no=62C6CEAA360BCxxxxxxxxxxxxxxx
|
||||
wx.pay.configs.wx1234567890abcdef.private-key-path=classpath:cert/app1/apiclient_key.pem
|
||||
wx.pay.configs.wx1234567890abcdef.private-cert-path=classpath:cert/app1/apiclient_cert.pem
|
||||
wx.pay.configs.wx1234567890abcdef.notify-url=https://example.com/pay/notify
|
||||
|
||||
# 公众号2配置
|
||||
wx.pay.configs.wx9876543210fedcba.app-id=wx9876543210fedcba
|
||||
wx.pay.configs.wx9876543210fedcba.mch-id=9876543210
|
||||
wx.pay.configs.wx9876543210fedcba.apiv3-key=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
wx.pay.configs.wx9876543210fedcba.cert-serial-no=73D7DFBB471CDxxxxxxxxxxxxxxx
|
||||
wx.pay.configs.wx9876543210fedcba.private-key-path=classpath:cert/app2/apiclient_key.pem
|
||||
wx.pay.configs.wx9876543210fedcba.private-cert-path=classpath:cert/app2/apiclient_cert.pem
|
||||
wx.pay.configs.wx9876543210fedcba.notify-url=https://example.com/pay/notify
|
||||
```
|
||||
|
||||
### 3. 使用示例
|
||||
|
||||
自动注入的类型:`WxPayMultiServices`
|
||||
|
||||
```java
|
||||
import com.binarywang.spring.starter.wxjava.pay.service.WxPayMultiServices;
|
||||
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
|
||||
import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryV3Result;
|
||||
import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
|
||||
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
|
||||
import com.github.binarywang.wxpay.service.WxPayService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class PayService {
|
||||
@Autowired
|
||||
private WxPayMultiServices wxPayMultiServices;
|
||||
|
||||
/**
|
||||
* 为不同的公众号创建支付订单
|
||||
*
|
||||
* @param configKey 配置标识(即 wx.pay.configs.<configKey> 中的 key,可以是 appId 或自定义标识)
|
||||
*/
|
||||
public void createOrder(String configKey, String openId, Integer totalFee, String body) throws Exception {
|
||||
// 根据配置标识获取对应的WxPayService
|
||||
WxPayService wxPayService = wxPayMultiServices.getWxPayService(configKey);
|
||||
|
||||
if (wxPayService == null) {
|
||||
throw new IllegalArgumentException("未找到配置标识对应的微信支付配置: " + configKey);
|
||||
}
|
||||
|
||||
// 使用WxPayService进行支付操作
|
||||
WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
|
||||
request.setOutTradeNo(generateOutTradeNo());
|
||||
request.setDescription(body);
|
||||
request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(totalFee));
|
||||
request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(openId));
|
||||
request.setNotifyUrl(wxPayService.getConfig().getNotifyUrl());
|
||||
|
||||
// V3统一下单
|
||||
WxPayUnifiedOrderV3Result.JsapiResult result =
|
||||
wxPayService.createOrderV3(TradeTypeEnum.JSAPI, request);
|
||||
|
||||
// 返回给前端用于调起支付
|
||||
// ...
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务商模式示例
|
||||
*/
|
||||
public void serviceProviderExample(String configKey) throws Exception {
|
||||
// 使用配置标识获取WxPayService
|
||||
WxPayService wxPayService = wxPayMultiServices.getWxPayService(configKey);
|
||||
|
||||
if (wxPayService == null) {
|
||||
throw new IllegalArgumentException("未找到配置: " + configKey);
|
||||
}
|
||||
|
||||
// 获取子商户的配置信息
|
||||
String subAppId = wxPayService.getConfig().getSubAppId();
|
||||
String subMchId = wxPayService.getConfig().getSubMchId();
|
||||
|
||||
// 进行支付操作
|
||||
// ...
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询订单示例
|
||||
*
|
||||
* @param configKey 配置标识(即 wx.pay.configs.<configKey> 中的 key)
|
||||
*/
|
||||
public void queryOrder(String configKey, String outTradeNo) throws Exception {
|
||||
WxPayService wxPayService = wxPayMultiServices.getWxPayService(configKey);
|
||||
|
||||
if (wxPayService == null) {
|
||||
throw new IllegalArgumentException("未找到配置标识对应的微信支付配置: " + configKey);
|
||||
}
|
||||
|
||||
// 查询订单
|
||||
WxPayOrderQueryV3Result result = wxPayService.queryOrderV3(null, outTradeNo);
|
||||
// 处理查询结果
|
||||
// ...
|
||||
}
|
||||
|
||||
private String generateOutTradeNo() {
|
||||
// 生成商户订单号
|
||||
return "ORDER_" + System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 配置说明
|
||||
|
||||
#### 必填配置项
|
||||
|
||||
| 配置项 | 说明 | 示例 |
|
||||
|--------|------|------|
|
||||
| appId | 公众号或小程序的appId | wx1234567890abcdef |
|
||||
| mchId | 商户号 | 1234567890 |
|
||||
|
||||
#### V2版本配置项
|
||||
|
||||
| 配置项 | 说明 | 是否必填 |
|
||||
|--------|------|----------|
|
||||
| mchKey | 商户密钥 | 是(V2) |
|
||||
| keyPath | p12证书文件路径 | 部分接口需要 |
|
||||
|
||||
#### V3版本配置项
|
||||
|
||||
| 配置项 | 说明 | 是否必填 |
|
||||
|--------|------|----------|
|
||||
| apiV3Key | API V3密钥 | 是(V3) |
|
||||
| certSerialNo | 证书序列号 | 是(V3) |
|
||||
| privateKeyPath | apiclient_key.pem路径 | 是(V3) |
|
||||
| privateCertPath | apiclient_cert.pem路径 | 是(V3) |
|
||||
|
||||
#### 服务商模式配置项
|
||||
|
||||
| 配置项 | 说明 | 是否必填 |
|
||||
|--------|------|----------|
|
||||
| subAppId | 子商户公众号appId | 服务商模式必填 |
|
||||
| subMchId | 子商户号 | 服务商模式必填 |
|
||||
|
||||
#### 可选配置项
|
||||
|
||||
| 配置项 | 说明 | 默认值 |
|
||||
|--------|------|--------|
|
||||
| notifyUrl | 支付结果通知URL | 无 |
|
||||
| refundNotifyUrl | 退款结果通知URL | 无 |
|
||||
| serviceId | 微信支付分serviceId | 无 |
|
||||
| payScoreNotifyUrl | 支付分回调地址 | 无 |
|
||||
| payScorePermissionNotifyUrl | 支付分授权回调地址 | 无 |
|
||||
| useSandboxEnv | 是否使用沙箱环境 | false |
|
||||
| apiHostUrl | 自定义API主机地址 | https://api.mch.weixin.qq.com |
|
||||
| strictlyNeedWechatPaySerial | 是否所有V3请求都添加序列号头 | false |
|
||||
| fullPublicKeyModel | 是否完全使用公钥模式 | false |
|
||||
| publicKeyId | 公钥ID | 无 |
|
||||
| publicKeyPath | 公钥文件路径 | 无 |
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 1. 如何选择配置的key?
|
||||
|
||||
配置的key(即 `wx.pay.configs.<configKey>` 中的 `<configKey>` 部分)可以自由选择:
|
||||
- 可以使用appId作为key(如 `wx.pay.configs.wx1234567890abcdef`),这样调用 `getWxPayService("wx1234567890abcdef")` 时就像直接用 appId 获取服务
|
||||
- 可以使用自定义标识(如 `wx.pay.configs.config1`),调用时使用 `getWxPayService("config1")`
|
||||
|
||||
**注意**:`getWxPayService(configKey)` 方法的参数是配置文件中定义的 key,而不是 appId。只有当你使用 appId 作为配置 key 时,才能直接传入 appId。
|
||||
|
||||
### 2. V2和V3配置可以混用吗?
|
||||
|
||||
可以。不同的配置可以使用不同的版本,例如:
|
||||
```yml
|
||||
wx:
|
||||
pay:
|
||||
configs:
|
||||
app1: # V2配置
|
||||
appId: wx111
|
||||
mchId: 111
|
||||
mchKey: xxx
|
||||
app2: # V3配置
|
||||
appId: wx222
|
||||
mchId: 222
|
||||
apiV3Key: yyy
|
||||
privateKeyPath: xxx
|
||||
```
|
||||
|
||||
### 3. 证书文件如何放置?
|
||||
|
||||
证书文件可以放在以下位置:
|
||||
- `src/main/resources` 目录下,使用 `classpath:` 前缀
|
||||
- 服务器绝对路径,直接填写完整路径
|
||||
- 建议为不同配置使用不同的目录组织证书
|
||||
|
||||
### 4. 服务商模式如何配置?
|
||||
|
||||
服务商模式需要同时配置服务商信息和子商户信息:
|
||||
- `appId` 和 `mchId` 填写服务商的信息
|
||||
- `subAppId` 和 `subMchId` 填写子商户的信息
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **配置安全**:生产环境中的密钥、证书等敏感信息,建议使用配置中心或环境变量管理
|
||||
2. **证书管理**:不同公众号的证书文件要分开存放,避免混淆
|
||||
3. **懒加载**:WxPayService 实例采用懒加载策略,只有在首次调用时才会创建
|
||||
4. **线程安全**:WxPayMultiServices 的实现是线程安全的
|
||||
5. **配置更新**:如需动态更新配置,可调用 `removeWxPayService(configKey)` 方法移除缓存的实例
|
||||
|
||||
## 更多信息
|
||||
|
||||
- [WxJava 项目首页](https://github.com/Wechat-Group/WxJava)
|
||||
- [微信支付官方文档](https://pay.weixin.qq.com/wiki/doc/api/)
|
||||
- [微信支付V3接口文档](https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml)
|
||||
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>wx-java-spring-boot-starters</artifactId>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<version>4.8.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>wx-java-pay-multi-spring-boot-starter</artifactId>
|
||||
<name>WxJava - Spring Boot Starter for Pay::支持多公众号关联配置</name>
|
||||
<description>微信支付开发的 Spring Boot Starter::支持多公众号关联配置</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>weixin-java-pay</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<version>${spring.boot.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>${spring.boot.version}</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>2.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.binarywang.spring.starter.wxjava.pay.config;
|
||||
|
||||
import com.binarywang.spring.starter.wxjava.pay.properties.WxPayMultiProperties;
|
||||
import com.binarywang.spring.starter.wxjava.pay.service.WxPayMultiServices;
|
||||
import com.binarywang.spring.starter.wxjava.pay.service.WxPayMultiServicesImpl;
|
||||
import com.github.binarywang.wxpay.service.WxPayService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 微信支付多公众号关联自动配置.
|
||||
*
|
||||
* @author Binary Wang
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(WxPayMultiProperties.class)
|
||||
@ConditionalOnClass(WxPayService.class)
|
||||
@ConditionalOnProperty(prefix = WxPayMultiProperties.PREFIX, value = "enabled", matchIfMissing = true)
|
||||
public class WxPayMultiAutoConfiguration {
|
||||
|
||||
/**
|
||||
* 构造微信支付多服务管理对象.
|
||||
*
|
||||
* @param wxPayMultiProperties 多配置属性
|
||||
* @return 微信支付多服务管理对象
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(WxPayMultiServices.class)
|
||||
public WxPayMultiServices wxPayMultiServices(WxPayMultiProperties wxPayMultiProperties) {
|
||||
return new WxPayMultiServicesImpl(wxPayMultiProperties);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.binarywang.spring.starter.wxjava.pay.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 微信支付多公众号关联配置属性类.
|
||||
*
|
||||
* @author Binary Wang
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@ConfigurationProperties(WxPayMultiProperties.PREFIX)
|
||||
public class WxPayMultiProperties implements Serializable {
|
||||
private static final long serialVersionUID = -8015955705346835955L;
|
||||
public static final String PREFIX = "wx.pay";
|
||||
|
||||
/**
|
||||
* 多个公众号的配置信息,key 可以是 appId 或自定义的标识.
|
||||
*/
|
||||
private Map<String, WxPaySingleProperties> configs = new HashMap<>();
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.binarywang.spring.starter.wxjava.pay.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 微信支付单个公众号配置属性类.
|
||||
*
|
||||
* @author Binary Wang
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class WxPaySingleProperties implements Serializable {
|
||||
private static final long serialVersionUID = 3978986361098922525L;
|
||||
|
||||
/**
|
||||
* 设置微信公众号或者小程序等的appid.
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 微信支付商户号.
|
||||
*/
|
||||
private String mchId;
|
||||
|
||||
/**
|
||||
* 微信支付商户密钥.
|
||||
*/
|
||||
private String mchKey;
|
||||
|
||||
/**
|
||||
* 服务商模式下的子商户公众账号ID,普通模式请不要配置.
|
||||
*/
|
||||
private String subAppId;
|
||||
|
||||
/**
|
||||
* 服务商模式下的子商户号,普通模式请不要配置.
|
||||
*/
|
||||
private String subMchId;
|
||||
|
||||
/**
|
||||
* apiclient_cert.p12文件的绝对路径,或者如果放在项目中,请以classpath:开头指定.
|
||||
*/
|
||||
private String keyPath;
|
||||
|
||||
/**
|
||||
* 微信支付分serviceId.
|
||||
*/
|
||||
private String serviceId;
|
||||
|
||||
/**
|
||||
* 证书序列号.
|
||||
*/
|
||||
private String certSerialNo;
|
||||
|
||||
/**
|
||||
* apiV3秘钥.
|
||||
*/
|
||||
private String apiv3Key;
|
||||
|
||||
/**
|
||||
* 微信支付异步回调地址,通知url必须为直接可访问的url,不能携带参数.
|
||||
*/
|
||||
private String notifyUrl;
|
||||
|
||||
/**
|
||||
* 退款结果异步回调地址,通知url必须为直接可访问的url,不能携带参数.
|
||||
*/
|
||||
private String refundNotifyUrl;
|
||||
|
||||
/**
|
||||
* 微信支付分回调地址.
|
||||
*/
|
||||
private String payScoreNotifyUrl;
|
||||
|
||||
/**
|
||||
* 微信支付分授权回调地址.
|
||||
*/
|
||||
private String payScorePermissionNotifyUrl;
|
||||
|
||||
/**
|
||||
* apiv3 商户apiclient_key.pem.
|
||||
*/
|
||||
private String privateKeyPath;
|
||||
|
||||
/**
|
||||
* apiv3 商户apiclient_cert.pem.
|
||||
*/
|
||||
private String privateCertPath;
|
||||
|
||||
/**
|
||||
* 公钥ID.
|
||||
*/
|
||||
private String publicKeyId;
|
||||
|
||||
/**
|
||||
* pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径.
|
||||
*/
|
||||
private String publicKeyPath;
|
||||
|
||||
/**
|
||||
* 微信支付是否使用仿真测试环境.
|
||||
* 默认不使用.
|
||||
*/
|
||||
private boolean useSandboxEnv = false;
|
||||
|
||||
/**
|
||||
* 自定义API主机地址,用于替换默认的 https://api.mch.weixin.qq.com.
|
||||
* 例如:http://proxy.company.com:8080
|
||||
*/
|
||||
private String apiHostUrl;
|
||||
|
||||
/**
|
||||
* 是否将全部v3接口的请求都添加Wechatpay-Serial请求头,默认不添加.
|
||||
*/
|
||||
private boolean strictlyNeedWechatPaySerial = false;
|
||||
|
||||
/**
|
||||
* 是否完全使用公钥模式(用以微信从平台证书到公钥的灰度切换),默认不使用.
|
||||
*/
|
||||
private boolean fullPublicKeyModel = false;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.binarywang.spring.starter.wxjava.pay.service;
|
||||
|
||||
import com.github.binarywang.wxpay.service.WxPayService;
|
||||
|
||||
/**
|
||||
* 微信支付 {@link WxPayService} 所有实例存放类.
|
||||
*
|
||||
* @author Binary Wang
|
||||
*/
|
||||
public interface WxPayMultiServices {
|
||||
/**
|
||||
* 通过配置标识获取 WxPayService.
|
||||
* <p>
|
||||
* 注意:configKey 是配置文件中定义的 key(如 wx.pay.configs.<configKey>.xxx),
|
||||
* 而不是 appId。如果使用 appId 作为配置 key,则可以直接传入 appId。
|
||||
* </p>
|
||||
*
|
||||
* @param configKey 配置标识(配置文件中 wx.pay.configs 下的 key)
|
||||
* @return WxPayService
|
||||
*/
|
||||
WxPayService getWxPayService(String configKey);
|
||||
|
||||
/**
|
||||
* 根据配置标识,从列表中移除一个 WxPayService 实例.
|
||||
* <p>
|
||||
* 注意:configKey 是配置文件中定义的 key(如 wx.pay.configs.<configKey>.xxx),
|
||||
* 而不是 appId。如果使用 appId 作为配置 key,则可以直接传入 appId。
|
||||
* </p>
|
||||
*
|
||||
* @param configKey 配置标识(配置文件中 wx.pay.configs 下的 key)
|
||||
*/
|
||||
void removeWxPayService(String configKey);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.binarywang.spring.starter.wxjava.pay.service;
|
||||
|
||||
import com.binarywang.spring.starter.wxjava.pay.properties.WxPayMultiProperties;
|
||||
import com.binarywang.spring.starter.wxjava.pay.properties.WxPaySingleProperties;
|
||||
import com.github.binarywang.wxpay.config.WxPayConfig;
|
||||
import com.github.binarywang.wxpay.service.WxPayService;
|
||||
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 微信支付多服务管理实现类.
|
||||
*
|
||||
* @author Binary Wang
|
||||
*/
|
||||
@Slf4j
|
||||
public class WxPayMultiServicesImpl implements WxPayMultiServices {
|
||||
private final Map<String, WxPayService> services = new ConcurrentHashMap<>();
|
||||
private final WxPayMultiProperties wxPayMultiProperties;
|
||||
|
||||
public WxPayMultiServicesImpl(WxPayMultiProperties wxPayMultiProperties) {
|
||||
this.wxPayMultiProperties = wxPayMultiProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxPayService getWxPayService(String configKey) {
|
||||
if (StringUtils.isBlank(configKey)) {
|
||||
log.warn("配置标识为空,无法获取WxPayService");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 使用 computeIfAbsent 实现线程安全的懒加载,避免使用 synchronized(this) 带来的性能问题
|
||||
return services.computeIfAbsent(configKey, key -> {
|
||||
WxPaySingleProperties properties = wxPayMultiProperties.getConfigs().get(key);
|
||||
if (properties == null) {
|
||||
log.warn("未找到配置标识为[{}]的微信支付配置", key);
|
||||
return null;
|
||||
}
|
||||
return this.buildWxPayService(properties);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeWxPayService(String configKey) {
|
||||
if (StringUtils.isBlank(configKey)) {
|
||||
log.warn("配置标识为空,无法移除WxPayService");
|
||||
return;
|
||||
}
|
||||
services.remove(configKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配置构建 WxPayService.
|
||||
*
|
||||
* @param properties 单个配置属性
|
||||
* @return WxPayService
|
||||
*/
|
||||
private WxPayService buildWxPayService(WxPaySingleProperties properties) {
|
||||
WxPayServiceImpl wxPayService = new WxPayServiceImpl();
|
||||
WxPayConfig payConfig = new WxPayConfig();
|
||||
|
||||
payConfig.setAppId(StringUtils.trimToNull(properties.getAppId()));
|
||||
payConfig.setMchId(StringUtils.trimToNull(properties.getMchId()));
|
||||
payConfig.setMchKey(StringUtils.trimToNull(properties.getMchKey()));
|
||||
payConfig.setSubAppId(StringUtils.trimToNull(properties.getSubAppId()));
|
||||
payConfig.setSubMchId(StringUtils.trimToNull(properties.getSubMchId()));
|
||||
payConfig.setKeyPath(StringUtils.trimToNull(properties.getKeyPath()));
|
||||
payConfig.setUseSandboxEnv(properties.isUseSandboxEnv());
|
||||
payConfig.setNotifyUrl(StringUtils.trimToNull(properties.getNotifyUrl()));
|
||||
payConfig.setRefundNotifyUrl(StringUtils.trimToNull(properties.getRefundNotifyUrl()));
|
||||
|
||||
// 以下是apiv3以及支付分相关
|
||||
payConfig.setServiceId(StringUtils.trimToNull(properties.getServiceId()));
|
||||
payConfig.setPayScoreNotifyUrl(StringUtils.trimToNull(properties.getPayScoreNotifyUrl()));
|
||||
payConfig.setPayScorePermissionNotifyUrl(StringUtils.trimToNull(properties.getPayScorePermissionNotifyUrl()));
|
||||
payConfig.setPrivateKeyPath(StringUtils.trimToNull(properties.getPrivateKeyPath()));
|
||||
payConfig.setPrivateCertPath(StringUtils.trimToNull(properties.getPrivateCertPath()));
|
||||
payConfig.setCertSerialNo(StringUtils.trimToNull(properties.getCertSerialNo()));
|
||||
payConfig.setApiV3Key(StringUtils.trimToNull(properties.getApiv3Key()));
|
||||
payConfig.setPublicKeyId(StringUtils.trimToNull(properties.getPublicKeyId()));
|
||||
payConfig.setPublicKeyPath(StringUtils.trimToNull(properties.getPublicKeyPath()));
|
||||
payConfig.setApiHostUrl(StringUtils.trimToNull(properties.getApiHostUrl()));
|
||||
payConfig.setStrictlyNeedWechatPaySerial(properties.isStrictlyNeedWechatPaySerial());
|
||||
payConfig.setFullPublicKeyModel(properties.isFullPublicKeyModel());
|
||||
|
||||
wxPayService.setConfig(payConfig);
|
||||
return wxPayService;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.binarywang.spring.starter.wxjava.pay.config.WxPayMultiAutoConfiguration
|
||||
@@ -0,0 +1,2 @@
|
||||
com.binarywang.spring.starter.wxjava.pay.config.WxPayMultiAutoConfiguration
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package com.binarywang.spring.starter.wxjava.pay;
|
||||
|
||||
import com.binarywang.spring.starter.wxjava.pay.config.WxPayMultiAutoConfiguration;
|
||||
import com.binarywang.spring.starter.wxjava.pay.properties.WxPayMultiProperties;
|
||||
import com.binarywang.spring.starter.wxjava.pay.properties.WxPaySingleProperties;
|
||||
import com.binarywang.spring.starter.wxjava.pay.service.WxPayMultiServices;
|
||||
import com.github.binarywang.wxpay.service.WxPayService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* 微信支付多公众号关联配置测试.
|
||||
*
|
||||
* @author Binary Wang
|
||||
*/
|
||||
@SpringBootTest(classes = {WxPayMultiAutoConfiguration.class, WxPayMultiServicesTest.TestApplication.class})
|
||||
@TestPropertySource(properties = {
|
||||
"wx.pay.configs.app1.app-id=wx1111111111111111",
|
||||
"wx.pay.configs.app1.mch-id=1111111111",
|
||||
"wx.pay.configs.app1.mch-key=11111111111111111111111111111111",
|
||||
"wx.pay.configs.app1.notify-url=https://example.com/pay/notify",
|
||||
"wx.pay.configs.app2.app-id=wx2222222222222222",
|
||||
"wx.pay.configs.app2.mch-id=2222222222",
|
||||
"wx.pay.configs.app2.apiv3-key=22222222222222222222222222222222",
|
||||
"wx.pay.configs.app2.cert-serial-no=2222222222222222",
|
||||
"wx.pay.configs.app2.private-key-path=classpath:cert/apiclient_key.pem",
|
||||
"wx.pay.configs.app2.private-cert-path=classpath:cert/apiclient_cert.pem"
|
||||
})
|
||||
public class WxPayMultiServicesTest {
|
||||
|
||||
@Autowired
|
||||
private WxPayMultiServices wxPayMultiServices;
|
||||
|
||||
@Autowired
|
||||
private WxPayMultiProperties wxPayMultiProperties;
|
||||
|
||||
@Test
|
||||
public void testConfiguration() {
|
||||
assertNotNull(wxPayMultiServices, "WxPayMultiServices should be autowired");
|
||||
assertNotNull(wxPayMultiProperties, "WxPayMultiProperties should be autowired");
|
||||
|
||||
// 验证配置正确加载
|
||||
assertEquals(2, wxPayMultiProperties.getConfigs().size(), "Should have 2 configurations");
|
||||
|
||||
WxPaySingleProperties app1Config = wxPayMultiProperties.getConfigs().get("app1");
|
||||
assertNotNull(app1Config, "app1 configuration should exist");
|
||||
assertEquals("wx1111111111111111", app1Config.getAppId());
|
||||
assertEquals("1111111111", app1Config.getMchId());
|
||||
assertEquals("11111111111111111111111111111111", app1Config.getMchKey());
|
||||
|
||||
WxPaySingleProperties app2Config = wxPayMultiProperties.getConfigs().get("app2");
|
||||
assertNotNull(app2Config, "app2 configuration should exist");
|
||||
assertEquals("wx2222222222222222", app2Config.getAppId());
|
||||
assertEquals("2222222222", app2Config.getMchId());
|
||||
assertEquals("22222222222222222222222222222222", app2Config.getApiv3Key());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWxPayService() {
|
||||
WxPayService app1Service = wxPayMultiServices.getWxPayService("app1");
|
||||
assertNotNull(app1Service, "Should get WxPayService for app1");
|
||||
assertEquals("wx1111111111111111", app1Service.getConfig().getAppId());
|
||||
assertEquals("1111111111", app1Service.getConfig().getMchId());
|
||||
|
||||
WxPayService app2Service = wxPayMultiServices.getWxPayService("app2");
|
||||
assertNotNull(app2Service, "Should get WxPayService for app2");
|
||||
assertEquals("wx2222222222222222", app2Service.getConfig().getAppId());
|
||||
assertEquals("2222222222", app2Service.getConfig().getMchId());
|
||||
|
||||
// 测试相同key返回相同实例
|
||||
WxPayService app1ServiceAgain = wxPayMultiServices.getWxPayService("app1");
|
||||
assertSame(app1Service, app1ServiceAgain, "Should return the same instance for the same key");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWxPayServiceWithInvalidKey() {
|
||||
WxPayService service = wxPayMultiServices.getWxPayService("nonexistent");
|
||||
assertNull(service, "Should return null for non-existent key");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveWxPayService() {
|
||||
// 首先获取一个服务实例
|
||||
WxPayService app1Service = wxPayMultiServices.getWxPayService("app1");
|
||||
assertNotNull(app1Service, "Should get WxPayService for app1");
|
||||
|
||||
// 移除服务
|
||||
wxPayMultiServices.removeWxPayService("app1");
|
||||
|
||||
// 再次获取时应该创建新实例
|
||||
WxPayService app1ServiceNew = wxPayMultiServices.getWxPayService("app1");
|
||||
assertNotNull(app1ServiceNew, "Should get new WxPayService for app1");
|
||||
assertNotSame(app1Service, app1ServiceNew, "Should return a new instance after removal");
|
||||
}
|
||||
|
||||
@SpringBootApplication
|
||||
static class TestApplication {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
package com.binarywang.spring.starter.wxjava.pay.example;
|
||||
|
||||
import com.binarywang.spring.starter.wxjava.pay.service.WxPayMultiServices;
|
||||
import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request;
|
||||
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
|
||||
import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryV3Result;
|
||||
import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result;
|
||||
import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
|
||||
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
|
||||
import com.github.binarywang.wxpay.service.WxPayService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 微信支付多公众号关联使用示例.
|
||||
* <p>
|
||||
* 本示例展示了如何使用 wx-java-pay-multi-spring-boot-starter 来管理多个公众号的支付配置。
|
||||
* </p>
|
||||
*
|
||||
* @author Binary Wang
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class WxPayMultiExample {
|
||||
|
||||
@Autowired
|
||||
private WxPayMultiServices wxPayMultiServices;
|
||||
|
||||
/**
|
||||
* 示例1:根据appId创建支付订单.
|
||||
* <p>
|
||||
* 适用场景:系统需要支持多个公众号,根据用户所在的公众号动态选择支付配置
|
||||
* </p>
|
||||
*
|
||||
* @param appId 公众号appId
|
||||
* @param openId 用户的openId
|
||||
* @param totalFee 支付金额(分)
|
||||
* @param body 商品描述
|
||||
* @return JSAPI支付参数
|
||||
*/
|
||||
public WxPayUnifiedOrderV3Result.JsapiResult createJsapiOrder(String appId, String openId,
|
||||
Integer totalFee, String body) {
|
||||
try {
|
||||
// 根据appId获取对应的WxPayService
|
||||
WxPayService wxPayService = wxPayMultiServices.getWxPayService(appId);
|
||||
|
||||
if (wxPayService == null) {
|
||||
log.error("未找到appId对应的微信支付配置: {}", appId);
|
||||
throw new IllegalArgumentException("未找到appId对应的微信支付配置");
|
||||
}
|
||||
|
||||
// 构建支付请求
|
||||
WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
|
||||
request.setOutTradeNo(generateOutTradeNo());
|
||||
request.setDescription(body);
|
||||
request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(totalFee));
|
||||
request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(openId));
|
||||
request.setNotifyUrl(wxPayService.getConfig().getNotifyUrl());
|
||||
|
||||
// 调用微信支付API创建订单
|
||||
WxPayUnifiedOrderV3Result.JsapiResult result =
|
||||
wxPayService.createOrderV3(TradeTypeEnum.JSAPI, request);
|
||||
|
||||
log.info("创建JSAPI支付订单成功,appId: {}, outTradeNo: {}", appId, request.getOutTradeNo());
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("创建JSAPI支付订单失败,appId: {}", appId, e);
|
||||
throw new RuntimeException("创建支付订单失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例2:服务商模式 - 为不同子商户创建订单.
|
||||
* <p>
|
||||
* 适用场景:服务商为多个子商户提供支付服务
|
||||
* </p>
|
||||
*
|
||||
* @param configKey 配置标识(在配置文件中定义)
|
||||
* @param subOpenId 子商户用户的openId
|
||||
* @param totalFee 支付金额(分)
|
||||
* @param body 商品描述
|
||||
* @return JSAPI支付参数
|
||||
*/
|
||||
public WxPayUnifiedOrderV3Result.JsapiResult createPartnerOrder(String configKey, String subOpenId,
|
||||
Integer totalFee, String body) {
|
||||
try {
|
||||
// 根据配置标识获取WxPayService
|
||||
WxPayService wxPayService = wxPayMultiServices.getWxPayService(configKey);
|
||||
|
||||
if (wxPayService == null) {
|
||||
log.error("未找到配置: {}", configKey);
|
||||
throw new IllegalArgumentException("未找到配置");
|
||||
}
|
||||
|
||||
// 获取子商户信息
|
||||
String subAppId = wxPayService.getConfig().getSubAppId();
|
||||
String subMchId = wxPayService.getConfig().getSubMchId();
|
||||
log.info("使用服务商模式,子商户appId: {}, 子商户号: {}", subAppId, subMchId);
|
||||
|
||||
// 构建支付请求
|
||||
WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
|
||||
request.setOutTradeNo(generateOutTradeNo());
|
||||
request.setDescription(body);
|
||||
request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(totalFee));
|
||||
request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(subOpenId));
|
||||
request.setNotifyUrl(wxPayService.getConfig().getNotifyUrl());
|
||||
|
||||
// 调用微信支付API创建订单
|
||||
WxPayUnifiedOrderV3Result.JsapiResult result =
|
||||
wxPayService.createOrderV3(TradeTypeEnum.JSAPI, request);
|
||||
|
||||
log.info("创建服务商支付订单成功,配置: {}, outTradeNo: {}", configKey, request.getOutTradeNo());
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("创建服务商支付订单失败,配置: {}", configKey, e);
|
||||
throw new RuntimeException("创建支付订单失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例3:查询订单状态.
|
||||
* <p>
|
||||
* 适用场景:查询不同公众号的订单支付状态
|
||||
* </p>
|
||||
*
|
||||
* @param appId 公众号appId
|
||||
* @param outTradeNo 商户订单号
|
||||
* @return 订单状态
|
||||
*/
|
||||
public String queryOrderStatus(String appId, String outTradeNo) {
|
||||
try {
|
||||
WxPayService wxPayService = wxPayMultiServices.getWxPayService(appId);
|
||||
|
||||
if (wxPayService == null) {
|
||||
log.error("未找到appId对应的微信支付配置: {}", appId);
|
||||
throw new IllegalArgumentException("未找到appId对应的微信支付配置");
|
||||
}
|
||||
|
||||
// 查询订单
|
||||
WxPayOrderQueryV3Result result = wxPayService.queryOrderV3(null, outTradeNo);
|
||||
String tradeState = result.getTradeState();
|
||||
|
||||
log.info("查询订单状态成功,appId: {}, outTradeNo: {}, 状态: {}", appId, outTradeNo, tradeState);
|
||||
return tradeState;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("查询订单状态失败,appId: {}, outTradeNo: {}", appId, outTradeNo, e);
|
||||
throw new RuntimeException("查询订单失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例4:申请退款.
|
||||
* <p>
|
||||
* 适用场景:为不同公众号的订单申请退款
|
||||
* </p>
|
||||
*
|
||||
* @param appId 公众号appId
|
||||
* @param outTradeNo 商户订单号
|
||||
* @param refundFee 退款金额(分)
|
||||
* @param totalFee 订单总金额(分)
|
||||
* @param reason 退款原因
|
||||
* @return 退款单号
|
||||
*/
|
||||
public String refund(String appId, String outTradeNo, Integer refundFee,
|
||||
Integer totalFee, String reason) {
|
||||
try {
|
||||
WxPayService wxPayService = wxPayMultiServices.getWxPayService(appId);
|
||||
|
||||
if (wxPayService == null) {
|
||||
log.error("未找到appId对应的微信支付配置: {}", appId);
|
||||
throw new IllegalArgumentException("未找到appId对应的微信支付配置");
|
||||
}
|
||||
|
||||
// 构建退款请求
|
||||
com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request request =
|
||||
new com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request();
|
||||
request.setOutTradeNo(outTradeNo);
|
||||
request.setOutRefundNo(generateRefundNo());
|
||||
request.setReason(reason);
|
||||
request.setNotifyUrl(wxPayService.getConfig().getRefundNotifyUrl());
|
||||
|
||||
com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request.Amount amount =
|
||||
new com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request.Amount();
|
||||
amount.setRefund(refundFee);
|
||||
amount.setTotal(totalFee);
|
||||
amount.setCurrency("CNY");
|
||||
request.setAmount(amount);
|
||||
|
||||
// 调用微信支付API申请退款
|
||||
WxPayRefundV3Result result = wxPayService.refundV3(request);
|
||||
|
||||
log.info("申请退款成功,appId: {}, outTradeNo: {}, outRefundNo: {}",
|
||||
appId, outTradeNo, request.getOutRefundNo());
|
||||
return request.getOutRefundNo();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("申请退款失败,appId: {}, outTradeNo: {}", appId, outTradeNo, e);
|
||||
throw new RuntimeException("申请退款失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例5:动态管理配置.
|
||||
* <p>
|
||||
* 适用场景:需要在运行时更新配置(如证书更新后需要重新加载)
|
||||
* </p>
|
||||
*
|
||||
* @param configKey 配置标识
|
||||
*/
|
||||
public void reloadConfig(String configKey) {
|
||||
try {
|
||||
// 移除缓存的WxPayService实例
|
||||
wxPayMultiServices.removeWxPayService(configKey);
|
||||
log.info("移除配置成功,下次获取时将重新创建: {}", configKey);
|
||||
|
||||
// 下次调用 getWxPayService 时会重新创建实例
|
||||
WxPayService wxPayService = wxPayMultiServices.getWxPayService(configKey);
|
||||
if (wxPayService != null) {
|
||||
log.info("重新加载配置成功: {}", configKey);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("重新加载配置失败: {}", configKey, e);
|
||||
throw new RuntimeException("重新加载配置失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成商户订单号.
|
||||
*
|
||||
* @return 商户订单号
|
||||
*/
|
||||
private String generateOutTradeNo() {
|
||||
return "ORDER_" + System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成商户退款单号.
|
||||
*
|
||||
* @return 商户退款单号
|
||||
*/
|
||||
private String generateRefundNo() {
|
||||
return "REFUND_" + System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user