支付模块,调通对 ping++ 的调用,以实现模拟支付。

当然,整体代码还是有点乱,后面花点时间重构下~

现在,缺一个异步 MQ 通知业务方订单支付成功
This commit is contained in:
YunaiV 2019-03-13 20:27:53 +08:00
parent c772da6716
commit 7d423c8ed2
24 changed files with 579 additions and 16 deletions

View File

@ -55,6 +55,12 @@
<!--<version>2.3</version>--> <!--<version>2.3</version>-->
<!--</dependency>--> <!--</dependency>-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.56</version>
</dependency>
</dependencies> </dependencies>

View File

@ -0,0 +1,20 @@
package cn.iocoder.common.framework.util;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateUtil {
/**
* @param date 时间若为空则返回空串
* @param pattern 时间格式化
* @return 格式化后的时间字符串.
*/
public static String format(Date date, String pattern) {
if (date == null) {
return "";
}
return new SimpleDateFormat(pattern).format(date);
}
}

View File

@ -0,0 +1,33 @@
package cn.iocoder.common.framework.util;
import java.util.Random;
public class MathUtil {
/**
* 随机对象
*/
private static final Random RANDOM = new Random(); // TODO 后续优化
/**
* 随机[min, max]范围内的数字
*
* @param min 随机开始
* @param max 随机结束
* @return 数字
*/
public static int random(int min, int max) {
if (min == max) {
return min;
}
if (min > max) {
int temp = min;
min = max;
max = temp;
}
// 随即开始
int diff = max - min + 1;
return RANDOM.nextInt(diff) + min;
}
}

View File

@ -31,7 +31,7 @@ public class PayDemoController {
// 调用支付服务创建交易订单 // 调用支付服务创建交易订单
PayTransactionCreateDTO payTransactionCreateDTO = new PayTransactionCreateDTO() PayTransactionCreateDTO payTransactionCreateDTO = new PayTransactionCreateDTO()
.setAppId("1024") .setAppId("POd4RC6a")
.setCreateIp(HttpUtil.getIp(request)) .setCreateIp(HttpUtil.getIp(request))
.setOrderId("1") .setOrderId("1")
.setOrderSubject("商品名" ) .setOrderSubject("商品名" )
@ -43,4 +43,55 @@ public class PayDemoController {
Assert.isTrue(result.isSuccess(), "一定会成功的"); Assert.isTrue(result.isSuccess(), "一定会成功的");
} }
@PostMapping("/pingxx")
public String pingxx() {
return "{\n" +
" \"id\": \"ch_n5COmHGG8iX5TWf5qDynvTaP\",\n" +
" \"object\": \"charge\",\n" +
" \"created\": 1552445643,\n" +
" \"livemode\": false,\n" +
" \"paid\": false,\n" +
" \"refunded\": false,\n" +
" \"reversed\": false,\n" +
" \"app\": \"app_aTyfXDjrvzDSbLuz\",\n" +
" \"channel\": \"wx_pub\",\n" +
" \"order_no\": \"1552445643093\",\n" +
" \"client_ip\": \"127.0.0.1\",\n" +
" \"amount\": 1,\n" +
" \"amount_settle\": 1,\n" +
" \"currency\": \"cny\",\n" +
" \"subject\": \"测试商品\",\n" +
" \"body\": \"测试描述\",\n" +
" \"time_paid\": null,\n" +
" \"time_expire\": 1552452843,\n" +
" \"time_settle\": null,\n" +
" \"transaction_no\": null,\n" +
" \"refunds\": {\n" +
" \"object\": \"list\",\n" +
" \"url\": \"/v1/charges/ch_n5COmHGG8iX5TWf5qDynvTaP/refunds\",\n" +
" \"has_more\": false,\n" +
" \"data\": []\n" +
" },\n" +
" \"amount_refunded\": 0,\n" +
" \"failure_code\": null,\n" +
" \"failure_msg\": null,\n" +
" \"metadata\": {},\n" +
" \"credential\": {\n" +
" \"object\": \"credential\",\n" +
" \"wx_pub\": {\n" +
" \"appId\": \"wxytom5krtuf54qjff\",\n" +
" \"timeStamp\": \"1552445643\",\n" +
" \"nonceStr\": \"5cc0206f78d8bf931980f5132d5ce394\",\n" +
" \"package\": \"prepay_id=1101000000190313rx9y5oahkkcsm5gg\",\n" +
" \"signType\": \"MD5\",\n" +
" \"paySign\": \"9F6E80E89439575B8414FA56ADB07228\"\n" +
" }\n" +
" },\n" +
" \"extra\": {\n" +
" \"open_id\": \"just_for_test\"\n" +
" },\n" +
" \"description\": null\n" +
"}";
}
} }

