This commit is contained in:
shuhongfan
2023-09-04 16:40:17 +08:00
commit cf5ac25c14
8267 changed files with 1305066 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
package com.sl.pay;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class PayApplication {
public static void main(String[] args) {
SpringApplication.run(PayApplication.class, args);
}
}

View File

@@ -0,0 +1,71 @@
package com.sl.pay.constant;
import com.sl.pay.enums.PayChannelEnum;
/**
* @ClassName TardingConstant.java
* @Description 交易常量类
*/
public class TradingConstant {
//【阿里云退款返回状态】
//REFUND_SUCCESS:成功
public static final String REFUND_SUCCESS = "REFUND_SUCCESS";
//【阿里云返回付款状态】
//TRADE_CLOSED:未付款交易超时关闭,或支付完成后全额退款
public static final String ALI_TRADE_CLOSED = "TRADE_CLOSED";
//TRADE_SUCCESS:交易支付成功
public static final String ALI_TRADE_SUCCESS = "TRADE_SUCCESS";
//TRADE_FINISHED:交易结束不可退款
public static final String ALI_TRADE_FINISHED = "TRADE_FINISHED";
//【微信退款返回状态】
//SUCCESS退款成功
public static final String WECHAT_REFUND_SUCCESS = "SUCCESS";
//CLOSED退款关闭
public static final String WECHAT_REFUND_CLOSED = "CLOSED";
//PROCESSING退款处理中
public static final String WECHAT_REFUND_PROCESSING = "PROCESSING";
//ABNORMAL退款异常
public static final String WECHAT_REFUND_ABNORMAL = "TRADE_CLOSED";
//【微信返回付款状态】
//SUCCESS支付成功
public static final String WECHAT_TRADE_SUCCESS = "SUCCESS";
//REFUND转入退款
public static final String WECHAT_TRADE_REFUND = "REFUND";
//NOTPAY未支付
public static final String WECHAT_TRADE_NOTPAY = "NOTPAY";
//CLOSED已关闭
public static final String WECHAT_TRADE_CLOSED = "CLOSED";
//REVOKED已撤销仅付款码支付会返回
public static final String WECHAT_TRADE_REVOKED = "REVOKED";
//USERPAYING用户支付中仅付款码支付会返回
public static final String WECHAT_TRADE_USERPAYING = "USERPAYING";
//PAYERROR支付失败仅付款码支付会返回
public static final String WECHAT_TRADE_PAYERROR = "PAYERROR";
//【平台:交易渠道】
//阿里支付
public static final String TRADING_CHANNEL_ALI_PAY = PayChannelEnum.ALI_PAY.name();
//微信支付
public static final String TRADING_CHANNEL_WECHAT_PAY = PayChannelEnum.WECHAT_PAY.name();
//现金
public static final String TRADING_CHANNEL_CASH_PAY = "CASH_PAY";
//免单挂账【信用渠道】
public static final String TRADING_CHANNEL_CREDIT_PAY = "CREDIT_PAY";
//【平台:交易动作】
//付款
public static final String TRADING_TYPE_FK = "FK";
//退款
public static final String TRADING_TYPE_TK = "TK";
//免单
public static final String TRADING_TYPE_MD = "MD";
//挂账
public static final String TRADING_TYPE_GZ = "GZ";
}

View File

@@ -0,0 +1,60 @@
package com.sl.pay.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.sl.pay.enums.RefundStatusEnum;
import com.sl.transport.common.entity.BaseEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
/**
* @Description退款记录表
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@TableName("sl_refund_record")
public class RefundRecordEntity extends BaseEntity {
private static final long serialVersionUID = -3998253241655800061L;
@ApiModelProperty(value = "交易系统订单号【对于三方来说:商户订单】")
private Long tradingOrderNo;
@ApiModelProperty(value = "业务系统订单号")
private Long productOrderNo;
@ApiModelProperty(value = "本次退款订单号")
private Long refundNo;
@ApiModelProperty(value = "商户号")
private Long enterpriseId;
@ApiModelProperty(value = "退款渠道【支付宝、微信、现金】")
private String tradingChannel;
@ApiModelProperty(value = "退款状态")
private RefundStatusEnum refundStatus;
@ApiModelProperty(value = "返回编码")
private String refundCode;
@ApiModelProperty(value = "返回信息")
private String refundMsg;
@ApiModelProperty(value = "备注【订单门店,桌台信息】")
private String memo;
@ApiModelProperty(value = "本次退款金额")
private BigDecimal refundAmount;
@ApiModelProperty(value = "原订单金额")
private BigDecimal total;
}

View File

@@ -0,0 +1,96 @@
package com.sl.pay.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.sl.pay.enums.TradingStateEnum;
import com.sl.transport.common.entity.BaseEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
/**
* 交易订单表
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@TableName("sl_trading")
public class TradingEntity extends BaseEntity {
private static final long serialVersionUID = -3427581867070559590L;
@ApiModelProperty(value = "openId标识")
private String openId;
@ApiModelProperty(value = "业务系统订单号")
private Long productOrderNo;
@ApiModelProperty(value = "交易系统订单号【对于三方来说:商户订单】")
private Long tradingOrderNo;
@ApiModelProperty(value = "支付渠道【支付宝、微信、现金、免单挂账】")
private String tradingChannel;
@ApiModelProperty(value = "交易类型【付款、退款、免单、挂账】")
private String tradingType;
@ApiModelProperty(value = "交易单状态【DFK待付款,FKZ付款中,QXDD取消订单,YJS已结算,MD免单,GZ挂账】")
private TradingStateEnum tradingState;
@ApiModelProperty(value = "收款人姓名")
private String payeeName;
@ApiModelProperty(value = "收款人账户ID")
private Long payeeId;
@ApiModelProperty(value = "付款人姓名")
private String payerName;
@ApiModelProperty(value = "付款人Id")
private Long payerId;
@ApiModelProperty(value = "交易金额")
private BigDecimal tradingAmount;
@ApiModelProperty(value = "退款金额【付款后】")
private BigDecimal refund;
@ApiModelProperty(value = "是否有退款YESNO")
private String isRefund;
@ApiModelProperty(value = "第三方交易返回编码【最终确认交易结果】")
private String resultCode;
@ApiModelProperty(value = "第三方交易返回提示消息【最终确认交易信息】")
private String resultMsg;
@ApiModelProperty(value = "第三方交易返回信息json【分析交易最终信息】")
private String resultJson;
@ApiModelProperty(value = "统一下单返回编码")
private String placeOrderCode;
@ApiModelProperty(value = "统一下单返回信息")
private String placeOrderMsg;
@ApiModelProperty(value = "统一下单返回信息json【用于生产二维码、Android ios唤醒支付等】")
private String placeOrderJson;
@ApiModelProperty(value = "商户号")
private Long enterpriseId;
@ApiModelProperty(value = "备注【订单门店,桌台信息】")
private String memo;
@ApiModelProperty(value = "二维码base64数据")
private String qrCode;
@ApiModelProperty(value = "是否有效")
protected String enableFlag;
}

View File

@@ -0,0 +1,38 @@
package com.sl.pay.enums;
import cn.hutool.core.util.EnumUtil;
import com.sl.transport.common.enums.BaseEnum;
/**
* 支付渠道枚举
*
* @author zzj
* @version 1.0
*/
public enum PayChannelEnum implements BaseEnum {
ALI_PAY(1, "支付宝"),
WECHAT_PAY(2, "微信支付");
private Integer code;
private String value;
PayChannelEnum(Integer code, String value) {
this.code = code;
this.value = value;
}
@Override
public Integer getCode() {
return this.code;
}
@Override
public String getValue() {
return this.value;
}
public static PayChannelEnum codeOf(Integer code) {
return EnumUtil.getBy(PayChannelEnum::getCode, code);
}
}

