1. 迁移三方 PayClient 的代码

This commit is contained in:
YunaiV
2020-11-28 23:32:51 +08:00
parent 0a14b530b6
commit d1b6118052
17 changed files with 94 additions and 356 deletions

View File

@@ -59,6 +59,14 @@
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 云服务相关 -->
<dependency>
<groupId>Pingplusplus</groupId>
<artifactId>pingpp-java</artifactId>
<version>2.2.4</version>
<type>jar</type>
</dependency>
<!-- Test 相关 -->
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -105,4 +113,15 @@
</plugins>
</build>
<repositories>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>central</id>
<name>bintray</name>
<url>http://jcenter.bintray.com</url>
</repository>
</repositories>
</project>

View File

@@ -0,0 +1 @@
package cn.iocoder.mall.payservice.client;

View File

@@ -0,0 +1,59 @@
package cn.iocoder.mall.payservice.client.thirdpay;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.payservice.client.thirdpay.dto.ThirdPayRefundSuccessRespDTO;
import cn.iocoder.mall.payservice.client.thirdpay.dto.ThirdPayTransactionSuccessRespDTO;
import cn.iocoder.mall.payservice.dal.mysql.dataobject.refund.PayRefundDO;
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionDO;
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionExtensionDO;
import java.util.Map;
/**
* 三方支付平台的 Client 抽象类
*/
public abstract class AbstractThirdPayClient {
/**
* 提交支付请求给支付平台,并返回请求结果
*
* @param transaction 支付交易数据
* @param transactionExtension 交易扩展数据
* @param extra 额外参数。用于支持不同支付平台的拓展字段。例如说,微信公众号支付,需要多传递一个 openid
* @return 请求结果
*/
public abstract CommonResult<String> submitTransaction(PayTransactionDO transaction,
PayTransactionExtensionDO transactionExtension,
Map<String, Object> extra);
/**
* 解析支付成功回调的参数,返回 TransactionSuccessBO 对象
*
* @param params 回调的参数
* @return 解析结果
*/
// TODO 芋艿,理论来说不会出现解析失败的情况,先返回这个参数列。等后面封装支付宝和微信支付的时候,在看看。
public abstract CommonResult<ThirdPayTransactionSuccessRespDTO> parseTransactionSuccessParams(String params);
/**
* 提交退款请求给支付平台,并返回请求结果
*
* @param refund 退款数据
* @param transactionExtension 交易扩展数据
* @param extra 额外参数。用于支持不同支付平台的拓展字段。
* @return 请求结果
*/
public abstract CommonResult<String> submitRefund(PayRefundDO refund,
PayTransactionExtensionDO transactionExtension,
Map<String, Object> extra);
/**
* 解析退款成功回调的参数,返回 RefundSuccessBO 对象
*
* @param params 回调的参数
* @return 解析结果
*/
// TODO 芋艿,理论来说不会出现解析失败的情况,先返回这个参数列。等后面封装支付宝和微信支付的时候,在看看。
public abstract CommonResult<ThirdPayRefundSuccessRespDTO> parseRefundSuccessParams(String params);
}

View File

@@ -0,0 +1,24 @@
package cn.iocoder.mall.payservice.client.thirdpay;
import cn.iocoder.mall.payservice.enums.PayChannelEnum;
import java.util.HashMap;
import java.util.Map;
public class PaySDKFactory {
private static Map<Integer, AbstractThirdPayClient> CLIENTS = new HashMap<>();
static {
CLIENTS.put(PayChannelEnum.PINGXX.getId(), new PingxxThirdPayClient());
}
public static AbstractThirdPayClient getSDK(Integer payChannel) {
AbstractThirdPayClient client = CLIENTS.get(payChannel);
if (client == null) {
throw new NullPointerException("找不到合适的 ThirdPayClient " + payChannel);
}
return client;
}
}

View File