View File

@ -1,12 +1,22 @@
package cn.iocoder.mall.pay.application.controller.users; package cn.iocoder.mall.pay.application.controller.users;
import cn.iocoder.common.framework.util.HttpUtil;
import cn.iocoder.common.framework.vo.CommonResult; import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.pay.api.PayTransactionService; import cn.iocoder.mall.pay.api.PayTransactionService;
import cn.iocoder.mall.pay.api.bo.PayTransactionSubmitBO;
import cn.iocoder.mall.pay.api.constant.PayChannelEnum;
import cn.iocoder.mall.pay.api.dto.PayTransactionSubmitDTO;
import com.alibaba.dubbo.config.annotation.Reference; import com.alibaba.dubbo.config.annotation.Reference;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
@RestController @RestController
@RequestMapping("users/transaction") // TODO 芋艿理论来说是用户无关的这里先酱紫先~ @RequestMapping("users/transaction") // TODO 芋艿理论来说是用户无关的这里先酱紫先~
public class PayTransactionController { public class PayTransactionController {
@ -15,8 +25,35 @@ public class PayTransactionController {
private PayTransactionService payService; private PayTransactionService payService;
@PostMapping("/submit") // TODO api 注释 @PostMapping("/submit") // TODO api 注释
public CommonResult submit() { // TODO 1. params 2. result // TODO result 后面改下
return null; public CommonResult<PayTransactionSubmitBO> submit(HttpServletRequest request,
@RequestParam("appId") String appId,
@RequestParam("orderId") String orderId,
@RequestParam("payChannel") Integer payChannel) {
PayTransactionSubmitDTO payTransactionSubmitDTO = new PayTransactionSubmitDTO()
.setAppId(appId).setOrderId(orderId).setPayChannel(payChannel)
.setCreateIp(HttpUtil.getIp(request));
// 提交支付提交
return payService.submitTransaction(payTransactionSubmitDTO);
}
@PostMapping(value = "pingxx_pay_success", consumes = MediaType.APPLICATION_JSON_VALUE)
// @GetMapping(value = "pingxx_pay_success")
public String pingxxSuccess(HttpServletRequest request) throws IOException {
// 读取 webhook
StringBuilder sb = new StringBuilder();
try (BufferedReader reader = request.getReader()) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
}
// JSONObject bodyObj = JSON.parseObject(sb.toString());
// bodyObj.put("webhookId", bodyObj.remove("id"));
// String body = bodyObj.toString();
payService.updateTransactionPaySuccess(PayChannelEnum.PINGXX.getId(), sb.toString());
return "";
} }
} }

View File

@ -0,0 +1,32 @@
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ReferenceConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.rpc.service.GenericService;
public class DubboGenericInvokerTest {
public static void main(String[] args) {
ApplicationConfig application = new ApplicationConfig();
application.setName("api-generic-consumer");
RegistryConfig registry = new RegistryConfig();
registry.setAddress("zookeeper://127.0.0.1:2181");
application.setRegistry(registry);
ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
// 弱类型接口名
reference.setInterface("cn.iocoder.mall.pay.api.PayDemoService");
// 声明为泛化接口
reference.setGeneric(true);
reference.setApplication(application);
// 用com.alibaba.dubbo.rpc.service.GenericService可以替代所有接口引用
GenericService genericService = reference.get();
String name = (String) genericService.$invoke("updatePaySuccess", new String[]{String.class.getName()}, new Object[]{"1"});
System.out.println(name);
}
}

View File

@ -0,0 +1,7 @@
package cn.iocoder.mall.pay.api;
public interface PayDemoService {
String updatePaySuccess(String orderId);
}

View File