View File

@@ -0,0 +1,38 @@
package com.sl.pay.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonValue;
import com.sl.transport.common.enums.BaseEnum;
/**
* 退款状态枚举
*
* @author zzj
* @version 1.0
*/
public enum RefundStatusEnum implements BaseEnum {
SENDING(1, "退款中"),
SUCCESS(2, "成功"),
FAIL(3, "失败");
@EnumValue
@JsonValue
private Integer code;
private String value;
RefundStatusEnum(Integer code, String value) {
this.code = code;
this.value = value;
}
@Override
public Integer getCode() {
return this.code;
}
@Override
public String getValue() {
return this.value;
}
}

View File

@@ -0,0 +1,68 @@
package com.sl.pay.enums;
import com.sl.transport.common.enums.BaseExceptionEnum;
/**
* 交易枚举
*/
public enum TradingEnum implements BaseExceptionEnum {
SUCCEED(1001, 200, "操作成功"),
ERROR(1002, "操作失败"),
CHECK_TRADING_FAIL(1003, "交易单校验失败"),
TRY_LOCK_TRADING_FAIL(1004, "交易单加锁失败"),
PAYING_TRADING_FAIL(1005, "交易单支付失败"),
TRADING_STATE_SUCCEED(1006, "交易单已完成"),
TRADING_STATE_PAYING(1007, "交易单交易中"),
CONFIG_EMPTY(1008, "支付配置为空"),
CONFIG_ERROR(1009, "支付配置错误"),
NATIVE_PAY_FAIL(1010, "统一下单交易失败"),
NATIVE_QRCODE_FAIL(1011, "生成二维码失败"),
REFUND_FAIL(1012, "查询统一下单交易退款失败"),
SAVE_OR_UPDATE_FAIL(1013, "交易单保存或修改失败"),
TRADING_TYPE_FAIL(1014, "未定义的交易类型"),
NATIVE_QUERY_FAIL(1015, "查询统一下单交易失败"),
NATIVE_REFUND_FAIL(1016, "统一下单退款交易失败"),
NATIVE_QUERY_REFUND_FAIL(1017, "统一下单查询退款失败"),
CASH_PAY_FAIL(1018, "现金交易失败"),
CASH_REFUND_FAIL(1019, "统一下单退款交易失败"),
CREDIT_PAY_FAIL(1020, "信用交易失败"),
LIST_TRADE_STATE_FAIL(1021, "按交易状态查询交易单失败"),
NOT_FOUND(1022, "交易单不存在"),
CLOSE_FAIL(1023, "关闭交易单失败"),
BASIC_REFUND_OUT_FAIL(1024, "退款金额超过订单总金额"),
REFUND_NOT_FOUND(1025, "退款记录不存在"),
REFUND_ALREADY_COMPLETED(1026, "退款记录已经完成"),
BASIC_REFUND_COUNT_OUT_FAIL(1027, "退款次数超出限制最多20次"),
TRADING_QUERY_PARAM_ERROR(1028, "查询交易单错误,订单号或交易单号至少传递一个"),
REFUND_QUERY_PARAM_ERROR(1029, "查询退款单错误,订单号或交易单号至少传递一个");
private Integer code;
private Integer status;
private String value;
TradingEnum(Integer code, String value) {
this.code = code;
this.value = value;
this.status = 500;
}
TradingEnum(Integer code, Integer status, String value) {
this.code = code;
this.value = value;
this.status = status;
}
public Integer getCode() {
return code;
}
public String getValue() {
return this.value;
}
@Override
public Integer getStatus() {
return this.status;
}
}

View File

@@ -0,0 +1,42 @@
package com.sl.pay.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonValue;
import com.sl.transport.common.enums.BaseEnum;
/**
* 交易单状态枚举
*
* @author zzj
* @version 1.0
*/
public enum TradingStateEnum implements BaseEnum {
DFK(1, "待付款"),
FKZ(2, "付款中"),
FKSB(3, "付款失败"),
YJS(4, "已结算(已付款)"),
QXDD(5, "取消订单"),
MD(6, "免单"),
GZ(7, "挂账");
@EnumValue
@JsonValue
private Integer code;
private String value;
TradingStateEnum(Integer code, String value) {
this.code = code;
this.value = value;
}
@Override
public Integer getCode() {
return this.code;
}
@Override
public String getValue() {
return this.value;
}
}

View File

@@ -0,0 +1,42 @@
package com.sl.pay.handler;
import com.sl.pay.entity.RefundRecordEntity;
import com.sl.pay.entity.TradingEntity;
import com.sl.transport.common.exception.SLException;
/**
* 基础支付功能的定义,具体业务由不同的支付渠道实现
*/
public interface BasicPayHandler {
/***
* 统一收单线下交易查询
* 该接口提供所有支付订单的查询,商户可以通过该接口主动查询订单状态,完成下一步的业务逻辑。
* @return 是否有变化
*/
Boolean queryTrading(TradingEntity trading) throws SLException;
/***
* 关闭交易
* @return 是否成功
*/
Boolean closeTrading(TradingEntity trading) throws SLException;
/***
* 统一收单交易退款接口
* 当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,
* 将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家帐号上。
* @param refundRecord 退款记录对象
* @return 是否有变化
*/
Boolean refundTrading(RefundRecordEntity refundRecord) throws SLException;
/***
* 统一收单交易退款查询接口
*
* @param refundRecord 退款交易单号
* @return 是否有变化
*/
Boolean queryRefundTrading(RefundRecordEntity refundRecord) throws SLException;
}

View File

@@ -0,0 +1,19 @@
package com.sl.pay.handler;
import com.sl.pay.entity.TradingEntity;
import com.sl.transport.common.exception.SLException;
/**
* Native支付方式Handler商户生成二维码用户扫描支付
*/
public interface NativePayHandler {
/***
* 统一收单线下交易预创建
* 收银员通过收银台或商户后台调用此接口,生成二维码后,展示给用户,由用户扫描二维码完成订单支付。
* @param tradingEntity 交易单
*/
void createDownLineTrading(TradingEntity tradingEntity) throws SLException;
}

View File

