Merge remote-tracking branch 'origin/master'

pull/1/head
sin 2019-03-13 10:18:48 +08:00
commit c772da6716
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;
}
}