完成对微信公众号支付的封装

This commit is contained in:
YunaiV 2021-10-23 11:00:36 +08:00
parent 23d8da7479
commit 1ed6656bbb
16 changed files with 436 additions and 80 deletions

View File

@ -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;
}
}

View 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>

View File

@ -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);
}

View File

@ -18,6 +18,6 @@ public interface PayClient {
Long getId();
// TODO 缺少注释
CommonResult<Object> unifiedOrder(PayOrderUnifiedReqDTO reqDTO);
CommonResult<?> unifiedOrder(PayOrderUnifiedReqDTO reqDTO);
}

View File

@ -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> {
}

View File

@ -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;

View File

@ -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 芋艿待完善
}

View File

@ -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;

View File

@ -11,7 +11,7 @@ import lombok.Data;
* @author 芋道源码
*/
@Data
public class AlipayPayConfig implements PayClientConfig {
public class AlipayPayClientConfig implements PayClientConfig {
/**
* 网关地址 - 线上

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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)));
}
}

View File

@ -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);
}
}

View File

@ -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, "调用异常");