@@ -0,0 +1,146 @@
package com.sl.pay.handler.alipay;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.util.ResponseChecker;
import com.alipay.easysdk.payment.common.models.AlipayTradeCloseResponse;
import com.alipay.easysdk.payment.common.models.AlipayTradeFastpayRefundQueryResponse;
import com.alipay.easysdk.payment.common.models.AlipayTradeQueryResponse;
import com.alipay.easysdk.payment.common.models.AlipayTradeRefundResponse;
import com.sl.pay.constant.TradingConstant;
import com.sl.pay.entity.RefundRecordEntity;
import com.sl.pay.entity.TradingEntity;
import com.sl.pay.enums.RefundStatusEnum;
import com.sl.pay.enums.TradingEnum;
import com.sl.pay.enums.TradingStateEnum;
import com.sl.pay.handler.BasicPayHandler;
import com.sl.transport.common.exception.SLException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 支付宝基础支付功能的实现
*/
@Slf4j
@Component("aliBasicPayHandler")
public class AliBasicPayHandler implements BasicPayHandler {
@Override
public Boolean queryTrading(TradingEntity trading) throws SLException {
//Factory使用配置
Factory.setOptions(AlipayConfig.getConfig());
AlipayTradeQueryResponse queryResponse;
try {
//调用支付宝API通用查询支付情况
queryResponse = Factory
.Payment
.Common()
.query(String.valueOf(trading.getTradingOrderNo()));
} catch (Exception e) {
String msg = StrUtil.format("查询支付宝统一下单失败trading = {}", trading);
log.error(msg, e);
throw new SLException(msg);
}
//修改交易单状态
trading.setResultCode(queryResponse.getCode());
trading.setResultMsg(queryResponse.getSubMsg());
trading.setResultJson(JSONUtil.toJsonStr(queryResponse));
boolean success = ResponseChecker.success(queryResponse);
//响应成功,分析交易状态
if (success) {
String tradeStatus = queryResponse.getTradeStatus();
if (StrUtil.equals(TradingConstant.ALI_TRADE_CLOSED, tradeStatus)) {
//支付取消TRADE_CLOSED未付款交易超时关闭或支付完成后全额退款
trading.setTradingState(TradingStateEnum.QXDD);
} else if (StrUtil.equalsAny(tradeStatus, TradingConstant.ALI_TRADE_SUCCESS, TradingConstant.ALI_TRADE_FINISHED)) {
// TRADE_SUCCESS交易支付成功
// TRADE_FINISHED交易结束不可退款
trading.setTradingState(TradingStateEnum.YJS);
} else {
//非最终状态不处理当前交易状态WAIT_BUYER_PAY交易创建等待买家付款不处理
return false;
}
return true;
}
throw new SLException(trading.getResultJson(), TradingEnum.NATIVE_QUERY_FAIL.getCode(), TradingEnum.NATIVE_QUERY_FAIL.getStatus());
}
@Override
public Boolean closeTrading(TradingEntity trading) throws SLException {
//Factory使用配置
Factory.setOptions(AlipayConfig.getConfig());
try {
//调用支付宝API通用查询支付情况
AlipayTradeCloseResponse closeResponse = Factory
.Payment
.Common()
.close(String.valueOf(trading.getTradingOrderNo()));
boolean success = ResponseChecker.success(closeResponse);
if (success) {
trading.setTradingState(TradingStateEnum.QXDD);
return true;
}
return false;
} catch (Exception e) {
throw new SLException(TradingEnum.CLOSE_FAIL, e);
}
}
@Override
public Boolean refundTrading(RefundRecordEntity refundRecord) throws SLException {
//Factory使用配置
Factory.setOptions(AlipayConfig.getConfig());
//调用支付宝API通用查询支付情况
AlipayTradeRefundResponse refundResponse;
try {
// 支付宝easy sdk
refundResponse = Factory
.Payment
.Common()
//扩展参数:退款单号
.optional("out_request_no", refundRecord.getRefundNo())
.refund(Convert.toStr(refundRecord.getTradingOrderNo()),
Convert.toStr(refundRecord.getRefundAmount()));
} catch (Exception e) {
String msg = StrUtil.format("调用支付宝退款接口出错refundRecord = {}", refundRecord);
log.error(msg, e);
throw new SLException(msg, TradingEnum.NATIVE_REFUND_FAIL.getCode(), TradingEnum.NATIVE_REFUND_FAIL.getStatus());
}
refundRecord.setRefundCode(refundResponse.getCode());
refundRecord.setRefundMsg(JSONUtil.toJsonStr(refundResponse));
boolean success = ResponseChecker.success(refundResponse);
if (success) {
refundRecord.setRefundStatus(RefundStatusEnum.SUCCESS);
return true;
}
throw new SLException(refundRecord.getRefundMsg(), TradingEnum.NATIVE_REFUND_FAIL.getCode(), TradingEnum.NATIVE_REFUND_FAIL.getStatus());
}
@Override
public Boolean queryRefundTrading(RefundRecordEntity refundRecord) throws SLException {
//Factory使用配置
Factory.setOptions(AlipayConfig.getConfig());
AlipayTradeFastpayRefundQueryResponse response;
try {
response = Factory.Payment.Common().queryRefund(
Convert.toStr(refundRecord.getTradingOrderNo()),
Convert.toStr(refundRecord.getRefundNo()));
} catch (Exception e) {
log.error("调用支付宝查询退款接口出错refundRecord = {}", refundRecord, e);
throw new SLException(TradingEnum.NATIVE_REFUND_FAIL, e);
}
refundRecord.setRefundCode(response.getCode());
refundRecord.setRefundMsg(JSONUtil.toJsonStr(response));
boolean success = ResponseChecker.success(response);
if (success) {
refundRecord.setRefundStatus(RefundStatusEnum.SUCCESS);
return true;
}
throw new SLException(refundRecord.getRefundMsg(), TradingEnum.NATIVE_REFUND_FAIL.getCode(), TradingEnum.NATIVE_REFUND_FAIL.getStatus());
}
}

View File

@@ -0,0 +1,20 @@
package com.sl.pay.handler.alipay;
import com.sl.pay.entity.TradingEntity;
import com.sl.pay.handler.NativePayHandler;
import com.sl.transport.common.exception.SLException;
import org.springframework.stereotype.Component;
/**
* 支付宝实现类
*/
@Component("aliNativePayHandler")
public class AliNativePayHandler implements NativePayHandler {
@Override
public void createDownLineTrading(TradingEntity tradingEntity) throws SLException {
}
}

View File