@ -12,6 +12,18 @@ public interface PayTransactionService {
CommonResult<PayTransactionSubmitBO> submitTransaction(PayTransactionSubmitDTO payTransactionSubmitDTO); CommonResult<PayTransactionSubmitBO> submitTransaction(PayTransactionSubmitDTO payTransactionSubmitDTO);
/**
* 更新交易支付成功
*
* 该接口用于不同支付平台支付成功后回调该接口
*
* @param payChannel 支付渠道
* @param params 回调参数
* 因为不同平台能够提供的参数不同所以使用 String 类型统一接收然后在使用不同的 AbstractPaySDK 进行处理
* @return 是否支付成功
*/
CommonResult<Boolean> updateTransactionPaySuccess(Integer payChannel, String params);
CommonResult cancelTransaction(); // TODO 1. params 2. result CommonResult cancelTransaction(); // TODO 1. params 2. result
} }

View File

@ -15,6 +15,8 @@ public enum PayErrorCodeEnum {
PAY_TRANSACTION_NOT_FOUND(100401000, "支付交易单不存在"), PAY_TRANSACTION_NOT_FOUND(100401000, "支付交易单不存在"),
PAY_TRANSACTION_STATUS_IS_NOT_WAITING(100401001, "支付交易单不处于待支付"), PAY_TRANSACTION_STATUS_IS_NOT_WAITING(100401001, "支付交易单不处于待支付"),
PAY_TRANSACTION_EXTENSION_NOT_FOUND(100401002, "支付交易拓展单不存在"),
PAY_TRANSACTION_EXTENSION_STATUS_IS_NOT_WAITING(100401003, "支付交易拓展单不处于待支付"),
; ;
private final int code; private final int code;

View File

@ -19,7 +19,6 @@
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId> <artifactId>dubbo</artifactId>
<scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>cn.iocoder.mall</groupId> <groupId>cn.iocoder.mall</groupId>

View File

@ -13,4 +13,7 @@ public abstract class AbstractPaySDK {
PayTransactionExtensionDO transactionExtension, PayTransactionExtensionDO transactionExtension,
Map<String, Object> extra); Map<String, Object> extra);
// TODO 芋艿理论来说不会出现解析失败的情况先返回这个参数列等后面封装支付宝和微信支付的时候在看看
public abstract CommonResult<TransactionPaySuccessBO> parseTransactionPaySuccessParams(String params);
} }

View File

