优化:支付功能(10550)
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
package com.gxwebsoft.common.core.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 证书配置属性
|
||||
* 支持Docker容器化部署的证书管理
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "certificate")
|
||||
public class CertificateConfigProperties {
|
||||
|
||||
/**
|
||||
* 证书加载模式
|
||||
* classpath: 从classpath加载
|
||||
* filesystem: 从文件系统加载
|
||||
* volume: 从Docker挂载卷加载
|
||||
*/
|
||||
private LoadMode loadMode = LoadMode.FILESYSTEM;
|
||||
|
||||
/**
|
||||
* 证书根目录(Docker挂载卷路径)
|
||||
*/
|
||||
private String certRootPath = "/app/certs";
|
||||
|
||||
/**
|
||||
* 微信支付证书配置
|
||||
*/
|
||||
private WechatPayCert wechatPay = new WechatPayCert();
|
||||
|
||||
/**
|
||||
* 支付宝证书配置
|
||||
*/
|
||||
private AlipayCert alipay = new AlipayCert();
|
||||
|
||||
/**
|
||||
* 证书加载模式枚举
|
||||
*/
|
||||
public enum LoadMode {
|
||||
CLASSPATH, // 从classpath加载
|
||||
FILESYSTEM, // 从文件系统加载
|
||||
VOLUME // 从Docker挂载卷加载
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class WechatPayCert {
|
||||
/**
|
||||
* 开发环境证书配置
|
||||
*/
|
||||
private DevCert dev = new DevCert();
|
||||
|
||||
/**
|
||||
* 生产环境证书基础路径
|
||||
*/
|
||||
private String prodBasePath = "/file";
|
||||
|
||||
@Data
|
||||
public static class DevCert {
|
||||
/**
|
||||
* API V3密钥
|
||||
*/
|
||||
private String apiV3Key = "zGufUcqa7ovgxRL0kF5OlPr482EZwtn9";
|
||||
|
||||
/**
|
||||
* 商户私钥证书文件名
|
||||
*/
|
||||
private String privateKeyFile = "apiclient_key.pem";
|
||||
|
||||
/**
|
||||
* 商户证书文件名
|
||||
*/
|
||||
private String apiclientCertFile = "apiclient_cert.pem";
|
||||
|
||||
/**
|
||||
* 微信支付平台证书文件名
|
||||
*/
|
||||
private String wechatpayCertFile = "wechatpay_cert.pem";
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class AlipayCert {
|
||||
/**
|
||||
* 应用私钥证书文件名
|
||||
*/
|
||||
private String appPrivateKeyFile = "app_private_key.pem";
|
||||
|
||||
/**
|
||||
* 应用公钥证书文件名
|
||||
*/
|
||||
private String appCertPublicKeyFile = "appCertPublicKey.crt";
|
||||
|
||||
/**
|
||||
* 支付宝公钥证书文件名
|
||||
*/
|
||||
private String alipayCertPublicKeyFile = "alipayCertPublicKey.crt";
|
||||
|
||||
/**
|
||||
* 支付宝根证书文件名
|
||||
*/
|
||||
private String alipayRootCertFile = "alipayRootCert.crt";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取证书文件的完整路径
|
||||
*
|
||||
* @param fileName 证书文件名
|
||||
* @return 完整路径
|
||||
*/
|
||||
public String getCertPath(String fileName) {
|
||||
switch (loadMode) {
|
||||
case CLASSPATH:
|
||||
return "classpath:certs/" + fileName;
|
||||
case VOLUME:
|
||||
return certRootPath + "/" + fileName;
|
||||
case FILESYSTEM:
|
||||
default:
|
||||
return fileName; // 使用原有的完整路径
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信支付开发环境证书路径
|
||||
*
|
||||
* @param certType 证书类型:privateKey, apiclientCert, wechatpayCert
|
||||
* @return 证书路径
|
||||
*/
|
||||
public String getWechatDevCertPath(String certType) {
|
||||
String fileName;
|
||||
switch (certType) {
|
||||
case "privateKey":
|
||||
fileName = wechatPay.getDev().getPrivateKeyFile();
|
||||
break;
|
||||
case "apiclientCert":
|
||||
fileName = wechatPay.getDev().getApiclientCertFile();
|
||||
break;
|
||||
case "wechatpayCert":
|
||||
fileName = wechatPay.getDev().getWechatpayCertFile();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown cert type: " + certType);
|
||||
}
|
||||
return getCertPath(fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支付宝证书路径
|
||||
*
|
||||
* @param certType 证书类型
|
||||
* @return 证书路径
|
||||
*/
|
||||
public String getAlipayCertPath(String certType) {
|
||||
String fileName;
|
||||
switch (certType) {
|
||||
case "appPrivateKey":
|
||||
fileName = alipay.getAppPrivateKeyFile();
|
||||
break;
|
||||
case "appCertPublicKey":
|
||||
fileName = alipay.getAppCertPublicKeyFile();
|
||||
break;
|
||||
case "alipayCertPublicKey":
|
||||
fileName = alipay.getAlipayCertPublicKeyFile();
|
||||
break;
|
||||
case "alipayRootCert":
|
||||
fileName = alipay.getAlipayRootCertFile();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown cert type: " + certType);
|
||||
}
|
||||
return getCertPath(fileName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
package com.gxwebsoft.common.core.utils;
|
||||
|
||||
import com.gxwebsoft.common.core.config.CertificateConfigProperties;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* 证书加载工具类
|
||||
* 支持多种证书加载方式,适配Docker容器化部署
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class CertificateLoader {
|
||||
|
||||
private final CertificateConfigProperties certConfig;
|
||||
|
||||
public CertificateLoader(CertificateConfigProperties certConfig) {
|
||||
this.certConfig = certConfig;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
log.info("证书加载器初始化,加载模式:{}", certConfig.getLoadMode());
|
||||
if (certConfig.getLoadMode() == CertificateConfigProperties.LoadMode.VOLUME) {
|
||||
log.info("Docker挂载卷证书路径:{}", certConfig.getCertRootPath());
|
||||
validateCertDirectory();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证证书目录是否存在
|
||||
*/
|
||||
private void validateCertDirectory() {
|
||||
File certDir = new File(certConfig.getCertRootPath());
|
||||
if (!certDir.exists()) {
|
||||
log.warn("证书目录不存在:{},将尝试创建", certConfig.getCertRootPath());
|
||||
if (!certDir.mkdirs()) {
|
||||
log.error("无法创建证书目录:{}", certConfig.getCertRootPath());
|
||||
}
|
||||
} else {
|
||||
log.info("证书目录验证成功:{}", certConfig.getCertRootPath());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载证书文件路径
|
||||
*
|
||||
* @param certPath 证书路径(可能是相对路径、绝对路径或classpath路径)
|
||||
* @return 实际的证书文件路径
|
||||
*/
|
||||
public String loadCertificatePath(String certPath) {
|
||||
if (!StringUtils.hasText(certPath)) {
|
||||
throw new IllegalArgumentException("证书路径不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
switch (certConfig.getLoadMode()) {
|
||||
case CLASSPATH:
|
||||
return loadFromClasspath(certPath);
|
||||
case VOLUME:
|
||||
return loadFromVolume(certPath);
|
||||
case FILESYSTEM:
|
||||
default:
|
||||
return loadFromFileSystem(certPath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("加载证书失败,路径:{}", certPath, e);
|
||||
throw new RuntimeException("证书加载失败:" + certPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从classpath加载证书
|
||||
*/
|
||||
private String loadFromClasspath(String certPath) throws IOException {
|
||||
String resourcePath = certPath.startsWith("classpath:") ?
|
||||
certPath.substring("classpath:".length()) : certPath;
|
||||
|
||||
ClassPathResource resource = new ClassPathResource(resourcePath);
|
||||
if (!resource.exists()) {
|
||||
throw new IOException("Classpath中找不到证书文件:" + resourcePath);
|
||||
}
|
||||
|
||||
// 将classpath中的文件复制到临时目录
|
||||
Path tempFile = Files.createTempFile("cert_", ".pem");
|
||||
try (InputStream inputStream = resource.getInputStream()) {
|
||||
Files.copy(inputStream, tempFile, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
String tempPath = tempFile.toAbsolutePath().toString();
|
||||
log.debug("从classpath加载证书:{} -> {}", resourcePath, tempPath);
|
||||
return tempPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Docker挂载卷加载证书
|
||||
*/
|
||||
private String loadFromVolume(String certPath) {
|
||||
// 如果是完整路径,直接使用
|
||||
if (certPath.startsWith("/") || certPath.contains(":")) {
|
||||
File file = new File(certPath);
|
||||
if (file.exists()) {
|
||||
log.debug("使用完整路径加载证书:{}", certPath);
|
||||
return certPath;
|
||||
}
|
||||
}
|
||||
|
||||
// 否则拼接挂载卷路径
|
||||
String fullPath = Paths.get(certConfig.getCertRootPath(), certPath).toString();
|
||||
File file = new File(fullPath);
|
||||
if (!file.exists()) {
|
||||
throw new RuntimeException("Docker挂载卷中找不到证书文件:" + fullPath);
|
||||
}
|
||||
|
||||
log.debug("从Docker挂载卷加载证书:{}", fullPath);
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件系统加载证书
|
||||
*/
|
||||
private String loadFromFileSystem(String certPath) {
|
||||
File file = new File(certPath);
|
||||
if (!file.exists()) {
|
||||
throw new RuntimeException("文件系统中找不到证书文件:" + certPath);
|
||||
}
|
||||
|
||||
log.debug("从文件系统加载证书:{}", certPath);
|
||||
return certPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查证书文件是否存在
|
||||
*
|
||||
* @param certPath 证书路径
|
||||
* @return 是否存在
|
||||
*/
|
||||
public boolean certificateExists(String certPath) {
|
||||
try {
|
||||
switch (certConfig.getLoadMode()) {
|
||||
case CLASSPATH:
|
||||
String resourcePath = certPath.startsWith("classpath:") ?
|
||||
certPath.substring("classpath:".length()) : certPath;
|
||||
ClassPathResource resource = new ClassPathResource(resourcePath);
|
||||
return resource.exists();
|
||||
case VOLUME:
|
||||
String fullPath = certPath.startsWith("/") ? certPath :
|
||||
Paths.get(certConfig.getCertRootPath(), certPath).toString();
|
||||
return new File(fullPath).exists();
|
||||
case FILESYSTEM:
|
||||
default:
|
||||
return new File(certPath).exists();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("检查证书文件存在性时出错:{}", certPath, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取证书文件的输入流
|
||||
*
|
||||
* @param certPath 证书路径
|
||||
* @return 输入流
|
||||
*/
|
||||
public InputStream getCertificateInputStream(String certPath) throws IOException {
|
||||
switch (certConfig.getLoadMode()) {
|
||||
case CLASSPATH:
|
||||
String resourcePath = certPath.startsWith("classpath:") ?
|
||||
certPath.substring("classpath:".length()) : certPath;
|
||||
ClassPathResource resource = new ClassPathResource(resourcePath);
|
||||
return resource.getInputStream();
|
||||
case VOLUME:
|
||||
case FILESYSTEM:
|
||||
default:
|
||||
String actualPath = loadCertificatePath(certPath);
|
||||
return Files.newInputStream(Paths.get(actualPath));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 列出证书目录中的所有文件
|
||||
*
|
||||
* @return 证书文件列表
|
||||
*/
|
||||
public String[] listCertificateFiles() {
|
||||
try {
|
||||
switch (certConfig.getLoadMode()) {
|
||||
case VOLUME:
|
||||
File certDir = new File(certConfig.getCertRootPath());
|
||||
if (certDir.exists() && certDir.isDirectory()) {
|
||||
return certDir.list();
|
||||
}
|
||||
break;
|
||||
case CLASSPATH:
|
||||
// classpath模式下不支持列出文件
|
||||
log.warn("Classpath模式下不支持列出证书文件");
|
||||
break;
|
||||
case FILESYSTEM:
|
||||
default:
|
||||
// 文件系统模式下证书可能分散在不同目录,不支持统一列出
|
||||
log.warn("文件系统模式下不支持列出证书文件");
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("列出证书文件时出错", e);
|
||||
}
|
||||
return new String[0];
|
||||
}
|
||||
}
|
||||
@@ -129,9 +129,9 @@ public class SettingServiceImpl extends ServiceImpl<SettingMapper, Setting> impl
|
||||
config =
|
||||
new RSAConfig.Builder()
|
||||
.merchantId("1246610101")
|
||||
.privateKeyFromPath("/Users/gxwebsoft/Documents/uploads/file/20230622/fb193d3bfff0467b83dc498435a4f433.pem")
|
||||
.privateKeyFromPath("/Users/gxwebsoft/cert/1246610101_20221225_cert/01ac632fea184e248d0375e9917063a4.pem")
|
||||
.merchantSerialNumber("2903B872D5CA36E525FAEC37AEDB22E54ECDE7B7")
|
||||
.wechatPayCertificatesFromPath("/Users/gxwebsoft/Documents/uploads/file/20230622/23329e924dae41af9b716825626dd44b.pem")
|
||||
.wechatPayCertificatesFromPath("/Users/gxwebsoft/cert/1246610101_20221225_cert/bac91dfb3ef143328dde489004c6d002.pem")
|
||||
.build();
|
||||
configMap.put(data.getTenantId().toString(),config);
|
||||
System.out.println("config = " + config);
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.gxwebsoft.shop.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 订单相关配置属性
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "shop.order")
|
||||
public class OrderConfigProperties {
|
||||
|
||||
/**
|
||||
* 测试账号配置
|
||||
*/
|
||||
private TestAccount testAccount = new TestAccount();
|
||||
|
||||
/**
|
||||
* 租户特殊规则配置
|
||||
*/
|
||||
private List<TenantRule> tenantRules;
|
||||
|
||||
/**
|
||||
* 默认订单配置
|
||||
*/
|
||||
private DefaultConfig defaultConfig = new DefaultConfig();
|
||||
|
||||
@Data
|
||||
public static class TestAccount {
|
||||
/**
|
||||
* 测试手机号列表
|
||||
*/
|
||||
private List<String> phoneNumbers;
|
||||
|
||||
/**
|
||||
* 测试支付金额
|
||||
*/
|
||||
private BigDecimal testPayAmount = new BigDecimal("0.01");
|
||||
|
||||
/**
|
||||
* 是否启用测试模式
|
||||
*/
|
||||
private boolean enabled = false;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class TenantRule {
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private Integer tenantId;
|
||||
|
||||
/**
|
||||
* 租户名称
|
||||
*/
|
||||
private String tenantName;
|
||||
|
||||
/**
|
||||
* 最小金额限制
|
||||
*/
|
||||
private BigDecimal minAmount;
|
||||
|
||||
/**
|
||||
* 金额限制提示信息
|
||||
*/
|
||||
private String minAmountMessage;
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class DefaultConfig {
|
||||
/**
|
||||
* 默认备注
|
||||
*/
|
||||
private String defaultComments = "暂无";
|
||||
|
||||
/**
|
||||
* 最小订单金额
|
||||
*/
|
||||
private BigDecimal minOrderAmount = BigDecimal.ZERO;
|
||||
|
||||
/**
|
||||
* 订单超时时间(分钟)
|
||||
*/
|
||||
private Integer orderTimeoutMinutes = 30;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为测试账号
|
||||
*/
|
||||
public boolean isTestAccount(String phone) {
|
||||
return testAccount.isEnabled() &&
|
||||
testAccount.getPhoneNumbers() != null &&
|
||||
testAccount.getPhoneNumbers().contains(phone);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取租户规则
|
||||
*/
|
||||
public TenantRule getTenantRule(Integer tenantId) {
|
||||
if (tenantRules == null) {
|
||||
return null;
|
||||
}
|
||||
return tenantRules.stream()
|
||||
.filter(rule -> rule.isEnabled() && rule.getTenantId().equals(tenantId))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
@@ -10,14 +10,18 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.gxwebsoft.bszx.entity.BszxBm;
|
||||
import com.gxwebsoft.bszx.entity.BszxPay;
|
||||
import com.gxwebsoft.common.core.config.ConfigProperties;
|
||||
import com.gxwebsoft.common.core.config.CertificateConfigProperties;
|
||||
import com.gxwebsoft.common.core.utils.RedisUtil;
|
||||
import com.gxwebsoft.common.core.utils.CertificateLoader;
|
||||
import com.gxwebsoft.common.core.web.BaseController;
|
||||
import com.gxwebsoft.common.system.entity.Payment;
|
||||
import com.gxwebsoft.shop.entity.ShopOrderGoods;
|
||||
import com.gxwebsoft.shop.service.ShopOrderGoodsService;
|
||||
import com.gxwebsoft.shop.service.ShopOrderService;
|
||||
import com.gxwebsoft.shop.service.OrderBusinessService;
|
||||
import com.gxwebsoft.shop.entity.ShopOrder;
|
||||
import com.gxwebsoft.shop.param.ShopOrderParam;
|
||||
import com.gxwebsoft.shop.dto.OrderCreateRequest;
|
||||
import com.gxwebsoft.common.core.web.ApiResult;
|
||||
import com.gxwebsoft.common.core.web.PageResult;
|
||||
import com.gxwebsoft.common.core.web.BatchParam;
|
||||
@@ -38,6 +42,7 @@ import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -58,9 +63,15 @@ public class ShopOrderController extends BaseController {
|
||||
@Resource
|
||||
private ShopOrderGoodsService shopOrderGoodsService;
|
||||
@Resource
|
||||
private OrderBusinessService orderBusinessService;
|
||||
@Resource
|
||||
private RedisUtil redisUtil;
|
||||
@Resource
|
||||
private ConfigProperties conf;
|
||||
@Resource
|
||||
private CertificateConfigProperties certConfig;
|
||||
@Resource
|
||||
private CertificateLoader certificateLoader;
|
||||
@Value("${spring.profiles.active}")
|
||||
String active;
|
||||
|
||||
@@ -88,7 +99,24 @@ public class ShopOrderController extends BaseController {
|
||||
|
||||
@ApiOperation("添加订单")
|
||||
@PostMapping()
|
||||
public ApiResult<?> save(@RequestBody ShopOrder shopOrder) {
|
||||
public ApiResult<?> save(@Valid @RequestBody OrderCreateRequest request) {
|
||||
User loginUser = getLoginUser();
|
||||
if (loginUser == null) {
|
||||
return fail("用户未登录");
|
||||
}
|
||||
|
||||
try {
|
||||
Map<String, String> wxOrderInfo = orderBusinessService.createOrder(request, loginUser);
|
||||
return success("下单成功", wxOrderInfo);
|
||||
} catch (Exception e) {
|
||||
logger.error("创建订单失败", e);
|
||||
return fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation("添加订单(兼容旧版本)")
|
||||
@PostMapping("/legacy")
|
||||
public ApiResult<?> saveLegacy(@RequestBody ShopOrder shopOrder) {
|
||||
// 记录当前登录用户id
|
||||
User loginUser = getLoginUser();
|
||||
if (loginUser != null) {
|
||||
@@ -188,19 +216,24 @@ public class ShopOrderController extends BaseController {
|
||||
Payment payment = redisUtil.get(key, Payment.class);
|
||||
String uploadPath = conf.getUploadPath();
|
||||
|
||||
// 开发环境
|
||||
String apiV3Key = "zGufUcqa7ovgxRL0kF5OlPr482EZwtn9";
|
||||
String privateKey = "/Users/gxwebsoft/JAVA/site-java/cert/websoft/af7261a1bc2a41f7887dbdf05611bb1f.pem";
|
||||
String apiclientCert = "/Users/gxwebsoft/JAVA/site-java/cert/websoft/wechatpay_4A3231584E93B6AE77820074D07EADEACCB7E223.pem";
|
||||
String pubKey = "/Users/gxwebsoft/JAVA/site-java/cert/websoft/wechatpay_4A3231584E93B6AE77820074D07EADEACCB7E223.pem";
|
||||
// 证书配置
|
||||
String apiV3Key;
|
||||
String apiclientCert;
|
||||
|
||||
// 开发环境 - 使用证书加载器
|
||||
if (active.equals("dev")) {
|
||||
apiV3Key = certConfig.getWechatPay().getDev().getApiV3Key();
|
||||
apiclientCert = certificateLoader.loadCertificatePath(
|
||||
certConfig.getWechatDevCertPath("wechatpayCert"));
|
||||
} else {
|
||||
// 生产环境
|
||||
if (ObjectUtil.isNotEmpty(payment)) {
|
||||
// 检查 payment 字段是否为空,并避免直接解析为数字
|
||||
apiV3Key = payment.getApiKey();
|
||||
privateKey = payment.getApiclientKey();
|
||||
apiclientCert = conf.getUploadPath().concat("/file").concat(payment.getApiclientCert());
|
||||
pubKey = uploadPath.concat("file").concat(payment.getPubKey());
|
||||
apiclientCert = certificateLoader.loadCertificatePath(
|
||||
conf.getUploadPath().concat("/file").concat(payment.getApiclientCert()));
|
||||
} else {
|
||||
throw new RuntimeException("生产环境未找到支付配置信息");
|
||||
}
|
||||
}
|
||||
|
||||
RequestParam requestParam = new RequestParam.Builder()
|
||||
|
||||
132
src/main/java/com/gxwebsoft/shop/dto/OrderCreateRequest.java
Normal file
132
src/main/java/com/gxwebsoft/shop/dto/OrderCreateRequest.java
Normal file
@@ -0,0 +1,132 @@
|
||||
package com.gxwebsoft.shop.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 订单创建请求DTO
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(value = "OrderCreateRequest", description = "订单创建请求")
|
||||
public class OrderCreateRequest {
|
||||
|
||||
@ApiModelProperty(value = "订单类型,0商城订单 1预定订单/外卖 2会员卡")
|
||||
@NotNull(message = "订单类型不能为空")
|
||||
@Min(value = 0, message = "订单类型值无效")
|
||||
@Max(value = 2, message = "订单类型值无效")
|
||||
private Integer type;
|
||||
|
||||
@ApiModelProperty(value = "快递/自提")
|
||||
private Integer deliveryType;
|
||||
|
||||
@ApiModelProperty(value = "下单渠道,0小程序预定 1俱乐部训练场 3活动订场")
|
||||
private Integer channel;
|
||||
|
||||
@ApiModelProperty(value = "商户ID")
|
||||
private Long merchantId;
|
||||
|
||||
@ApiModelProperty(value = "商户名称")
|
||||
private String merchantName;
|
||||
|
||||
@ApiModelProperty(value = "商户编号")
|
||||
private String merchantCode;
|
||||
|
||||
@ApiModelProperty(value = "使用的优惠券id")
|
||||
private Integer couponId;
|
||||
|
||||
@ApiModelProperty(value = "使用的会员卡id")
|
||||
private String cardId;
|
||||
|
||||
@ApiModelProperty(value = "收货地址")
|
||||
private String address;
|
||||
|
||||
@ApiModelProperty(value = "地址纬度")
|
||||
private String addressLat;
|
||||
|
||||
@ApiModelProperty(value = "地址经度")
|
||||
private String addressLng;
|
||||
|
||||
@ApiModelProperty(value = "自提店铺id")
|
||||
private Integer selfTakeMerchantId;
|
||||
|
||||
@ApiModelProperty(value = "自提店铺")
|
||||
private String selfTakeMerchantName;
|
||||
|
||||
@ApiModelProperty(value = "配送开始时间")
|
||||
private String sendStartTime;
|
||||
|
||||
@ApiModelProperty(value = "配送结束时间")
|
||||
private String sendEndTime;
|
||||
|
||||
@ApiModelProperty(value = "发货店铺id")
|
||||
private Integer expressMerchantId;
|
||||
|
||||
@ApiModelProperty(value = "发货店铺")
|
||||
private String expressMerchantName;
|
||||
|
||||
@ApiModelProperty(value = "订单总额")
|
||||
@NotNull(message = "订单总额不能为空")
|
||||
@DecimalMin(value = "0.01", message = "订单总额必须大于0")
|
||||
@Digits(integer = 10, fraction = 2, message = "订单总额格式不正确")
|
||||
private BigDecimal totalPrice;
|
||||
|
||||
@ApiModelProperty(value = "减少的金额,使用VIP会员折扣、优惠券抵扣、优惠券折扣后减去的价格")
|
||||
@DecimalMin(value = "0", message = "减少金额不能为负数")
|
||||
private BigDecimal reducePrice;
|
||||
|
||||
@ApiModelProperty(value = "实际付款")
|
||||
@DecimalMin(value = "0", message = "实际付款不能为负数")
|
||||
private BigDecimal payPrice;
|
||||
|
||||
@ApiModelProperty(value = "用于统计")
|
||||
private BigDecimal price;
|
||||
|
||||
@ApiModelProperty(value = "价钱,用于积分赠送")
|
||||
private BigDecimal money;
|
||||
|
||||
@ApiModelProperty(value = "教练价格")
|
||||
private BigDecimal coachPrice;
|
||||
|
||||
@ApiModelProperty(value = "购买数量")
|
||||
@Min(value = 1, message = "购买数量必须大于0")
|
||||
private Integer totalNum;
|
||||
|
||||
@ApiModelProperty(value = "教练id")
|
||||
private Integer coachId;
|
||||
|
||||
@ApiModelProperty(value = "来源ID,存商品ID")
|
||||
private Integer formId;
|
||||
|
||||
@ApiModelProperty(value = "支付类型,0余额支付, 1微信支付,102微信Native,2会员卡支付,3支付宝,4现金,5POS机,6VIP月卡,7VIP年卡,8VIP次卡,9IC月卡,10IC年卡,11IC次卡,12免费,13VIP充值卡,14IC充值卡,15积分支付,16VIP季卡,17IC季卡,18代付")
|
||||
private Integer payType;
|
||||
|
||||
@ApiModelProperty(value = "代付支付方式")
|
||||
private Integer friendPayType;
|
||||
|
||||
@ApiModelProperty(value = "优惠类型:0无、1抵扣优惠券、2折扣优惠券、3、VIP月卡、4VIP年卡,5VIP次卡、6VIP会员卡、7IC月卡、8IC年卡、9IC次卡、10IC会员卡、11免费订单、12VIP充值卡、13IC充值卡、14VIP季卡、15IC季卡")
|
||||
private Integer couponType;
|
||||
|
||||
@ApiModelProperty(value = "优惠说明")
|
||||
private String couponDesc;
|
||||
|
||||
@ApiModelProperty(value = "预约详情开始时间数组")
|
||||
private String startTime;
|
||||
|
||||
@ApiModelProperty(value = "备注")
|
||||
@Size(max = 500, message = "备注长度不能超过500字符")
|
||||
private String comments;
|
||||
|
||||
@ApiModelProperty(value = "排序号")
|
||||
private Integer sortNumber;
|
||||
|
||||
@ApiModelProperty(value = "租户id")
|
||||
@NotNull(message = "租户ID不能为空")
|
||||
private Integer tenantId;
|
||||
}
|
||||
@@ -15,6 +15,9 @@ import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import javax.validation.constraints.*;
|
||||
import javax.validation.groups.Default;
|
||||
|
||||
/**
|
||||
* 订单
|
||||
*
|
||||
@@ -73,6 +76,9 @@ public class ShopOrder implements Serializable {
|
||||
@ApiModelProperty(value = "IC卡号")
|
||||
private String icCard;
|
||||
|
||||
@ApiModelProperty(value = "收货人id")
|
||||
private Integer addressId;
|
||||
|
||||
@ApiModelProperty(value = "收货地址")
|
||||
private String address;
|
||||
|
||||
@@ -99,12 +105,17 @@ public class ShopOrder implements Serializable {
|
||||
private String expressMerchantName;
|
||||
|
||||
@ApiModelProperty(value = "订单总额")
|
||||
@NotNull(message = "订单总额不能为空")
|
||||
@DecimalMin(value = "0.01", message = "订单总额必须大于0")
|
||||
@Digits(integer = 10, fraction = 2, message = "订单总额格式不正确")
|
||||
private BigDecimal totalPrice;
|
||||
|
||||
@ApiModelProperty(value = "减少的金额,使用VIP会员折扣、优惠券抵扣、优惠券折扣后减去的价格")
|
||||
@DecimalMin(value = "0", message = "减少金额不能为负数")
|
||||
private BigDecimal reducePrice;
|
||||
|
||||
@ApiModelProperty(value = "实际付款")
|
||||
@DecimalMin(value = "0", message = "实际付款不能为负数")
|
||||
private BigDecimal payPrice;
|
||||
|
||||
@ApiModelProperty(value = "用于统计")
|
||||
@@ -120,6 +131,7 @@ public class ShopOrder implements Serializable {
|
||||
private BigDecimal coachPrice;
|
||||
|
||||
@ApiModelProperty(value = "购买数量")
|
||||
@Min(value = 1, message = "购买数量必须大于0")
|
||||
private Integer totalNum;
|
||||
|
||||
@ApiModelProperty(value = "教练id")
|
||||
@@ -218,6 +230,7 @@ public class ShopOrder implements Serializable {
|
||||
private String mobile;
|
||||
|
||||
@ApiModelProperty(value = "备注")
|
||||
@Size(max = 500, message = "备注长度不能超过500字符")
|
||||
private String comments;
|
||||
|
||||
@ApiModelProperty(value = "排序号")
|
||||
@@ -228,6 +241,7 @@ public class ShopOrder implements Serializable {
|
||||
private Integer deleted;
|
||||
|
||||
@ApiModelProperty(value = "租户id")
|
||||
@NotNull(message = "租户ID不能为空")
|
||||
private Integer tenantId;
|
||||
|
||||
@ApiModelProperty(value = "修改时间")
|
||||
|
||||
@@ -59,6 +59,9 @@
|
||||
<if test="param.phone != null">
|
||||
AND a.phone LIKE CONCAT('%', #{param.phone}, '%')
|
||||
</if>
|
||||
<if test="param.addressId != null">
|
||||
AND a.address_id = #{param.addressId}
|
||||
</if>
|
||||
<if test="param.address != null">
|
||||
AND a.address LIKE CONCAT('%', #{param.address}, '%')
|
||||
</if>
|
||||
|
||||
@@ -82,6 +82,9 @@ public class ShopOrderParam extends BaseParam {
|
||||
@ApiModelProperty(value = "手机号码")
|
||||
private String phone;
|
||||
|
||||
@ApiModelProperty(value = "收货人id")
|
||||
private Integer addressId;
|
||||
|
||||
@ApiModelProperty(value = "收货地址")
|
||||
private String address;
|
||||
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
package com.gxwebsoft.shop.service;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.gxwebsoft.common.core.exception.BusinessException;
|
||||
import com.gxwebsoft.common.system.entity.User;
|
||||
import com.gxwebsoft.shop.config.OrderConfigProperties;
|
||||
import com.gxwebsoft.shop.dto.OrderCreateRequest;
|
||||
import com.gxwebsoft.shop.entity.ShopOrder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 订单业务服务类
|
||||
* 处理订单创建的核心业务逻辑
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class OrderBusinessService {
|
||||
|
||||
@Resource
|
||||
private ShopOrderService shopOrderService;
|
||||
|
||||
@Resource
|
||||
private OrderConfigProperties orderConfig;
|
||||
|
||||
/**
|
||||
* 创建订单
|
||||
*
|
||||
* @param request 订单创建请求
|
||||
* @param loginUser 当前登录用户
|
||||
* @return 微信支付订单信息
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Map<String, String> createOrder(OrderCreateRequest request, User loginUser) {
|
||||
// 1. 参数校验
|
||||
validateOrderRequest(request, loginUser);
|
||||
|
||||
// 2. 构建订单对象
|
||||
ShopOrder shopOrder = buildShopOrder(request, loginUser);
|
||||
|
||||
// 3. 应用业务规则
|
||||
applyBusinessRules(shopOrder, loginUser);
|
||||
|
||||
// 4. 保存订单
|
||||
boolean saved = shopOrderService.save(shopOrder);
|
||||
if (!saved) {
|
||||
throw new BusinessException("订单保存失败");
|
||||
}
|
||||
|
||||
// 5. 创建微信支付订单
|
||||
try {
|
||||
return shopOrderService.createWxOrder(shopOrder);
|
||||
} catch (Exception e) {
|
||||
log.error("创建微信支付订单失败,订单号:{}", shopOrder.getOrderNo(), e);
|
||||
throw new BusinessException("创建支付订单失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验订单请求参数
|
||||
*/
|
||||
private void validateOrderRequest(OrderCreateRequest request, User loginUser) {
|
||||
if (loginUser == null) {
|
||||
throw new BusinessException("用户未登录");
|
||||
}
|
||||
|
||||
if (request.getTotalPrice() == null || request.getTotalPrice().compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new BusinessException("商品金额不能为0");
|
||||
}
|
||||
|
||||
// 检查租户特殊规则
|
||||
OrderConfigProperties.TenantRule tenantRule = orderConfig.getTenantRule(request.getTenantId());
|
||||
if (tenantRule != null && tenantRule.getMinAmount() != null) {
|
||||
if (request.getTotalPrice().compareTo(tenantRule.getMinAmount()) < 0) {
|
||||
throw new BusinessException(tenantRule.getMinAmountMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建订单对象
|
||||
*/
|
||||
private ShopOrder buildShopOrder(OrderCreateRequest request, User loginUser) {
|
||||
ShopOrder shopOrder = new ShopOrder();
|
||||
|
||||
// 复制请求参数到订单对象
|
||||
BeanUtils.copyProperties(request, shopOrder);
|
||||
|
||||
// 设置用户相关信息
|
||||
shopOrder.setUserId(loginUser.getUserId());
|
||||
shopOrder.setOpenid(loginUser.getOpenid());
|
||||
shopOrder.setPayUserId(loginUser.getUserId());
|
||||
|
||||
// 生成订单号
|
||||
if (shopOrder.getOrderNo() == null) {
|
||||
shopOrder.setOrderNo(Long.toString(IdUtil.getSnowflakeNextId()));
|
||||
}
|
||||
|
||||
// 设置默认备注
|
||||
if (shopOrder.getComments() == null) {
|
||||
shopOrder.setComments(orderConfig.getDefaultConfig().getDefaultComments());
|
||||
}
|
||||
|
||||
// 设置默认支付状态
|
||||
shopOrder.setPayStatus(false);
|
||||
|
||||
return shopOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用业务规则
|
||||
*/
|
||||
private void applyBusinessRules(ShopOrder shopOrder, User loginUser) {
|
||||
// 测试账号处理
|
||||
if (orderConfig.isTestAccount(loginUser.getPhone())) {
|
||||
BigDecimal testAmount = orderConfig.getTestAccount().getTestPayAmount();
|
||||
shopOrder.setPrice(testAmount);
|
||||
shopOrder.setTotalPrice(testAmount);
|
||||
log.info("应用测试账号规则,用户:{},测试金额:{}", loginUser.getPhone(), testAmount);
|
||||
}
|
||||
|
||||
// 其他业务规则可以在这里添加
|
||||
// 例如:VIP折扣、优惠券处理等
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验订单金额
|
||||
*/
|
||||
public void validateOrderAmount(BigDecimal amount, Integer tenantId) {
|
||||
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new BusinessException("订单金额必须大于0");
|
||||
}
|
||||
|
||||
OrderConfigProperties.TenantRule tenantRule = orderConfig.getTenantRule(tenantId);
|
||||
if (tenantRule != null && tenantRule.getMinAmount() != null) {
|
||||
if (amount.compareTo(tenantRule.getMinAmount()) < 0) {
|
||||
throw new BusinessException(tenantRule.getMinAmountMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为测试账号
|
||||
*/
|
||||
public boolean isTestAccount(String phone) {
|
||||
return orderConfig.isTestAccount(phone);
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,10 @@
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.gxwebsoft.common.core.config.ConfigProperties;
|
||||
import com.gxwebsoft.common.core.config.CertificateConfigProperties;
|
||||
import com.gxwebsoft.common.core.exception.BusinessException;
|
||||
import com.gxwebsoft.common.core.utils.RedisUtil;
|
||||
import com.gxwebsoft.common.core.utils.CertificateLoader;
|
||||
import com.gxwebsoft.common.system.entity.Payment;
|
||||
import com.gxwebsoft.common.system.param.PaymentParam;
|
||||
import com.gxwebsoft.common.system.service.PaymentService;
|
||||
@@ -60,10 +62,10 @@
|
||||
private PaymentService paymentService;
|
||||
@Resource
|
||||
private SettingService settingService;
|
||||
|
||||
public static String privateKeyPath = "/Users/gxwebsoft/Downloads/ef7f7e0430cb47019d06b93f885bf95f/apiclient_key.pem";
|
||||
public static String privateCertPath = "/Users/gxwebsoft/JAVA/com.gxwebsoft.core/src/main/resources/cert/apiclient_cert.pem";
|
||||
public static String wechatpayCertPath = "/Users/gxwebsoft/Downloads/ef7f7e0430cb47019d06b93f885bf95f/wechatpay_55729BDEC2502C301BA02CDC28E4CEE4DE4D1DB9.pem"; // 平台证书
|
||||
@Resource
|
||||
private CertificateConfigProperties certConfig;
|
||||
@Resource
|
||||
private CertificateLoader certificateLoader;
|
||||
|
||||
@Override
|
||||
public PageResult<ShopOrder> pageRel(ShopOrderParam param) {
|
||||
@@ -216,21 +218,28 @@
|
||||
* @return
|
||||
*/
|
||||
public JsapiServiceExtension getWxService(ShopOrder order) {
|
||||
final String uploadPath = config.getUploadPath();
|
||||
final Payment payment = getPayment(order);
|
||||
String privateKey = uploadPath.concat("/file").concat(payment.getApiclientKey()); // 秘钥证书
|
||||
String apiclientCert = uploadPath.concat("/file").concat(payment.getApiclientCert());
|
||||
String pubKey = uploadPath.concat("/file").concat(payment.getPubKey()); // 公钥证书
|
||||
String privateKey;
|
||||
String apiclientCert;
|
||||
String pubKey = null;
|
||||
|
||||
// 开发环境配置
|
||||
// 开发环境配置 - 使用证书加载器
|
||||
if (active.equals("dev")) {
|
||||
privateKey = privateKeyPath;
|
||||
apiclientCert = wechatpayCertPath;
|
||||
privateKey = certificateLoader.loadCertificatePath(
|
||||
certConfig.getWechatDevCertPath("privateKey"));
|
||||
apiclientCert = certificateLoader.loadCertificatePath(
|
||||
certConfig.getWechatDevCertPath("wechatpayCert"));
|
||||
} else {
|
||||
// 生产环境配置 - 从上传目录加载
|
||||
final String uploadPath = config.getUploadPath();
|
||||
privateKey = certificateLoader.loadCertificatePath(
|
||||
uploadPath.concat("/file").concat(payment.getApiclientKey()));
|
||||
apiclientCert = certificateLoader.loadCertificatePath(
|
||||
uploadPath.concat("/file").concat(payment.getApiclientCert()));
|
||||
if (payment.getPubKey() != null && !payment.getPubKey().isEmpty()) {
|
||||
pubKey = certificateLoader.loadCertificatePath(
|
||||
uploadPath.concat("/file").concat(payment.getPubKey()));
|
||||
}
|
||||
|
||||
// 生成环境配置
|
||||
if (active.equals("prod")) {
|
||||
|
||||
}
|
||||
|
||||
// 兼容公钥
|
||||
|
||||
@@ -44,3 +44,12 @@ config:
|
||||
# 开发环境接口
|
||||
server-url: http://127.0.0.1:9091/api
|
||||
upload-path: /Users/gxwebsoft/Documents/uploads/ # window(D:\Temp)
|
||||
|
||||
# 开发环境证书配置
|
||||
certificate:
|
||||
load-mode: CLASSPATH # 开发环境从classpath加载
|
||||
wechat-pay:
|
||||
dev:
|
||||
private-key-file: "certs/dev/apiclient_key.pem"
|
||||
apiclient-cert-file: "certs/dev/apiclient_cert.pem"
|
||||
wechatpay-cert-file: "certs/dev/wechatpay_cert.pem"
|
||||
|
||||
@@ -53,3 +53,8 @@ config:
|
||||
bucketName: oss-gxwebsoft
|
||||
bucketDomain: https://oss.wsdns.cn
|
||||
aliyunDomain: https://oss-gxwebsoft.oss-cn-shenzhen.aliyuncs.com
|
||||
|
||||
# 生产环境证书配置
|
||||
certificate:
|
||||
load-mode: VOLUME # 生产环境从Docker挂载卷加载
|
||||
cert-root-path: /app/certs
|
||||
|
||||
@@ -100,4 +100,50 @@ config:
|
||||
bucketDomain: https://oss.wsdns.cn
|
||||
aliyunDomain: https://oss-gxwebsoft.oss-cn-shenzhen.aliyuncs.com
|
||||
|
||||
# 商城订单配置
|
||||
shop:
|
||||
order:
|
||||
# 测试账号配置
|
||||
test-account:
|
||||
enabled: true
|
||||
phone-numbers:
|
||||
- "13737128880"
|
||||
test-pay-amount: 0.01
|
||||
|
||||
# 租户特殊规则配置
|
||||
tenant-rules:
|
||||
- tenant-id: 10324
|
||||
tenant-name: "百色中学"
|
||||
min-amount: 10
|
||||
min-amount-message: "捐款金额最低不能少于10元,感谢您的爱心捐赠^_^"
|
||||
enabled: true
|
||||
|
||||
# 默认配置
|
||||
default-config:
|
||||
default-comments: "暂无"
|
||||
min-order-amount: 0
|
||||
order-timeout-minutes: 30
|
||||
|
||||
# 证书配置
|
||||
certificate:
|
||||
# 证书加载模式: CLASSPATH, FILESYSTEM, VOLUME
|
||||
load-mode: FILESYSTEM
|
||||
# Docker挂载卷证书路径
|
||||
cert-root-path: /app/certs
|
||||
|
||||
# 微信支付证书配置
|
||||
wechat-pay:
|
||||
dev:
|
||||
api-v3-key: "zGufUcqa7ovgxRL0kF5OlPr482EZwtn9"
|
||||
private-key-file: "apiclient_key.pem"
|
||||
apiclient-cert-file: "apiclient_cert.pem"
|
||||
wechatpay-cert-file: "wechatpay_cert.pem"
|
||||
prod-base-path: "/file"
|
||||
|
||||
# 支付宝证书配置
|
||||
alipay:
|
||||
app-private-key-file: "app_private_key.pem"
|
||||
app-cert-public-key-file: "appCertPublicKey.crt"
|
||||
alipay-cert-public-key-file: "alipayCertPublicKey.crt"
|
||||
alipay-root-cert-file: "alipayRootCert.crt"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user