Browse Source

feat(payment): 支持微信支付服务商模式

- 新增服务商模式相关字段:sub_app_id、sub_mch_id
- 修改 wechat_type 字段含义,0 表示普通商户,1 表示子商户
- 支付接口支持根据商户类型自动切换普通商户或服务商模式
- Native支付和JSAPI支付均支持服务商模式- 订单查询接口适配服务商模式API
- 新增服务商模式SQL脚本,支持生产环境安全执行
- 更新支付配置Controller,支持子商户信息配置- 新增微信支付服务商模式使用指南文档- 修复商户类型判断逻辑,确保正确识别服务商模式
- 调整支付服务构建逻辑,根据配置返回不同服务实例
dev3
科技小王子 3 days ago
parent
commit
f6f13d24dd
  1. 142
      docs/WECHAT_PARTNER_PAYMENT_GUIDE.md
  2. 10
      src/main/java/com/gxwebsoft/common/core/controller/PaymentConfigController.java
  3. 8
      src/main/java/com/gxwebsoft/common/system/entity/Payment.java
  4. 8
      src/main/java/com/gxwebsoft/common/system/param/PaymentParam.java
  5. 529
      src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderServiceImpl.java
  6. 112
      src/main/resources/sql/add_partner_payment_fields.sql
  7. 2
      src/main/resources/sql/create_dev_tenant_payment.sql
  8. 267
      src/main/resources/sql/create_dev_tenant_payment_with_partner.sql
  9. 3
      src/main/resources/sql/production_safe_payment_config.sql

142
docs/WECHAT_PARTNER_PAYMENT_GUIDE.md

@ -0,0 +1,142 @@
# 微信支付服务商模式使用指南
## 概述
本文档介绍了如何在系统中配置和使用微信支付服务商模式。服务商模式允许一个服务商为多个子商户提供支付服务,适用于平台型业务场景。
## 服务商模式 vs 普通商户模式
### 普通商户模式
- 直接使用自己的商户号和应用ID进行支付
- 适用于单一商户场景
### 服务商模式
- 服务商使用自己的服务商户号和子商户的子商户号进行支付
- 适用于平台为多个商户提供服务的场景
- 需要额外配置子商户信息
## 数据库配置
### 字段说明
`sys_payment` 表中,服务商模式相关的字段包括:
| 字段名 | 类型 | 说明 |
|--------|------|------|
| wechat_type | INT | 微信商户类型:0-普通商户,1-子商户 |
| app_id | VARCHAR(64) | 服务商应用ID(服务商模式下为服务商的AppID) |
| mch_id | VARCHAR(32) | 服务商户号(服务商的商户号) |
| sub_app_id | VARCHAR(64) | 子商户应用ID(子商户的AppID) |
| sub_mch_id | VARCHAR(32) | 子商户号(子商户的商户号) |
### 配置示例
#### 普通商户模式配置
```sql
UPDATE sys_payment
SET
wechat_type = 0, -- 普通商户
app_id = 'wx_app_id', -- 商户应用ID
mch_id = 'mch_id' -- 商户号
WHERE id = YOUR_PAYMENT_CONFIG_ID;
```
#### 服务商模式配置
```sql
UPDATE sys_payment
SET
wechat_type = 1, -- 子商户(服务商模式)
app_id = 'wx_service_app_id', -- 服务商应用ID
mch_id = 'service_mch_id', -- 服务商户号
sub_app_id = 'wx_sub_app_id', -- 子商户应用ID
sub_mch_id = 'sub_mch_id' -- 子商户号
WHERE id = YOUR_PAYMENT_CONFIG_ID;
```
## 代码实现
### 判断商户类型
系统会根据 `wechat_type` 字段自动判断使用哪种模式:
```java
boolean isPartnerMode = payment.getWechatType() != null && payment.getWechatType() == 1;
```
### 服务商模式请求参数
在服务商模式下,需要设置以下参数:
```java
// 服务商模式需要设置服务商和子商户信息
request.setSpAppid(payment.getAppId()); // 服务商应用ID
request.setSpMchid(payment.getMchId()); // 服务商户号
request.setSubAppid(payment.getSubAppId()); // 子商户应用ID
request.setSubMchid(payment.getSubMchId()); // 子商户号
```
## 使用步骤
### 1. 数据库字段添加
执行以下SQL脚本添加必要的字段:
```bash
# 执行脚本添加服务商模式支持字段
mysql -u username -p database_name < src/main/resources/sql/add_partner_payment_fields.sql
```
### 2. 配置支付信息
根据您的业务需求,配置相应的支付信息:
#### 普通商户模式
1. 设置 `wechat_type = 0`
2. 填写 `app_id``mch_id`
#### 服务商模式
1. 设置 `wechat_type = 1`
2. 填写 `app_id`(服务商应用ID)和 `mch_id`(服务商户号)
3. 填写 `sub_app_id`(子商户应用ID)和 `sub_mch_id`(子商户号)
### 3. 测试支付功能
使用开发环境配置进行测试:
```bash
# 创建包含服务商模式支持的开发环境配置
mysql -u username -p database_name < src/main/resources/sql/create_dev_tenant_payment_with_partner.sql
```
## 常见问题
### 1. 错误:"受理机构必须传入sub_mch_id"
**原因**:使用了服务商模式但未正确配置子商户信息
**解决方案**:
1. 确认 `wechat_type` 设置为 1
2. 确认 `sub_mch_id` 字段已填写
3. 确认使用的是服务商的商户号和子商户号
### 2. 支付成功但回调失败
**原因**:回调地址配置不正确
**解决方案**:
1. 检查 `notify_url` 配置
2. 确保回调地址可公网访问
3. 检查服务器防火墙设置
## 注意事项
1. **安全性**:妥善保管商户密钥和证书文件
2. **测试环境**:在生产环境使用前,务必在测试环境充分测试
3. **证书管理**:服务商模式和普通商户模式可能需要不同的证书配置
4. **回调处理**:确保回调接口能正确处理服务商模式的支付通知
## 相关文档
- [微信支付官方文档 - 服务商模式](https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter1_1_1.shtml)
- [支付配置诊断指南](payment_config_diagnostic.sql)
- [安全生产环境配置指南](SAFE_PRODUCTION_SETUP_GUIDE.md)