@@ -0,0 +1,62 @@
package com.sl.pay.handler.alipay;
import com.alipay.easysdk.kernel.Config;
/**
* 支付宝支付的配置
*/
public class AlipayConfig {
/**
* 获取支付宝的配置(沙箱环境)
*
* @return 支付宝的配置
*/
public static Config getConfig() {
Config config = new Config();
config.protocol = "https";
config.gatewayHost = "openapi.alipaydev.com";
config.signType = "RSA2";
config.appId = "2016072800110403";
// 为避免私钥随源码泄露,推荐从文件中读取私钥字符串而不是写入源码中
config.merchantPrivateKey = "MIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQDC+zz3NxpflpUlgNIkHWUjlal5JzaqtswH4b4c5c/tshm7zYNHYDu5n9vXW6X8DjnY4JKi9o4iQkps/M7CSOObhdxhzg4m2cppXS8iphnKAdeOb5AV740KOv84addPlNesghKZIV2Q2hpQL067D1Pt4BjbYPAt02dWDIo6yEiGyclCp0xgXm0zBDNZC69MPz634Xz4SZFnfqWrlct4UhKbZj9DvKjq7LIwEapJWe9WscUXChjtuqIjiXPJffq5+AwdKlVFs2yuPlZ7dnYnjHZ1rOpyIxmLL9mo3F2hwVgEisccZ6sdkP+XZnuAZwB28FENLQ9BO/iDk2QyK6rC8HuxAgMBAAECgf8m1+ZNWaznXhhYYFF84F5FuIsKl8VMPR5oc5BsMVw6/kAiKc79aS58zFv/i0KF9E+R5StRS80FBi/Nho+qJNszIHfHBEXNd5XbZ7T8MhrxI2zN82vA8vgmEZrGok9Ci2Rr+X27A+qf8NNNJh+JJIST3xEUozs15eewPM9M5pmMQE1kr7cnX3L9Mq9vF5kDTGR1qPo7aOvMNOPYueAvUqIEQ3mwMW8vN0PZ4OgSyPTQcVMLH50GJ1mCNl2AFK2To4HneOwVtzhnq6FL5gWaOfpsssBVual9Kwv1OxljBY9VRv65+4Eu595MuZ/U8nVD2Sv2nJyb5a4GX2Xg3qEAMC0CgYEA9o4GQ9IG6BRN8oA/a3Rvf/wVgE5Y6/i83WOqG/JNI/kLvgO/nOkIUpNILOOLVLW02328GW2QT9bPB1iyOVXGTUojfN9eCK8lvTT/OhRvOrn/jZxQOUqX1ckyOaNwf0zraKReryehjs3mC0anc1qmAFV2G+pWKeXeQ9P9S0Q9m4cCgYEAynNuSpz1HulEb97vPkvIsOOwselAMPCgNnIBKHDxxMYCs7AVh5o8eF/UvuTYfoXjHl/XQAPYIcxqvjJSY7z1FnPBcTX0fH0idk3aB10pYJ8u/Uvm+EZ3NW/XB8uDT2ggO0/9yO7bGmwrfH0nggGnftbrR5CUCz4SYFzWfkNhrQcCgYEA9W8GD4dItshXm7pM55Pd1sLDSJk1bZmLsLsgwHcgkVm+daXXncRgM4tHwt+0eFv1sLVmhM2LZd7I8mA58ldPcusFjq3wNqbb45CZLWDgJM649WgQeCeoI/oH1Fa9iwuwH9fYJXZHHhgbE0h/MpafPx/T1hAdO9IGwvCJad6BkYsCgYAthJyEnBukso/Xk0k5OXLLCrZ8mdP77O4or6NBA3voYRjUa7+7eFsmOW2s6ekfMV0qRCQyWWrr1nkdgFTz5dU5EhepK5dnsf7gmIpv42tdNQ0smDc7FSTSV+4B66j/Xor4qviFh+atXyq716vdWB/frdTCh2gxyuf40IbNZrr8WwKBgELF5s5ktXcOqRDw8J3JW5CXiwKVsLX6if+mK77NdoGChX1ffX+0BKKLOqMZfhpB0HpqlA/20AwKhX0/BiiV2devl+jabzwST8vg9aT7NHjOePOMdE2bJ+fcJGhLqOdg5Qo+yM/a3irBUYFvJpK66TG46LFbLNxugKOqxZvXyucM";
//注证书文件路径支持设置为文件系统中的路径或CLASS_PATH中的路径优先从文件系统中加载加载失败后会继续尝试从CLASS_PATH中加载
// config.merchantCertPath = "<-- 请填写您的应用公钥证书文件路径,例如:/foo/appCertPublicKey_2019051064521003.crt -->";
// config.alipayCertPath = "<-- 请填写您的支付宝公钥证书文件路径,例如:/foo/alipayCertPublicKey_RSA2.crt -->";
// config.alipayRootCertPath = "<-- 请填写您的支付宝根证书文件路径,例如:/foo/alipayRootCert.crt -->";
//注:如果采用非证书模式,则无需赋值上面的三个证书路径,改为赋值如下的支付宝公钥字符串即可
config.alipayPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgg7i0fycEzG6z2PUE/t8Mc3M+/c48R4f2soZeI+A0G1gX0/ejLFnVPBofkWJ1mVzXjR4bTlX/DhXnDad1xt4TqPEp65uPuWRt1gSwcaTM41DenoXCcfPwd3J6Vf2AVJ4YEv7716ovK+/e+UzxL9M2mm8jIb1Pr1p2DDc66MlAkqHwIVGYDK+tHJJy4fjFhqRz3NSRSeY2uZi8aOtF5otSIrYxZFrYjMjrdGGVj67mKUQi/ZDaoaTzTH64tEQ21IXbmjJH0EmVdxseJ/N39heHmeXSzcpJCDirXZGqDO2PpZOLH4zibkZEjd/Q6wRFGVWaWFhQ7KtbnDuqQt/74M8SQIDAQAB";
//可设置异步通知接收服务地址(可选)
config.notifyUrl = "https://www.test.com/callback";
//可设置AES密钥调用AES加解密相关接口时需要可选
config.encryptKey = "D/9fBsybuxt5ARPYcWDL5g==";
return config;
}
/**
* 获取支付宝的配置(正式环境)
*
* @return 支付宝的配置
*/
// public static Config getConfig() {
// Config config = new Config();
// config.protocol = "https";
// config.gatewayHost = "openapi.alipay.com";
// config.signType = "RSA2";
// config.appId = "2021003141676135";
// // 为避免私钥随源码泄露,推荐从文件中读取私钥字符串而不是写入源码中
// config.merchantPrivateKey = "MIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQC12YM9mR+HFQYTx/fHKHZbgszVtDHDB0B/ysWl3MbcPpGtjcZlDr5aynRMRLaoduRHT++A98IaNVIVGj9RHdXrX2j9I/Uz6fYDH63cdu6FZ6Pk82yPwNZW7pebprbVHInR/7gzsKQWSWEST70BgjCRqlbfAE6xzUZFTeYxciCjptm0rUQ2MC24xRdkvZByIDIYFnQ/AdmSFqNtKDR2WpEV/M8aBjyuPPomRJZ1X8oudWuJIU4ySdas04fCbDxD10TY/wyQcDHXuG1IrQpXme4DOGQeJZ0/aOFphBkDFUyPGfYMmLshOPNdBKi2IqWHPPs4XsV4Rv6+tvTSnMF2uGqHAgMBAAECggEAPa1sifPpcZN74DGupGng2uDeQI1BY3iOM8m+h6b9+61tE4RGifgaMAkCsOuNWE4a1uURwphFyUXUdTvVxdlsuMw/e7w6akUsH5sbCO99rtmcCQdXBtrM1+dMnIpK8LUhOYyWGVIMFVMGDYPmAyD5AC7aEAC2sC+DafYl4RdoYpidq1YxeE7DVw1aQHCI2mKhYjZG+3RDDGDfNFvdyH61MgdYjoGkeXNvARzEXgfWvfiTrHZ3H1SYgvOEHofzKDTrWsQL2dvaEsc55Jiw0AgNUVcgby7al8PUekTJoK3ZvrE3pSWaUirBcqsqWISHjeR7Xx501CHIha8EnZwlnDoM4QKBgQDtnYzwQ5mHg7cRHD8Z6QdTpvBvYSEPesiUT/HeI+AKQKDCVJxKiLvJagc6zZkOzV9bZDS/WLgzXWMyxUb+OTjht0jLWAMcf7NfFp3tPKq9wkmQQ/vQSBQ1lFmO6A4Zq1eoGKeUCB4pKBG6cSM+t8+ruhm7s1ZUt+6EBwCVN/izrQKBgQDD62jIm+6NFErdidUaIrGiFUrzqdR13w6JOexfk+O6Aau3wRqsr7Wz4nqQvVVxGMRpXbOH06zpeiS+vMmjwgO973VoLAmH+hJ0GZz8qj3zA2GEOFWjD2V7tqeRvGkQvz0v46pl+8sBJkrRHLN7DWNYY5NDI+b7exwqcTc/LL19gwJ/a6r4MeZvqvgD+7zQ2uy8ZSs/xzg7wsfgG1QeRIn8+qhOL8AnEZ7jeGCS5hJDSHHGw6KkRA/vZ1bpnBfIE2naXGywj3NR9Zfnry6QYO8cbt+adcRYVghTH/QYoKiFuxvonEKPrIQBJqUBY3ngforLjwTEpEie1cSCT1Dc8sBp8QKBgQCaz8fqzRyBKknGKQXVMxj+JKknRUl3IpzP3o9jLu9BqdRQzSwQzH9d91Y2TQXY6mM5hys35xG5JCUo+vCyj7p5OWCiwjl90yMFzr93/+YXwtIpsoIo6R+d1EUxKZoz+4mT7+hT0dUlwWZZOr6wO3IHBBf3c8UvbqZg+zlWmDnblQKBgEs6jwMkb5zaG2fyBJ7PJUN/8nIz8V+X0SxQfcEqIX0J+EC+7MAgFjcdZFp+lca3Vd9z+8Ksd4rMzMa5y856ositL2NZ+K0fs8i8EBaQPny61OgCFUuXEuv5keB2YuGMSns5FYRWuByrtDXl4PxzKXvq05iKWLKCaCq9v4momvKZ";
// //注证书文件路径支持设置为文件系统中的路径或CLASS_PATH中的路径优先从文件系统中加载加载失败后会继续尝试从CLASS_PATH中加载
// // config.merchantCertPath = "<-- 请填写您的应用公钥证书文件路径,例如:/foo/appCertPublicKey_2019051064521003.crt -->";
// // config.alipayCertPath = "<-- 请填写您的支付宝公钥证书文件路径,例如:/foo/alipayCertPublicKey_RSA2.crt -->";
// // config.alipayRootCertPath = "<-- 请填写您的支付宝根证书文件路径,例如:/foo/alipayRootCert.crt -->";
// //注:如果采用非证书模式,则无需赋值上面的三个证书路径,改为赋值如下的支付宝公钥字符串即可
// config.alipayPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhUnjdAKwZApwZEcfq+5L0pa77Vg3mqcoXv+th8RR0SYotkPsH1f2JkbS48ySaSCM6YNWSMNfqp5qdOla2zUJOBnJ/yaBg7s7fVD6V3M2mEog8kCDYGKt/3P4VII3xYl8lFYMQ3IcFRELkxCBBCA8JDKmf5z2R4F/Z/jFFEuOwxaJvp+7Ke9OzZHYdWGNnU6QP8YYLYUeX7VNZLHEuly34ExAw6A+yJkNDsYEho2Lu31QjT2pLh9g+88MlRfiI92iN25O9NVdeM4f5RcpvBPrBQZQs9tlFmALYSFS3prIf3FAobWM+W7iwxT6J25nFIhst1DdJQfIBpaeRUJVTkn99QIDAQAB";
// //可设置异步通知接收服务地址(可选)
// config.notifyUrl = "https://www.test.com/callback";
// //可设置AES密钥调用AES加解密相关接口时需要可选
// config.encryptKey = "VCS4bdmoAgXRaOq/TQ4MwA==";
// return config;
// }
}

