支付模块,发起交易,暂时是比较 demo 的代码。未完成~

This commit is contained in:
YunaiV 2019-03-13 00:56:03 +08:00
parent 0c8a79a8ed
commit 6a217b12c6
14 changed files with 338 additions and 9 deletions

View File

@ -1,4 +1,4 @@
package cn.iocoder.mall.pay.scheduler;
package cn.iocoder.mall.pay.application.scheduler;
/**
* TODO

View File

@ -2,13 +2,15 @@ package cn.iocoder.mall.pay.api;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.pay.api.bo.PayTransactionBO;
import cn.iocoder.mall.pay.api.bo.PayTransactionSubmitBO;
import cn.iocoder.mall.pay.api.dto.PayTransactionCreateDTO;
import cn.iocoder.mall.pay.api.dto.PayTransactionSubmitDTO;
public interface PayTransactionService {
CommonResult<PayTransactionBO> createTransaction(PayTransactionCreateDTO payTransactionCreateDTO);
CommonResult submitTransaction(); // TODO 1. params 2. result
CommonResult<PayTransactionSubmitBO> submitTransaction(PayTransactionSubmitDTO payTransactionSubmitDTO);
CommonResult cancelTransaction(); // TODO 1. params 2. result

View File

@ -0,0 +1,35 @@
package cn.iocoder.mall.pay.api.bo;
/**
* 支付交易提交结果 BO
*/
public class PayTransactionSubmitBO {
/**
* 支付交易拓展单编号
*/
private Integer id;
/**
* 调用三方平台的响应结果
*/
private String invokeResponse;
public Integer getId() {
return id;
}
public PayTransactionSubmitBO setId(Integer id) {
this.id = id;
return this;
}
public String getInvokeResponse() {
return invokeResponse;
}
public PayTransactionSubmitBO setInvokeResponse(String invokeResponse) {
this.invokeResponse = invokeResponse;
return this;
}
}

View File

@ -8,7 +8,10 @@ public enum PayChannelEnum {
WEIXIN_APP(100, "wx", "微信 App 支付"),
WEIXIN_PUB(100, "wx", "微信 JS API 支付"),
ALIPAY(200, "alipay", "微信支付");
ALIPAY(200, "alipay", "微信支付"),
PINGXX(9999, "ping++", "ping++ 支付"),
;
/**
* 渠道编号

View File

@ -12,7 +12,8 @@ public enum PayErrorCodeEnum {
PAY_APP_IS_DISABLE(1004000001, "App 已经被禁用"),
// ========== TRANSACTION 模块 ==========
PAY_TRANSACTION_NOT_FOUND(100401000, "支付交易单不存在"),
PAY_TRANSACTION_STATUS_IS_NOT_WAITING(100401001, "支付交易单不处于待支付"),
;

View File

@ -0,0 +1,68 @@
package cn.iocoder.mall.pay.api.dto;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* 支付交易提交 DTO
*/
public class PayTransactionSubmitDTO {
/**
* 应用编号
*/
@NotEmpty(message = "应用编号不能为空")
private String appId;
/**
* 发起交易的 IP
*/
@NotEmpty(message = "IP 不能为空")
private String createIp;
/**
* 业务线的订单编号
*/
@NotEmpty(message = "订单号不能为空")
private String orderId;
/**
* 支付渠道
*/
@NotNull(message = "支付渠道")
private Integer payChannel;
public String getAppId() {
return appId;
}
public PayTransactionSubmitDTO setAppId(String appId) {
this.appId = appId;
return this;
}
public String getCreateIp() {
return createIp;
}
public PayTransactionSubmitDTO setCreateIp(String createIp) {
this.createIp = createIp;
return this;
}
public String getOrderId() {
return orderId;
}
public PayTransactionSubmitDTO setOrderId(String orderId) {
this.orderId = orderId;
return this;
}
public Integer getPayChannel() {
return payChannel;
}
public PayTransactionSubmitDTO setPayChannel(Integer payChannel) {
this.payChannel = payChannel;
return this;
}
}

View File

@ -60,6 +60,13 @@
<version>27.0.1-jre</version>
</dependency>
<dependency>
<groupId>Pingplusplus</groupId>
<artifactId>pingpp-java</artifactId>
<version>2.2.4</version>
<type>jar</type>
</dependency>
</dependencies>
<build>
@ -85,4 +92,15 @@
</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,16 @@
package cn.iocoder.mall.pay.client;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.pay.dataobject.PayTransactionDO;
import cn.iocoder.mall.pay.dataobject.PayTransactionExtensionDO;
import java.util.Map;
public abstract class AbstractPaySDK {
// extra 属性用于支持不同支付平台的拓展字段例如说微信公众号支付需要多传递一个 openid
public abstract CommonResult<String> submitTransaction(PayTransactionDO transaction,
PayTransactionExtensionDO transactionExtension,
Map<String, Object> extra);
}

View File

@ -0,0 +1,24 @@
package cn.iocoder.mall.pay.client;
import cn.iocoder.mall.pay.api.constant.PayChannelEnum;
import java.util.HashMap;
import java.util.Map;
public class PaySDKFactory {
private static Map<Integer, AbstractPaySDK> SDKS = new HashMap<>();
static {
SDKS.put(PayChannelEnum.PINGXX.getId(), new PingxxPaySDK());
}
public static AbstractPaySDK getSDK(Integer payChannel) {
AbstractPaySDK sdk = SDKS.get(payChannel);
if (sdk == null) {
throw new NullPointerException("找不到合适的 PaySDK " + payChannel);
}
return sdk;
}
}

View File

@ -0,0 +1,80 @@
package cn.iocoder.mall.pay.client;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.pay.dataobject.PayTransactionDO;
import cn.iocoder.mall.pay.dataobject.PayTransactionExtensionDO;
import com.google.common.collect.ImmutableMap;
import com.pingplusplus.Pingpp;
import com.pingplusplus.exception.*;
import com.pingplusplus.model.Charge;
import java.util.HashMap;
import java.util.Map;
// TODO 代码略乱后面重构下
public class PingxxPaySDK extends AbstractPaySDK {
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 e) {
e.printStackTrace();
} catch (InvalidRequestException e) {
e.printStackTrace();
} catch (APIConnectionException e) {
e.printStackTrace();
} catch (APIException e) {
e.printStackTrace();
} catch (ChannelException e) {
e.printStackTrace();
} catch (RateLimitException e) {
e.printStackTrace();
}
return null;
}
private Map<String, Object> createChargeRequest(PayTransactionDO transaction, PayTransactionExtensionDO transactionExtension, Map<String, Object> extra) {
// 计算支付渠道和支付额外参数
String channel = "wx_pub"; // 因为 ping++ 是用来做模拟支付的渠道所以这里强制就选择了 wx_pub 微信公众号支付
extra = new HashMap<>(); // TODO 临时
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;
}
public static void main(String[] args) {
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 PingxxPaySDK().submitTransaction(transaction, extension, null);
}
}