@@ -0,0 +1,149 @@
package cn.iocoder.mall.payservice.client.thirdpay;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.payservice.client.thirdpay.dto.ThirdPayRefundSuccessRespDTO;
import cn.iocoder.mall.payservice.client.thirdpay.dto.ThirdPayTransactionSuccessRespDTO;
import cn.iocoder.mall.payservice.dal.mysql.dataobject.refund.PayRefundDO;
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionDO;
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionExtensionDO;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.ImmutableMap;
import com.pingplusplus.Pingpp;
import com.pingplusplus.exception.*;
import com.pingplusplus.model.Charge;
import com.pingplusplus.model.Refund;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
// TODO 代码略乱,后面重构下
public class PingxxThirdPayClient extends AbstractThirdPayClient {
static {
Pingpp.privateKeyPath = "/Users/yunai/Downloads/pingxx.pem";
Pingpp.apiKey = "sk_test_8a9SGSXLKqX1ennjX9DenvbT";
}
@Override
public CommonResult<String> submitTransaction(PayTransactionDO transaction,
PayTransactionExtensionDO transactionExtension,
Map<String, Object> extra) {
Map<String, Object> reqObj = createChargeRequest(transaction, transactionExtension, extra);
// 请求ping++
try {
Charge charge = Charge.create(reqObj);
System.out.println(charge.toString());
return CommonResult.success(charge.toString());
} catch (AuthenticationException | InvalidRequestException |
APIConnectionException | APIException |
ChannelException | RateLimitException e) {
e.printStackTrace();
throw new RuntimeException(e); // TODO 芋艿,后续优化
}
}
private static Map<String, Object> createChargeRequest(PayTransactionDO transaction,
PayTransactionExtensionDO transactionExtension,
Map<String, Object> extra) {
// 计算支付渠道和支付额外参数
String channel = "wx_pub"; // 因为 ping++ 是用来做模拟支付的渠道,所以这里强制就选择了 wx_pub 微信公众号支付
extra = new HashMap<>(); // TODO 临时,后面用 extra
extra.put("open_id", "just_for_test");
// 生成支付对象
Map<String, Object> reqObj = new HashMap<>();
reqObj.put("subject", transaction.getOrderSubject());
reqObj.put("body", transaction.getOrderDescription());
reqObj.put("description", transaction.getOrderMemo());
reqObj.put("amount", transaction.getPrice());
reqObj.put("order_no", transactionExtension.getTransactionCode());
reqObj.put("channel", channel);
reqObj.put("currency", "cny");
reqObj.put("client_ip", transactionExtension.getCreateIp());
reqObj.put("app", ImmutableMap.of("id", "app_aTyfXDjrvzDSbLuz")); // TODO 写死先
reqObj.put("extra", extra);
return reqObj;
}
@Override
public CommonResult<ThirdPayTransactionSuccessRespDTO> parseTransactionSuccessParams(String params) {
JSONObject paramsObj = JSON.parseObject(params);
JSONObject chargeObj = paramsObj.getJSONObject("data").getJSONObject("object");
ThirdPayTransactionSuccessRespDTO successRespDTO = new ThirdPayTransactionSuccessRespDTO()
.setTransactionCode(chargeObj.getString("order_no"))
.setPaymentTime(new Date(chargeObj.getLong("time_paid") * 1000))
.setTradeNo(chargeObj.getString("transaction_no"));
return CommonResult.success(successRespDTO);
}
@Override
public CommonResult<String> submitRefund(PayRefundDO refund,
PayTransactionExtensionDO transactionExtension,
Map<String, Object> extra) {
// 解析出 chargeId
JSONObject paramsObj = JSON.parseObject(transactionExtension.getExtensionData());
JSONObject chargeObj = paramsObj.getJSONObject("data").getJSONObject("object");
String chargeId = chargeObj.getString("id");
// 请求ping++
Map<String, Object> reqObj = createRefundRequest(refund, chargeId, refund.getOrderDescription(), refund.getPrice());
try {
Refund pingxxRefund = Refund.create(chargeId, reqObj);
System.out.println(pingxxRefund.toString());
return CommonResult.success(pingxxRefund.toString());
} catch (AuthenticationException | InvalidRequestException |
APIConnectionException | APIException |
ChannelException | RateLimitException e) {
e.printStackTrace();
throw new RuntimeException(e); // TODO 芋艿,后续优化
}
}
// {"id":"evt_400190427005305341228202","created":1556297585,"livemode":false,"type":"refund.succeeded","data":{"object":{"id":"re_HO0m9GOGOi50KCmX104ufHe1","object":"refund","order_no":"HO0m9GOGOi50KCmX104ufHe1","amount":1,"created":1556297585,"succeed":true,"status":"succeeded","time_succeed":1556297585,"description":"测试下退款","failure_code":null,"failure_msg":null,"metadata":{},"charge":"ch_y1iXjLnDS4G4OO4uT4a5C4W1","charge_order_no":"20190427004410165545","transaction_no":"201904270053053608824","extra":{}}},"object":"event","request":"iar_Oa188KCiHC40iLibbHX5WrHC","pending_webhooks":0}
@Override
public CommonResult<ThirdPayRefundSuccessRespDTO> parseRefundSuccessParams(String params) {
JSONObject paramsObj = JSON.parseObject(params);
JSONObject chargeObj = paramsObj.getJSONObject("data").getJSONObject("object");
ThirdPayRefundSuccessRespDTO successRespDTO = new ThirdPayRefundSuccessRespDTO()
.setRefundCode(chargeObj.getJSONObject("metadata").getString("refundCode"))
.setRefundTime(new Date(chargeObj.getLong("time_succeed") * 1000))
.setTradeNo(chargeObj.getString("transaction_no"))
// TODO 芋艿,需要测试下,退款失败
.setSuccess(chargeObj.containsValue("failure_code") || chargeObj.containsValue("failure_msg"));
return CommonResult.success(successRespDTO);
}
private Map<String, Object> createRefundRequest(PayRefundDO refund, String chargeId, String orderDescription, Integer price) {
Map<String, Object> reqObj = new HashMap<>();
// reqObj.put("CHARGE_ID", chargeId);
reqObj.put("description", orderDescription);
reqObj.put("amount", price);
Map<String, Object> metadata = new HashMap<>();
metadata.put("refundCode", refund.getRefundCode());
reqObj.put("metadata", metadata);
return reqObj;
}
public static void main(String[] args) {
if (true) { // 测试支付请求
PayTransactionDO transaction = new PayTransactionDO();
transaction.setOrderSubject("测试商品");
transaction.setOrderDescription("测试描述");
transaction.setPrice(1);
PayTransactionExtensionDO extension = new PayTransactionExtensionDO();
extension.setTransactionCode(System.currentTimeMillis() + "");
extension.setCreateIp("127.0.0.1");
new PingxxThirdPayClient().submitTransaction(transaction, extension, null);
}
if (false) { // 测试退款请求
PayRefundDO refund = new PayRefundDO().setPrice(9999999).setOrderDescription("测试描述");
PayTransactionExtensionDO transactionExtension = new PayTransactionExtensionDO()
.setExtensionData("{\"id\":\"evt_400190423100354205607502\",\"created\":1555985033,\"livemode\":false,\"type\":\"charge.succeeded\",\"data\":{\"object\":{\"id\":\"ch_DCGyXTmDGuHKb1C0yTzjPOGC\",\"object\":\"charge\",\"created\":1555985032,\"livemode\":false,\"paid\":true,\"refunded\":false,\"reversed\":false,\"app\":\"app_aTyfXDjrvzDSbLuz\",\"channel\":\"wx_pub\",\"order_no\":\"20190423100352158401\",\"client_ip\":\"114.87.158.59\",\"amount\":10,\"amount_settle\":10,\"currency\":\"cny\",\"subject\":\"kafka 实战\",\"body\":\"测试描述\",\"extra\":{\"open_id\":\"just_for_test\",\"bank_type\":\"your bank type\"},\"time_paid\":1555985033,\"time_expire\":1555992232,\"time_settle\":null,\"transaction_no\":\"1244341374201904238178164740\",\"refunds\":{\"object\":\"list\",\"url\":\"/v1/charges/ch_DCGyXTmDGuHKb1C0yTzjPOGC/refunds\",\"has_more\":false,\"data\":[]},\"amount_refunded\":0,\"failure_code\":null,\"failure_msg\":null,\"metadata\":{},\"credential\":{},\"description\":\"测试备注\"}},\"object\":\"event\",\"request\":\"iar_4e9mPODW5ujPqLen5OOmvL8S\",\"pending_webhooks\":0}");
new PingxxThirdPayClient().submitRefund(refund, transactionExtension, null);
}
}
}

