|
|
@@ -11,22 +11,25 @@ import org.springframework.stereotype.Component;
|
|
|
import shop.alien.entity.result.R;
|
|
|
import shop.alien.entity.store.MerchantPaymentOrder;
|
|
|
import shop.alien.entity.store.RefundRecord;
|
|
|
-import shop.alien.entity.store.StorePaymentConfig;
|
|
|
+import shop.alien.entity.store.StoreInfo;
|
|
|
import shop.alien.entity.store.UserReservation;
|
|
|
import shop.alien.entity.store.UserReservationOrder;
|
|
|
+import shop.alien.mapper.StoreInfoMapper;
|
|
|
import shop.alien.store.service.MerchantPaymentOrderService;
|
|
|
import shop.alien.store.service.RefundRecordAsyncService;
|
|
|
import shop.alien.store.service.RefundRecordService;
|
|
|
-import shop.alien.store.service.StorePaymentConfigService;
|
|
|
import shop.alien.store.service.ReservationOrderPaymentTimeoutService;
|
|
|
import shop.alien.store.service.UserReservationOrderService;
|
|
|
import shop.alien.store.service.UserReservationService;
|
|
|
import shop.alien.store.strategy.merchantPayment.MerchantPaymentStrategy;
|
|
|
+import shop.alien.store.strategy.payment.impl.WeChatPartnerPaymentStrategyImpl;
|
|
|
import shop.alien.store.strategy.payment.impl.WeChatPaymentStrategyImpl;
|
|
|
import shop.alien.store.util.WXPayUtility;
|
|
|
import shop.alien.util.common.UniqueRandomNumGenerator;
|
|
|
import shop.alien.util.common.constant.PaymentEnum;
|
|
|
+import shop.alien.util.system.OSUtil;
|
|
|
|
|
|
+import javax.annotation.PostConstruct;
|
|
|
import java.io.IOException;
|
|
|
import java.math.BigDecimal;
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
@@ -37,7 +40,9 @@ import java.util.*;
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
/**
|
|
|
- * 商户微信支付策略(预订订单,使用 StorePaymentConfig 按门店配置,业务逻辑与支付宝一致)
|
|
|
+ * 商户微信支付策略(预订订单):业务与支付宝侧一致;支付接口走微信服务商模式(特约商户),
|
|
|
+ * 与 {@link WeChatPartnerPaymentStrategyImpl} 一致使用 sp_mchid + sub_mchid、服务商证书;
|
|
|
+ * 不查 {@code StorePaymentConfig};预下单请求体不设 sub_appid(与 Partner 的 APP 预下单一致)。
|
|
|
*
|
|
|
* @author system
|
|
|
*/
|
|
|
@@ -59,20 +64,47 @@ public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrateg
|
|
|
/** 退款记录状态:退款失败 */
|
|
|
private static final String REFUND_STATUS_FAIL = "FAIL";
|
|
|
|
|
|
- @Value("${payment.wechatPay.host:https://api.mch.weixin.qq.com}")
|
|
|
+ @Value("${payment.wechatPartnerPay.host:https://api.mch.weixin.qq.com}")
|
|
|
private String wechatPayApiHost;
|
|
|
- @Value("${payment.wechatPay.prePayPath:/v3/pay/transactions/app}")
|
|
|
+ @Value("${payment.wechatPartnerPay.prePayPath:/v3/pay/partner/transactions/app}")
|
|
|
private String prePayPath;
|
|
|
- @Value("${payment.wechatPay.searchOrderByOutTradeNoPath:/v3/pay/transactions/out-trade-no/{out_trade_no}}")
|
|
|
+ @Value("${payment.wechatPartnerPay.searchOrderByOutTradeNoPath:/v3/pay/partner/transactions/out-trade-no/{out_trade_no}}")
|
|
|
private String searchOrderByOutTradeNoPath;
|
|
|
- @Value("${payment.wechatPay.refundPath:/v3/refund/domestic/refunds}")
|
|
|
+ @Value("${payment.wechatPartnerPay.refundPath:/v3/refund/domestic/refunds}")
|
|
|
private String refundPath;
|
|
|
+
|
|
|
+ @Value("${payment.wechatPartnerPay.business.spAppId}")
|
|
|
+ private String spAppId;
|
|
|
+ @Value("${payment.wechatPartnerPay.business.spMchId}")
|
|
|
+ private String spMchId;
|
|
|
+
|
|
|
+ @Value("${payment.wechatPartnerPay.business.win.privateKeyPath}")
|
|
|
+ private String privateWinKeyPath;
|
|
|
+ @Value("${payment.wechatPartnerPay.business.linux.privateKeyPath}")
|
|
|
+ private String privateLinuxKeyPath;
|
|
|
+ @Value("${payment.wechatPartnerPay.business.win.wechatPayPublicKeyFilePath}")
|
|
|
+ private String wechatWinPayPublicKeyFilePath;
|
|
|
+ @Value("${payment.wechatPartnerPay.business.linux.wechatPayPublicKeyFilePath}")
|
|
|
+ private String wechatLinuxPayPublicKeyFilePath;
|
|
|
+ @Value("${payment.wechatPartnerPay.business.merchantSerialNumber}")
|
|
|
+ private String merchantSerialNumber;
|
|
|
+ @Value("${payment.wechatPartnerPay.business.wechatPayPublicKeyId}")
|
|
|
+ private String wechatPayPublicKeyId;
|
|
|
+
|
|
|
+ /** 服务商下单回调 URL:优先 partner 配置(与 {@link WeChatPartnerPaymentStrategyImpl} 一致),否则回退直连配置 */
|
|
|
+ @Value("${payment.wechatPartnerPay.business.prePayNotifyUrl:}")
|
|
|
+ private String partnerPrePayNotifyUrl;
|
|
|
@Value("${payment.wechatPay.business.prePayNotifyUrl:}")
|
|
|
- private String prePayNotifyUrl;
|
|
|
+ private String directPrePayNotifyUrl;
|
|
|
+ @Value("${payment.wechatPartnerPay.business.refundNotifyUrl:}")
|
|
|
+ private String partnerRefundNotifyUrl;
|
|
|
@Value("${payment.wechatPay.business.refundNotifyUrl:}")
|
|
|
- private String refundNotifyUrl;
|
|
|
+ private String directRefundNotifyUrl;
|
|
|
+
|
|
|
+ private PrivateKey partnerPrivateKey;
|
|
|
+ private PublicKey partnerWechatPayPublicKey;
|
|
|
|
|
|
- private final StorePaymentConfigService storePaymentConfigService;
|
|
|
+ private final StoreInfoMapper storeInfoMapper;
|
|
|
private final UserReservationOrderService userReservationOrderService;
|
|
|
private final MerchantPaymentOrderService merchantPaymentOrderService;
|
|
|
private final RefundRecordAsyncService refundRecordAsyncService;
|
|
|
@@ -81,6 +113,22 @@ public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrateg
|
|
|
private final UserReservationService userReservationService;
|
|
|
private final StringRedisTemplate stringRedisTemplate;
|
|
|
|
|
|
+ @PostConstruct
|
|
|
+ public void loadPartnerCertificates() {
|
|
|
+ String privateKeyPath;
|
|
|
+ String wechatPayPublicKeyFilePath;
|
|
|
+ if ("windows".equals(OSUtil.getOsName())) {
|
|
|
+ privateKeyPath = privateWinKeyPath;
|
|
|
+ wechatPayPublicKeyFilePath = wechatWinPayPublicKeyFilePath;
|
|
|
+ } else {
|
|
|
+ privateKeyPath = privateLinuxKeyPath;
|
|
|
+ wechatPayPublicKeyFilePath = wechatLinuxPayPublicKeyFilePath;
|
|
|
+ }
|
|
|
+ log.info("[MerchantWechat] 加载服务商商户证书私钥与平台公钥,os={}, keyPath={}", OSUtil.getOsName(), privateKeyPath);
|
|
|
+ this.partnerPrivateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyPath);
|
|
|
+ this.partnerWechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public R<Map<String, Object>> createPrePay(Integer storeId, Integer orderId, String amountYuan, String subject, Integer userId, String smid) {
|
|
|
if (storeId == null || orderId == null) {
|
|
|
@@ -92,16 +140,15 @@ public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrateg
|
|
|
if (StringUtils.isBlank(subject)) {
|
|
|
return R.fail("订单描述不能为空");
|
|
|
}
|
|
|
- StorePaymentConfig config = storePaymentConfigService.getByStoreId(storeId);
|
|
|
- if (config == null) {
|
|
|
- return R.fail("该门店未配置支付参数");
|
|
|
+ String subMchid = resolveSubMchidFromStore(storeId);
|
|
|
+ if (StringUtils.isBlank(subMchid)) {
|
|
|
+ return R.fail("门店未配置微信特约商户号 wechat_sub_mchid");
|
|
|
}
|
|
|
- if (StringUtils.isBlank(config.getWechatAppId()) || StringUtils.isBlank(config.getWechatMchId())
|
|
|
- || StringUtils.isBlank(config.getMerchantSerialNumber()) || StringUtils.isBlank(config.getApiV3Key())
|
|
|
- || StringUtils.isBlank(config.getWechatPayPublicKeyId())
|
|
|
- || config.getWechatPrivateKeyFile() == null || config.getWechatPrivateKeyFile().length == 0
|
|
|
- || config.getWechatPayPublicKeyFile() == null || config.getWechatPayPublicKeyFile().length == 0) {
|
|
|
- return R.fail("门店微信支付配置不完整");
|
|
|
+ if (StringUtils.isBlank(spMchId) || StringUtils.isBlank(spAppId)) {
|
|
|
+ return R.fail("服务商微信支付未配置(payment.wechatPartnerPay.business.spMchId / spAppId)");
|
|
|
+ }
|
|
|
+ if (partnerPrivateKey == null || partnerWechatPayPublicKey == null) {
|
|
|
+ return R.fail("服务商微信证书未加载,请检查 payment.wechatPartnerPay 私钥与平台公钥路径");
|
|
|
}
|
|
|
UserReservationOrder order = userReservationOrderService.getById(orderId);
|
|
|
if (order == null) {
|
|
|
@@ -162,28 +209,29 @@ public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrateg
|
|
|
merchantPaymentOrderService.save(paymentOrder);
|
|
|
|
|
|
try {
|
|
|
- PrivateKey privateKey = WXPayUtility.loadPrivateKeyFromString(bytesToUtf8String(config.getWechatPrivateKeyFile()));
|
|
|
-
|
|
|
- WeChatPaymentStrategyImpl.CommonPrepayRequest request = new WeChatPaymentStrategyImpl.CommonPrepayRequest();
|
|
|
- request.appid = config.getWechatAppId();
|
|
|
- request.mchid = config.getWechatMchId();
|
|
|
+ WeChatPartnerPaymentStrategyImpl.PartnerAppPrepayRequest request = new WeChatPartnerPaymentStrategyImpl.PartnerAppPrepayRequest();
|
|
|
+ request.spAppid = spAppId;
|
|
|
+ request.spMchid = spMchId;
|
|
|
+ request.subMchid = subMchid;
|
|
|
request.description = subject;
|
|
|
request.outTradeNo = outTradeNo;
|
|
|
- request.notifyUrl = StringUtils.isNotBlank(prePayNotifyUrl) ? prePayNotifyUrl : "";
|
|
|
+ request.notifyUrl = resolveNotifyUrl(partnerPrePayNotifyUrl, directPrePayNotifyUrl);
|
|
|
request.amount = new WeChatPaymentStrategyImpl.CommonAmountInfo();
|
|
|
request.amount.total = amount.multiply(new BigDecimal(100)).longValue();
|
|
|
request.amount.currency = "CNY";
|
|
|
|
|
|
- WeChatPaymentStrategyImpl.DirectAPIv3AppPrepayResponse response = prePayOrderRun(config, privateKey, request);
|
|
|
+ WeChatPaymentStrategyImpl.DirectAPIv3AppPrepayResponse response = partnerPrePayOrderRun(request);
|
|
|
if (response == null || StringUtils.isBlank(response.prepayId)) {
|
|
|
return R.fail("微信预支付失败");
|
|
|
}
|
|
|
|
|
|
long timestamp = System.currentTimeMillis() / 1000;
|
|
|
String nonce = WXPayUtility.createNonce(32);
|
|
|
- String message = String.format("%s\n%s\n%s\n%s\n", config.getWechatAppId(), timestamp, nonce, response.prepayId);
|
|
|
+ // APP 调起支付验签串(与微信文档一致):appId\ntimeStamp\nnonceStr\nprepayId\n
|
|
|
+ // 须与传给 OpenSDK 的 appId、timeStamp、nonceStr、prepayId 完全一致;此处与直连 {@link WeChatPaymentStrategyImpl} 一致
|
|
|
+ String message = String.format("%s\n%s\n%s\n%s\n", spAppId, timestamp, nonce, response.prepayId);
|
|
|
Signature sign = Signature.getInstance("SHA256withRSA");
|
|
|
- sign.initSign(privateKey);
|
|
|
+ sign.initSign(partnerPrivateKey);
|
|
|
sign.update(message.getBytes(StandardCharsets.UTF_8));
|
|
|
String signStr = Base64.getEncoder().encodeToString(sign.sign());
|
|
|
|
|
|
@@ -193,8 +241,11 @@ public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrateg
|
|
|
data.put("orderId", order.getId());
|
|
|
data.put("paymentNo", paymentOrder.getPaymentNo());
|
|
|
data.put("prepayId", response.prepayId);
|
|
|
- data.put("appId", config.getWechatAppId());
|
|
|
- data.put("mchId", config.getWechatMchId());
|
|
|
+ data.put("appId", spAppId);
|
|
|
+ data.put("spAppId", spAppId);
|
|
|
+ data.put("spMchId", spMchId);
|
|
|
+ data.put("subMchId", subMchid);
|
|
|
+ data.put("mchId", subMchid);
|
|
|
data.put("sign", signStr);
|
|
|
data.put("timestamp", String.valueOf(timestamp));
|
|
|
data.put("nonce", nonce);
|
|
|
@@ -220,10 +271,14 @@ public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrateg
|
|
|
log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=参数为空", storeId, outTradeNo);
|
|
|
return R.fail("门店ID和商户订单号不能为空");
|
|
|
}
|
|
|
- StorePaymentConfig config = storePaymentConfigService.getByStoreId(storeId);
|
|
|
- if (config == null) {
|
|
|
- log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=未配置支付参数", storeId, outTradeNo);
|
|
|
- return R.fail("该门店未配置支付参数");
|
|
|
+ String subMchid = resolveSubMchidFromStore(storeId);
|
|
|
+ if (StringUtils.isBlank(subMchid)) {
|
|
|
+ log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=未配置特约商户号", storeId, outTradeNo);
|
|
|
+ return R.fail("门店未配置微信特约商户号 wechat_sub_mchid");
|
|
|
+ }
|
|
|
+ if (partnerPrivateKey == null || partnerWechatPayPublicKey == null) {
|
|
|
+ log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=服务商证书未加载", storeId, outTradeNo);
|
|
|
+ return R.fail("服务商微信证书未加载");
|
|
|
}
|
|
|
MerchantPaymentOrder paymentOrder = merchantPaymentOrderService.getByOutTradeNo(outTradeNo);
|
|
|
if (paymentOrder == null) {
|
|
|
@@ -236,13 +291,7 @@ public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrateg
|
|
|
return R.fail("预订订单不存在");
|
|
|
}
|
|
|
try {
|
|
|
- PrivateKey privateKey = WXPayUtility.loadPrivateKeyFromString(bytesToUtf8String(config.getWechatPrivateKeyFile()));
|
|
|
- PublicKey wechatPayPublicKey = WXPayUtility.loadPublicKeyFromString(bytesToUtf8String(config.getWechatPayPublicKeyFile()));
|
|
|
-
|
|
|
- WeChatPaymentStrategyImpl.QueryByWxTradeNoRequest req = new WeChatPaymentStrategyImpl.QueryByWxTradeNoRequest();
|
|
|
- req.transactionId = outTradeNo;
|
|
|
- req.mchid = config.getWechatMchId();
|
|
|
- WeChatPaymentStrategyImpl.DirectAPIv3QueryResponse response = searchOrderRun(config, privateKey, wechatPayPublicKey, req);
|
|
|
+ WeChatPaymentStrategyImpl.DirectAPIv3QueryResponse response = partnerSearchOrderRun(outTradeNo, subMchid);
|
|
|
if (response == null) {
|
|
|
log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=微信返回为空", storeId, outTradeNo);
|
|
|
return R.fail("查询失败");
|
|
|
@@ -306,9 +355,12 @@ public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrateg
|
|
|
if (StringUtils.isBlank(refundAmount) || new BigDecimal(refundAmount).compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
return R.fail("退款金额必须大于0");
|
|
|
}
|
|
|
- StorePaymentConfig config = storePaymentConfigService.getByStoreId(storeId);
|
|
|
- if (config == null) {
|
|
|
- return R.fail("该门店未配置支付参数");
|
|
|
+ String subMchid = resolveSubMchidFromStore(storeId);
|
|
|
+ if (StringUtils.isBlank(subMchid)) {
|
|
|
+ return R.fail("门店未配置微信特约商户号 wechat_sub_mchid");
|
|
|
+ }
|
|
|
+ if (partnerPrivateKey == null || partnerWechatPayPublicKey == null) {
|
|
|
+ return R.fail("服务商微信证书未加载");
|
|
|
}
|
|
|
MerchantPaymentOrder paymentOrder = merchantPaymentOrderService.getByOutTradeNo(outTradeNo);
|
|
|
if (paymentOrder == null) {
|
|
|
@@ -322,14 +374,12 @@ public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrateg
|
|
|
return R.fail("订单未支付或已退款,无法退款");
|
|
|
}
|
|
|
try {
|
|
|
- PrivateKey privateKey = WXPayUtility.loadPrivateKeyFromString(bytesToUtf8String(config.getWechatPrivateKeyFile()));
|
|
|
- PublicKey wechatPayPublicKey = WXPayUtility.loadPublicKeyFromString(bytesToUtf8String(config.getWechatPayPublicKeyFile()));
|
|
|
-
|
|
|
- WeChatPaymentStrategyImpl.CreateRequest request = new WeChatPaymentStrategyImpl.CreateRequest();
|
|
|
+ WeChatPartnerPaymentStrategyImpl.PartnerRefundCreateRequest request = new WeChatPartnerPaymentStrategyImpl.PartnerRefundCreateRequest();
|
|
|
+ request.subMchid = subMchid;
|
|
|
request.outTradeNo = outTradeNo;
|
|
|
request.outRefundNo = UniqueRandomNumGenerator.generateUniqueCode(19);
|
|
|
request.reason = StringUtils.isNotBlank(refundReason) ? refundReason : "用户申请退款";
|
|
|
- request.notifyUrl = StringUtils.isNotBlank(refundNotifyUrl) ? refundNotifyUrl : "";
|
|
|
+ request.notifyUrl = resolveNotifyUrl(partnerRefundNotifyUrl, directRefundNotifyUrl);
|
|
|
request.amount = new WeChatPaymentStrategyImpl.AmountReq();
|
|
|
request.amount.refund = new BigDecimal(refundAmount).multiply(new BigDecimal(100)).longValue();
|
|
|
// 微信 V3 amount.total 单位为分,depositAmount 为元,需 *100 转分
|
|
|
@@ -341,7 +391,7 @@ public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrateg
|
|
|
int maxAttempts = 3;
|
|
|
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
|
try {
|
|
|
- response = refundRun(config, privateKey, wechatPayPublicKey, request);
|
|
|
+ response = partnerRefundRun(request);
|
|
|
break;
|
|
|
} catch (WXPayUtility.ApiException e) {
|
|
|
lastApiException = e;
|
|
|
@@ -472,15 +522,14 @@ public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrateg
|
|
|
return PaymentEnum.WECHAT_PAY.getType();
|
|
|
}
|
|
|
|
|
|
- private WeChatPaymentStrategyImpl.DirectAPIv3AppPrepayResponse prePayOrderRun(StorePaymentConfig config,
|
|
|
- PrivateKey privateKey,
|
|
|
- WeChatPaymentStrategyImpl.CommonPrepayRequest request) throws IOException {
|
|
|
+ private WeChatPaymentStrategyImpl.DirectAPIv3AppPrepayResponse partnerPrePayOrderRun(
|
|
|
+ WeChatPartnerPaymentStrategyImpl.PartnerAppPrepayRequest request) throws IOException {
|
|
|
String uri = prePayPath;
|
|
|
String reqBody = WXPayUtility.toJson(request);
|
|
|
Request.Builder reqBuilder = new Request.Builder().url(wechatPayApiHost + uri);
|
|
|
reqBuilder.addHeader("Accept", "application/json");
|
|
|
- reqBuilder.addHeader("Wechatpay-Serial", config.getWechatPayPublicKeyId());
|
|
|
- reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(config.getWechatMchId(), config.getMerchantSerialNumber(), privateKey, POSTMETHOD, uri, reqBody));
|
|
|
+ reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
|
|
|
+ reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(spMchId, merchantSerialNumber, partnerPrivateKey, POSTMETHOD, uri, reqBody));
|
|
|
reqBuilder.addHeader("Content-Type", "application/json");
|
|
|
MediaType jsonMediaType = MediaType.parse("application/json; charset=utf-8");
|
|
|
RequestBody body = RequestBody.create(reqBody, jsonMediaType);
|
|
|
@@ -489,29 +538,26 @@ public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrateg
|
|
|
try (Response httpResponse = client.newCall(reqBuilder.build()).execute()) {
|
|
|
String respBody = WXPayUtility.extractBody(httpResponse);
|
|
|
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
|
|
|
- PublicKey publicKey = WXPayUtility.loadPublicKeyFromString(bytesToUtf8String(config.getWechatPayPublicKeyFile()));
|
|
|
- WXPayUtility.validateResponse(config.getWechatPayPublicKeyId(), publicKey, httpResponse.headers(), respBody);
|
|
|
+ WXPayUtility.validateResponse(wechatPayPublicKeyId, partnerWechatPayPublicKey, httpResponse.headers(), respBody);
|
|
|
return WXPayUtility.fromJson(respBody, WeChatPaymentStrategyImpl.DirectAPIv3AppPrepayResponse.class);
|
|
|
}
|
|
|
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private WeChatPaymentStrategyImpl.DirectAPIv3QueryResponse searchOrderRun(StorePaymentConfig config,
|
|
|
- PrivateKey privateKey,
|
|
|
- PublicKey wechatPayPublicKey,
|
|
|
- WeChatPaymentStrategyImpl.QueryByWxTradeNoRequest request) throws IOException {
|
|
|
- String uri = searchOrderByOutTradeNoPath.replace("{out_trade_no}", WXPayUtility.urlEncode(request.transactionId));
|
|
|
+ private WeChatPaymentStrategyImpl.DirectAPIv3QueryResponse partnerSearchOrderRun(String outTradeNo, String subMchid) throws IOException {
|
|
|
+ String uri = searchOrderByOutTradeNoPath.replace("{out_trade_no}", WXPayUtility.urlEncode(outTradeNo));
|
|
|
Map<String, Object> args = new HashMap<>();
|
|
|
- args.put("mchid", config.getWechatMchId());
|
|
|
+ args.put("sp_mchid", spMchId);
|
|
|
+ args.put("sub_mchid", subMchid);
|
|
|
String queryString = WXPayUtility.urlEncode(args);
|
|
|
if (!queryString.isEmpty()) {
|
|
|
uri = uri + "?" + queryString;
|
|
|
}
|
|
|
Request.Builder reqBuilder = new Request.Builder().url(wechatPayApiHost + uri);
|
|
|
reqBuilder.addHeader("Accept", "application/json");
|
|
|
- reqBuilder.addHeader("Wechatpay-Serial", config.getWechatPayPublicKeyId());
|
|
|
- reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(config.getWechatMchId(), config.getMerchantSerialNumber(), privateKey, GETMETHOD, uri, null));
|
|
|
+ reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
|
|
|
+ reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(spMchId, merchantSerialNumber, partnerPrivateKey, GETMETHOD, uri, null));
|
|
|
reqBuilder.method(GETMETHOD, null);
|
|
|
OkHttpClient client = new OkHttpClient.Builder()
|
|
|
.connectTimeout(10, TimeUnit.SECONDS)
|
|
|
@@ -520,23 +566,20 @@ public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrateg
|
|
|
try (Response httpResponse = client.newCall(reqBuilder.build()).execute()) {
|
|
|
String respBody = WXPayUtility.extractBody(httpResponse);
|
|
|
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
|
|
|
- WXPayUtility.validateResponse(config.getWechatPayPublicKeyId(), wechatPayPublicKey, httpResponse.headers(), respBody);
|
|
|
+ WXPayUtility.validateResponse(wechatPayPublicKeyId, partnerWechatPayPublicKey, httpResponse.headers(), respBody);
|
|
|
return WXPayUtility.fromJson(respBody, WeChatPaymentStrategyImpl.DirectAPIv3QueryResponse.class);
|
|
|
}
|
|
|
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private WeChatPaymentStrategyImpl.Refund refundRun(StorePaymentConfig config,
|
|
|
- PrivateKey privateKey,
|
|
|
- PublicKey wechatPayPublicKey,
|
|
|
- WeChatPaymentStrategyImpl.CreateRequest request) throws IOException {
|
|
|
+ private WeChatPaymentStrategyImpl.Refund partnerRefundRun(WeChatPartnerPaymentStrategyImpl.PartnerRefundCreateRequest request) throws IOException {
|
|
|
String uri = refundPath;
|
|
|
String reqBody = WXPayUtility.toJson(request);
|
|
|
Request.Builder reqBuilder = new Request.Builder().url(wechatPayApiHost + uri);
|
|
|
reqBuilder.addHeader("Accept", "application/json");
|
|
|
- reqBuilder.addHeader("Wechatpay-Serial", config.getWechatPayPublicKeyId());
|
|
|
- reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(config.getWechatMchId(), config.getMerchantSerialNumber(), privateKey, POSTMETHOD, uri, reqBody));
|
|
|
+ reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
|
|
|
+ reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(spMchId, merchantSerialNumber, partnerPrivateKey, POSTMETHOD, uri, reqBody));
|
|
|
reqBuilder.addHeader("Content-Type", "application/json");
|
|
|
MediaType jsonMediaType = MediaType.parse("application/json; charset=utf-8");
|
|
|
RequestBody body = RequestBody.create(reqBody, jsonMediaType);
|
|
|
@@ -545,15 +588,37 @@ public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrateg
|
|
|
try (Response httpResponse = client.newCall(reqBuilder.build()).execute()) {
|
|
|
String respBody = WXPayUtility.extractBody(httpResponse);
|
|
|
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
|
|
|
- WXPayUtility.validateResponse(config.getWechatPayPublicKeyId(), wechatPayPublicKey, httpResponse.headers(), respBody);
|
|
|
+ WXPayUtility.validateResponse(wechatPayPublicKeyId, partnerWechatPayPublicKey, httpResponse.headers(), respBody);
|
|
|
return WXPayUtility.fromJson(respBody, WeChatPaymentStrategyImpl.Refund.class);
|
|
|
}
|
|
|
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /** 仅在使用处将证书 byte[] 转为 String(UTF-8),不参与存储,保证存取一致 */
|
|
|
- private static String bytesToUtf8String(byte[] bytes) {
|
|
|
- return bytes == null ? null : new String(bytes, StandardCharsets.UTF_8);
|
|
|
+ private static String resolveNotifyUrl(String primary, String fallback) {
|
|
|
+ if (StringUtils.isNotBlank(primary)) {
|
|
|
+ return primary.trim();
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(fallback)) {
|
|
|
+ return fallback.trim();
|
|
|
+ }
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+
|
|
|
+ private String resolveSubMchidFromStore(Integer storeId) {
|
|
|
+ if (storeId == null) {
|
|
|
+ log.warn("[MerchantWechat] storeId 为空,无法解析 wechat_sub_mchid");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ StoreInfo info = storeInfoMapper.selectById(storeId);
|
|
|
+ if (info == null) {
|
|
|
+ log.warn("[MerchantWechat] 门店不存在 storeId={}", storeId);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ if (StringUtils.isBlank(info.getWechatSubMchid())) {
|
|
|
+ log.warn("[MerchantWechat] 门店未配置 wechat_sub_mchid storeId={}", storeId);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return info.getWechatSubMchid().trim();
|
|
|
}
|
|
|
}
|