mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2024-11-22 23:31:52 +08:00
完成对微信公众号支付的封装
This commit is contained in:
parent
23d8da7479
commit
1ed6656bbb
@ -0,0 +1,34 @@
|
||||
package cn.iocoder.yudao.framework.common.util.io;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 文件工具类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class FileUtils {
|
||||
|
||||
/**
|
||||
* 创建临时文件
|
||||
* 该文件会在 JVM 退出时,进行删除
|
||||
*
|
||||
* @param data 文件内容
|
||||
* @return 文件
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static File createTempFile(String data) {
|
||||
// 创建文件,通过 UUID 保证唯一
|
||||
File file = File.createTempFile(IdUtil.simpleUUID(), null);
|
||||
// 标记 JVM 退出时,自动删除
|
||||
file.deleteOnExit();
|
||||
// 写入内容
|
||||
FileUtil.writeUtf8String(data, file);
|
||||
return file;
|
||||
}
|
||||
|
||||
}
|
@ -11,7 +11,10 @@
|
||||
|
||||
<artifactId>yudao-spring-boot-starter-biz-pay</artifactId>
|
||||
<name>${artifactId}</name>
|
||||
<description>支付拓展,基于 IJPay 简单封装,支持微信、支付宝等常见支付渠道</description>
|
||||
<description>支付拓展,接入国内多个支付渠道
|
||||
1. 支付宝,基于官方 SDK 接入
|
||||
2. 微信支付,基于 weixin-java-pay 接入
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@ -34,17 +37,36 @@
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 三方云服务相关 -->
|
||||
<dependency>
|
||||
<groupId>com.github.javen205</groupId>
|
||||
<artifactId>IJPay-AliPay</artifactId>
|
||||
<version>2.7.8</version>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.javen205</groupId>
|
||||
<artifactId>IJPay-WxPay</artifactId>
|
||||
<version>2.7.8</version>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 三方云服务相关 -->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.github.javen205</groupId>-->
|
||||
<!-- <artifactId>IJPay-AliPay</artifactId>-->
|
||||
<!-- <version>2.7.8</version>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.github.javen205</groupId>-->
|
||||
<!-- <artifactId>IJPay-WxPay</artifactId>-->
|
||||
<!-- <version>2.7.8</version>-->
|
||||
<!-- </dependency>-->
|
||||
<dependency>
|
||||
<groupId>com.alipay.sdk</groupId>
|
||||
<artifactId>alipay-sdk-java</artifactId>
|
||||
<version>4.17.9.ALL</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>weixin-java-pay</artifactId>
|
||||
<version>4.1.9.B</version>
|
||||
</dependency>
|
||||
<!-- TODO 芋艿:清理 -->
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@ -0,0 +1,33 @@
|
||||
package cn.iocoder.yudao.framework.pay.core.client;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 将 API 的错误码,转换为通用的错误码
|
||||
*
|
||||
* @see PayCommonResult
|
||||
* @see PayFrameworkErrorCodeConstants
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractPayCodeMapping {
|
||||
|
||||
public final ErrorCode apply(String apiCode, String apiMsg) {
|
||||
if (apiCode == null) {
|
||||
log.error("[apply][API 错误码为空,请排查]");
|
||||
return PayFrameworkErrorCodeConstants.EXCEPTION;
|
||||
}
|
||||
ErrorCode errorCode = this.apply0(apiCode, apiMsg);
|
||||
if (errorCode == null) {
|
||||
log.error("[apply][API 错误码({}) 错误提示({}) 无法匹配]", apiCode, apiMsg);
|
||||
return PayFrameworkErrorCodeConstants.PAY_UNKNOWN;
|
||||
}
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
protected abstract ErrorCode apply0(String apiCode, String apiMsg);
|
||||
|
||||
}
|
@ -18,6 +18,6 @@ public interface PayClient {
|
||||
Long getId();
|
||||
|
||||
// TODO 缺少注释
|
||||
CommonResult<Object> unifiedOrder(PayOrderUnifiedReqDTO reqDTO);
|
||||
CommonResult<?> unifiedOrder(PayOrderUnifiedReqDTO reqDTO);
|
||||
|
||||
}
|
||||
|
@ -1,17 +0,0 @@
|
||||
package cn.iocoder.yudao.framework.pay.core.client;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 将 API 的错误码,转换为通用的错误码
|
||||
*
|
||||
* @see PayCommonResult
|
||||
* @see PayFrameworkErrorCodeConstants
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface PayCodeMapping extends Function<String, ErrorCode> {
|
||||
}
|
@ -35,16 +35,13 @@ public class PayCommonResult<T> extends CommonResult<T> {
|
||||
private PayCommonResult() {
|
||||
}
|
||||
|
||||
public static <T> PayCommonResult<T> build(String apiCode, String apiMsg, T data, PayCodeMapping codeMapping) {
|
||||
public static <T> PayCommonResult<T> build(String apiCode, String apiMsg, T data, AbstractPayCodeMapping codeMapping) {
|
||||
Assert.notNull(codeMapping, "参数 codeMapping 不能为空");
|
||||
PayCommonResult<T> result = new PayCommonResult<T>().setApiCode(apiCode).setApiMsg(apiMsg);
|
||||
result.setData(data);
|
||||
// 翻译错误码
|
||||
if (codeMapping != null) {
|
||||
ErrorCode errorCode = codeMapping.apply(apiCode);
|
||||
if (errorCode == null) {
|
||||
errorCode = PayFrameworkErrorCodeConstants.EXCEPTION;
|
||||
}
|
||||
ErrorCode errorCode = codeMapping.apply(apiCode, apiMsg);
|
||||
result.setCode(errorCode.getCode()).setMsg(errorCode.getMsg());
|
||||
}
|
||||
return result;
|
||||
|
@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.pay.core.client.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
|
||||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
@ -39,6 +40,12 @@ public class PayOrderUnifiedReqDTO {
|
||||
@NotEmpty(message = "商品描述信息不能为空")
|
||||
@Length(max = 128, message = "商品描述信息长度不能超过128")
|
||||
private String body;
|
||||
/**
|
||||
* 支付结果的回调地址
|
||||
*/
|
||||
@NotEmpty(message = "支付结果的回调地址不能为空")
|
||||
@URL(message = "支付结果的回调地址必须是 URL 格式")
|
||||
private String notifyUrl;
|
||||
|
||||
// ========== 订单相关字段 ==========
|
||||
|
||||
@ -55,4 +62,7 @@ public class PayOrderUnifiedReqDTO {
|
||||
@NotNull(message = "支付过期时间不能为空")
|
||||
private Date expireTime;
|
||||
|
||||
// ========== 拓展参数 ==========
|
||||
// TODO 芋艿:待完善
|
||||
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package cn.iocoder.yudao.framework.pay.core.client.impl;
|
||||
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCodeMapping;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
@ -24,7 +24,7 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
|
||||
/**
|
||||
* 错误码枚举类
|
||||
*/
|
||||
protected PayCodeMapping codeMapping;
|
||||
protected AbstractPayCodeMapping codeMapping;
|
||||
/**
|
||||
* 支付配置
|
||||
*/
|
||||
@ -34,7 +34,7 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
|
||||
return amount / 100.0;
|
||||
}
|
||||
|
||||
public AbstractPayClient(Long channelId, String channelCode, Config config, PayCodeMapping codeMapping) {
|
||||
public AbstractPayClient(Long channelId, String channelCode, Config config, AbstractPayCodeMapping codeMapping) {
|
||||
this.channelId = channelId;
|
||||
this.channelCode = channelCode;
|
||||
this.codeMapping = codeMapping;
|
||||
|
@ -11,7 +11,7 @@ import lombok.Data;
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class AlipayPayConfig implements PayClientConfig {
|
||||
public class AlipayPayClientConfig implements PayClientConfig {
|
||||
|
||||
/**
|
||||
* 网关地址 - 线上
|
@ -1,12 +1,17 @@
|
||||
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCodeMapping;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
|
||||
|
||||
public class AlipayPayCodeMapping implements PayCodeMapping {
|
||||
/**
|
||||
* 支付宝的 PayCodeMapping 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class AlipayPayCodeMapping extends AbstractPayCodeMapping {
|
||||
|
||||
@Override
|
||||
public ErrorCode apply(String s) {
|
||||
protected ErrorCode apply0(String apiCode, String apiMsg) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,9 @@ import com.alipay.api.domain.AlipayTradePrecreateModel;
|
||||
import com.alipay.api.request.AlipayTradePrecreateRequest;
|
||||
import com.alipay.api.response.AlipayTradePrecreateResponse;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
|
||||
/**
|
||||
* 支付宝【扫码支付】的 PayClient 实现类
|
||||
@ -19,11 +22,12 @@ import lombok.SneakyThrows;
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class AlipayQrPayClient extends AbstractPayClient<AlipayPayConfig> {
|
||||
@Slf4j
|
||||
public class AlipayQrPayClient extends AbstractPayClient<AlipayPayClientConfig> {
|
||||
|
||||
private DefaultAlipayClient client;
|
||||
|
||||
public AlipayQrPayClient(Long channelId, String channelCode, AlipayPayConfig config) {
|
||||
public AlipayQrPayClient(Long channelId, String channelCode, AlipayPayClientConfig config) {
|
||||
super(channelId, channelCode, config, new AlipayPayCodeMapping());
|
||||
}
|
||||
|
||||
@ -32,17 +36,18 @@ public class AlipayQrPayClient extends AbstractPayClient<AlipayPayConfig> {
|
||||
protected void doInit() {
|
||||
AlipayConfig alipayConfig = new AlipayConfig();
|
||||
BeanUtil.copyProperties(config, alipayConfig, false);
|
||||
// 真实客户端
|
||||
this.client = new DefaultAlipayClient(alipayConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<Object> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
public CommonResult<AlipayTradePrecreateResponse> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
// 构建 AlipayTradePrecreateModel 请求
|
||||
AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
|
||||
model.setOutTradeNo(reqDTO.getMerchantOrderId());
|
||||
model.setSubject(reqDTO.getSubject());
|
||||
model.setBody(reqDTO.getBody());
|
||||
model.setTotalAmount(calculateAmount(reqDTO.getAmount()).toString());
|
||||
model.setTotalAmount(calculateAmount(reqDTO.getAmount()).toString()); // 单位:元
|
||||
// TODO 芋艿:clientIp + expireTime
|
||||
// 构建 AlipayTradePrecreateRequest
|
||||
AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
|
||||
@ -53,6 +58,7 @@ public class AlipayQrPayClient extends AbstractPayClient<AlipayPayConfig> {
|
||||
try {
|
||||
response = client.execute(request);
|
||||
} catch (AlipayApiException e) {
|
||||
log.error("[unifiedOrder][request({}) 发起支付失败]", toJsonString(reqDTO), e);
|
||||
return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
|
||||
}
|
||||
|
||||
@ -64,10 +70,10 @@ public class AlipayQrPayClient extends AbstractPayClient<AlipayPayConfig> {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
AlipayPayConfig config = new AlipayPayConfig();
|
||||
AlipayPayClientConfig config = new AlipayPayClientConfig();
|
||||
config.setAppId("2021000118634035");
|
||||
config.setServerUrl(AlipayPayConfig.SERVER_URL_SANDBOX);
|
||||
config.setSignType(AlipayPayConfig.SIGN_TYPE_DEFAULT);
|
||||
config.setServerUrl(AlipayPayClientConfig.SERVER_URL_SANDBOX);
|
||||
config.setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT);
|
||||
config.setPrivateKey("MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCHsEV1cDupwJv890x84qbppUtRIfhaKSwSVN0thCcsDCaAsGR5MZslDkO8NCT9V4r2SVXjyY7eJUZlZd1M0C8T01Tg4UOx5LUbic0O3A1uJMy6V1n9IyYwbAW3AEZhBd5bSbPgrqvmv3NeWSTQT6Anxnllf+2iDH6zyA2fPl7cYyQtbZoDJQFGqr4F+cGh2R6akzRKNoBkAeMYwoY6es2lX8sJxCVPWUmxNUoL3tScwlSpd7Bxw0q9c/X01jMwuQ0+Va358zgFiGERTE6yD01eu40OBDXOYO3z++y+TAYHlQQ2toMO63trepo88X3xV3R44/1DH+k2pAm2IF5ixiLrAgMBAAECggEAPx3SoXcseaD7rmcGcE0p4SMfbsUDdkUSmBBbtfF0GzwnqNLkWa+mgE0rWt9SmXngTQH97vByAYmLPl1s3G82ht1V7Sk7yQMe74lhFllr8eEyTjeVx3dTK1EEM4TwN+936DTXdFsr4TELJEcJJdD0KaxcCcfBLRDs2wnitEFZ9N+GoZybVmY8w0e0MI7PLObUZ2l0X4RurQnfG9ZxjXjC7PkeMVv7cGGylpNFi3BbvkRhdhLPDC2E6wqnr9e7zk+hiENivAezXrtxtwKovzCtnWJ1r0IO14Rh47H509Ic0wFnj+o5YyUL4LdmpL7yaaH6fM7zcSLFjNZPHvZCKPwYcQKBgQDQFho98QvnL8ex4v6cry4VitGpjSXm1qP3vmMQk4rTsn8iPWtcxPjqGEqOQJjdi4Mi0VZKQOLFwlH0kl95wNrD/isJ4O1yeYfX7YAXApzHqYNINzM79HemO3Yx1qLMW3okRFJ9pPRzbQ9qkTpsaegsmyX316zOBhzGRYjKbutTYwKBgQCm7phr9XdFW5Vh+XR90mVs483nrLmMiDKg7YKxSLJ8amiDjzPejCn7i95Hah08P+2MIZLIPbh2VLacczR6ltRRzN5bg5etFuqSgfkuHyxpoDmpjbe08+Q2h8JBYqcC5Nhv1AKU4iOUhVLHo/FBAQliMcGc/J3eiYTFC7EsNx382QKBgClb20doe7cttgFTXswBvaUmfFm45kmla924B7SpvrQpDD/f+VDtDZRp05fGmxuduSjYdtA3aVtpLiTwWu22OUUvZZqHDGruYOO4Hvdz23mL5b4ayqImCwoNU4bAZIc9v18p/UNf3/55NNE3oGcf/bev9rH2OjCQ4nM+Ktwhg8CFAoGACSgvbkShzUkv0ZcIf9ppu+ZnJh1AdGgINvGwaJ8vQ0nm/8h8NOoFZ4oNoGc+wU5Ubops7dUM6FjPR5e+OjdJ4E7Xp7d5O4J1TaIZlCEbo5OpdhaTDDcQvrkFu+Z4eN0qzj+YAKjDAOOrXc4tbr5q0FsgXscwtcNfaBuzFVTUrUkCgYEAwzPnMNhWG3zOWLUs2QFA2GP4Y+J8cpUYfj6pbKKzeLwyG9qBwF1NJpN8m+q9q7V9P2LY+9Lp9e1mGsGeqt5HMEA3P6vIpcqLJLqE/4PBLLRzfccTcmqb1m71+erxTRhHBRkGS+I7dZEb3olQfnS1Y1tpMBxiwYwR3LW4oXuJwj8=");
|
||||
config.setAlipayPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnq90KnF4dTnlzzmxpujbI05OYqi5WxAS6cL0gnZFv2gK51HExF8v/BaP7P979PhFMgWTqmOOI+Dtno5s+yD09XTY1WkshbLk6i4g2Xlr8fyW9ODnkU88RI2w9UdPhQU4cPPwBNlrsYhKkVK2OxwM3kFqjoBBY0CZoZCsSQ3LDH5WeZqPArlsS6xa2zqJBuuoKjMrdpELl3eXSjP8K54eDJCbeetCZNKWLL3DPahTPB7LZikfYmslb0QUvCgGapD0xkS7eVq70NaL1G57MWABs4tbfWgxike4Daj3EfUrzIVspQxj7w8HEj9WozJPgL88kSJSits0pqD3n5r8HSuseQIDAQAB");
|
||||
AlipayQrPayClient client = new AlipayQrPayClient(1L, "biu", config);
|
||||
@ -79,4 +85,5 @@ public class AlipayQrPayClient extends AbstractPayClient<AlipayPayConfig> {
|
||||
reqDTO.setMerchantOrderId(String.valueOf(System.currentTimeMillis()));
|
||||
client.unifiedOrder(reqDTO);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,11 +19,11 @@ import lombok.SneakyThrows;
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class AlipayWapPayClient extends AbstractPayClient<AlipayPayConfig> {
|
||||
public class AlipayWapPayClient extends AbstractPayClient<AlipayPayClientConfig> {
|
||||
|
||||
private DefaultAlipayClient client;
|
||||
|
||||
public AlipayWapPayClient(Long channelId, String channelCode, AlipayPayConfig config) {
|
||||
public AlipayWapPayClient(Long channelId, String channelCode, AlipayPayClientConfig config) {
|
||||
super(channelId, channelCode, config, new AlipayPayCodeMapping());
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ public class AlipayWapPayClient extends AbstractPayClient<AlipayPayConfig> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<Object> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
public CommonResult<AlipayTradeWapPayResponse> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
// 构建 AlipayTradeWapPayModel 请求
|
||||
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
|
||||
model.setOutTradeNo(reqDTO.getMerchantOrderId());
|
||||
@ -65,10 +65,10 @@ public class AlipayWapPayClient extends AbstractPayClient<AlipayPayConfig> {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
AlipayPayConfig config = new AlipayPayConfig();
|
||||
AlipayPayClientConfig config = new AlipayPayClientConfig();
|
||||
config.setAppId("2021000118634035");
|
||||
config.setServerUrl(AlipayPayConfig.SERVER_URL_SANDBOX);
|
||||
config.setSignType(AlipayPayConfig.SIGN_TYPE_DEFAULT);
|
||||
config.setServerUrl(AlipayPayClientConfig.SERVER_URL_SANDBOX);
|
||||
config.setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT);
|
||||
config.setPrivateKey("MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCHsEV1cDupwJv890x84qbppUtRIfhaKSwSVN0thCcsDCaAsGR5MZslDkO8NCT9V4r2SVXjyY7eJUZlZd1M0C8T01Tg4UOx5LUbic0O3A1uJMy6V1n9IyYwbAW3AEZhBd5bSbPgrqvmv3NeWSTQT6Anxnllf+2iDH6zyA2fPl7cYyQtbZoDJQFGqr4F+cGh2R6akzRKNoBkAeMYwoY6es2lX8sJxCVPWUmxNUoL3tScwlSpd7Bxw0q9c/X01jMwuQ0+Va358zgFiGERTE6yD01eu40OBDXOYO3z++y+TAYHlQQ2toMO63trepo88X3xV3R44/1DH+k2pAm2IF5ixiLrAgMBAAECggEAPx3SoXcseaD7rmcGcE0p4SMfbsUDdkUSmBBbtfF0GzwnqNLkWa+mgE0rWt9SmXngTQH97vByAYmLPl1s3G82ht1V7Sk7yQMe74lhFllr8eEyTjeVx3dTK1EEM4TwN+936DTXdFsr4TELJEcJJdD0KaxcCcfBLRDs2wnitEFZ9N+GoZybVmY8w0e0MI7PLObUZ2l0X4RurQnfG9ZxjXjC7PkeMVv7cGGylpNFi3BbvkRhdhLPDC2E6wqnr9e7zk+hiENivAezXrtxtwKovzCtnWJ1r0IO14Rh47H509Ic0wFnj+o5YyUL4LdmpL7yaaH6fM7zcSLFjNZPHvZCKPwYcQKBgQDQFho98QvnL8ex4v6cry4VitGpjSXm1qP3vmMQk4rTsn8iPWtcxPjqGEqOQJjdi4Mi0VZKQOLFwlH0kl95wNrD/isJ4O1yeYfX7YAXApzHqYNINzM79HemO3Yx1qLMW3okRFJ9pPRzbQ9qkTpsaegsmyX316zOBhzGRYjKbutTYwKBgQCm7phr9XdFW5Vh+XR90mVs483nrLmMiDKg7YKxSLJ8amiDjzPejCn7i95Hah08P+2MIZLIPbh2VLacczR6ltRRzN5bg5etFuqSgfkuHyxpoDmpjbe08+Q2h8JBYqcC5Nhv1AKU4iOUhVLHo/FBAQliMcGc/J3eiYTFC7EsNx382QKBgClb20doe7cttgFTXswBvaUmfFm45kmla924B7SpvrQpDD/f+VDtDZRp05fGmxuduSjYdtA3aVtpLiTwWu22OUUvZZqHDGruYOO4Hvdz23mL5b4ayqImCwoNU4bAZIc9v18p/UNf3/55NNE3oGcf/bev9rH2OjCQ4nM+Ktwhg8CFAoGACSgvbkShzUkv0ZcIf9ppu+ZnJh1AdGgINvGwaJ8vQ0nm/8h8NOoFZ4oNoGc+wU5Ubops7dUM6FjPR5e+OjdJ4E7Xp7d5O4J1TaIZlCEbo5OpdhaTDDcQvrkFu+Z4eN0qzj+YAKjDAOOrXc4tbr5q0FsgXscwtcNfaBuzFVTUrUkCgYEAwzPnMNhWG3zOWLUs2QFA2GP4Y+J8cpUYfj6pbKKzeLwyG9qBwF1NJpN8m+q9q7V9P2LY+9Lp9e1mGsGeqt5HMEA3P6vIpcqLJLqE/4PBLLRzfccTcmqb1m71+erxTRhHBRkGS+I7dZEb3olQfnS1Y1tpMBxiwYwR3LW4oXuJwj8=");
|
||||
config.setAlipayPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnq90KnF4dTnlzzmxpujbI05OYqi5WxAS6cL0gnZFv2gK51HExF8v/BaP7P979PhFMgWTqmOOI+Dtno5s+yD09XTY1WkshbLk6i4g2Xlr8fyW9ODnkU88RI2w9UdPhQU4cPPwBNlrsYhKkVK2OxwM3kFqjoBBY0CZoZCsSQ3LDH5WeZqPArlsS6xa2zqJBuuoKjMrdpELl3eXSjP8K54eDJCbeetCZNKWLL3DPahTPB7LZikfYmslb0QUvCgGapD0xkS7eVq70NaL1G57MWABs4tbfWgxike4Daj3EfUrzIVspQxj7w8HEj9WozJPgL88kSJSits0pqD3n5r8HSuseQIDAQAB");
|
||||
AlipayWapPayClient client = new AlipayWapPayClient(1L, "biubiubiu", config);
|
||||
|
@ -0,0 +1,50 @@
|
||||
package cn.iocoder.yudao.framework.pay.core.client.impl.wx;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 微信支付 PayCodeMapping 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class WXCodeMapping extends AbstractPayCodeMapping {
|
||||
|
||||
/**
|
||||
* 错误码 - 成功
|
||||
* 由于 weixin-java-pay 封装的 Result 未返回 code,所以自己定义下
|
||||
*/
|
||||
public static final String CODE_SUCCESS = "SUCCESS";
|
||||
/**
|
||||
* 错误提示 - 成功
|
||||
*/
|
||||
public static final String MESSAGE_SUCCESS = "成功";
|
||||
|
||||
@Override
|
||||
protected ErrorCode apply0(String apiCode, String apiMsg) {
|
||||
if (Objects.equals(apiCode, CODE_SUCCESS)) {
|
||||
return GlobalErrorCodeConstants.SUCCESS;
|
||||
}
|
||||
if (Objects.equals(apiCode, "FAIL")) {
|
||||
if (Objects.equals(apiMsg, "AppID不存在,请检查后再试")) {
|
||||
return PAY_CONFIG_APP_ID_ERROR;
|
||||
}
|
||||
if (Objects.equals(apiMsg, "签名错误,请检查后再试")
|
||||
|| Objects.equals(apiMsg, "签名错误")) {
|
||||
return PAY_CONFIG_SIGN_ERROR;
|
||||
}
|
||||
}
|
||||
if (Objects.equals(apiCode, "PARAM_ERROR")) {
|
||||
if (Objects.equals(apiMsg, "无效的openid")) {
|
||||
return PAY_OPENID_ERROR;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
package cn.iocoder.yudao.framework.pay.core.client.impl.wx;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
// TODO 芋艿:参数校验
|
||||
/**
|
||||
* 微信支付的 PayClientConfig 实现类
|
||||
* 属性主要来自 {@link com.github.binarywang.wxpay.config.WxPayConfig} 的必要属性
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class WXPayClientConfig implements PayClientConfig {
|
||||
|
||||
// TODO 芋艿:V2 or V3 客户端
|
||||
/**
|
||||
* API 版本 - V2
|
||||
*
|
||||
* https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_1
|
||||
*/
|
||||
public static final String API_VERSION_V2 = "v2";
|
||||
/**
|
||||
* API 版本 - V3
|
||||
*
|
||||
* https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay-1.shtml
|
||||
*/
|
||||
public static final String API_VERSION_V3 = "v3";
|
||||
|
||||
/**
|
||||
* 公众号或者小程序的 appid
|
||||
*/
|
||||
private String appId;
|
||||
/**
|
||||
* 商户号
|
||||
*/
|
||||
private String mchId;
|
||||
/**
|
||||
* API 版本
|
||||
*/
|
||||
private String apiVersion;
|
||||
|
||||
// ========== V2 版本的参数 ==========
|
||||
|
||||
/**
|
||||
* 商户密钥
|
||||
*/
|
||||
private String mchKey;
|
||||
// /**
|
||||
// * apiclient_cert.p12 证书文件的绝对路径或者以 classpath: 开头的类路径.
|
||||
// * 对应的字符串
|
||||
// *
|
||||
// * 注意,可通过 {@link #main(String[])} 读取
|
||||
// */
|
||||
// private String keyContent;
|
||||
|
||||
// ========== V3 版本的参数 ==========
|
||||
/**
|
||||
* apiclient_key.pem 证书文件的绝对路径或者以 classpath: 开头的类路径.
|
||||
* 对应的字符串
|
||||
*
|
||||
* 注意,可通过 {@link #main(String[])} 读取
|
||||
*/
|
||||
private String privateKeyContent;
|
||||
/**
|
||||
* apiclient_cert.pem 证书文件的绝对路径或者以 classpath: 开头的类路径.
|
||||
* 对应的字符串
|
||||
*
|
||||
* 注意,可通过 {@link #main(String[])} 读取
|
||||
*/
|
||||
private String privateCertContent;
|
||||
/**
|
||||
* apiV3 秘钥值
|
||||
*/
|
||||
private String apiV3Key;
|
||||
|
||||
public static void main(String[] args) throws FileNotFoundException {
|
||||
String path = "/Users/yunai/Downloads/wx_pay/apiclient_cert.p12";
|
||||
// String path = "/Users/yunai/Downloads/wx_pay/apiclient_key.pem";
|
||||
// String path = "/Users/yunai/Downloads/wx_pay/apiclient_cert.pem";
|
||||
System.out.println(IoUtil.readUtf8(new FileInputStream(path)));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,148 @@
|
||||
package cn.iocoder.yudao.framework.pay.core.client.impl.wx;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
|
||||
import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
|
||||
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
|
||||
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
|
||||
import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
|
||||
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
|
||||
import com.github.binarywang.wxpay.config.WxPayConfig;
|
||||
import com.github.binarywang.wxpay.constant.WxPayConstants;
|
||||
import com.github.binarywang.wxpay.exception.WxPayException;
|
||||
import com.github.binarywang.wxpay.service.WxPayService;
|
||||
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
import static cn.hutool.core.util.ObjectUtil.defaultIfNull;
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.CODE_SUCCESS;
|
||||
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.MESSAGE_SUCCESS;
|
||||
|
||||
/**
|
||||
* 微信支付(公众号)的 PayClient 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
|
||||
|
||||
private WxPayService client;
|
||||
|
||||
public WXPubPayClient(Long channelId, String channelCode, WXPayClientConfig config) {
|
||||
super(channelId, channelCode, config, new WXCodeMapping());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInit() {
|
||||
WxPayConfig payConfig = new WxPayConfig();
|
||||
BeanUtil.copyProperties(config, payConfig, "keyContent");
|
||||
payConfig.setTradeType(WxPayConstants.TradeType.JSAPI); // 设置使用 JS API 支付方式
|
||||
// if (StrUtil.isNotEmpty(config.getKeyContent())) {
|
||||
// payConfig.setKeyContent(config.getKeyContent().getBytes(StandardCharsets.UTF_8));
|
||||
// }
|
||||
if (StrUtil.isNotEmpty(config.getPrivateKeyContent())) {
|
||||
// weixin-pay-java 存在 BUG,无法直接设置内容,所以创建临时文件来解决
|
||||
payConfig.setPrivateKeyPath(FileUtils.createTempFile(config.getPrivateKeyContent()).getPath());
|
||||
}
|
||||
if (StrUtil.isNotEmpty(config.getPrivateCertContent())) {
|
||||
// weixin-pay-java 存在 BUG,无法直接设置内容,所以创建临时文件来解决
|
||||
payConfig.setPrivateCertPath(FileUtils.createTempFile(config.getPrivateCertContent()).getPath());
|
||||
}
|
||||
// 真实客户端
|
||||
this.client = new WxPayServiceImpl();
|
||||
client.setConfig(payConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<WxPayMpOrderResult> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
WxPayMpOrderResult response;
|
||||
try {
|
||||
switch (config.getApiVersion()) {
|
||||
case WXPayClientConfig.API_VERSION_V2:
|
||||
response = this.unifiedOrderV2(reqDTO);
|
||||
break;
|
||||
case WXPayClientConfig.API_VERSION_V3:
|
||||
WxPayUnifiedOrderV3Result.JsapiResult responseV3 = this.unifiedOrderV3(reqDTO);
|
||||
// 将 V3 的结果,统一转换成 V2。返回的字段是一致的
|
||||
response = new WxPayMpOrderResult();
|
||||
BeanUtil.copyProperties(responseV3, response, true);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
||||
}
|
||||
} catch (WxPayException e) {
|
||||
log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
|
||||
return PayCommonResult.build(defaultIfNull(e.getErrCode(), e.getReturnCode()),
|
||||
defaultIfNull(e.getErrCodeDes(), e.getReturnMsg()),null, codeMapping);
|
||||
}
|
||||
return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping);
|
||||
}
|
||||
|
||||
private WxPayMpOrderResult unifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
|
||||
// 构建 WxPayUnifiedOrderRequest 对象
|
||||
WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder()
|
||||
.outTradeNo(reqDTO.getMerchantOrderId())
|
||||
// TODO 芋艿:貌似没 title?
|
||||
.body(reqDTO.getBody())
|
||||
.totalFee(reqDTO.getAmount()) // 单位分
|
||||
.timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss"))
|
||||
.spbillCreateIp(reqDTO.getClientIp())
|
||||
.openid("ockUAwIZ-0OeMZl9ogcZ4ILrGba0") // TODO 芋艿:先随便写死
|
||||
.notifyUrl(reqDTO.getNotifyUrl())
|
||||
.build();
|
||||
// 执行请求
|
||||
return client.createOrder(request);
|
||||
}
|
||||
|
||||
private WxPayUnifiedOrderV3Result.JsapiResult unifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
|
||||
// 构建 WxPayUnifiedOrderRequest 对象
|
||||
WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
|
||||
request.setOutTradeNo(reqDTO.getMerchantOrderId());
|
||||
// TODO 芋艿:貌似没 title?
|
||||
request.setDescription(reqDTO.getBody());
|
||||
request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount())); // 单位分
|
||||
request.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss"));
|
||||
request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid("ockUAwIZ-0OeMZl9ogcZ4ILrGba0")); // TODO 芋艿:先随便写死
|
||||
request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getClientIp()));
|
||||
request.setNotifyUrl(reqDTO.getNotifyUrl());
|
||||
// 执行请求
|
||||
return client.createOrderV3(TradeTypeEnum.JSAPI, request);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws FileNotFoundException {
|
||||
WXPayClientConfig config = new WXPayClientConfig();
|
||||
config.setAppId("wx041349c6f39b268b");
|
||||
config.setMchId("1545083881");
|
||||
config.setMchKey("0alL64UDQdlCwiKZ73ib7ypaIjMns06p");
|
||||
config.setApiVersion(WXPayClientConfig.API_VERSION_V3);
|
||||
// config.setKeyContent(IoUtil.readUtf8(new FileInputStream("/Users/yunai/Downloads/wx_pay/apiclient_cert.p12")));
|
||||
config.setPrivateKeyContent(IoUtil.readUtf8(new FileInputStream("/Users/yunai/Downloads/wx_pay/apiclient_key.pem")));
|
||||
config.setPrivateCertContent(IoUtil.readUtf8(new FileInputStream("/Users/yunai/Downloads/wx_pay/apiclient_cert.pem")));
|
||||
config.setApiV3Key("joerVi8y5DJ3o4ttA0o1uH47Xz1u2Ase");
|
||||
|
||||
WXPubPayClient client = new WXPubPayClient(1L, "biu", config);
|
||||
client.init();
|
||||
|
||||
PayOrderUnifiedReqDTO reqDTO = new PayOrderUnifiedReqDTO();
|
||||
reqDTO.setAmount(123);
|
||||
reqDTO.setSubject("IPhone 13");
|
||||
reqDTO.setBody("biubiubiu");
|
||||
reqDTO.setMerchantOrderId(String.valueOf(System.currentTimeMillis()));
|
||||
reqDTO.setClientIp("127.0.0.1");
|
||||
reqDTO.setNotifyUrl("http://127.0.0.1:8080");
|
||||
CommonResult<WxPayMpOrderResult> result = client.unifiedOrder(reqDTO);
|
||||
System.out.println(result);
|
||||
}
|
||||
|
||||
}
|
@ -11,36 +11,15 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
*/
|
||||
public interface PayFrameworkErrorCodeConstants {
|
||||
|
||||
ErrorCode PAY_UNKNOWN = new ErrorCode(2001000000, "未知错误,需要解析");
|
||||
ErrorCode PAY_UNKNOWN = new ErrorCode(2002000000, "未知错误,需要解析");
|
||||
|
||||
// // ========== 权限 / 限流等相关 2001000100 ==========
|
||||
//
|
||||
// ErrorCode SMS_PERMISSION_DENY = new ErrorCode(2001000100, "没有发送短信的权限");
|
||||
// // 云片:可以配置 IP 白名单,只有在白名单中才可以发送短信
|
||||
// ErrorCode SMS_IP_DENY = new ErrorCode(2001000100, "IP 不允许发送短信");
|
||||
//
|
||||
// // 阿里云:将短信发送频率限制在正常的业务限流范围内。默认短信验证码:使用同一签名,对同一个手机号验证码,支持 1 条 / 分钟,5 条 / 小时,累计 10 条 / 天。
|
||||
// ErrorCode SMS_SEND_BUSINESS_LIMIT_CONTROL = new ErrorCode(2001000102, "指定手机的发送限流");
|
||||
// // 阿里云:已经达到您在控制台设置的短信日发送量限额值。在国内消息设置 > 安全设置,修改发送总量阈值。
|
||||
// ErrorCode SMS_SEND_DAY_LIMIT_CONTROL = new ErrorCode(2001000103, "每天的发送限流");
|
||||
//
|
||||
// ErrorCode SMS_SEND_CONTENT_INVALID = new ErrorCode(2001000104, "短信内容有敏感词");
|
||||
//
|
||||
// // ========== 模板相关 2001000200 ==========
|
||||
// ErrorCode SMS_TEMPLATE_INVALID = new ErrorCode(2001000200, "短信模板不合法"); // 包括短信模板不存在
|
||||
// ErrorCode SMS_TEMPLATE_PARAM_ERROR = new ErrorCode(2001000201, "模板参数不正确");
|
||||
//
|
||||
// // ========== 签名相关 2001000300 ==========
|
||||
// ErrorCode SMS_SIGN_INVALID = new ErrorCode(2001000300, "短信签名不可用");
|
||||
//
|
||||
// // ========== 账户相关 2001000400 ==========
|
||||
// ErrorCode SMS_ACCOUNT_MONEY_NOT_ENOUGH = new ErrorCode(2001000400, "账户余额不足");
|
||||
// ErrorCode SMS_ACCOUNT_INVALID = new ErrorCode(2001000401, "apiKey 不存在");
|
||||
//
|
||||
// // ========== 其它相关 2001000900 开头 ==========
|
||||
// ErrorCode SMS_API_PARAM_ERROR = new ErrorCode(2001000900, "请求参数缺失");
|
||||
// ErrorCode SMS_MOBILE_INVALID = new ErrorCode(2001000901, "手机格式不正确");
|
||||
// ErrorCode SMS_MOBILE_BLACK = new ErrorCode(2001000902, "手机号在黑名单中");
|
||||
// ========== 配置相关相关 2002000100 ==========
|
||||
ErrorCode PAY_CONFIG_APP_ID_ERROR = new ErrorCode(2002000100, "支付渠道 AppId 不正确");
|
||||
ErrorCode PAY_CONFIG_SIGN_ERROR = new ErrorCode(2002000100, "签名错误"); // 例如说,微信支付,配置错了 mchId 或者 mchKey
|
||||
|
||||
|
||||
// ========== 其它相关 2002000900 开头 ==========
|
||||
ErrorCode PAY_OPENID_ERROR = new ErrorCode(2002000900, "无效的 openid"); // 例如说,微信 openid 未授权过
|
||||
|
||||
ErrorCode EXCEPTION = new ErrorCode(2002000999, "调用异常");
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user