View File

@@ -0,0 +1,34 @@
package cn.iocoder.mall.payservice.client.thirdpay.dto;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* 三方平台的交易退款成功 Response DTO
*/
@Data
@Accessors(chain = true)
public class ThirdPayRefundSuccessRespDTO {
/**
* 生成传输给第三方的订单号
*
* 唯一索引
*/
private String refundCode;
/**
* 第三方的流水号
*/
private String tradeNo;
/**
* 第三方退款成功的时间
*/
private Date refundTime;
/**
* 是否成功
*/
private Boolean success;
}

View File

@@ -0,0 +1,30 @@
package cn.iocoder.mall.payservice.client.thirdpay.dto;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* 三方平台的交易支付成功 Response DTO
*/
@Data
@Accessors(chain = true)
public class ThirdPayTransactionSuccessRespDTO {
/**
* 生成传输给第三方的订单号
*
* 唯一索引
*/
private String transactionCode;
/**
* 第三方的流水号
*/
private String tradeNo;
/**
* 第三方支付成功的时间
*/
private Date paymentTime;
}

View File

@@ -0,0 +1,103 @@
package cn.iocoder.mall.payservice.dal.mysql.dataobject.refund;
import cn.iocoder.mall.mybatis.core.dataobject.DeletableDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* 退款单 DO
*/
@TableName("pay_refund")
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class PayRefundDO extends DeletableDO {
/**
* 编号,自增
*/
private Integer id;
/**
* 支付交易编号
*/
private Integer transactionId;
/**
* 生成传输给第三方的退款号
*
* 唯一索引
*/
private String refundCode;
/**
* 应用编号
*
* 不同业务线分配不同的 appId
* 举个例子,
* 1. 电商系统的订单appId = 1024
* 2. 活动系统的订单appId = 2048
*/
private String appId;
/**
* 业务线的订单编号
*
* 1. 使用 String 的原因是,业务线可能使用 String 做为编号
* 2. 每个 appId 下orderId 唯一
*/
private String orderId;
/**
* 发起交易的 IP
*/
private String createIp;
/**
* 业务退款描述
*/
private String orderDescription;
/**
* 退款金额,单位:分。
*
* TODO 暂时不考虑货币类型。
*/
private Integer price;
/**
* 退款状态
*
* @see cn.iocoder.mall.pay.api.constant.PayRefundStatus
*/
private Integer status;
/**
* 回调业务线完成时间
*/
private Date finishTime;
/**
* 异步通知地址
*/
private String notifyUrl;
/**
* 扩展内容
*
* 异步通知的时候填充回调的数据
*/
private String extensionData;
/**
* 退款渠道
*/
private Integer refundChannel;
/**
* 第三方退款成功的时间
*/
private Date refundTime;
/**
* 收到第三方系统通知的时间
*
* 一般情况下,即第三方系统的异步通知
*/
private Date notifyTime;
/**
* 第三方的流水号
*/
private String tradeNo;
}

View File

@@ -0,0 +1,15 @@
package cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* TODO 重复支付的交易
*
* 可能不靠这个表,而是差错处理。
*/
@Data
@Accessors(chain = true)
@Deprecated
public class PayRepeatTransactionDO {
}

View File

@@ -0,0 +1,54 @@
package cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction;
import cn.iocoder.mall.mybatis.core.dataobject.DeletableDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 交易扩展表
*/
@TableName("pay_transaction_extension")
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class PayTransactionExtensionDO extends DeletableDO {
/**
* 编号,自增
*/
private Integer id;
/**
* 交易编号 {@link PayTransactionDO#getId()}
*/
private Integer transactionId;
/**
* 选择的支付渠道
*/
private Integer payChannel;
/**
* 生成传输给第三方的订单号
*
* 唯一索引
*/
private String transactionCode;
/**
* 扩展内容
*
* 异步通知的时候填充回调的数据
*/
private String extensionData;
/**
* 发起交易的 IP
*/
private String createIp;
/**
* 状态
*
* @see cn.iocoder.mall.payservice.enums.transaction.PayTransactionStatusEnum
* 注意,只包含上述枚举的 WAITING 和 SUCCESS
*/
private Integer status;
}