View File

@@ -0,0 +1,190 @@
package com.sl.pay.handler.wechat;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.sl.pay.constant.TradingConstant;
import com.sl.pay.entity.RefundRecordEntity;
import com.sl.pay.entity.TradingEntity;
import com.sl.pay.enums.RefundStatusEnum;
import com.sl.pay.enums.TradingStateEnum;
import com.sl.pay.handler.BasicPayHandler;
import com.sl.pay.handler.wechat.response.WeChatResponse;
import com.sl.transport.common.exception.SLException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import static com.sl.pay.enums.TradingEnum.*;
/**
* 微信基础支付功能的实现
*/
@Slf4j
@Component("weChatBasicPayHandler")
public class WeChatBasicPayHandler implements BasicPayHandler {
@Override
public Boolean queryTrading(TradingEntity trading) throws SLException {
// 获取微信支付的client对象
WechatPayHttpClient client = WechatPayHttpClient.get();
//请求地址
String apiPath = StrUtil.format("/v3/pay/transactions/out-trade-no/{}", trading.getTradingOrderNo());
//请求参数
Map<String, Object> params = MapUtil.<String, Object>builder()
.put("mchid", client.getMchId())
.build();
WeChatResponse response;
try {
response = client.doGet(apiPath, params);
} catch (Exception e) {
log.error("调用微信接口出错apiPath = {}, params = {}", apiPath, JSONUtil.toJsonStr(params), e);
throw new SLException(NATIVE_REFUND_FAIL, e);
}
if (response.isOk()) {
JSONObject jsonObject = JSONUtil.parseObj(response.getBody());
// 交易状态,枚举值:
// SUCCESS支付成功
// REFUND转入退款
// NOTPAY未支付
// CLOSED已关闭
// REVOKED已撤销仅付款码支付会返回
// USERPAYING用户支付中仅付款码支付会返回
// PAYERROR支付失败仅付款码支付会返回
String tradeStatus = jsonObject.getStr("trade_state");
if (StrUtil.equalsAny(tradeStatus, TradingConstant.WECHAT_TRADE_CLOSED, TradingConstant.WECHAT_TRADE_REVOKED)) {
trading.setTradingState(TradingStateEnum.QXDD);
} else if (StrUtil.equalsAny(tradeStatus, TradingConstant.WECHAT_REFUND_SUCCESS, TradingConstant.WECHAT_TRADE_REFUND)) {
trading.setTradingState(TradingStateEnum.YJS);
} else if (StrUtil.equalsAny(tradeStatus, TradingConstant.WECHAT_TRADE_NOTPAY)) {
//如果是未支付需要判断下时间超过2小时未知的订单需要关闭订单以及设置状态为QXDD
long between = LocalDateTimeUtil.between(trading.getCreated(), LocalDateTimeUtil.now(), ChronoUnit.HOURS);
if (between >= 2) {
return this.closeTrading(trading);
}
} else {
//非最终状态不处理
return false;
}
//修改交易单状态
trading.setResultCode(tradeStatus);
trading.setResultMsg(jsonObject.getStr("trade_state_desc"));
trading.setResultJson(response.getBody());
return true;
}
throw new SLException(response.getBody(), NATIVE_REFUND_FAIL.getCode(), NATIVE_REFUND_FAIL.getCode());
}
@Override
public Boolean closeTrading(TradingEntity trading) throws SLException {
// 获取微信支付的client对象
WechatPayHttpClient client = WechatPayHttpClient.get();
//请求地址
String apiPath = StrUtil.format("/v3/pay/transactions/out-trade-no/{}/close", trading.getTradingOrderNo());
//请求参数
Map<String, Object> params = MapUtil.<String, Object>builder()
.put("mchid", client.getMchId())
.build();
try {
WeChatResponse response = client.doPost(apiPath, params);
if (response.getStatus() == 204) {
trading.setTradingState(TradingStateEnum.QXDD);
return true;
}
return false;
} catch (Exception e) {
throw new SLException(CLOSE_FAIL, e);
}
}
@Override
public Boolean refundTrading(RefundRecordEntity refundRecord) throws SLException {
// 获取微信支付的client对象
WechatPayHttpClient client = WechatPayHttpClient.get();
//请求地址
String apiPath = "/v3/refund/domestic/refunds";
//请求参数
Map<String, Object> params = MapUtil.<String, Object>builder()
.put("out_refund_no", Convert.toStr(refundRecord.getRefundNo()))
.put("out_trade_no", Convert.toStr(refundRecord.getTradingOrderNo()))
.put("amount", MapUtil.<String, Object>builder()
.put("refund", NumberUtil.mul(refundRecord.getRefundAmount(), 100)) //本次退款金额
.put("total", NumberUtil.mul(refundRecord.getTotal(), 100)) //原订单金额
.put("currency", "CNY") //币种
.build())
.build();
WeChatResponse response;
try {
response = client.doPost(apiPath, params);
} catch (Exception e) {
log.error("调用微信接口出错apiPath = {}, params = {}", apiPath, JSONUtil.toJsonStr(params), e);
throw new SLException(NATIVE_REFUND_FAIL, e);
}
refundRecord.setRefundCode(Convert.toStr(response.getStatus()));
refundRecord.setRefundMsg(response.getBody());
if (response.isOk()) {
JSONObject jsonObject = JSONUtil.parseObj(response.getBody());
// SUCCESS退款成功
// CLOSED退款关闭
// PROCESSING退款处理中
// ABNORMAL退款异常
String status = jsonObject.getStr("status");
if (StrUtil.equals(status, TradingConstant.WECHAT_REFUND_PROCESSING)) {
refundRecord.setRefundStatus(RefundStatusEnum.SENDING);
} else if (StrUtil.equals(status, TradingConstant.WECHAT_REFUND_SUCCESS)) {
refundRecord.setRefundStatus(RefundStatusEnum.SUCCESS);
} else {
refundRecord.setRefundStatus(RefundStatusEnum.FAIL);
}
return true;
}
throw new SLException(refundRecord.getRefundMsg(), NATIVE_REFUND_FAIL.getCode(), NATIVE_REFUND_FAIL.getStatus());
}
@Override
public Boolean queryRefundTrading(RefundRecordEntity refundRecord) throws SLException {
// 获取微信支付的client对象
WechatPayHttpClient client = WechatPayHttpClient.get();
//请求地址
String apiPath = StrUtil.format("/v3/refund/domestic/refunds/{}", refundRecord.getRefundNo());
WeChatResponse response;
try {
response = client.doGet(apiPath);
} catch (Exception e) {
log.error("调用微信接口出错apiPath = {}", apiPath, e);
throw new SLException(NATIVE_QUERY_REFUND_FAIL, e);
}
refundRecord.setRefundCode(Convert.toStr(response.getStatus()));
refundRecord.setRefundMsg(response.getBody());
if (response.isOk()) {
JSONObject jsonObject = JSONUtil.parseObj(response.getBody());
// SUCCESS退款成功
// CLOSED退款关闭
// PROCESSING退款处理中
// ABNORMAL退款异常
String status = jsonObject.getStr("status");
if (StrUtil.equals(status, TradingConstant.WECHAT_REFUND_PROCESSING)) {
refundRecord.setRefundStatus(RefundStatusEnum.SENDING);
} else if (StrUtil.equals(status, TradingConstant.WECHAT_REFUND_SUCCESS)) {
refundRecord.setRefundStatus(RefundStatusEnum.SUCCESS);
} else {
refundRecord.setRefundStatus(RefundStatusEnum.FAIL);
}
return true;
}
throw new SLException(response.getBody(), NATIVE_QUERY_REFUND_FAIL.getCode(), NATIVE_QUERY_REFUND_FAIL.getStatus());
}
}