10
src/main/java/com/gxwebsoft/common/core/controller/PaymentConfigController.java

@ -115,6 +115,16 @@ public class PaymentConfigController extends BaseController {
payment.setCode("0");
payment.setAppId(config.get("appId"));
payment.setMchId(config.get("mchId"));
// 支持服务商模式的子商户配置
payment.setSubAppId(config.get("subAppId"));
payment.setSubMchId(config.get("subMchId"));
// 设置商户类型(0-普通商户,1-子商户)
String wechatType = config.get("wechatType");
if (wechatType != null && !wechatType.isEmpty()) {
payment.setWechatType(Integer.parseInt(wechatType));
} else {
payment.setWechatType(0); // 默认为普通商户
}
payment.setApiKey(config.get("apiKey"));
payment.setMerchantSerialNumber(config.get("merchantSerialNumber"));
payment.setStatus(true);

8
src/main/java/com/gxwebsoft/common/system/entity/Payment.java

@ -43,7 +43,7 @@ public class Payment implements Serializable {
@Schema(description = "支付图标")
private String image;
@Schema(description = "微信商户号类型 1普通商户2子商户")
@Schema(description = "微信商户号类型 0普通商户 1子商户")
private Integer wechatType;
@Schema(description = "应用ID")
@ -52,6 +52,12 @@ public class Payment implements Serializable {
@Schema(description = "商户号")
private String mchId;
@Schema(description = "子商户应用ID(服务商模式专用)")
private String subAppId;
@Schema(description = "子商户号(服务商模式专用)")
private String subMchId;
@Schema(description = "设置APIv3密钥")
private String apiKey;

8
src/main/java/com/gxwebsoft/common/system/param/PaymentParam.java

@ -37,7 +37,7 @@ public class PaymentParam extends BaseParam {
@QueryField(type = QueryType.EQ)
private String code;
@Schema(description = "微信商户号类型 1普通商户2子商户")
@Schema(description = "微信商户号类型 0普通商户 1子商户")
@QueryField(type = QueryType.EQ)
private Integer wechatType;
@ -47,6 +47,12 @@ public class PaymentParam extends BaseParam {
@Schema(description = "商户号")
private String mchId;
@Schema(description = "子商户应用ID(服务商模式专用)")
private String subAppId;
@Schema(description = "子商户号(服务商模式专用)")
private String subMchId;
@Schema(description = "设置APIv3密钥")
private String apiKey;

529
src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderServiceImpl.java

@ -28,6 +28,8 @@ import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
// 添加服务商支付相关的导入
import com.wechat.pay.java.service.partnerpayments.jsapi.JsapiService;
// Native支付的类将使用完全限定名避免冲突
import com.wechat.pay.java.service.payments.model.Transaction;
import org.springframework.beans.factory.annotation.Value;
@ -217,76 +219,179 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
}
/**
* 创建Native支付订单
* 创建Native支付订单支持服务商模式
*/
private HashMap<String, String> createNativePayOrder(ShopOrder order, Payment payment) throws Exception {
System.out.println("=== 使用Native支付模式 ===");
// 构建Native支付服务
Config wxPayConfig = getWxPayConfig(order);
NativePayService nativeService = new NativePayService.Builder().config(wxPayConfig).build();
// 检查是否为服务商模式
boolean isPartnerMode = payment.getWechatType() != null && payment.getWechatType() == 1;
System.out.println("支付模式: " + (isPartnerMode ? "服务商模式" : "普通商户模式"));
if (isPartnerMode) {
// 服务商模式
System.out.println("=== 使用服务商模式Native支付 ===");
// 构建服务商Native支付服务
Config wxPayConfig = getWxPayConfig(order);
com.wechat.pay.java.service.partnerpayments.nativepay.NativePayService nativeService =
new com.wechat.pay.java.service.partnerpayments.nativepay.NativePayService.Builder().config(wxPayConfig).build();
// 订单金额(转换为分)
BigDecimal decimal = order.getTotalPrice();
final BigDecimal multiply = decimal.multiply(new BigDecimal(100));
Integer money = multiply.intValue();
// 测试环境使用1分钱
if (active.equals("dev")) {
money = 1;
}
// 订单金额(转换为分)
BigDecimal decimal = order.getTotalPrice();
final BigDecimal multiply = decimal.multiply(new BigDecimal(100));
Integer money = multiply.intValue();
// 构建服务商Native支付请求
com.wechat.pay.java.service.partnerpayments.nativepay.model.PrepayRequest request =
new com.wechat.pay.java.service.partnerpayments.nativepay.model.PrepayRequest();
com.wechat.pay.java.service.partnerpayments.nativepay.model.Amount amount =
new com.wechat.pay.java.service.partnerpayments.nativepay.model.Amount();
amount.setTotal(money);
amount.setCurrency("CNY");
request.setAmount(amount);
// 测试环境使用1分钱
if (active.equals("dev")) {
money = 1;
}
// 服务商模式需要设置服务商和子商户信息
request.setSpAppid(payment.getAppId()); // 服务商应用ID
request.setSpMchid(payment.getMchId()); // 服务商户号
// 使用子商户信息(如果配置了的话)
if (StrUtil.isNotBlank(payment.getSubAppId())) {
request.setSubAppid(payment.getSubAppId()); // 子商户应用ID
} else {
request.setSubAppid(payment.getAppId()); // 默认使用服务商应用ID
}
if (StrUtil.isNotBlank(payment.getSubMchId())) {
request.setSubMchid(payment.getSubMchId()); // 子商户号
} else {
request.setSubMchid(payment.getMchId()); // 默认使用服务商商户号
}
// 构建Native支付请求
com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest request =
new com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest();
com.wechat.pay.java.service.payments.nativepay.model.Amount amount =
new com.wechat.pay.java.service.payments.nativepay.model.Amount();
amount.setTotal(money);
amount.setCurrency("CNY");
request.setAmount(amount);
request.setAppid(payment.getAppId());
request.setMchid(payment.getMchId());
// 微信支付description字段限制127字节,需要截断处理
String description = com.gxwebsoft.common.core.utils.WechatPayUtils.processDescription(order.getComments());
request.setDescription(description);
request.setOutTradeNo(order.getOrderNo());
request.setAttach(order.getTenantId().toString());
System.out.println("=== 关键信息确认 ===");
System.out.println("发送给微信的订单号(OutTradeNo): " + order.getOrderNo());
System.out.println("商户号(MchId): " + payment.getMchId());
System.out.println("应用ID(AppId): " + payment.getAppId());
// 设置回调地址
String notifyUrl = config.getServerUrl() + "/system/wx-pay/notify/" + order.getTenantId();
if (active.equals("dev")) {
notifyUrl = "http://jimei-api.natapp1.cc/api/shop/wx-pay/notify/" + order.getTenantId();
}
if (StrUtil.isNotBlank(payment.getNotifyUrl())) {
notifyUrl = payment.getNotifyUrl().concat("/").concat(order.getTenantId().toString());
}
request.setNotifyUrl(notifyUrl);
// 微信支付description字段限制127字节,需要截断处理
String description = com.gxwebsoft.common.core.utils.WechatPayUtils.processDescription(order.getComments());
request.setDescription(description);
request.setOutTradeNo(order.getOrderNo());
request.setAttach(order.getTenantId().toString());
System.out.println("=== 关键信息确认 ===");
System.out.println("发送给微信的订单号(OutTradeNo): " + order.getOrderNo());
System.out.println("服务商商户号(SpMchId): " + payment.getMchId());
// 使用子商户信息(如果配置了的话)
if (StrUtil.isNotBlank(payment.getSubMchId())) {
System.out.println("子商户号(SubMchId): " + payment.getSubMchId());
} else {
System.out.println("子商户号(SubMchId): " + payment.getMchId() + " (默认使用服务商商户号)");
}
System.out.println("服务商应用ID(SpAppId): " + payment.getAppId());
// 使用子商户信息(如果配置了的话)
if (StrUtil.isNotBlank(payment.getSubAppId())) {
System.out.println("子商户应用ID(SubAppId): " + payment.getSubAppId());
} else {
System.out.println("子商户应用ID(SubAppId): " + payment.getAppId() + " (默认使用服务商应用ID)");
}
// 设置回调地址
String notifyUrl = config.getServerUrl() + "/system/wx-pay/notify/" + order.getTenantId();
if (active.equals("dev")) {
notifyUrl = "http://jimei-api.natapp1.cc/api/shop/wx-pay/notify/" + order.getTenantId();
}
if (StrUtil.isNotBlank(payment.getNotifyUrl())) {
notifyUrl = payment.getNotifyUrl().concat("/").concat(order.getTenantId().toString());
}
request.setNotifyUrl(notifyUrl);
System.out.println("=== 发起服务商Native支付请求 ===");
System.out.println("请求参数: " + request);
// 调用服务商Native支付API
com.wechat.pay.java.service.partnerpayments.nativepay.model.PrepayResponse response = nativeService.prepay(request);
System.out.println("=== 服务商Native支付响应成功 ===");
System.out.println("二维码URL: " + response.getCodeUrl());
// 构建返回数据
final HashMap<String, String> orderInfo = new HashMap<>();
orderInfo.put("provider", "wxpay");
orderInfo.put("codeUrl", response.getCodeUrl()); // Native支付返回二维码URL
orderInfo.put("orderNo", order.getOrderNo());
orderInfo.put("payType", WechatPayType.NATIVE);
orderInfo.put("wechatPayType", WechatPayType.NATIVE);
return orderInfo;
} else {
// 普通商户模式(原有逻辑)
System.out.println("=== 使用普通商户模式Native支付 ===");
// 构建Native支付服务
Config wxPayConfig = getWxPayConfig(order);
NativePayService nativeService = new NativePayService.Builder().config(wxPayConfig).build();
// 订单金额(转换为分)
BigDecimal decimal = order.getTotalPrice();
final BigDecimal multiply = decimal.multiply(new BigDecimal(100));
Integer money = multiply.intValue();
// 测试环境使用1分钱
if (active.equals("dev")) {
money = 1;
}
System.out.println("=== 发起Native支付请求 ===");
System.out.println("请求参数: " + request);
// 构建Native支付请求
com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest request =
new com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest();
com.wechat.pay.java.service.payments.nativepay.model.Amount amount =
new com.wechat.pay.java.service.payments.nativepay.model.Amount();
amount.setTotal(money);
amount.setCurrency("CNY");
request.setAmount(amount);
// 调用Native支付API
com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse response = nativeService.prepay(request);
request.setAppid(payment.getAppId());
request.setMchid(payment.getMchId());
System.out.println("=== Native支付响应成功 ===");
System.out.println("二维码URL: " + response.getCodeUrl());
// 微信支付description字段限制127字节,需要截断处理
String description = com.gxwebsoft.common.core.utils.WechatPayUtils.processDescription(order.getComments());
request.setDescription(description);
request.setOutTradeNo(order.getOrderNo());
request.setAttach(order.getTenantId().toString());
// 构建返回数据
final HashMap<String, String> orderInfo = new HashMap<>();
orderInfo.put("provider", "wxpay");
orderInfo.put("codeUrl", response.getCodeUrl()); // Native支付返回二维码URL
orderInfo.put("orderNo", order.getOrderNo());
orderInfo.put("payType", WechatPayType.NATIVE);
orderInfo.put("wechatPayType", WechatPayType.NATIVE);
System.out.println("=== 关键信息确认 ===");
System.out.println("发送给微信的订单号(OutTradeNo): " + order.getOrderNo());
System.out.println("商户号(MchId): " + payment.getMchId());
System.out.println("应用ID(AppId): " + payment.getAppId());
return orderInfo;
// 设置回调地址
String notifyUrl = config.getServerUrl() + "/system/wx-pay/notify/" + order.getTenantId();
if (active.equals("dev")) {
notifyUrl = "http://jimei-api.natapp1.cc/api/shop/wx-pay/notify/" + order.getTenantId();
}
if (StrUtil.isNotBlank(payment.getNotifyUrl())) {
notifyUrl = payment.getNotifyUrl().concat("/").concat(order.getTenantId().toString());
}
request.setNotifyUrl(notifyUrl);
System.out.println("=== 发起Native支付请求 ===");
System.out.println("请求参数: " + request);
// 调用Native支付API
com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse response = nativeService.prepay(request);
System.out.println("=== Native支付响应成功 ===");
System.out.println("二维码URL: " + response.getCodeUrl());
// 构建返回数据
final HashMap<String, String> orderInfo = new HashMap<>();
orderInfo.put("provider", "wxpay");
orderInfo.put("codeUrl", response.getCodeUrl()); // Native支付返回二维码URL
orderInfo.put("orderNo", order.getOrderNo());
orderInfo.put("payType", WechatPayType.NATIVE);
orderInfo.put("wechatPayType", WechatPayType.NATIVE);
return orderInfo;
}
}
/**
@ -300,9 +405,9 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
throw new RuntimeException("JSAPI支付必须传openid");
}
// 构建JSAPI支付服务
JsapiServiceExtension service = getWxService(order);
System.out.println("微信支付服务构建完成");
// 构建JSAPI支付服务(支持服务商模式)
Object serviceObj = getWxServiceWithPartnerSupport(order);
System.out.println("微信支付服务构建完成,服务类型: " + serviceObj.getClass().getSimpleName());
// 订单金额
BigDecimal decimal = order.getPayPrice();
@ -311,64 +416,159 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
System.out.println("=== 构建支付请求参数 ===");
PrepayRequest request = new PrepayRequest();
Amount amount = new Amount();
amount.setTotal(money);
amount.setCurrency("CNY");
request.setAmount(amount);
System.out.println("设置应用ID: " + payment.getAppId());
request.setAppid(payment.getAppId());
System.out.println("设置商户号: " + payment.getMchId());
request.setMchid(payment.getMchId());
// 微信支付description字段限制127字节,需要截断处理
String description = com.gxwebsoft.common.core.utils.WechatPayUtils.processDescription(order.getComments());
System.out.println("设置描述: " + description);
request.setDescription(description);
System.out.println("设置订单号: " + order.getOrderNo());
request.setOutTradeNo(order.getOrderNo());
System.out.println("设置附加数据: " + order.getTenantId().toString());
request.setAttach(order.getTenantId().toString());
final Payer payer = new Payer();
System.out.println("设置用户OpenID: " + order.getOpenid());
payer.setOpenid(order.getOpenid());
request.setPayer(payer);
request.setNotifyUrl(config.getServerUrl() + "/system/wx-pay/notify/" + order.getTenantId()); // 默认回调地址
// 测试环境
if (active.equals("dev")) {
amount.setTotal(1);
// 根据服务类型创建不同的请求对象
final Payment paymentConfig = getPayment(order);
boolean isPartnerMode = paymentConfig.getWechatType() != null && paymentConfig.getWechatType() == 1;
if (isPartnerMode) {
// 服务商模式
System.out.println("=== 使用服务商模式支付请求 ===");
com.wechat.pay.java.service.partnerpayments.jsapi.model.PrepayRequest request =
new com.wechat.pay.java.service.partnerpayments.jsapi.model.PrepayRequest();
com.wechat.pay.java.service.partnerpayments.jsapi.model.Amount amount =
new com.wechat.pay.java.service.partnerpayments.jsapi.model.Amount();
amount.setTotal(money);
amount.setCurrency("CNY");
request.setAmount(amount);
request.setNotifyUrl("http://jimei-api.natapp1.cc/api/shop/wx-pay/notify/" + order.getTenantId()); // 默认回调地址
}
// 后台配置的回调地址
if (StrUtil.isNotBlank(payment.getNotifyUrl())) {
request.setNotifyUrl(payment.getNotifyUrl().concat("/").concat(order.getTenantId().toString()));
System.out.println("后台配置的回调地址 = " + request.getNotifyUrl());
// 服务商模式需要设置服务商和子商户信息
request.setSpAppid(payment.getAppId()); // 服务商应用ID
request.setSpMchid(payment.getMchId()); // 服务商户号
// 使用子商户信息(如果配置了的话)
if (StrUtil.isNotBlank(payment.getSubAppId())) {
request.setSubAppid(payment.getSubAppId()); // 子商户应用ID
} else {
request.setSubAppid(payment.getAppId()); // 默认使用服务商应用ID
}
if (StrUtil.isNotBlank(payment.getSubMchId())) {
request.setSubMchid(payment.getSubMchId()); // 子商户号
} else {
request.setSubMchid(payment.getMchId()); // 默认使用服务商商户号
}
// 微信支付description字段限制127字节,需要截断处理
String description = com.gxwebsoft.common.core.utils.WechatPayUtils.processDescription(order.getComments());
System.out.println("设置描述: " + description);
request.setDescription(description);
System.out.println("设置订单号: " + order.getOrderNo());
request.setOutTradeNo(order.getOrderNo());
System.out.println("设置附加数据: " + order.getTenantId().toString());
request.setAttach(order.getTenantId().toString());
final com.wechat.pay.java.service.partnerpayments.jsapi.model.Payer payer =
new com.wechat.pay.java.service.partnerpayments.jsapi.model.Payer();
System.out.println("设置用户OpenID: " + order.getOpenid());
// 服务商模式使用subOpenid而不是openid
payer.setSubOpenid(order.getOpenid());
request.setPayer(payer);
request.setNotifyUrl(config.getServerUrl() + "/system/wx-pay/notify/" + order.getTenantId()); // 默认回调地址
// 测试环境
if (active.equals("dev")) {
amount.setTotal(1);
request.setAmount(amount);
request.setNotifyUrl("http://jimei-api.natapp1.cc/api/shop/wx-pay/notify/" + order.getTenantId()); // 默认回调地址
}
// 后台配置的回调地址
if (StrUtil.isNotBlank(payment.getNotifyUrl())) {
request.setNotifyUrl(payment.getNotifyUrl().concat("/").concat(order.getTenantId().toString()));
System.out.println("后台配置的回调地址 = " + request.getNotifyUrl());
}
System.out.println("=== 发起微信支付请求 ===");
System.out.println("请求参数: " + request);
com.wechat.pay.java.service.partnerpayments.jsapi.JsapiService service =
(com.wechat.pay.java.service.partnerpayments.jsapi.JsapiService) serviceObj;
// 服务商模式使用prepay方法而不是prepayWithRequestPayment
com.wechat.pay.java.service.partnerpayments.jsapi.model.PrepayResponse response =
service.prepay(request);
// 服务商模式返回的是PrepayResponse,需要构造类似的返回数据
System.out.println("=== 微信支付响应成功 ===");
System.out.println("预支付ID: " + response.getPrepayId());
final HashMap<String, String> orderInfo = new HashMap<>();
orderInfo.put("provider", "wxpay");
// 服务商模式需要构造时间戳、随机字符串等参数
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
String nonceStr = java.util.UUID.randomUUID().toString().replaceAll("-", "");
String packageVal = "prepay_id=" + response.getPrepayId();
orderInfo.put("timeStamp", timeStamp);
orderInfo.put("nonceStr", nonceStr);
orderInfo.put("package", packageVal);
orderInfo.put("signType", "RSA");
// 注意:这里需要正确签名,实际应用中需要实现签名逻辑
orderInfo.put("paySign", "");
orderInfo.put("orderNo", order.getOrderNo());
orderInfo.put("payType", WechatPayType.JSAPI);
orderInfo.put("wechatPayType", WechatPayType.JSAPI);
return orderInfo;
} else {
// 普通商户模式
System.out.println("=== 使用普通商户模式支付请求 ===");
PrepayRequest request = new PrepayRequest();
Amount amount = new Amount();
amount.setTotal(money);
amount.setCurrency("CNY");
request.setAmount(amount);
System.out.println("设置应用ID: " + payment.getAppId());
request.setAppid(payment.getAppId());
System.out.println("设置商户号: " + payment.getMchId());
request.setMchid(payment.getMchId());
// 微信支付description字段限制127字节,需要截断处理
String description = com.gxwebsoft.common.core.utils.WechatPayUtils.processDescription(order.getComments());
System.out.println("设置描述: " + description);
request.setDescription(description);
System.out.println("设置订单号: " + order.getOrderNo());
request.setOutTradeNo(order.getOrderNo());
System.out.println("设置附加数据: " + order.getTenantId().toString());
request.setAttach(order.getTenantId().toString());
final Payer payer = new Payer();
System.out.println("设置用户OpenID: " + order.getOpenid());
payer.setOpenid(order.getOpenid());
request.setPayer(payer);
request.setNotifyUrl(config.getServerUrl() + "/system/wx-pay/notify/" + order.getTenantId()); // 默认回调地址
// 测试环境
if (active.equals("dev")) {
amount.setTotal(1);
request.setAmount(amount);
request.setNotifyUrl("http://jimei-api.natapp1.cc/api/shop/wx-pay/notify/" + order.getTenantId()); // 默认回调地址
}
// 后台配置的回调地址
if (StrUtil.isNotBlank(payment.getNotifyUrl())) {
request.setNotifyUrl(payment.getNotifyUrl().concat("/").concat(order.getTenantId().toString()));
System.out.println("后台配置的回调地址 = " + request.getNotifyUrl());
}
System.out.println("=== 发起微信支付请求 ===");
System.out.println("请求参数: " + request);
JsapiServiceExtension service = (JsapiServiceExtension) serviceObj;
PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(request);
System.out.println("=== 微信支付响应成功 ===");
System.out.println("预支付ID: " + response.getPackageVal());
final HashMap<String, String> orderInfo = new HashMap<>();
orderInfo.put("provider", "wxpay");
orderInfo.put("timeStamp", response.getTimeStamp());
orderInfo.put("nonceStr", response.getNonceStr());
orderInfo.put("package", response.getPackageVal());
orderInfo.put("signType", "RSA");
orderInfo.put("paySign", response.getPaySign());
orderInfo.put("orderNo", order.getOrderNo());
orderInfo.put("payType", WechatPayType.JSAPI);
orderInfo.put("wechatPayType", WechatPayType.JSAPI);
return orderInfo;
}
System.out.println("=== 发起微信支付请求 ===");
System.out.println("请求参数: " + request);
PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(request);
System.out.println("=== 微信支付响应成功 ===");
System.out.println("预支付ID: " + response.getPackageVal());
final HashMap<String, String> orderInfo = new HashMap<>();
orderInfo.put("provider", "wxpay");
orderInfo.put("timeStamp", response.getTimeStamp());
orderInfo.put("nonceStr", response.getNonceStr());
orderInfo.put("package", response.getPackageVal());
orderInfo.put("signType", "RSA");
orderInfo.put("paySign", response.getPaySign());
orderInfo.put("orderNo", order.getOrderNo());
orderInfo.put("payType", WechatPayType.JSAPI);
orderInfo.put("wechatPayType", WechatPayType.JSAPI);
return orderInfo;
}
@Override
@ -385,24 +585,61 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
public Boolean queryOrderByOutTradeNo(ShopOrder shopOrder) {
// 后台微信支付配置信息
final Payment payment = getPayment(shopOrder);
QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();
queryRequest.setMchid(payment.getMchId());
queryRequest.setOutTradeNo(shopOrder.getOrderNo());
// 构建service
JsapiServiceExtension service = getWxService(shopOrder);
try {
Transaction result = service.queryOrderByOutTradeNo(queryRequest);
if (result.getTradeState().equals(Transaction.TradeStateEnum.SUCCESS)) {
shopOrder.setPayStatus(true);
shopOrder.setPayTime(LocalDateTime.now());
shopOrder.setTransactionId(result.getTransactionId());
updateById(shopOrder);
return true;
// 检查是否为服务商模式
boolean isPartnerMode = payment.getWechatType() != null && payment.getWechatType() == 1;
if (isPartnerMode) {
// 服务商模式查询
com.wechat.pay.java.service.partnerpayments.jsapi.model.QueryOrderByOutTradeNoRequest queryRequest =
new com.wechat.pay.java.service.partnerpayments.jsapi.model.QueryOrderByOutTradeNoRequest();
queryRequest.setSpMchid(payment.getMchId()); // 服务商商户号
queryRequest.setOutTradeNo(shopOrder.getOrderNo());
// 构建服务商模式service
Object serviceObj = getWxServiceWithPartnerSupport(shopOrder);
com.wechat.pay.java.service.partnerpayments.jsapi.JsapiService service =
(com.wechat.pay.java.service.partnerpayments.jsapi.JsapiService) serviceObj;
try {
com.wechat.pay.java.service.partnerpayments.jsapi.model.Transaction result =
service.queryOrderByOutTradeNo(queryRequest);
if (result.getTradeState().equals(
com.wechat.pay.java.service.partnerpayments.jsapi.model.Transaction.TradeStateEnum.SUCCESS)) {
shopOrder.setPayStatus(true);
shopOrder.setPayTime(LocalDateTime.now());
shopOrder.setTransactionId(result.getTransactionId());
updateById(shopOrder);
return true;
}
} catch (ServiceException e) {
// API返回失败, 例如ORDER_NOT_EXISTS
System.out.printf("code=[%s], message=[%s]\n", e.getErrorCode(), e.getErrorMessage());
System.out.printf("reponse body=[%s]\n", e.getResponseBody());
}
} else {
// 普通商户模式查询
QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();
queryRequest.setMchid(payment.getMchId());
queryRequest.setOutTradeNo(shopOrder.getOrderNo());
// 构建普通商户模式service
Object serviceObj = getWxServiceWithPartnerSupport(shopOrder);
JsapiServiceExtension service = (JsapiServiceExtension) serviceObj;
try {
Transaction result = service.queryOrderByOutTradeNo(queryRequest);
if (result.getTradeState().equals(Transaction.TradeStateEnum.SUCCESS)) {
shopOrder.setPayStatus(true);
shopOrder.setPayTime(LocalDateTime.now());
shopOrder.setTransactionId(result.getTransactionId());
updateById(shopOrder);
return true;
}
} catch (ServiceException e) {
// API返回失败, 例如ORDER_NOT_EXISTS
System.out.printf("code=[%s], message=[%s]\n", e.getErrorCode(), e.getErrorMessage());
System.out.printf("reponse body=[%s]\n", e.getResponseBody());
}
} catch (ServiceException e) {
// API返回失败, 例如ORDER_NOT_EXISTS
System.out.printf("code=[%s], message=[%s]\n", e.getErrorCode(), e.getErrorMessage());
System.out.printf("reponse body=[%s]\n", e.getResponseBody());
}
return false;
}
@ -560,12 +797,12 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
}
/**
* 构建微信支付
* 构建微信支付服务支持服务商模式
*
* @param order
* @return
* @return Object 可能是JsapiServiceExtension普通商户或JsapiService服务商
*/
public JsapiServiceExtension getWxService(ShopOrder order) {
public Object getWxServiceWithPartnerSupport(ShopOrder order) {
try {
final Payment payment = getPayment(order);
@ -590,7 +827,11 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
certificateDiagnostic.diagnoseCertificateConfig(payment, order.getTenantId(), active);
System.out.println(diagnosticResult.getFullReport());
// 检查是否为服务商模式(wechatType为2)
boolean isPartnerMode = payment.getWechatType() != null && payment.getWechatType() == 2;
System.out.println("=== 微信支付模式检查 ===");
System.out.println("商户类型: " + (isPartnerMode ? "服务商模式" : "普通商户模式"));
System.out.println("WechatType: " + payment.getWechatType());
// 构建微信支付配置
Config config = null;
@ -907,8 +1148,16 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
}
}
// 构建service
return new JsapiServiceExtension.Builder().config(config).build();
// 根据商户类型返回不同的服务对象
if (isPartnerMode) {
// 服务商模式
System.out.println("=== 构建服务商模式支付服务 ===");
return new com.wechat.pay.java.service.partnerpayments.jsapi.JsapiService.Builder().config(config).build();
} else {
// 普通商户模式
System.out.println("=== 构建普通商户模式支付服务 ===");
return new JsapiServiceExtension.Builder().config(config).build();
}
} catch (Exception e) {
System.err.println("=== 构建微信支付服务失败 ===");
System.err.println("错误信息: " + e.getMessage());

112
src/main/resources/sql/add_partner_payment_fields.sql

@ -0,0 +1,112 @@
-- 为sys_payment表添加服务商模式支持字段
-- 此脚本可以安全地在生产数据库执行
-- 不会破坏现有数据,只添加必要的字段支持
-- ========================================
-- 1. 检查当前环境(手动确认)
-- ========================================
SELECT
'请确认这是您要修改的数据库' as warning,
DATABASE() as current_database,
NOW() as execution_time;
-- ========================================
-- 2. 添加服务商模式支持字段(如果不存在)
-- ========================================
-- 检查是否需要添加子商户应用ID字段
SELECT
CASE
WHEN COUNT(*) = 0 THEN '需要添加sub_app_id字段'
ELSE 'sub_app_id字段已存在'
END as sub_app_id_field_status
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'sys_payment'
AND COLUMN_NAME = 'sub_app_id';
-- 添加子商户应用ID字段(如果不存在)
SET @sql = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'sys_payment'
AND COLUMN_NAME = 'sub_app_id') = 0,
'ALTER TABLE sys_payment ADD COLUMN sub_app_id VARCHAR(64) DEFAULT NULL COMMENT "子商户应用ID(服务商模式专用)" AFTER mch_id;',
'SELECT "sub_app_id column already exists" as result;'
));
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 检查是否需要添加子商户号字段
SELECT
CASE
WHEN COUNT(*) = 0 THEN '需要添加sub_mch_id字段'
ELSE 'sub_mch_id字段已存在'
END as sub_mch_id_field_status
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'sys_payment'
AND COLUMN_NAME = 'sub_mch_id';
-- 添加子商户号字段(如果不存在)
SET @sql = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'sys_payment'
AND COLUMN_NAME = 'sub_mch_id') = 0,
'ALTER TABLE sys_payment ADD COLUMN sub_mch_id VARCHAR(32) DEFAULT NULL COMMENT "子商户号(服务商模式专用)" AFTER sub_app_id;',
'SELECT "sub_mch_id column already exists" as result;'
));
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- ========================================
-- 3. 更新现有配置的字段注释(可选)
-- ========================================
-- 更新wechat_type字段的注释,使其更明确
ALTER TABLE sys_payment MODIFY COLUMN wechat_type INT DEFAULT NULL COMMENT '微信商户号类型 0普通商户 1子商户';
-- ========================================
-- 4. 验证字段添加结果
-- ========================================
-- 检查所有支付配置表字段
SELECT
'支付配置表字段检查' as check_type,
COLUMN_NAME as column_name,
COLUMN_TYPE as column_type,
IS_NULLABLE as is_nullable,
COLUMN_DEFAULT as column_default,
COLUMN_COMMENT as column_comment
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'sys_payment'
AND COLUMN_NAME IN ('wechat_type', 'app_id', 'mch_id', 'sub_app_id', 'sub_mch_id')
ORDER BY ORDINAL_POSITION;
-- ========================================
-- 5. 示例:如何配置服务商模式支付
-- ========================================
/*
-- 服务商模式支付配置示例(请根据实际情况修改)
UPDATE sys_payment
SET
wechat_type = 2, -- 设置为子商户类型
sub_app_id = 'wx_sub_app_id_here', -- 子商户应用ID
sub_mch_id = 'sub_mch_id_here' -- 子商户号
WHERE id = YOUR_PAYMENT_CONFIG_ID;
-- 普通商户模式支付配置示例(默认配置)
UPDATE sys_payment
SET
wechat_type = 1, -- 设置为普通商户类型
sub_app_id = NULL, -- 清除子商户应用ID
sub_mch_id = NULL -- 清除子商户号
WHERE id = YOUR_PAYMENT_CONFIG_ID;
*/
SELECT '服务商模式支持字段添加完成!' as result;