@ -3,11 +3,14 @@ package cn.iocoder.mall.pay.client;
import cn.iocoder.common.framework.vo.CommonResult; import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.pay.dataobject.PayTransactionDO; import cn.iocoder.mall.pay.dataobject.PayTransactionDO;
import cn.iocoder.mall.pay.dataobject.PayTransactionExtensionDO; import cn.iocoder.mall.pay.dataobject.PayTransactionExtensionDO;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.pingplusplus.Pingpp; import com.pingplusplus.Pingpp;
import com.pingplusplus.exception.*; import com.pingplusplus.exception.*;
import com.pingplusplus.model.Charge; import com.pingplusplus.model.Charge;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -25,7 +28,7 @@ public class PingxxPaySDK extends AbstractPaySDK {
// 请求ping++ // 请求ping++
try { try {
Charge charge = Charge.create(reqObj); Charge charge = Charge.create(reqObj);
// System.out.println(charge.toString()); System.out.println(charge.toString());
return CommonResult.success(charge.toString()); return CommonResult.success(charge.toString());
} catch (AuthenticationException e) { } catch (AuthenticationException e) {
e.printStackTrace(); e.printStackTrace();
@ -43,6 +46,16 @@ public class PingxxPaySDK extends AbstractPaySDK {
return null; return null;
} }
@Override
public CommonResult<TransactionPaySuccessBO> parseTransactionPaySuccessParams(String params) {
JSONObject paramsObj = JSON.parseObject(params);
JSONObject chargeObj = paramsObj.getJSONObject("data").getJSONObject("object");
TransactionPaySuccessBO transactionPaySuccessBO = new TransactionPaySuccessBO()
.setTransactionCode(chargeObj.getString("order_no"))
.setPaymentTime(new Date(chargeObj.getLong("time_paid") * 1000))
.setTradeNo(chargeObj.getString("transaction_no"));
return CommonResult.success(transactionPaySuccessBO);
}
private Map<String, Object> createChargeRequest(PayTransactionDO transaction, PayTransactionExtensionDO transactionExtension, Map<String, Object> extra) { private Map<String, Object> createChargeRequest(PayTransactionDO transaction, PayTransactionExtensionDO transactionExtension, Map<String, Object> extra) {
// 计算支付渠道和支付额外参数 // 计算支付渠道和支付额外参数

View File

@ -0,0 +1,52 @@
package cn.iocoder.mall.pay.client;
import java.util.Date;
/**
* 交易支付成功结果
*/
public class TransactionPaySuccessBO {
/**
* 生成传输给第三方的订单号
*
* 唯一索引
*/
private String transactionCode;
/**
* 第三方的流水号
*/
private String tradeNo;
/**
* 第三方支付成功的时间
*/
private Date paymentTime;
public String getTransactionCode() {
return transactionCode;
}
public TransactionPaySuccessBO setTransactionCode(String transactionCode) {
this.transactionCode = transactionCode;
return this;
}
public String getTradeNo() {
return tradeNo;
}
public TransactionPaySuccessBO setTradeNo(String tradeNo) {
this.tradeNo = tradeNo;
return this;
}
public Date getPaymentTime() {
return paymentTime;
}
public TransactionPaySuccessBO setPaymentTime(Date paymentTime) {
this.paymentTime = paymentTime;
return this;
}
}

View File

@ -1,7 +1,7 @@
package cn.iocoder.mall.pay.config; package cn.iocoder.mall.pay.config;
import cn.iocoder.common.framework.util.ServiceExceptionUtil; import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.mall.admin.api.constant.AdminErrorCodeEnum; import cn.iocoder.mall.pay.api.constant.PayErrorCodeEnum;
import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
@ -18,7 +18,7 @@ public class ServiceExceptionConfiguration {
// } catch (IOException e) { // } catch (IOException e) {
// throw new RuntimeException(e); // throw new RuntimeException(e);
// } // }
for (AdminErrorCodeEnum item : AdminErrorCodeEnum.values()) { for (PayErrorCodeEnum item : PayErrorCodeEnum.values()) {
ServiceExceptionUtil.put(item.getCode(), item.getMessage()); ServiceExceptionUtil.put(item.getCode(), item.getMessage());
} }
} }

View File

@ -1,11 +1,17 @@
package cn.iocoder.mall.pay.dao; package cn.iocoder.mall.pay.dao;
import cn.iocoder.mall.pay.dataobject.PayTransactionExtensionDO; import cn.iocoder.mall.pay.dataobject.PayTransactionExtensionDO;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@Repository @Repository
public interface PayTransactionExtensionMapper { public interface PayTransactionExtensionMapper {
void insert(PayTransactionExtensionDO payTransactionExtensionDO); void insert(PayTransactionExtensionDO entity);
int update(@Param("entity") PayTransactionExtensionDO entity,
@Param("whereStatus") Integer whereStatus);
PayTransactionExtensionDO selectByTransactionCode(@Param("transactionCode") String transactionCode);
} }

View File

@ -9,7 +9,12 @@ public interface PayTransactionMapper {
void insert(PayTransactionDO entity); void insert(PayTransactionDO entity);
int update(@Param("entity") PayTransactionDO entity,
@Param("whereStatus") Integer whereStatus);
PayTransactionDO selectByAppIdAndOrderId(@Param("appId") String appId, PayTransactionDO selectByAppIdAndOrderId(@Param("appId") String appId,
@Param("orderId") String orderId); @Param("orderId") String orderId);
PayTransactionDO selectById(@Param("id") Integer appId);
} }

View File

@ -0,0 +1,13 @@
package cn.iocoder.mall.pay.dao;
import cn.iocoder.mall.pay.dataobject.PayTransactionNotifyTaskDO;
import org.springframework.stereotype.Repository;
@Repository
public interface PayTransactionNotifyTaskMapper {
void insert(PayTransactionNotifyTaskDO entity);
int update(PayTransactionNotifyTaskDO entity);
}

View File

@ -51,9 +51,10 @@ public class PayTransactionNotifyTaskDO extends BaseDO {
* 最大可通知次数 * 最大可通知次数
*/ */
private Integer maxNotifyTimes; private Integer maxNotifyTimes;
/**
// TODO notify url * 通知地址
*/
private String notifyUrl;
public Integer getTransactionId() { public Integer getTransactionId() {
return transactionId; return transactionId;
@ -136,4 +137,12 @@ public class PayTransactionNotifyTaskDO extends BaseDO {
return this; return this;
} }
public String getNotifyUrl() {
return notifyUrl;
}
public PayTransactionNotifyTaskDO setNotifyUrl(String notifyUrl) {
this.notifyUrl = notifyUrl;
return this;
}
} }

View File

@ -0,0 +1,15 @@
package cn.iocoder.mall.pay.service;
import cn.iocoder.mall.pay.api.PayDemoService;
import org.springframework.stereotype.Service;
@Service
@com.alibaba.dubbo.config.annotation.Service
public class PayDemoServiceImpl implements PayDemoService {
@Override
public String updatePaySuccess(String orderId) {
return "你好呀";
}
}

View File

@ -1,25 +1,33 @@
package cn.iocoder.mall.pay.service; package cn.iocoder.mall.pay.service;
import cn.iocoder.common.framework.util.DateUtil;
import cn.iocoder.common.framework.util.MathUtil;
import cn.iocoder.common.framework.util.ServiceExceptionUtil; import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.common.framework.vo.CommonResult; import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.pay.api.PayTransactionService; import cn.iocoder.mall.pay.api.PayTransactionService;
import cn.iocoder.mall.pay.api.bo.PayTransactionBO; import cn.iocoder.mall.pay.api.bo.PayTransactionBO;
import cn.iocoder.mall.pay.api.bo.PayTransactionSubmitBO; import cn.iocoder.mall.pay.api.bo.PayTransactionSubmitBO;
import cn.iocoder.mall.pay.api.constant.PayErrorCodeEnum; import cn.iocoder.mall.pay.api.constant.PayErrorCodeEnum;
import cn.iocoder.mall.pay.api.constant.PayTransactionNotifyStatusEnum;
import cn.iocoder.mall.pay.api.constant.PayTransactionStatusEnum; import cn.iocoder.mall.pay.api.constant.PayTransactionStatusEnum;
import cn.iocoder.mall.pay.api.dto.PayTransactionCreateDTO; import cn.iocoder.mall.pay.api.dto.PayTransactionCreateDTO;
import cn.iocoder.mall.pay.api.dto.PayTransactionSubmitDTO; import cn.iocoder.mall.pay.api.dto.PayTransactionSubmitDTO;
import cn.iocoder.mall.pay.client.AbstractPaySDK;
import cn.iocoder.mall.pay.client.PaySDKFactory; import cn.iocoder.mall.pay.client.PaySDKFactory;
import cn.iocoder.mall.pay.client.TransactionPaySuccessBO;
import cn.iocoder.mall.pay.convert.PayTransactionConvert; import cn.iocoder.mall.pay.convert.PayTransactionConvert;
import cn.iocoder.mall.pay.dao.PayTransactionExtensionMapper; import cn.iocoder.mall.pay.dao.PayTransactionExtensionMapper;
import cn.iocoder.mall.pay.dao.PayTransactionMapper; import cn.iocoder.mall.pay.dao.PayTransactionMapper;
import cn.iocoder.mall.pay.dao.PayTransactionNotifyTaskMapper;
import cn.iocoder.mall.pay.dataobject.PayAppDO; import cn.iocoder.mall.pay.dataobject.PayAppDO;
import cn.iocoder.mall.pay.dataobject.PayTransactionDO; import cn.iocoder.mall.pay.dataobject.PayTransactionDO;
import cn.iocoder.mall.pay.dataobject.PayTransactionExtensionDO; import cn.iocoder.mall.pay.dataobject.PayTransactionExtensionDO;
import cn.iocoder.mall.pay.dataobject.PayTransactionNotifyTaskDO;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date; import java.util.Date;
@ -34,6 +42,8 @@ public class PayServiceImpl implements PayTransactionService {
@Autowired @Autowired
private PayTransactionExtensionMapper payTransactionExtensionMapper; private PayTransactionExtensionMapper payTransactionExtensionMapper;
@Autowired @Autowired
private PayTransactionNotifyTaskMapper payTransactionNotifyTaskMapper;
@Autowired
private PayAppServiceImpl payAppService; private PayAppServiceImpl payAppService;
@Override @Override
@ -77,17 +87,18 @@ public class PayServiceImpl implements PayTransactionService {
if (payTransaction == null) { // 是否存在 if (payTransaction == null) { // 是否存在
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_NOT_FOUND.getCode()); return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_NOT_FOUND.getCode());
} }
if (PayTransactionStatusEnum.WAITTING.getValue().equals(payTransaction.getStatus())) { // 校验状态 if (!PayTransactionStatusEnum.WAITTING.getValue().equals(payTransaction.getStatus())) { // 校验状态必须是待支付
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_STATUS_IS_NOT_WAITING.getCode()); return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_STATUS_IS_NOT_WAITING.getCode());
} }
// 插入 PayTransactionExtensionDO // 插入 PayTransactionExtensionDO
PayTransactionExtensionDO payTransactionExtensionDO = PayTransactionConvert.INSTANCE.convert(payTransactionSubmitDTO) PayTransactionExtensionDO payTransactionExtensionDO = PayTransactionConvert.INSTANCE.convert(payTransactionSubmitDTO)
.setTransactionId(payTransaction.getId()) .setTransactionId(payTransaction.getId())
.setTransactionCode("TODO") .setTransactionCode(generateTransactionCode())
.setStatus(PayTransactionStatusEnum.WAITTING.getValue()); .setStatus(PayTransactionStatusEnum.WAITTING.getValue());
payTransactionExtensionMapper.insert(payTransactionExtensionDO); payTransactionExtensionMapper.insert(payTransactionExtensionDO);
// 调用三方接口 // 调用三方接口
CommonResult<String> invokeResult = PaySDKFactory.getSDK(payTransactionSubmitDTO.getPayChannel()).submitTransaction(payTransaction, payTransactionExtensionDO, null); // TODO 暂时传入 extra = null AbstractPaySDK paySDK = PaySDKFactory.getSDK(payTransactionSubmitDTO.getPayChannel());
CommonResult<String> invokeResult = paySDK.submitTransaction(payTransaction, payTransactionExtensionDO, null); // TODO 暂时传入 extra = null
if (invokeResult.isError()) { if (invokeResult.isError()) {
return CommonResult.error(invokeResult); return CommonResult.error(invokeResult);
} }
@ -99,8 +110,88 @@ public class PayServiceImpl implements PayTransactionService {
} }
@Override @Override
@Transactional
public CommonResult<Boolean> updateTransactionPaySuccess(Integer payChannel, String params) {
// TODO 芋艿记录回调日志
// 解析传入的参数 TransactionPaySuccessBO 对象
AbstractPaySDK paySDK = PaySDKFactory.getSDK(payChannel);
CommonResult<TransactionPaySuccessBO> paySuccessResult = paySDK.parseTransactionPaySuccessParams(params);
if (paySuccessResult.isError()) {
return CommonResult.error(paySuccessResult);
}
// TODO 芋艿先最严格的校验即使调用方重复调用实际哪个订单已经被重复回调的支付也返回 false 也没问题因为实际已经回调成功了
// 1.1 查询 PayTransactionExtensionDO
PayTransactionExtensionDO payTransactionExtension = payTransactionExtensionMapper.selectByTransactionCode(paySuccessResult.getData().getTransactionCode());
if (payTransactionExtension == null) {
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_EXTENSION_NOT_FOUND.getCode());
}
if (!PayTransactionStatusEnum.WAITTING.getValue().equals(payTransactionExtension.getStatus())) { // 校验状态必须是待支付
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_EXTENSION_STATUS_IS_NOT_WAITING.getCode());
}
// 1.2 更新 PayTransactionExtensionDO
PayTransactionExtensionDO updatePayTransactionExtension = new PayTransactionExtensionDO()
.setId(payTransactionExtension.getId())
.setStatus(PayTransactionStatusEnum.SUCCESS.getValue())
.setExtensionData(params);
int updateCounts = payTransactionExtensionMapper.update(updatePayTransactionExtension, PayTransactionStatusEnum.WAITTING.getValue());
if (updateCounts == 0) { // 校验状态必须是待支付
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_TRANSACTION_EXTENSION_STATUS_IS_NOT_WAITING.getCode());
}
// 2.1
PayTransactionDO payTransactionDO = payTransactionMapper.selectById(payTransactionExtension.getTransactionId());
if (payTransactionDO == null) {
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_NOT_FOUND.getCode());
}
if (!PayTransactionStatusEnum.WAITTING.getValue().equals(payTransactionDO.getStatus())) { // 校验状态必须是待支付
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_TRANSACTION_STATUS_IS_NOT_WAITING.getCode());
}
// 2.2 更新 PayTransactionDO
PayTransactionDO updatePayTransaction = new PayTransactionDO()
.setId(payTransactionDO.getId())
.setStatus(PayTransactionStatusEnum.SUCCESS.getValue())
.setExtensionId(payTransactionExtension.getId())
.setPayChannel(payChannel)
.setPaymentTime(paySuccessResult.getData().getPaymentTime())
.setNotifyTime(new Date())
.setTradeNo(paySuccessResult.getData().getTradeNo());
updateCounts = payTransactionMapper.update(updatePayTransaction, PayTransactionStatusEnum.WAITTING.getValue());
if (updateCounts == 0) { // 校验状态必须是待支付 TODO 这种类型需要思考下需要返回错误但是又要保证事务回滚
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_TRANSACTION_STATUS_IS_NOT_WAITING.getCode());
}
// 3. 插入
PayTransactionNotifyTaskDO payTransactionNotifyTask = new PayTransactionNotifyTaskDO()
.setTransactionId(payTransactionExtension.getTransactionId()).setTransactionExtensionId(payTransactionExtension.getId())
.setAppId(payTransactionDO.getAppId()).setOrderId(payTransactionDO.getOrderId())
.setStatus(PayTransactionNotifyStatusEnum.WAITING.getValue())
.setNotifyTimes(0).setMaxNotifyTimes(5)
.setNotifyUrl(payTransactionDO.getNotifyUrl());
payTransactionNotifyTaskMapper.insert(payTransactionNotifyTask);
// 返回结果
return CommonResult.success(true);
}
@Override // TODO 芋艿后面去实现
public CommonResult cancelTransaction() { public CommonResult cancelTransaction() {
return null; return null;
} }
private String generateTransactionCode() {
// wx
// 2014
// 10
// 27
// 20
// 09
// 39
// 5522657
// a690389285100
// 目前的算法
// 时间序列年月日时分秒 14
// 纯随机6 TODO 此处估计是会有问题的后续在调整
return DateUtil.format(new Date(), "yyyyMMddHHmmss") + // 时间序列
MathUtil.random(100000, 999999) // 随机为什么是这个范围因为偷懒
;
}
} }