View File

@@ -0,0 +1,19 @@
package com.sl.pay.handler.wechat;
import com.sl.pay.entity.TradingEntity;
import com.sl.pay.handler.NativePayHandler;
import com.sl.transport.common.exception.SLException;
import org.springframework.stereotype.Component;
/**
* 微信二维码支付
*/
@Component("wechatNativePayHandler")
public class WechatNativePayHandler implements NativePayHandler {
@Override
public void createDownLineTrading(TradingEntity tradingEntity) throws SLException {
}
}

View File

@@ -0,0 +1,170 @@
package com.sl.pay.handler.wechat;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.net.url.UrlPath;
import cn.hutool.core.net.url.UrlQuery;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.sl.pay.handler.wechat.response.WeChatResponse;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import java.io.ByteArrayInputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.util.Map;
/**
* 微信支付远程调用对象
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WechatPayHttpClient {
private String mchId; //商户号
private String appId; //应用号
private String privateKey; //私钥字符串
private String mchSerialNo; //商户证书序列号
private String apiV3Key; //V3密钥
private String domain; //请求域名
private String notifyUrl; //请求地址
public static WechatPayHttpClient get() {
//通过渠道对象转化成微信支付的client对象
return WechatPayHttpClient.builder()
.appId("wx6592a2db3f85ed25")
.domain("api.mch.weixin.qq.com")
.privateKey("-----BEGIN PRIVATE KEY-----\n" +
"MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDBHGgIh80193Gh\n" +
"dpD1LtMZfTRpcWI0fImyuBCyrd3gYb3rrsARebGcHdJsQA3mVjVqVp5ybhEZDPa4\n" +
"ecoK4Ye1hTppNpI/lmLt4/uUV/zhF5ahli7hi+116Ty6svHSbuMQBuUZeTFOwGrx\n" +
"jvofU/4pGIwh8ZvkcSnyOp9uX2177UVxDBkhgbZbJp9XF2b83vUa5eHo93CziPzn\n" +
"3hFdAlBCdTXB7DH+m0nN3Jou0szGukvq7cIgGpHku4ycKSTkIhhl9WRhN6OoSEJx\n" +
"q88MXzjkzTruc85PHN52aUTUifwg3T8Y4XqFQ61dTnEmgxeD2O6/pLdB9gLsp6yC\n" +
"GqN5Lqk7AgMBAAECggEBAL4X+WzUSbSjFS9NKNrCMjm4H1zgqTxjj6TnPkC1mGEl\n" +
"tjAHwLgzJBw62wWGdGhWWpSIGccpBBm1wjTMZpAZfF66fEpP1t1Ta6UjtGZNyvfF\n" +
"IZmE3jdWZ/WXGBnsxtFQKKKBNwrBW0Fbdqq9BQjLxLitmlxbmwrgPttcy855j6vZ\n" +
"qq4MBT1v8CtUT/gz4UWW2xWovVnmWOrRSScv7Nh0pMbRpPLkNHXrBwSSNz/keORz\n" +
"XB9JSm85wlkafa7n5/IJbdTml3A/uAgW3q3JZZQotHxQsYvD4Zb5Cnc9CPAXE5L2\n" +
"Yk877kVXZMGt5QPIVcPMj/72AMtaJT67Y0fN0RYHEGkCgYEA38BIGDY6pePgPbxB\n" +
"7N/l6Df0/OKPP0u8mqR4Q0aQD3VxeGiZUN1uWXEFKsKwlOxLfIFIFk1/6zQeC0xe\n" +
"tNTKk0gTL8hpMUTNkE7vI9gFWws2LY6DE86Lm0bdFEIwh6d7Fr7zZtyQKPzMsesC\n" +
"3XV9sdSUExEi5o/VwAyf+xZlOXcCgYEA3PGZYlILjg3esPNkhDz2wxFw432i8l/B\n" +
"CPD8ZtqIV9eguu4fVtFYcUVfawBb0T11RamJkc4eiSOqayC+2ehgb+GyRLJNK4Fq\n" +
"bFcsIT+CK0HlscZw51jrMR0MxTc4RzuOIMoYDeZqeGB6/YnNyG4pw2sD8bIwHm84\n" +
"06gtJsX/v10CgYAo8g3/aEUZQHcztPS3fU2cTkkl0ev24Ew2XGypmwsX2R0XtMSB\n" +
"uNPNyFHyvkgEKK2zrhDcC/ihuRraZHJcUyhzBViFgP5HBtk7VEaM36YzP/z9Hzw7\n" +
"bqu7kZ85atdoq6xpwC3Yn/o9le17jY8rqamD1mv2hUdGvAGYsHbCQxnpBwKBgHTk\n" +
"eaMUBzr7yZLS4p435tHje1dQVBJpaKaDYPZFrhbTZR0g+IGlNmaPLmFdCjbUjiPy\n" +
"A2+Znnwt227cHz0IfWUUAo3ny3419QkmwZlBkWuzbIO2mms7lwsf9G6uvV6qepKM\n" +
"eVd5TWEsokVbT/03k27pQmfwPxcK/wS0GFdIL/udAoGAOYdDqY5/aadWCyhzTGI6\n" +
"qXPLvC+fsJBPhK2RXyc+jYV0KmrEv4ewxlK5NksuFsNkyB7wlI1oMCa/xB3T/2vT\n" +
"BALgGFPi8BJqceUjtnTYtI4R2JIVEl08RtEJwyU5JZ2rvWcilsotVZYwfuLZ9Kfd\n" +
"hkTrgNxlp/KKkr+UuKce4Vs=\n" +
"-----END PRIVATE KEY-----\n")
.mchId("1561414331")
.mchSerialNo("25FBDE3EFD31B03A4377EB9A4A47C517969E6620")
.apiV3Key("CZBK51236435wxpay435434323FFDuv3")
.notifyUrl("https://www.itcast.cn/")
.build();
}
/***
* 构建CloseableHttpClient远程请求对象
* @return org.apache.http.impl.client.CloseableHttpClient
*/
public CloseableHttpClient createHttpClient() throws Exception {
// 加载商户私钥privateKey私钥字符串
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes(StandardCharsets.UTF_8)));
// 加载平台证书mchId商户号,mchSerialNo商户证书序列号,apiV3KeyV3密钥
PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, merchantPrivateKey);
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
// 向证书管理器增加需要自动更新平台证书的商户信息
CertificatesManager certificatesManager = CertificatesManager.getInstance();
certificatesManager.putMerchant(mchId, wechatPay2Credentials, apiV3Key.getBytes(StandardCharsets.UTF_8));
// 初始化httpClient
return com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, merchantPrivateKey)
.withValidator(new WechatPay2Validator(certificatesManager.getVerifier(mchId)))
.build();
}
/***
* 支持post请求的远程调用
*
* @param apiPath api地址
* @param params 携带请求参数
* @return 返回字符串
*/
public WeChatResponse doPost(String apiPath, Map<String, Object> params) throws Exception {
String url = StrUtil.format("https://{}{}", this.domain, apiPath);
HttpPost httpPost = new HttpPost(url);
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
String body = JSONUtil.toJsonStr(params);
httpPost.setEntity(new StringEntity(body, CharsetUtil.UTF_8));
CloseableHttpResponse response = this.createHttpClient().execute(httpPost);
return new WeChatResponse(response);
}
/***
* 支持get请求的远程调用
* @param apiPath api地址
* @param params 在路径中请求的参数
* @return 返回字符串
*/
public WeChatResponse doGet(String apiPath, Map<String, Object> params) throws Exception {
URI uri = UrlBuilder.create()
.setHost(this.domain)
.setScheme("https")
.setPath(UrlPath.of(apiPath, CharsetUtil.CHARSET_UTF_8))
.setQuery(UrlQuery.of(params))
.setCharset(CharsetUtil.CHARSET_UTF_8)
.toURI();
return this.doGet(uri);
}
/***
* 支持get请求的远程调用
* @param apiPath api地址
* @return 返回字符串
*/
public WeChatResponse doGet(String apiPath) throws Exception {
URI uri = UrlBuilder.create()
.setHost(this.domain)
.setScheme("https")
.setPath(UrlPath.of(apiPath, CharsetUtil.CHARSET_UTF_8))
.setCharset(CharsetUtil.CHARSET_UTF_8)
.toURI();
return this.doGet(uri);
}
private WeChatResponse doGet(URI uri) throws Exception {
HttpGet httpGet = new HttpGet(uri);
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = this.createHttpClient().execute(httpGet);
return new WeChatResponse(response);
}
}