2
src/main/resources/sql/create_dev_tenant_payment.sql

@ -67,7 +67,7 @@ INSERT IGNORE INTO sys_payment (
0, -- 微信支付
'wechat_dev',
'/static/images/wechat_pay.png',
1, -- 普通商户
0, -- 普通商户
'wx1234567890abcdef', -- 开发环境AppID
'1234567890', -- 开发环境商户号
'your_dev_api_key_32_characters_long',

267
src/main/resources/sql/create_dev_tenant_payment_with_partner.sql

@ -0,0 +1,267 @@
-- 创建开发专用租户和支付配置(包含服务商模式支持)
-- 用于隔离开发环境和生产环境的支付回调地址
-- ========================================
-- 1. 创建开发专用租户(如果不存在)
-- ========================================
-- 检查是否已存在开发租户
SELECT 'Checking for dev tenant...' as status;
-- 插入开发租户(租户ID使用 9999 避免与生产冲突)
INSERT IGNORE INTO sys_tenant (
tenant_id,
tenant_name,
tenant_code,
contact_person,
contact_phone,
contact_email,
status,
deleted,
create_time,
update_time,
comments
) VALUES (
9999,
'开发测试租户',
'DEV_TENANT',
'开发者',
'13800000000',
'dev@websoft.top',
1,
0,
NOW(),
NOW(),
'专用于开发环境测试,不影响生产环境'
);
-- ========================================
-- 2. 创建开发环境专用支付配置(普通商户模式)
-- ========================================
-- 微信支付开发配置(普通商户模式)
INSERT IGNORE INTO sys_payment (
name,
type,
code,
image,
wechat_type,
app_id,
mch_id,
api_key,
apiclient_cert,
apiclient_key,
pub_key,
pub_key_id,
merchant_serial_number,
notify_url,
comments,
sort_number,
status,
deleted,
tenant_id,
create_time,
update_time
) VALUES (
'微信支付-开发环境(普通商户)',
0, -- 微信支付
'wechat_dev_normal',
'/static/images/wechat_pay.png',
1, -- 普通商户
'wx1234567890abcdef', -- 开发环境AppID
'1234567890', -- 开发环境商户号
'your_dev_api_key_32_characters_long',
'dev/wechat/apiclient_cert.pem',
'dev/wechat/apiclient_key.pem',
'dev/wechat/wechatpay_cert.pem',
'your_pub_key_id',
'your_merchant_serial_number',
'http://frps-10550.s209.websoft.top/api/shop/shop-order/notify', -- 开发环境回调地址
'开发环境专用配置(普通商户模式),使用本地回调地址',
1,
1, -- 启用
0, -- 未删除
9999, -- 开发租户ID
NOW(),
NOW()
);
-- ========================================
-- 3. 创建开发环境专用支付配置(服务商模式)
-- ========================================
-- 微信支付开发配置(服务商模式)
INSERT IGNORE INTO sys_payment (
name,
type,
code,
image,
wechat_type,
app_id,
mch_id,
sub_app_id,
sub_mch_id,
api_key,
apiclient_cert,
apiclient_key,
pub_key,
pub_key_id,
merchant_serial_number,
notify_url,
comments,
sort_number,
status,
deleted,
tenant_id,
create_time,
update_time
) VALUES (
'微信支付-开发环境(服务商模式)',
0, -- 微信支付
'wechat_dev_partner',
'/static/images/wechat_pay.png',
1, -- 子商户(服务商模式)
'wx_service_app_id_here', -- 服务商应用ID
'service_mch_id_here', -- 服务商户号
'wx_sub_app_id_here', -- 子商户应用ID
'sub_mch_id_here', -- 子商户号
'your_dev_api_key_32_characters_long',
'dev/wechat/apiclient_cert.pem',
'dev/wechat/apiclient_key.pem',
'dev/wechat/wechatpay_cert.pem',
'your_pub_key_id',
'your_merchant_serial_number',
'http://frps-10550.s209.websoft.top/api/shop/shop-order/notify', -- 开发环境回调地址
'开发环境专用配置(服务商模式),使用本地回调地址',
2,
1, -- 启用
0, -- 未删除
9999, -- 开发租户ID
NOW(),
NOW()
);
-- 支付宝开发配置
INSERT IGNORE INTO sys_payment (
name,
type,
code,
app_id,
mch_id,
api_key,
notify_url,
comments,
sort_number,
status,
deleted,
tenant_id,
create_time,
update_time
) VALUES (
'支付宝-开发环境',
1, -- 支付宝
'alipay_dev',
'your_dev_alipay_app_id',
'your_dev_alipay_mch_id',
'your_dev_alipay_private_key',
'http://frps-10550.s209.websoft.top/api/shop/shop-order/notify', -- 开发环境回调地址
'开发环境专用支付宝配置',
3,
1, -- 启用
0, -- 未删除
9999, -- 开发租户ID
NOW(),
NOW()
);
-- ========================================
-- 4. 创建开发环境用户(可选)
-- ========================================
-- 创建开发专用用户
INSERT IGNORE INTO sys_user (
user_id,
username,
password,
nickname,
avatar,
sex,
phone,
email,
email_verified,
real_name,
id_card,
birthday,
department_id,
status,
deleted,
tenant_id,
create_time,
update_time,
comments
) VALUES (
99999,
'dev_user',
'$2a$10$yKTnKzKqKqKqKqKqKqKqKOKqKqKqKqKqKqKqKqKqKqKqKqKqKqKqK', -- 密码: dev123456
'开发测试用户',
'/static/images/default_avatar.png',
1,
'13800000001',
'dev_user@websoft.top',
1,
'开发者',
'000000000000000000',
'1990-01-01',
1,
0, -- 正常状态
0, -- 未删除
9999, -- 开发租户ID
NOW(),
NOW(),
'开发环境专用测试用户'
);
-- ========================================
-- 5. 验证创建结果
-- ========================================
-- 检查租户创建结果
SELECT
'Tenant Check' as check_type,
tenant_id,
tenant_name,
tenant_code,
status
FROM sys_tenant
WHERE tenant_id = 9999;
-- 检查支付配置创建结果
SELECT
'Payment Config Check' as check_type,
id,
name,
type,
wechat_type,
app_id,
mch_id,
sub_app_id,
sub_mch_id,
notify_url,
tenant_id,
status
FROM sys_payment
WHERE tenant_id = 9999
ORDER BY sort_number;
-- 检查用户创建结果
SELECT
'User Check' as check_type,
user_id,
username,
nickname,
tenant_id,
status
FROM sys_user
WHERE tenant_id = 9999;
SELECT '开发环境配置创建完成(包含服务商模式支持)!' as result;

3
src/main/resources/sql/production_safe_payment_config.sql

@ -42,6 +42,9 @@ PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 更新wechat_type字段的注释,使其更明确
ALTER TABLE sys_payment MODIFY COLUMN wechat_type INT DEFAULT NULL COMMENT '微信商户号类型 0普通商户 1子商户';
-- ========================================
-- 3. 为现有支付配置添加环境标识
-- ========================================

Loading…
Cancel
Save