View File

@ -2,7 +2,9 @@ package cn.iocoder.mall.pay.convert;
import cn.iocoder.mall.pay.api.bo.PayTransactionBO;
import cn.iocoder.mall.pay.api.dto.PayTransactionCreateDTO;
import cn.iocoder.mall.pay.api.dto.PayTransactionSubmitDTO;
import cn.iocoder.mall.pay.dataobject.PayTransactionDO;
import cn.iocoder.mall.pay.dataobject.PayTransactionExtensionDO;
import org.mapstruct.Mapper;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
@ -18,4 +20,7 @@ public interface PayTransactionConvert {
@Mappings({})
PayTransactionBO convert(PayTransactionDO payTransactionDO);
@Mappings({})
PayTransactionExtensionDO convert(PayTransactionSubmitDTO payTransactionSubmitDTO);
}

View File

@ -0,0 +1,11 @@
package cn.iocoder.mall.pay.dao;
import cn.iocoder.mall.pay.dataobject.PayTransactionExtensionDO;
import org.springframework.stereotype.Repository;
@Repository
public interface PayTransactionExtensionMapper {
void insert(PayTransactionExtensionDO payTransactionExtensionDO);
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.mall.pay.dao;
import cn.iocoder.mall.pay.dataobject.PayTransactionDO;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Repository
@ -8,4 +9,7 @@ public interface PayTransactionMapper {
void insert(PayTransactionDO entity);
PayTransactionDO selectByAppIdAndOrderId(@Param("appId") String appId,
@Param("orderId") String orderId);
}

View File

@ -1,24 +1,43 @@
package cn.iocoder.mall.pay.service;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.pay.api.PayTransactionService;
import cn.iocoder.mall.pay.api.bo.PayTransactionBO;
import cn.iocoder.mall.pay.api.bo.PayTransactionSubmitBO;
import cn.iocoder.mall.pay.api.constant.PayErrorCodeEnum;
import cn.iocoder.mall.pay.api.constant.PayTransactionStatusEnum;
import cn.iocoder.mall.pay.api.dto.PayTransactionCreateDTO;
import cn.iocoder.mall.pay.api.dto.PayTransactionSubmitDTO;
import cn.iocoder.mall.pay.client.PaySDKFactory;
import cn.iocoder.mall.pay.convert.PayTransactionConvert;
import cn.iocoder.mall.pay.dao.PayTransactionExtensionMapper;
import cn.iocoder.mall.pay.dao.PayTransactionMapper;
import cn.iocoder.mall.pay.dataobject.PayAppDO;
import cn.iocoder.mall.pay.dataobject.PayTransactionDO;
import cn.iocoder.mall.pay.dataobject.PayTransactionExtensionDO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
@com.alibaba.dubbo.config.annotation.Service(validation = "true")
public class PayServiceImpl implements PayTransactionService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private PayTransactionMapper payTransactionMapper;
@Autowired
private PayTransactionExtensionMapper payTransactionExtensionMapper;
@Autowired
private PayAppServiceImpl payAppService;
@Override
@SuppressWarnings("Duplicates")
public CommonResult<PayTransactionBO> createTransaction(PayTransactionCreateDTO payTransactionCreateDTO) {
// 校验 App
CommonResult<PayAppDO> appResult = payAppService.validPayApp(payTransactionCreateDTO.getAppId());
@ -26,12 +45,57 @@ public class PayServiceImpl implements PayTransactionService {
return CommonResult.error(appResult);
}
// 插入 PayTransactionDO
return null;
PayTransactionDO payTransaction = payTransactionMapper.selectByAppIdAndOrderId(
payTransactionCreateDTO.getAppId(), payTransactionCreateDTO.getOrderId());
if (payTransaction != null) {
logger.warn("[createTransaction][appId({}) orderId({}) exists]", payTransactionCreateDTO.getAppId(),
payTransactionCreateDTO.getOrderId()); // 理论来说不会出现这个情况
// TODO 芋艿 可能要考虑更新订单例如说业务线订单可以修改价格
} else {
payTransaction = PayTransactionConvert.INSTANCE.convert(payTransactionCreateDTO);
payTransaction.setStatus(PayTransactionStatusEnum.WAITTING.getValue())
.setNotifyUrl(appResult.getData().getNotifyUrl());
payTransaction.setCreateTime(new Date());
payTransactionMapper.insert(payTransaction);
}
// 返回成功
return CommonResult.success(PayTransactionConvert.INSTANCE.convert(payTransaction));
}
@Override
public CommonResult submitTransaction() {
return null;
@SuppressWarnings("Duplicates")
public CommonResult<PayTransactionSubmitBO> submitTransaction(PayTransactionSubmitDTO payTransactionSubmitDTO) {
// TODO 校验支付渠道是否有效
// 校验 App 是否有效
CommonResult<PayAppDO> appResult = payAppService.validPayApp(payTransactionSubmitDTO.getAppId());
if (appResult.isError()) {
return CommonResult.error(appResult);
}
// 获得 PayTransactionDO 并校验其是否存在
PayTransactionDO payTransaction = payTransactionMapper.selectByAppIdAndOrderId(
payTransactionSubmitDTO.getAppId(), payTransactionSubmitDTO.getOrderId());
if (payTransaction == null) { // 是否存在
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_NOT_FOUND.getCode());
}
if (PayTransactionStatusEnum.WAITTING.getValue().equals(payTransaction.getStatus())) { // 校验状态
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_STATUS_IS_NOT_WAITING.getCode());
}
// 插入 PayTransactionExtensionDO
PayTransactionExtensionDO payTransactionExtensionDO = PayTransactionConvert.INSTANCE.convert(payTransactionSubmitDTO)
.setTransactionId(payTransaction.getId())
.setTransactionCode("TODO")
.setStatus(PayTransactionStatusEnum.WAITTING.getValue());
payTransactionExtensionMapper.insert(payTransactionExtensionDO);
// 调用三方接口
CommonResult<String> invokeResult = PaySDKFactory.getSDK(payTransactionSubmitDTO.getPayChannel()).submitTransaction(payTransaction, payTransactionExtensionDO, null); // TODO 暂时传入 extra = null
if (invokeResult.isError()) {
return CommonResult.error(invokeResult);
}
// TODO 轮询三方接口是否已经支付的任务
// 返回成功
PayTransactionSubmitBO payTransactionSubmitBO = new PayTransactionSubmitBO()
.setId(payTransactionExtensionDO.getId()).setInvokeResponse(invokeResult.getData());
return CommonResult.success(payTransactionSubmitBO);
}
@Override
@ -39,6 +103,4 @@ public class PayServiceImpl implements PayTransactionService {
return null;
}
}