View File

@@ -0,0 +1,31 @@
package com.sl.pay.handler.wechat.response;
import cn.hutool.core.util.CharsetUtil;
import lombok.Data;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;
@Data
public class WeChatResponse {
private int status; //响应状态码
private String body; //响应体数据
public WeChatResponse() {
}
public WeChatResponse(CloseableHttpResponse response) {
this.status = response.getStatusLine().getStatusCode();
try {
this.body = EntityUtils.toString(response.getEntity(), CharsetUtil.UTF_8);
} catch (Exception e) {
// 如果出现异常响应体为null
}
}
public Boolean isOk() {
return this.status == 200;
}
}

View File

@@ -0,0 +1,19 @@
server:
port: 18099
tomcat:
uri-encoding: UTF-8
threads:
max: 1000
min-spare: 30
spring:
application:
name: sl-express-pay
mvc:
pathmatch:
#解决异常swagger Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
#因为Springfox使用的路径匹配是基于AntPathMatcher的而Spring Boot 2.6.X使用的是PathPatternMatcher
matching-strategy: ant_path_matcher
redis: #redis的配置
port: 6379
host: 192.168.150.101
password: 123321

View File

@@ -0,0 +1,56 @@
package com.sl.pay.handler.alipay;
import com.sl.pay.entity.RefundRecordEntity;
import com.sl.pay.entity.TradingEntity;
import com.sl.pay.handler.BasicPayHandler;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.math.BigDecimal;
@SpringBootTest
class AliBasicPayHandlerTest {
@Resource(name = "aliBasicPayHandler")
BasicPayHandler basicPayHandler;
@Test
void queryTrading() {
TradingEntity tradingEntity = new TradingEntity();
tradingEntity.setTradingOrderNo(11223388L); //交易单号
Boolean result = this.basicPayHandler.queryTrading(tradingEntity);
System.out.println("执行是否成功:" + result);
System.out.println(tradingEntity);
}
@Test
void closeTrading() {
TradingEntity tradingEntity = new TradingEntity();
tradingEntity.setTradingOrderNo(11223377L); //交易单号
Boolean result = this.basicPayHandler.closeTrading(tradingEntity);
System.out.println("执行是否成功:" + result);
System.out.println(tradingEntity);
}
@Test
void refundTrading() {
RefundRecordEntity refundRecordEntity = new RefundRecordEntity();
refundRecordEntity.setTradingOrderNo(11223388L); //交易单号
refundRecordEntity.setRefundNo(11223380L); //退款单号
refundRecordEntity.setRefundAmount(BigDecimal.valueOf(0.1)); //退款金额
Boolean result = this.basicPayHandler.refundTrading(refundRecordEntity);
System.out.println("执行是否成功:" + result);
System.out.println(refundRecordEntity);
}
@Test
void queryRefundTrading() {
RefundRecordEntity refundRecordEntity = new RefundRecordEntity();
refundRecordEntity.setTradingOrderNo(11223388L); //交易单号
refundRecordEntity.setRefundNo(11223388L); //退款单号
Boolean result = this.basicPayHandler.queryRefundTrading(refundRecordEntity);
System.out.println("执行是否成功:" + result);
System.out.println(refundRecordEntity);
}
}