View File

@ -3,7 +3,7 @@
<mapper namespace="cn.iocoder.mall.pay.dao.PayAppMapper"> <mapper namespace="cn.iocoder.mall.pay.dao.PayAppMapper">
<sql id="FIELDS"> <sql id="FIELDS">
id, name, status, create_time id, name, notify_url, status, create_time
</sql> </sql>
<!--<insert id="insert" parameterType="RoleDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">--> <!--<insert id="insert" parameterType="RoleDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">-->
@ -27,7 +27,7 @@
<!--WHERE id = #{id}--> <!--WHERE id = #{id}-->
<!--</update>--> <!--</update>-->
<select id="selectById" parameterType="Integer" resultType="PayAppDO"> <select id="selectById" parameterType="String" resultType="PayAppDO">
SELECT SELECT
<include refid="FIELDS"/> <include refid="FIELDS"/>
FROM app FROM app

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.mall.pay.dao.PayTransactionExtensionMapper">
<sql id="FIELDS">
id, transaction_id, pay_channel, transaction_code, extension_data,
create_ip, status, create_time
</sql>
<insert id="insert" parameterType="PayTransactionExtensionDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
INSERT INTO transaction_extension (
transaction_id, pay_channel, transaction_code, extension_data,
create_ip, status
) VALUES (
#{transactionId}, #{payChannel}, #{transactionCode}, #{extensionData},
#{createIp}, #{status}
)
</insert>
<update id="update">
UPDATE transaction_extension
<set>
<if test="entity.extensionData != null">
, extension_data = #{entity.extensionData}
</if>
<if test="entity.status != null">
, status = #{entity.status}
</if>
</set>
WHERE id = #{entity.id}
<if test="whereStatus != null">
AND status = #{whereStatus}
</if>
</update>
<select id="selectByTransactionCode" parameterType="String" resultType="PayTransactionExtensionDO">
SELECT
<include refid="FIELDS"/>
FROM transaction_extension
WHERE transaction_code = #{transactionCode}
LIMIT 1
</select>
</mapper>

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.mall.pay.dao.PayTransactionMapper">
<sql id="FIELDS">
id, app_id, create_ip, order_id, order_subject,
order_description, order_memo, price, status, expire_time,
finish_time, notify_url, extension_id, pay_channel, payment_time,
notify_time, trade_no, create_time
</sql>
<insert id="insert" parameterType="PayTransactionDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
INSERT INTO transaction (
app_id, create_ip, order_id, order_subject,
order_description, order_memo, price, status, expire_time,
finish_time, notify_url, extension_id, pay_channel, payment_time,
notify_time, trade_no, create_time
) VALUES (
#{appId}, #{createIp}, #{orderId}, #{orderSubject},
#{orderDescription}, #{orderMemo}, #{price}, #{status}, #{expireTime},
#{finishTime}, #{notifyUrl}, #{extensionId}, #{payChannel}, #{paymentTime},
#{notifyTime}, #{tradeNo}, #{createTime}
)
</insert>
<update id="update">
UPDATE transaction
<set>
<if test="entity.status != null">
, status = #{entity.status}
</if>
<if test="entity.extensionId != null">
, extension_id = #{entity.extensionId}
</if>
<if test="entity.payChannel != null">
, pay_channel = #{entity.payChannel}
</if>
<if test="entity.paymentTime != null">
, payment_time = #{entity.paymentTime}
</if>
<if test="entity.notifyTime != null">
, notify_time = #{entity.notifyTime}
</if>
<if test="entity.tradeNo != null">
, trade_no = #{entity.tradeNo}
</if>
</set>
WHERE id = #{entity.id}
<if test="whereStatus != null">
AND status = #{whereStatus}
</if>
</update>
<select id="selectByAppIdAndOrderId" resultType="PayTransactionDO">
SELECT
<include refid="FIELDS"/>
FROM transaction
WHERE app_id = #{appId}
AND order_id = #{orderId}
</select>
<select id="selectById" parameterType="Integer" resultType="PayTransactionDO">
SELECT
<include refid="FIELDS"/>
FROM transaction
WHERE id = #{id}
</select>
</mapper>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.mall.pay.dao.PayTransactionNotifyTaskMapper">
<sql id="FIELDS">
id, transaction_id, transaction_extension_id, app_id, order_id,
status, last_Notify_time, notify_times, max_notify_times, create_time
</sql>
<insert id="insert" parameterType="PayTransactionNotifyTaskDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
INSERT INTO transaction_notify_task (
transaction_id, transaction_extension_id, app_id, order_id,
status, last_notify_time, notify_times, max_notify_times
) VALUES (
#{transactionId}, #{transactionExtensionId}, #{appId}, #{orderId},
#{status}, #{lastNotifyTime}, #{notifyTimes}, #{maxNotifyTimes}
)
</insert>
<update id="update" parameterType="PayTransactionNotifyTaskDO">
UPDATE transaction_notify_task
<set>
<if test="status != null">
, status = #{status}
</if>
<if test="lastNotifyTime != null">
, last_notify_time = #{lastNotifyTime}
</if>
<if test="notifyTimes != null">
, notify_times = #{notifyTimes}
</if>
</set>
WHERE id = #{id}
</update>
<!--<select id="selectByTransactionCode" parameterType="String" resultType="PayTransactionExtensionDO">-->
<!--SELECT-->
<!--<include refid="FIELDS"/>-->
<!--FROM transaction_extension-->
<!--WHERE transaction_code = #{transactionCode}-->
<!--LIMIT 1-->
<!--</select>-->
</mapper>