View File

@@ -0,0 +1,32 @@
package com.sl.pay.handler.alipay;
import com.sl.pay.entity.TradingEntity;
import com.sl.pay.handler.NativePayHandler;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.math.BigDecimal;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class AliNativePayHandlerTest {
@Resource(name = "aliNativePayHandler")
NativePayHandler nativePayHandler;
@Test
void createDownLineTrading() {
TradingEntity tradingEntity = new TradingEntity();
tradingEntity.setProductOrderNo(12345L); //订单号
tradingEntity.setTradingOrderNo(11223388L); //交易单号
tradingEntity.setMemo("运费");
tradingEntity.setTradingAmount(BigDecimal.valueOf(1));
this.nativePayHandler.createDownLineTrading(tradingEntity);
System.out.println("二维码信息:" + tradingEntity.getPlaceOrderMsg());
System.out.println(tradingEntity);
}
}

View File

@@ -0,0 +1,59 @@
package com.sl.pay.handler.wechat;
import com.sl.pay.entity.RefundRecordEntity;
import com.sl.pay.entity.TradingEntity;
import com.sl.pay.handler.BasicPayHandler;
import com.sl.pay.handler.alipay.AliBasicPayHandler;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@SpringBootTest
class WechatBasicPayHandlerTest {
@Resource(name = "weChatBasicPayHandler")
BasicPayHandler basicPayHandler;
@Test
void queryTrading() {
TradingEntity tradingEntity = new TradingEntity();
tradingEntity.setTradingOrderNo(11223388L); //交易单号
tradingEntity.setCreated(LocalDateTime.now());
Boolean result = this.basicPayHandler.queryTrading(tradingEntity);
System.out.println("执行是否成功:" + result);
System.out.println(tradingEntity);
}
@Test
void closeTrading() {
TradingEntity tradingEntity = new TradingEntity();
tradingEntity.setTradingOrderNo(11223377L); //交易单号
Boolean result = this.basicPayHandler.closeTrading(tradingEntity);
System.out.println("执行是否成功:" + result);
System.out.println(tradingEntity);
}
@Test
void refundTrading() {
RefundRecordEntity refundRecordEntity = new RefundRecordEntity();
refundRecordEntity.setTradingOrderNo(11223388L); //交易单号
refundRecordEntity.setRefundNo(11223380L); //退款单号
refundRecordEntity.setRefundAmount(BigDecimal.valueOf(0.1)); //退款金额
Boolean result = this.basicPayHandler.refundTrading(refundRecordEntity);
System.out.println("执行是否成功:" + result);
System.out.println(refundRecordEntity);
}
@Test
void queryRefundTrading() {
RefundRecordEntity refundRecordEntity = new RefundRecordEntity();
refundRecordEntity.setTradingOrderNo(11223388L); //交易单号
refundRecordEntity.setRefundNo(11223388L); //退款单号
Boolean result = this.basicPayHandler.queryRefundTrading(refundRecordEntity);
System.out.println("执行是否成功:" + result);
System.out.println(refundRecordEntity);
}
}

View File

@@ -0,0 +1,32 @@
package com.sl.pay.handler.wechat;
import com.sl.pay.entity.TradingEntity;
import com.sl.pay.handler.NativePayHandler;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.math.BigDecimal;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class WechatNativePayHandlerTest {
@Resource(name = "wechatNativePayHandler")
NativePayHandler nativePayHandler;
@Test
void createDownLineTrading() {
TradingEntity tradingEntity = new TradingEntity();
tradingEntity.setProductOrderNo(12345L); //订单号
tradingEntity.setTradingOrderNo(11223388L); //交易单号
tradingEntity.setMemo("运费");
tradingEntity.setTradingAmount(BigDecimal.valueOf(1));
this.nativePayHandler.createDownLineTrading(tradingEntity);
System.out.println("二维码信息:" + tradingEntity.getPlaceOrderMsg());
System.out.println(tradingEntity);
}
}