Commit 65220cfeeffdbd45574f880099a0d53a8863acf9

Authored by huanggang
1 parent 07f54ce2

upgrade after testing

Showing 33 changed files with 575 additions and 127 deletions
cashier-boss/src/main/java/com/diligrp/cashier/boss/Constants.java
... ... @@ -3,6 +3,8 @@ package com.diligrp.cashier.boss;
3 3 public final class Constants {
4 4 // 商户信息缓存Key
5 5 public static final String MERCHANT_REDIS_KEY = "cashier:merchant:%s";
  6 + // 商户信息过期时长-单位秒 - 1个小时
  7 + public static final int MERCHANT_TIMEOUT_SECONDS = 60 * 60;
6 8 // TOKEN信息缓存Key
7 9 public static final String TOKEN_REDIS_KEY = "cashier:token:%s";
8 10 // TOKEN参数名称
... ... @@ -11,8 +13,8 @@ public final class Constants {
11 13 public static final String TOKEN_SIGN_ALGORITHM = "HmacSHA256";
12 14 // TOKEN的签名长度
13 15 public static final int TOKEN_SIGN_LENGTH = 8;
14   - // TOKEN过期时长,单位秒
15   - public static final long TOKEN_TIMEOUT_SECONDS = 60;
  16 + // TOKEN过期时长,单位秒 - 两分钟
  17 + public static final long TOKEN_TIMEOUT_SECONDS = 120;
16 18  
17 19 public final static String CONTENT_TYPE = "application/json;charset=UTF-8";
18 20  
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/controller/CashierDeskController.java
... ... @@ -35,6 +35,7 @@ public class CashierDeskController {
35 35 AssertUtils.notNull(request.getAmount(), "amount missed");
36 36 AssertUtils.isTrue(request.getAmount() > 0, "Invalid amount");
37 37 AssertUtils.notEmpty(request.getOutTradeNo(), "outTradeNo missed");
  38 + AssertUtils.notEmpty(request.getRedirectUrl(), "redirectUrl missed");
38 39  
39 40 CashierOrder cashierOrder = CashierOrderConverter.INSTANCE.convert(request);
40 41 Merchant merchant = merchantService.loadCashierMerchant(request.getMchId());
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/domain/CashierOrderDTO.java
... ... @@ -17,6 +17,8 @@ public class CashierOrderDTO {
17 17 private String outTradeNo;
18 18 // 回调地址
19 19 private String notifyUrl;
  20 + // 页面回调地址
  21 + private String redirectUrl;
20 22 // 交易描述
21 23 private String description;
22 24 // 附加数据
... ... @@ -86,6 +88,14 @@ public class CashierOrderDTO {
86 88 this.notifyUrl = notifyUrl;
87 89 }
88 90  
  91 + public String getRedirectUrl() {
  92 + return redirectUrl;
  93 + }
  94 +
  95 + public void setRedirectUrl(String redirectUrl) {
  96 + this.redirectUrl = redirectUrl;
  97 + }
  98 +
89 99 public String getDescription() {
90 100 return description;
91 101 }
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/domain/CashierOrderToken.java
... ... @@ -21,6 +21,8 @@ public class CashierOrderToken {
21 21 private Integer cashierType;
22 22 // 业务系统用户标识
23 23 private String userId;
  24 + // 页面回调地址
  25 + private String redirectUrl;
24 26  
25 27 public static String encode(Long tokenId, SecretKeySpec secretKey) {
26 28 try {
... ... @@ -70,11 +72,12 @@ public class CashierOrderToken {
70 72 public CashierOrderToken() {
71 73 }
72 74  
73   - public CashierOrderToken(Long mchId, String tradeId, Integer cashierType, String userId) {
  75 + public CashierOrderToken(Long mchId, String tradeId, Integer cashierType, String userId, String redirectUrl) {
74 76 this.mchId = mchId;
75 77 this.tradeId = tradeId;
76 78 this.cashierType = cashierType;
77 79 this.userId = userId;
  80 + this.redirectUrl = redirectUrl;
78 81 }
79 82  
80 83 public Long getMchId() {
... ... @@ -109,6 +112,14 @@ public class CashierOrderToken {
109 112 this.userId = userId;
110 113 }
111 114  
  115 + public String getRedirectUrl() {
  116 + return redirectUrl;
  117 + }
  118 +
  119 + public void setRedirectUrl(String redirectUrl) {
  120 + this.redirectUrl = redirectUrl;
  121 + }
  122 +
112 123 @Override
113 124 public String toString() {
114 125 return JsonUtils.toJsonString(this);
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/domain/CashierOrderInfo.java renamed to cashier-boss/src/main/java/com/diligrp/cashier/boss/domain/CashierOrderVO.java
1 1 package com.diligrp.cashier.boss.domain;
2 2  
  3 +import com.diligrp.cashier.pipeline.type.ChannelType;
  4 +
3 5 import java.util.List;
4 6  
5   -public class CashierOrderInfo {
  7 +public class CashierOrderVO {
6 8 // 业务系统用户标识
7 9 private final String userId;
  10 + // 页面回调地址
  11 + private final String redirectUrl;
8 12 // 支付通道
9 13 private final List<PaymentPipeline> pipelines;
10 14  
11   - public CashierOrderInfo(String userId, List<PaymentPipeline> pipelines) {
  15 + public CashierOrderVO(String userId, String redirectUrl, List<PaymentPipeline> pipelines) {
12 16 this.userId = userId;
  17 + this.redirectUrl = redirectUrl;
13 18 this.pipelines = pipelines;
14 19 }
15 20  
... ... @@ -17,6 +22,10 @@ public class CashierOrderInfo {
17 22 return userId;
18 23 }
19 24  
  25 + public String getRedirectUrl() {
  26 + return redirectUrl;
  27 + }
  28 +
20 29 public List<PaymentPipeline> getPipelines() {
21 30 return pipelines;
22 31 }
... ... @@ -27,9 +36,12 @@ public class CashierOrderInfo {
27 36 // 支付渠道
28 37 private final Integer channelId;
29 38  
30   - public PaymentPipeline(Long pipelineId, Integer channelId) {
  39 + private final String channelName;
  40 +
  41 + public PaymentPipeline(Long pipelineId, ChannelType channelType) {
31 42 this.pipelineId = pipelineId;
32   - this.channelId = channelId;
  43 + this.channelId = channelType.getCode();
  44 + this.channelName = channelType.getName();
33 45 }
34 46  
35 47 public Long getPipelineId() {
... ... @@ -39,5 +51,9 @@ public class CashierOrderInfo {
39 51 public Integer getChannelId() {
40 52 return channelId;
41 53 }
  54 +
  55 + public String getChannelName() {
  56 + return channelName;
  57 + }
42 58 }
43 59 }
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/service/ICashierDeskService.java
1 1 package com.diligrp.cashier.boss.service;
2 2  
3   -import com.diligrp.cashier.boss.domain.CashierOrderInfo;
  3 +import com.diligrp.cashier.boss.domain.CashierOrderVO;
4 4 import com.diligrp.cashier.boss.domain.CashierPaymentUrl;
5 5 import com.diligrp.cashier.pipeline.domain.OnlinePaymentStatus;
6 6 import com.diligrp.cashier.trade.domain.*;
... ... @@ -18,7 +18,7 @@ public interface ICashierDeskService {
18 18 /**
19 19 * 根据收银台TOKEN信息获取订单信息
20 20 */
21   - CashierOrderInfo getCashierOrderByToken(String token);
  21 + CashierOrderVO getCashierOrderByToken(String token);
22 22  
23 23 /**
24 24 * 提交收银台支付
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/service/impl/CashierDeskManager.java 0 → 100644
  1 +package com.diligrp.cashier.boss.service.impl;
  2 +
  3 +import com.diligrp.cashier.boss.domain.CashierPaymentUrl;
  4 +import com.diligrp.cashier.boss.service.ICashierDeskService;
  5 +import com.diligrp.cashier.boss.service.IMerchantService;
  6 +import com.diligrp.cashier.boss.util.CashierOrderConverter2;
  7 +import com.diligrp.cashier.boss.util.OnlineRefundConverter;
  8 +import com.diligrp.cashier.shared.spi.ICashierDeskManager;
  9 +import com.diligrp.cashier.shared.spi.domain.CashierOrderBO;
  10 +import com.diligrp.cashier.shared.spi.domain.CashierRefundBO;
  11 +import com.diligrp.cashier.shared.spi.domain.PaymentUrlBO;
  12 +import com.diligrp.cashier.shared.spi.domain.RefundResultBO;
  13 +import com.diligrp.cashier.shared.util.AssertUtils;
  14 +import com.diligrp.cashier.trade.domain.CashierOrder;
  15 +import com.diligrp.cashier.trade.domain.Merchant;
  16 +import com.diligrp.cashier.trade.domain.OnlineRefundDTO;
  17 +import com.diligrp.cashier.trade.domain.OnlineRefundResult;
  18 +import jakarta.annotation.Resource;
  19 +import org.springframework.stereotype.Service;
  20 +
  21 +@Service("cashierDeskManager")
  22 +public class CashierDeskManager implements ICashierDeskManager {
  23 +
  24 + @Resource
  25 + private ICashierDeskService cashierDeskService;
  26 +
  27 + @Resource
  28 + private IMerchantService merchantService;
  29 +
  30 + @Override
  31 + public PaymentUrlBO submitOrder(CashierOrderBO request) {
  32 + // 基本参数校验
  33 + AssertUtils.notNull(request.getMchId(), "mchId missed");
  34 + AssertUtils.notEmpty(request.getUserId(), "userId missed");
  35 + AssertUtils.notNull(request.getCashierType(), "cashierType missed");
  36 + AssertUtils.notEmpty(request.getGoods(), "goods missed");
  37 + AssertUtils.notNull(request.getAmount(), "amount missed");
  38 + AssertUtils.isTrue(request.getAmount() > 0, "Invalid amount");
  39 + AssertUtils.notEmpty(request.getOutTradeNo(), "outTradeNo missed");
  40 + AssertUtils.notEmpty(request.getRedirectUrl(), "redirectUrl missed");
  41 +
  42 + CashierOrder cashierOrder = CashierOrderConverter2.INSTANCE.convert(request);
  43 + Merchant merchant = merchantService.loadCashierMerchant(request.getMchId());
  44 + CashierPaymentUrl paymentUrl = cashierDeskService.doSubmit(merchant, cashierOrder);
  45 + return new PaymentUrlBO(paymentUrl.tradeId(), paymentUrl.paymentUrl());
  46 + }
  47 +
  48 + @Override
  49 + public RefundResultBO doRefund(CashierRefundBO request) {
  50 + AssertUtils.notEmpty(request.getTradeId(), "tradeId missed");
  51 + AssertUtils.notNull(request.getAmount(), "amount missed");
  52 + AssertUtils.isTrue(request.getAmount() > 0, "Invalid amount");
  53 +
  54 + OnlineRefundDTO onlineRefund = OnlineRefundConverter.INSTANCE.convert(request);
  55 + OnlineRefundResult response = cashierDeskService.sendRefundRequest(onlineRefund);
  56 + return new RefundResultBO(response.getRefundId(), response.getTradeId(), response.getState(),
  57 + response.getWhen(), response.getMessage());
  58 + }
  59 +}
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/service/impl/CashierDeskServiceImpl.java
... ... @@ -2,7 +2,7 @@ package com.diligrp.cashier.boss.service.impl;
2 2  
3 3 import com.diligrp.cashier.boss.CashierDeskProperties;
4 4 import com.diligrp.cashier.boss.Constants;
5   -import com.diligrp.cashier.boss.domain.CashierOrderInfo;
  5 +import com.diligrp.cashier.boss.domain.CashierOrderVO;
6 6 import com.diligrp.cashier.boss.domain.CashierOrderToken;
7 7 import com.diligrp.cashier.boss.domain.CashierPaymentUrl;
8 8 import com.diligrp.cashier.boss.exception.BossServiceException;
... ... @@ -53,7 +53,8 @@ public class CashierDeskServiceImpl implements ICashierDeskService {
53 53 public CashierPaymentUrl doSubmit(Merchant merchant, CashierOrder order) {
54 54 String tradeId = cashierPaymentService.doSubmit(merchant, order);
55 55 String token = CashierOrderToken.encode(Long.valueOf(tradeId), cashierDeskProperties.getSecretKey());
56   - CashierOrderToken orderToken = new CashierOrderToken(merchant.getMchId(), tradeId, order.getType().getCode(), order.getUserId());
  56 + CashierOrderToken orderToken = new CashierOrderToken(merchant.getMchId(), tradeId, order.getType().getCode(),
  57 + order.getUserId(), order.getRedirectUrl());
57 58  
58 59 String tokenKey = String.format(Constants.TOKEN_REDIS_KEY, token);
59 60 stringRedisTemplate.opsForValue().set(tokenKey, orderToken.toString(), Constants.TOKEN_TIMEOUT_SECONDS, TimeUnit.SECONDS);
... ... @@ -68,12 +69,12 @@ public class CashierDeskServiceImpl implements ICashierDeskService {
68 69 }
69 70  
70 71 @Override
71   - public CashierOrderInfo getCashierOrderByToken(String token) {
  72 + public CashierOrderVO getCashierOrderByToken(String token) {
72 73 CashierOrderToken.decode(token, cashierDeskProperties.getSecretKey());
73 74 String tokenKey = String.format(Constants.TOKEN_REDIS_KEY, token);
74 75 String payload = stringRedisTemplate.opsForValue().get(tokenKey);
75 76 if (ObjectUtils.isEmpty(payload)) {
76   - throw new BossServiceException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "收银台订单超时过期,不能支付");
  77 + throw new BossServiceException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "TOKEN超时过期,不能完成支付");
77 78 }
78 79  
79 80 CashierOrderToken orderToken = CashierOrderToken.decodeCashierOrder(payload);
... ... @@ -85,9 +86,9 @@ public class CashierDeskServiceImpl implements ICashierDeskService {
85 86 if (pipelines.isEmpty()) {
86 87 throw new BossServiceException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "商户无可用的支付通道");
87 88 }
88   - List<CashierOrderInfo.PaymentPipeline> pipelineList = pipelines.stream().map(pipeline ->
89   - new CashierOrderInfo.PaymentPipeline(pipeline.pipelineId(), pipeline.supportedChannel().getCode())).toList();
90   - return new CashierOrderInfo(orderToken.getUserId(), pipelineList);
  89 + List<CashierOrderVO.PaymentPipeline> pipelineList = pipelines.stream().map(pipeline ->
  90 + new CashierOrderVO.PaymentPipeline(pipeline.pipelineId(), pipeline.supportedChannel())).toList();
  91 + return new CashierOrderVO(orderToken.getUserId(), orderToken.getRedirectUrl(), pipelineList);
91 92 }
92 93  
93 94 /**
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/service/impl/MerchantServiceImpl.java
... ... @@ -13,6 +13,7 @@ import org.springframework.data.redis.core.StringRedisTemplate;
13 13 import org.springframework.stereotype.Service;
14 14  
15 15 import java.util.Objects;
  16 +import java.util.concurrent.TimeUnit;
16 17  
17 18 /**
18 19 * 支付平台接入许可服务
... ... @@ -36,8 +37,8 @@ public class MerchantServiceImpl implements IMerchantService {
36 37 Merchant merchant = Merchant.decode(payload);
37 38 if (Objects.isNull(merchant)) {
38 39 merchant = merchantDao.findByMchId(mchId).map(new MerchantConverter(cashierDeskProperties)::convert)
39   - .orElseThrow(() -> new BossServiceException(ErrorCode.OBJECT_NOT_FOUND, "商家信息未注册"));
40   - stringRedisTemplate.opsForValue().set(cacheKey, merchant.toString());
  40 + .orElseThrow(() -> new BossServiceException(ErrorCode.OBJECT_NOT_FOUND, "商户信息未注册: " + mchId));
  41 + stringRedisTemplate.opsForValue().set(cacheKey, merchant.toString(), Constants.MERCHANT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
41 42 }
42 43  
43 44 return merchant;
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/util/CashierOrderConverter.java
... ... @@ -19,6 +19,7 @@ public class CashierOrderConverter implements IConverter&lt;CashierOrderDTO, Cashie
19 19 cashierOrder.setTimeout(cashierOrderDTO.getTimeout());
20 20 cashierOrder.setOutTradeNo(cashierOrderDTO.getOutTradeNo());
21 21 cashierOrder.setNotifyUrl(cashierOrderDTO.getNotifyUrl());
  22 + cashierOrder.setRedirectUrl(cashierOrderDTO.getRedirectUrl());
22 23 cashierOrder.setDescription(cashierOrderDTO.getDescription());
23 24 cashierOrder.setAttach(cashierOrderDTO.getAttach());
24 25 return cashierOrder;
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/util/CashierOrderConverter2.java 0 → 100644
  1 +package com.diligrp.cashier.boss.util;
  2 +
  3 +import com.diligrp.cashier.pipeline.type.CashierType;
  4 +import com.diligrp.cashier.shared.codec.IConverter;
  5 +import com.diligrp.cashier.shared.spi.domain.CashierOrderBO;
  6 +import com.diligrp.cashier.trade.domain.CashierOrder;
  7 +
  8 +public class CashierOrderConverter2 implements IConverter<CashierOrderBO, CashierOrder> {
  9 +
  10 + public static IConverter<CashierOrderBO, CashierOrder> INSTANCE = new CashierOrderConverter2();
  11 +
  12 + @Override
  13 + public CashierOrder convert(CashierOrderBO cashierOrderDTO) {
  14 + CashierOrder cashierOrder = new CashierOrder();
  15 + cashierOrder.setUserId(cashierOrderDTO.getUserId());
  16 + cashierOrder.setType(CashierType.getByCode(cashierOrderDTO.getCashierType()));
  17 + cashierOrder.setGoods(cashierOrderDTO.getGoods());
  18 + cashierOrder.setAmount(cashierOrderDTO.getAmount());
  19 + cashierOrder.setTimeout(cashierOrderDTO.getTimeout());
  20 + cashierOrder.setOutTradeNo(cashierOrderDTO.getOutTradeNo());
  21 + cashierOrder.setNotifyUrl(cashierOrderDTO.getNotifyUrl());
  22 + cashierOrder.setRedirectUrl(cashierOrderDTO.getRedirectUrl());
  23 + cashierOrder.setDescription(cashierOrderDTO.getDescription());
  24 + cashierOrder.setAttach(cashierOrderDTO.getAttach());
  25 + return cashierOrder;
  26 + }
  27 +}
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/util/OnlineRefundConverter.java 0 → 100644
  1 +package com.diligrp.cashier.boss.util;
  2 +
  3 +import com.diligrp.cashier.shared.codec.IConverter;
  4 +import com.diligrp.cashier.shared.spi.domain.CashierRefundBO;
  5 +import com.diligrp.cashier.trade.domain.OnlineRefundDTO;
  6 +
  7 +public class OnlineRefundConverter implements IConverter<CashierRefundBO, OnlineRefundDTO> {
  8 +
  9 + public static IConverter<CashierRefundBO, OnlineRefundDTO> INSTANCE = new OnlineRefundConverter();
  10 +
  11 + @Override
  12 + public OnlineRefundDTO convert(CashierRefundBO cashierRefund) {
  13 + OnlineRefundDTO onlineRefund = new OnlineRefundDTO();
  14 + onlineRefund.setTradeId(cashierRefund.getTradeId());
  15 + onlineRefund.setAmount(cashierRefund.getAmount());
  16 + onlineRefund.setNotifyUrl(cashierRefund.getNotifyUrl());
  17 + onlineRefund.setDescription(cashierRefund.getDescription());
  18 + return onlineRefund;
  19 + }
  20 +}
... ...
cashier-boss/src/main/resources/logback-spring.xml
... ... @@ -26,7 +26,7 @@
26 26 </encoder>
27 27 <!-- 过滤器:只输出INFO及以上级别,减少控制台冗余 -->
28 28 <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
29   - <level>INFO</level>
  29 + <level>DEBUG</level>
30 30 </filter>
31 31 </appender>
32 32  
... ...
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/type/ChannelType.java
... ... @@ -15,11 +15,11 @@ import java.util.stream.Stream;
15 15 */
16 16 public enum ChannelType implements IEnumType {
17 17  
18   - WXPAY("微信渠道", 10),
  18 + WXPAY("微信支付", 10),
19 19  
20   - ALIPAY("支付宝渠道", 11),
  20 + ALIPAY("支付宝", 11),
21 21  
22   - DILIPAY("地利渠道", 19), // 地利园区卡支付
  22 + DILIPAY("园区卡支付", 19),
23 23  
24 24 ICBC("工商银行", 20),
25 25  
... ...
cashier-shared/src/main/java/com/diligrp/cashier/shared/spi/ICashierDeskManager.java 0 → 100644
  1 +package com.diligrp.cashier.shared.spi;
  2 +
  3 +import com.diligrp.cashier.shared.spi.domain.CashierOrderBO;
  4 +import com.diligrp.cashier.shared.spi.domain.CashierRefundBO;
  5 +import com.diligrp.cashier.shared.spi.domain.PaymentUrlBO;
  6 +import com.diligrp.cashier.shared.spi.domain.RefundResultBO;
  7 +
  8 +public interface ICashierDeskManager {
  9 +
  10 + /**
  11 + * 提交收银台订单
  12 + */
  13 + PaymentUrlBO submitOrder(CashierOrderBO request);
  14 +
  15 + /**
  16 + * 交易退款
  17 + */
  18 + RefundResultBO doRefund(CashierRefundBO request);
  19 +}
... ...
cashier-shared/src/main/java/com/diligrp/cashier/shared/spi/IPaymentEventListener.java
1 1 package com.diligrp.cashier.shared.spi;
2 2  
  3 +import com.diligrp.cashier.shared.spi.domain.PaymentResultBO;
  4 +import com.diligrp.cashier.shared.spi.domain.RefundResultBO;
  5 +
3 6 public interface IPaymentEventListener {
4 7  
5   - void onEvent(PaymentEvent event);
  8 + void onEvent(PaymentResultBO event);
6 9  
7   - void onEvent(RefundEvent event);
  10 + void onEvent(RefundResultBO event);
8 11 }
... ...
cashier-shared/src/main/java/com/diligrp/cashier/shared/spi/domain/CashierOrderBO.java 0 → 100644
  1 +package com.diligrp.cashier.shared.spi.domain;
  2 +
  3 +public class CashierOrderBO {
  4 + // 接入商户
  5 + private Long mchId;
  6 + // 业务系统用户标识
  7 + private String userId;
  8 + // 收银台类型 - H5收银台等
  9 + private Integer cashierType;
  10 + // 商品描述
  11 + private String goods;
  12 + // 申请金额
  13 + private Long amount;
  14 + // 超时间隔时间 - 秒
  15 + private Integer timeout;
  16 + // 外部流水号
  17 + private String outTradeNo;
  18 + // 回调地址
  19 + private String notifyUrl;
  20 + // 页面回调地址
  21 + private String redirectUrl;
  22 + // 交易描述
  23 + private String description;
  24 + // 附加数据
  25 + private String attach;
  26 +
  27 + public Long getMchId() {
  28 + return mchId;
  29 + }
  30 +
  31 + public void setMchId(Long mchId) {
  32 + this.mchId = mchId;
  33 + }
  34 +
  35 + public String getUserId() {
  36 + return userId;
  37 + }
  38 +
  39 + public void setUserId(String userId) {
  40 + this.userId = userId;
  41 + }
  42 +
  43 + public Integer getCashierType() {
  44 + return cashierType;
  45 + }
  46 +
  47 + public void setCashierType(Integer cashierType) {
  48 + this.cashierType = cashierType;
  49 + }
  50 +
  51 + public String getGoods() {
  52 + return goods;
  53 + }
  54 +
  55 + public void setGoods(String goods) {
  56 + this.goods = goods;
  57 + }
  58 +
  59 + public Long getAmount() {
  60 + return amount;
  61 + }
  62 +
  63 + public void setAmount(Long amount) {
  64 + this.amount = amount;
  65 + }
  66 +
  67 + public Integer getTimeout() {
  68 + return timeout;
  69 + }
  70 +
  71 + public void setTimeout(Integer timeout) {
  72 + this.timeout = timeout;
  73 + }
  74 +
  75 + public String getOutTradeNo() {
  76 + return outTradeNo;
  77 + }
  78 +
  79 + public void setOutTradeNo(String outTradeNo) {
  80 + this.outTradeNo = outTradeNo;
  81 + }
  82 +
  83 + public String getNotifyUrl() {
  84 + return notifyUrl;
  85 + }
  86 +
  87 + public void setNotifyUrl(String notifyUrl) {
  88 + this.notifyUrl = notifyUrl;
  89 + }
  90 +
  91 + public String getRedirectUrl() {
  92 + return redirectUrl;
  93 + }
  94 +
  95 + public void setRedirectUrl(String redirectUrl) {
  96 + this.redirectUrl = redirectUrl;
  97 + }
  98 +
  99 + public String getDescription() {
  100 + return description;
  101 + }
  102 +
  103 + public void setDescription(String description) {
  104 + this.description = description;
  105 + }
  106 +
  107 + public String getAttach() {
  108 + return attach;
  109 + }
  110 +
  111 + public void setAttach(String attach) {
  112 + this.attach = attach;
  113 + }
  114 +}
... ...
cashier-shared/src/main/java/com/diligrp/cashier/shared/spi/domain/CashierRefundBO.java 0 → 100644
  1 +package com.diligrp.cashier.shared.spi.domain;
  2 +
  3 +/**
  4 + * 退款申请
  5 + */
  6 +public class CashierRefundBO {
  7 + // 原支付号
  8 + private final String tradeId;
  9 + // 退款金额
  10 + private final Long amount;
  11 + // 回调地址
  12 + private final String notifyUrl;
  13 + // 退款原因
  14 + private final String description;
  15 +
  16 + public CashierRefundBO(String tradeId, Long amount, String notifyUrl, String description) {
  17 + this.tradeId = tradeId;
  18 + this.amount = amount;
  19 + this.notifyUrl = notifyUrl;
  20 + this.description = description;
  21 + }
  22 +
  23 + public String getTradeId() {
  24 + return tradeId;
  25 + }
  26 +
  27 + public Long getAmount() {
  28 + return amount;
  29 + }
  30 +
  31 + public String getNotifyUrl() {
  32 + return notifyUrl;
  33 + }
  34 +
  35 + public String getDescription() {
  36 + return description;
  37 + }
  38 +}
... ...
cashier-shared/src/main/java/com/diligrp/cashier/shared/spi/PaymentEvent.java renamed to cashier-shared/src/main/java/com/diligrp/cashier/shared/spi/domain/PaymentResultBO.java
1   -package com.diligrp.cashier.shared.spi;
  1 +package com.diligrp.cashier.shared.spi.domain;
2 2  
3 3 import java.time.LocalDateTime;
4 4  
5   -public class PaymentEvent {
  5 +public class PaymentResultBO {
6 6 // 交易号
7 7 private final String tradeId;
8 8 // 支付ID
... ... @@ -20,8 +20,8 @@ public class PaymentEvent {
20 20 // 交易描述
21 21 private final String message;
22 22  
23   - public PaymentEvent(String tradeId, String paymentId, int state, String outTradeNo, Integer outPayType,
24   - String payerId, LocalDateTime when, String message) {
  23 + public PaymentResultBO(String tradeId, String paymentId, Integer state, String outTradeNo, Integer outPayType,
  24 + String payerId, LocalDateTime when, String message) {
25 25 this.tradeId = tradeId;
26 26 this.paymentId = paymentId;
27 27 this.state = state;
... ...
cashier-shared/src/main/java/com/diligrp/cashier/shared/spi/domain/PaymentUrlBO.java 0 → 100644
  1 +package com.diligrp.cashier.shared.spi.domain;
  2 +
  3 +public record PaymentUrlBO(String tradeId, String paymentUrl) {
  4 +}
... ...
cashier-shared/src/main/java/com/diligrp/cashier/shared/spi/RefundEvent.java renamed to cashier-shared/src/main/java/com/diligrp/cashier/shared/spi/domain/RefundResultBO.java
1   -package com.diligrp.cashier.shared.spi;
  1 +package com.diligrp.cashier.shared.spi.domain;
2 2  
3 3 import java.time.LocalDateTime;
4 4  
5   -public class RefundEvent {
  5 +public class RefundResultBO {
6 6 // 退款单号
7 7 private final String refundId;
8 8 // 原支付ID
... ... @@ -14,7 +14,7 @@ public class RefundEvent {
14 14 // 交易描述
15 15 private final String message;
16 16  
17   - public RefundEvent(String refundId, String tradeId, int state, LocalDateTime when, String message) {
  17 + public RefundResultBO(String refundId, String tradeId, int state, LocalDateTime when, String message) {
18 18 this.refundId = refundId;
19 19 this.tradeId = tradeId;
20 20 this.state = state;
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/Constants.java
... ... @@ -11,16 +11,19 @@ public final class Constants {
11 11 // 支付通道延时路由KEY
12 12 public static final String PAYMENT_DELAY_KEY = "cashier.payment.delayKey";
13 13  
14   - // 默认订单超时时间-秒, 十分钟
15   - public static final int DEFAULT_ORDER_TIMEOUT_SECONDS = 10 * 60 * 1000;
  14 + // 默认交易订单超时时间-秒, 5分钟
  15 + public static final int DEFAULT_TRADE_TIMEOUT = 5 * 60;
16 16  
17   - // 最小订单超时时间-秒, 一分钟
18   - public static final int MIN_ORDER_TIMEOUT_SECONDS = 60 * 1000;
  17 + // 最大交易订单超时时间-秒, 10分钟
  18 + public static final int MAX_TRADE_TIMEOUT = 10 * 60;
  19 +
  20 + // 最小交易订单超时时间-秒, 1分钟
  21 + public static final int MIN_TRADE_TIMEOUT = 60;
19 22  
20 23 // 支付订单分布式锁
21 24 public static final String TRADE_LOCK_REDIS_KEY = "cashier:lock:trade:%s";
22 25  
23   - // 支付订单分布式锁超时时长-秒
24   - public static final int TRADE_LOCK_TIMEOUT_SECONDS = 15 * 1000;
  26 + // 支付订单分布式锁超时时长-秒, 15秒
  27 + public static final int TRADE_LOCK_TIMEOUT = 15;
25 28  
26 29 }
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/domain/CashierOrder.java
... ... @@ -17,6 +17,8 @@ public class CashierOrder {
17 17 private String outTradeNo;
18 18 // 回调地址
19 19 private String notifyUrl;
  20 + // 页面回调地址
  21 + private String redirectUrl;
20 22 // 交易描述
21 23 private String description;
22 24 // 附加数据
... ... @@ -78,6 +80,14 @@ public class CashierOrder {
78 80 this.notifyUrl = notifyUrl;
79 81 }
80 82  
  83 + public String getRedirectUrl() {
  84 + return redirectUrl;
  85 + }
  86 +
  87 + public void setRedirectUrl(String redirectUrl) {
  88 + this.redirectUrl = redirectUrl;
  89 + }
  90 +
81 91 public String getDescription() {
82 92 return description;
83 93 }
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/domain/MerchantParams.java
... ... @@ -7,7 +7,6 @@ import java.util.Objects;
7 7 public class MerchantParams {
8 8 // 收银台配置
9 9 private CashierParams cashier;
10   - // TODO: 支付结果页面-跳转大润发使用
11 10  
12 11 public static MerchantParams decode(String params) {
13 12 return JsonUtils.fromJsonString(params, MerchantParams.class);
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/domain/OnlinePaymentResult.java
... ... @@ -2,14 +2,30 @@ package com.diligrp.cashier.trade.domain;
2 2  
3 3 import com.diligrp.cashier.pipeline.type.OutPaymentType;
4 4 import com.diligrp.cashier.pipeline.type.PaymentState;
5   -import com.diligrp.cashier.shared.spi.PaymentEvent;
6 5  
7 6 import java.time.LocalDateTime;
8 7  
9 8 /**
10 9 * 在线支付结果 - 用于业务系统支付结果通知
11 10 */
12   -public class OnlinePaymentResult extends PaymentEvent {
  11 +public class OnlinePaymentResult {
  12 + // 交易号
  13 + private final String tradeId;
  14 + // 支付ID
  15 + private final String paymentId;
  16 + // 支付状态
  17 + private final Integer state;
  18 + // 业务系统订单号
  19 + private final String outTradeNo;
  20 + // 实际支付方式
  21 + private final Integer outPayType;
  22 + // 支付人信息
  23 + private final String payerId;
  24 + // 发生时间
  25 + private final LocalDateTime when;
  26 + // 交易描述
  27 + private final String message;
  28 +
13 29 public static OnlinePaymentResult of(String tradeId, String paymentId, PaymentState state, String outTradeNo,
14 30 OutPaymentType outPayType, String payerId, LocalDateTime when, String message) {
15 31 Integer outPayTypeCode = outPayType != null ? outPayType.getCode() : null;
... ... @@ -18,6 +34,45 @@ public class OnlinePaymentResult extends PaymentEvent {
18 34  
19 35 public OnlinePaymentResult(String tradeId, String paymentId, int state, String outTradeNo, Integer outPayType,
20 36 String payerId, LocalDateTime when, String message) {
21   - super(tradeId, paymentId, state, outTradeNo, outPayType, payerId, when, message);
  37 + this.tradeId = tradeId;
  38 + this.paymentId = paymentId;
  39 + this.state = state;
  40 + this.outTradeNo = outTradeNo;
  41 + this.outPayType = outPayType;
  42 + this.payerId = payerId;
  43 + this.when = when;
  44 + this.message = message;
  45 + }
  46 +
  47 + public String getTradeId() {
  48 + return tradeId;
  49 + }
  50 +
  51 + public String getPaymentId() {
  52 + return paymentId;
  53 + }
  54 +
  55 + public Integer getState() {
  56 + return state;
  57 + }
  58 +
  59 + public String getOutTradeNo() {
  60 + return outTradeNo;
  61 + }
  62 +
  63 + public Integer getOutPayType() {
  64 + return outPayType;
  65 + }
  66 +
  67 + public String getPayerId() {
  68 + return payerId;
  69 + }
  70 +
  71 + public LocalDateTime getWhen() {
  72 + return when;
  73 + }
  74 +
  75 + public String getMessage() {
  76 + return message;
22 77 }
23 78 }
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/domain/OnlineRefundDTO.java
... ... @@ -9,7 +9,7 @@ public class OnlineRefundDTO {
9 9 // 退款金额
10 10 private Long amount;
11 11 // 回调地址
12   - private String notifyUri;
  12 + private String notifyUrl;
13 13 // 退款原因
14 14 private String description;
15 15  
... ... @@ -29,12 +29,12 @@ public class OnlineRefundDTO {
29 29 this.amount = amount;
30 30 }
31 31  
32   - public String getNotifyUri() {
33   - return notifyUri;
  32 + public String getNotifyUrl() {
  33 + return notifyUrl;
34 34 }
35 35  
36   - public void setNotifyUri(String notifyUri) {
37   - this.notifyUri = notifyUri;
  36 + public void setNotifyUrl(String notifyUrl) {
  37 + this.notifyUrl = notifyUrl;
38 38 }
39 39  
40 40 public String getDescription() {
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/domain/OnlineRefundResult.java
1 1 package com.diligrp.cashier.trade.domain;
2 2  
3   -import com.diligrp.cashier.shared.spi.RefundEvent;
4   -
5 3 import java.time.LocalDateTime;
6 4  
7 5 /**
8 6 * 退款结果
9 7 */
10   -public class OnlineRefundResult extends RefundEvent {
  8 +public class OnlineRefundResult {
  9 + // 退款单号
  10 + private final String refundId;
  11 + // 原支付ID
  12 + private final String tradeId;
  13 + // 支付状态
  14 + private final Integer state;
  15 + // 发生时间
  16 + private final LocalDateTime when;
  17 + // 交易描述
  18 + private final String message;
  19 +
11 20 public OnlineRefundResult(String refundId, String tradeId, int state, LocalDateTime when, String message) {
12   - super(refundId, tradeId, state, when, message);
  21 + this.refundId = refundId;
  22 + this.tradeId = tradeId;
  23 + this.state = state;
  24 + this.when = when;
  25 + this.message = message;
  26 + }
  27 +
  28 + public String getRefundId() {
  29 + return refundId;
  30 + }
  31 +
  32 + public String getTradeId() {
  33 + return tradeId;
  34 + }
  35 +
  36 + public Integer getState() {
  37 + return state;
  38 + }
  39 +
  40 + public LocalDateTime getWhen() {
  41 + return when;
  42 + }
  43 +
  44 + public String getMessage() {
  45 + return message;
13 46 }
14 47 }
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/manager/PaymentResultManager.java
... ... @@ -3,10 +3,12 @@ package com.diligrp.cashier.trade.manager;
3 3 import com.diligrp.cashier.shared.service.ServiceEndpointSupport;
4 4 import com.diligrp.cashier.shared.service.ThreadPoolService;
5 5 import com.diligrp.cashier.shared.spi.IPaymentEventListener;
  6 +import com.diligrp.cashier.shared.spi.domain.PaymentResultBO;
  7 +import com.diligrp.cashier.shared.spi.domain.RefundResultBO;
6 8 import com.diligrp.cashier.shared.util.JsonUtils;
7 9 import com.diligrp.cashier.shared.util.ObjectUtils;
8   -import com.diligrp.cashier.trade.domain.OnlineRefundResult;
9 10 import com.diligrp.cashier.trade.domain.OnlinePaymentResult;
  11 +import com.diligrp.cashier.trade.domain.OnlineRefundResult;
10 12 import jakarta.annotation.Resource;
11 13 import org.slf4j.Logger;
12 14 import org.slf4j.LoggerFactory;
... ... @@ -14,6 +16,8 @@ import org.springframework.beans.factory.ObjectProvider;
14 16 import org.springframework.stereotype.Service;
15 17  
16 18 import java.util.List;
  19 +import java.util.concurrent.Future;
  20 +import java.util.concurrent.TimeUnit;
17 21  
18 22 @Service("paymentResultManager")
19 23 public class PaymentResultManager {
... ... @@ -24,34 +28,46 @@ public class PaymentResultManager {
24 28 private ObjectProvider<IPaymentEventListener> eventListeners;
25 29  
26 30 /**
  31 + * 通知业务系统在线支付通道处理结果,并阻塞一段时间保证通知完成
  32 + */
  33 + public void notifyPaymentResult(String uri, OnlinePaymentResult paymentResult, long waitMillis) {
  34 + Future<?> future = notifyPaymentResult(uri, paymentResult);
  35 + try {
  36 + future.get(waitMillis, TimeUnit.MILLISECONDS);
  37 + } catch (Exception ex) {
  38 + // Ignore exception
  39 + }
  40 + }
  41 + /**
27 42 * 通知业务系统在线支付通道处理结果
28 43 */
29   - public void notifyPaymentResult(String uri, OnlinePaymentResult payload) {
30   - ThreadPoolService.getIoThreadPoll().submit(() -> {
  44 + public Future<?> notifyPaymentResult(String uri, OnlinePaymentResult paymentResult) {
  45 + return ThreadPoolService.getIoThreadPoll().submit(() -> {
31 46 List<IPaymentEventListener> lifeCycles = eventListeners.stream().toList();
32   - for (IPaymentEventListener listener : lifeCycles) {
33   - try {
34   - listener.onEvent(payload);
35   - } catch (Exception ex) {
36   - LOG.error("Failed to notify trade payment result", ex);
  47 + LOG.info("Notifying online payment result: {}, {}", paymentResult.getTradeId(), paymentResult.getPaymentId());
  48 + if (!lifeCycles.isEmpty()) {
  49 + PaymentResultBO paymentEvent = new PaymentResultBO(paymentResult.getTradeId(), paymentResult.getPaymentId(),
  50 + paymentResult.getState(), paymentResult.getOutTradeNo(), paymentResult.getOutPayType(),
  51 + paymentResult.getPayerId(), paymentResult.getWhen(), paymentResult.getMessage());
  52 + for (IPaymentEventListener listener : lifeCycles) {
  53 + try {
  54 + listener.onEvent(paymentEvent);
  55 + } catch (Exception ex) {
  56 + LOG.error("Failed to notify online payment result", ex);
  57 + }
37 58 }
38 59 }
39   - });
40 60  
41   - if (ObjectUtils.isEmpty(uri)) {
42   - return;
43   - }
44   -
45   - ThreadPoolService.getIoThreadPoll().submit(() -> {
46   - try {
47   - String body = JsonUtils.toJsonString(payload);
48   - LOG.info("Notifying online trade payment result: {}", body);
49   - ServiceEndpointSupport.HttpResult httpResult = new NotifyHttpClient(uri).send(body);
50   - if (httpResult.statusCode != 200) {
51   - LOG.error("Failed to notify trade payment result");
52   - }
53   - } catch (Exception ex) {
54   - LOG.error("Failed to notify trade payment result", ex);
  61 + if (ObjectUtils.isEmpty(uri)) {
  62 + try {
  63 + String payload = JsonUtils.toJsonString(paymentResult);
  64 + ServiceEndpointSupport.HttpResult httpResult = new NotifyHttpClient(uri).send(payload);
  65 + if (httpResult.statusCode != 200) {
  66 + LOG.error("Failed to notify online payment result");
  67 + }
  68 + } catch (Exception ex) {
  69 + LOG.error("Failed to notify online payment result", ex);
  70 + };
55 71 }
56 72 });
57 73 }
... ... @@ -59,14 +75,18 @@ public class PaymentResultManager {
59 75 /**
60 76 * 通知业务系统退款处理结果
61 77 */
62   - public void notifyRefundResult(String uri, OnlineRefundResult payload) {
  78 + public void notifyRefundResult(String uri, OnlineRefundResult refundResult) {
63 79 ThreadPoolService.getIoThreadPoll().submit(() -> {
64 80 List<IPaymentEventListener> lifeCycles = eventListeners.stream().toList();
65   - for (IPaymentEventListener listener : lifeCycles) {
66   - try {
67   - listener.onEvent(payload);
68   - } catch (Exception ex) {
69   - LOG.error("Failed to notify trade refund result", ex);
  81 + if (!lifeCycles.isEmpty()) {
  82 + RefundResultBO refundEvent = new RefundResultBO(refundResult.getRefundId(), refundResult.getTradeId(),
  83 + refundResult.getState(), refundResult.getWhen(), refundResult.getMessage());
  84 + for (IPaymentEventListener listener : lifeCycles) {
  85 + try {
  86 + listener.onEvent(refundEvent);
  87 + } catch (Exception ex) {
  88 + LOG.error("Failed to notify trade refund result", ex);
  89 + }
70 90 }
71 91 }
72 92 });
... ... @@ -77,9 +97,9 @@ public class PaymentResultManager {
77 97  
78 98 ThreadPoolService.getIoThreadPoll().submit(() -> {
79 99 try {
80   - String body = JsonUtils.toJsonString(payload);
81   - LOG.info("Notifying online trade refund result: {}", body);
82   - ServiceEndpointSupport.HttpResult httpResult = new NotifyHttpClient(uri).send(body);
  100 + String payload = JsonUtils.toJsonString(refundResult);
  101 + LOG.info("Notifying online trade refund result: {}", payload);
  102 + ServiceEndpointSupport.HttpResult httpResult = new NotifyHttpClient(uri).send(payload);
83 103 if (httpResult.statusCode != 200) {
84 104 LOG.error("Failed to notify trade refund result");
85 105 }
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/manager/TaskMessageConsumer.java
... ... @@ -35,7 +35,7 @@ public class TaskMessageConsumer {
35 35 ? properties.getContentEncoding() : StandardCharsets.UTF_8.name();
36 36 try {
37 37 String body = new String(packet, charSet);
38   - LOG.info("Receiving online pipeline async task request: {}", body);
  38 + LOG.info("Receiving async delay task message: {}", body);
39 39 TaskMessage task = TaskMessage.fromJson(body);
40 40 int times = NumberUtils.str2Int(task.getParams(), Integer.MAX_VALUE);
41 41 if (task.getType() == TaskMessage.TYPE_CASHIER_ORDER_SCAN) {
... ... @@ -46,7 +46,7 @@ public class TaskMessageConsumer {
46 46 LOG.error("Never happened");
47 47 }
48 48 } catch (Exception ex) {
49   - LOG.error("Consume online pipeline async message exception", ex);
  49 + LOG.error("Consume async delay task message exception", ex);
50 50 }
51 51 }
52 52 }
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/manager/TaskMessageSender.java
... ... @@ -47,10 +47,10 @@ public class TaskMessageSender {
47 47 properties.setHeader("x-delay", String.valueOf(delayInMillis));
48 48 String payload = JsonUtils.toJsonString(task);
49 49 Message message = new Message(payload.getBytes(StandardCharsets.UTF_8), properties);
50   - LOG.info("Sending online payment order scan request for {}", task.getPayload());
  50 + LOG.info("Sending async delay task message for {}", task.getPayload());
51 51 rabbitTemplate.send(Constants.PAYMENT_DELAY_EXCHANGE, Constants.PAYMENT_DELAY_KEY, message);
52 52 } catch (Exception ex) {
53   - LOG.error("Failed to send online payment order scan request for {}", task.getPayload(), ex);
  53 + LOG.error("Failed to send async delay task message for {}", task.getPayload(), ex);
54 54 }
55 55 });
56 56 }
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/service/impl/CashierAssistantServiceImpl.java
1 1 package com.diligrp.cashier.trade.service.impl;
2 2  
3   -import com.diligrp.cashier.pipeline.Constants;
4 3 import com.diligrp.cashier.pipeline.core.OnlinePipeline;
5 4 import com.diligrp.cashier.pipeline.core.PaymentPipeline;
6 5 import com.diligrp.cashier.pipeline.domain.OnlinePaymentResponse;
... ... @@ -9,6 +8,7 @@ import com.diligrp.cashier.pipeline.domain.OnlineRefundOrder;
9 8 import com.diligrp.cashier.pipeline.domain.OnlineRefundResponse;
10 9 import com.diligrp.cashier.pipeline.service.IPaymentPipelineManager;
11 10 import com.diligrp.cashier.pipeline.type.PaymentState;
  11 +import com.diligrp.cashier.trade.Constants;
12 12 import com.diligrp.cashier.trade.dao.IOnlinePaymentDao;
13 13 import com.diligrp.cashier.trade.domain.TradeStateDTO;
14 14 import com.diligrp.cashier.trade.model.OnlinePayment;
... ... @@ -49,13 +49,13 @@ public class CashierAssistantServiceImpl implements ICashierAssistantService {
49 49  
50 50 @Override
51 51 public void scanCashierTradeOrder(String tradeId, int times) {
52   - LOG.debug("scanCashierTradeOrder{}: processing cashier order {}", times, tradeId);
53   - String lockKey = String.format(com.diligrp.cashier.trade.Constants.TRADE_LOCK_REDIS_KEY, tradeId);
  52 + LOG.debug("scanCashierTradeOrder{}: processing cashier trade order {}", times, tradeId);
  53 + String lockKey = String.format(Constants.TRADE_LOCK_REDIS_KEY, tradeId);
54 54 RLock lock = redissonClient.getLock(lockKey);
55 55 try {
56 56 lock.lock();
57 57 LocalDateTime now = LocalDateTime.now();
58   - // 理论上只会存在一条支付中的支付订单
  58 + // 查询交易订单下状态为支付中的支付订单, 理论上只有一条支付记录
59 59 List<OnlinePayment> onlinePayments = onlinePaymentDao.listOnlinePayments(tradeId,
60 60 TradeType.TRADE.getCode(), PaymentState.PROCESSING.getCode());
61 61 // 关闭所有未支付完成的支付订单
... ... @@ -65,7 +65,7 @@ public class CashierAssistantServiceImpl implements ICashierAssistantService {
65 65 if (pipeline instanceof OnlinePipeline<?> onlinePipeline) {
66 66 OnlinePrepayOrder order = new OnlinePrepayOrder(payment.getPaymentId(), payment.getOutTradeNo());
67 67 // 微信服务商模式,还需子商户
68   - order.attach(Constants.PARAM_MCH_ID, payment.getOutMchId());
  68 + order.attach(com.diligrp.cashier.pipeline.Constants.PARAM_MCH_ID, payment.getOutMchId());
69 69 OnlinePaymentResponse response = onlinePipeline.queryPrepayResponse(order);
70 70 if (!PaymentState.isFinished(response.getState().getCode())) {
71 71 try {
... ... @@ -75,7 +75,7 @@ public class CashierAssistantServiceImpl implements ICashierAssistantService {
75 75 response.getOutPayType(), response.getPayerId(), response.getWhen(),
76 76 PaymentState.FAILED, "自动关闭超时的支付订单");
77 77 } catch (Exception ex) {
78   - LOG.error("scanOnlinePrepayOrder: close online prepare order exception", ex);
  78 + LOG.error("scanCashierTradeOrder: close online prepare order exception", ex);
79 79 }
80 80 }
81 81 cashierPaymentService.notifyPaymentResponse(response);
... ... @@ -84,7 +84,7 @@ public class CashierAssistantServiceImpl implements ICashierAssistantService {
84 84 // 交易订单仍然未完成则关闭交易订单,交易订单不能继续支付
85 85 TradeOrder trade = tradeAssistantService.findByTradeId(tradeId);
86 86 if (!TradeState.isFinished(trade.getState())) {
87   - TradeStateDTO tradeStateDTO = TradeStateDTO.of(trade.getTradeId(), TradeState.FAILED, trade.getVersion(), now);
  87 + TradeStateDTO tradeStateDTO = TradeStateDTO.of(trade.getTradeId(), TradeState.CLOSED, trade.getVersion(), now);
88 88 tradeAssistantService.proceedTradeOrder(tradeStateDTO);
89 89 }
90 90 } finally {
... ... @@ -106,7 +106,7 @@ public class CashierAssistantServiceImpl implements ICashierAssistantService {
106 106 OnlinePipeline<?> pipeline = paymentPipelineManager.findPipelineById(refund.getPipelineId(), OnlinePipeline.class);
107 107 OnlineRefundOrder order = new OnlineRefundOrder(refundId, refund.getOutTradeNo());
108 108 // 微信服务商模式,还需子商户
109   - order.attach(Constants.PARAM_MCH_ID, refund.getOutMchId());
  109 + order.attach(com.diligrp.cashier.pipeline.Constants.PARAM_MCH_ID, refund.getOutMchId());
110 110 OnlineRefundResponse response = pipeline.queryRefundResponse(order);
111 111 cashierPaymentService.notifyRefundResult(response);
112 112 }
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/service/impl/CashierPaymentServiceImpl.java
... ... @@ -20,8 +20,8 @@ import com.diligrp.cashier.trade.dao.IOnlinePaymentDao;
20 20 import com.diligrp.cashier.trade.dao.ITradeOrderDao;
21 21 import com.diligrp.cashier.trade.domain.*;
22 22 import com.diligrp.cashier.trade.exception.TradePaymentException;
23   -import com.diligrp.cashier.trade.manager.TaskMessageSender;
24 23 import com.diligrp.cashier.trade.manager.PaymentResultManager;
  24 +import com.diligrp.cashier.trade.manager.TaskMessageSender;
25 25 import com.diligrp.cashier.trade.model.OnlinePayment;
26 26 import com.diligrp.cashier.trade.model.TradeOrder;
27 27 import com.diligrp.cashier.trade.service.ICashierPaymentService;
... ... @@ -84,8 +84,9 @@ public class CashierPaymentServiceImpl implements ICashierPaymentService {
84 84 public String doSubmit(Merchant merchant, CashierOrder cashierOrder) {
85 85 LocalDateTime now = LocalDateTime.now();
86 86 String tradeId = snowflakeKeyManager.getKeyGenerator(SnowflakeKey.TRADE_ID).nextId();
87   - int timeout = Objects.isNull(cashierOrder.getTimeout()) ? Constants.DEFAULT_ORDER_TIMEOUT_SECONDS : cashierOrder.getTimeout();
88   - timeout = Math.max(timeout, Constants.MIN_ORDER_TIMEOUT_SECONDS);
  87 + // 订单超时时间在1至10分钟之间,默认为5分钟
  88 + int timeout = Objects.isNull(cashierOrder.getTimeout()) ? Constants.DEFAULT_TRADE_TIMEOUT : cashierOrder.getTimeout();
  89 + timeout = Math.min(Math.max(timeout, Constants.MIN_TRADE_TIMEOUT), Constants.MAX_TRADE_TIMEOUT);
89 90 TradeOrder tradeOrder = TradeOrder.builder().mchId(merchant.getMchId()).tradeId(tradeId)
90 91 .type(cashierOrder.getType().getCode()).outTradeNo(cashierOrder.getOutTradeNo()).amount(cashierOrder.getAmount())
91 92 .maxAmount(cashierOrder.getAmount()).goods(cashierOrder.getGoods()).timeout(timeout).state(TradeState.PENDING.getCode())
... ... @@ -93,10 +94,9 @@ public class CashierPaymentServiceImpl implements ICashierPaymentService {
93 94 .source(0).version(0).createdTime(now).modifiedTime(now).build();
94 95 tradeOrderDao.insertTradeOrder(tradeOrder);
95 96  
96   - // TODO: userId是否需要存储
97 97 // 兜底处理交易订单,根据支付结果选择关闭或完成交易订单
98 98 TaskMessage message = new TaskMessage(TaskMessage.TYPE_CASHIER_ORDER_SCAN, tradeId, "1");
99   - taskMessageSender.sendDelayTaskMessage(message, timeout);
  99 + taskMessageSender.sendDelayTaskMessage(message, timeout * 1000); // 转换成毫秒
100 100 return tradeId;
101 101 }
102 102  
... ... @@ -113,7 +113,7 @@ public class CashierPaymentServiceImpl implements ICashierPaymentService {
113 113 String lockKey = String.format(Constants.TRADE_LOCK_REDIS_KEY, cashierPayment.getTradeId());
114 114 RLock lock = redissonClient.getLock(lockKey);
115 115 try {
116   - boolean locked = lock.tryLock(Constants.TRADE_LOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
  116 + boolean locked = lock.tryLock(Constants.TRADE_LOCK_TIMEOUT, TimeUnit.SECONDS);
117 117 if (locked) {
118 118 TradeOrder trade = tradeAssistantService.findByTradeId(cashierPayment.getTradeId());
119 119 CashierType cashierType = CashierType.getByCode(trade.getType());
... ... @@ -131,52 +131,50 @@ public class CashierPaymentServiceImpl implements ICashierPaymentService {
131 131  
132 132 LocalDateTime now = LocalDateTime.now();
133 133 // 获取支付通道
134   - PaymentPipeline<?> paymentPipeline = paymentPipelineManager.findPipelineById(
  134 + PaymentPipeline<?> pipeline = paymentPipelineManager.findPipelineById(
135 135 cashierPayment.getPipelineId(), PaymentPipeline.class);
136 136 String paymentId = snowflakeKeyManager.getKeyGenerator(SnowflakeKey.PAYMENT_ID).nextId();
137   - if (paymentPipeline instanceof OnlinePipeline<?> pipeline) { // 在线支付通道
  137 + if (pipeline instanceof OnlinePipeline<?> onlinePipeline) { // 在线支付通道
138 138 // 在线支付通道: 不同的收银台类型使用不同的支付方式(目前只支持小程序收银台)
139 139 // 小程序收银台将使用在线支付通道的小程序支付
140 140 MiniProPrepayRequest request = new MiniProPaymentConverter(trade, paymentId, now).convert(cashierPayment);
141   - MiniProPrepayResponse response = pipeline.sendMiniProPrepayRequest(request);
  141 + MiniProPrepayResponse response = onlinePipeline.sendMiniProPrepayRequest(request);
142 142 // 微信服务商模式下outMchId为签约子商户
143 143 String outMchId = request.getString(com.diligrp.cashier.pipeline.Constants.PARAM_MCH_ID);
144 144 OnlinePayment payment = OnlinePayment.builder().outMchId(outMchId).tradeId(trade.getTradeId())
145   - .type(TradeType.TRADE).paymentId(paymentId).channelId(pipeline.supportedChannel())
146   - .payType(PaymentType.MINI_PRO).pipelineId(pipeline.pipelineId()).goods(trade.getGoods())
  145 + .type(TradeType.TRADE).paymentId(paymentId).channelId(onlinePipeline.supportedChannel())
  146 + .payType(PaymentType.MINI_PRO).pipelineId(onlinePipeline.pipelineId()).goods(trade.getGoods())
147 147 .amount(trade.getAmount()).payerId(request.getOpenId()).outTradeNo(response.getOutTradeNo())
148 148 .state(response.getState()).version(0).createdTime(now).modifiedTime(now).build();
149 149 onlinePaymentDao.insertOnlinePayment(payment);
150 150 return response;
151 151 }
152   - if (paymentPipeline instanceof DiliCardPipeline pipeline) { // 园区卡支付通道
  152 + if (pipeline instanceof DiliCardPipeline cardPipeline) { // 园区卡支付通道
153 153 // 园区卡支付通道: 所有的收银台类型使用的是同一种园区卡支付流程
154 154 CardPaymentRequest request = new CardPaymentConverter(trade, paymentId, now).convert(cashierPayment);
155 155 // 修改支付状态为支付中,防止重复支付
156   - CardPaymentResponse response = pipeline.sendPaymentRequest(request);
157   - if (PaymentState.isFinished(response.getState().getCode())) {
  156 + CardPaymentResponse response = cardPipeline.sendPaymentRequest(request);
  157 + // 支付成功插入支付记录并修改交易订单状态,失败可以继续支付
  158 + if (response.getState() == PaymentState.SUCCESS) {
158 159 // 园区卡支付通道outMchId为市场ID
159   - String outMchId = pipeline.params().getOutMchId();
  160 + String outMchId = cardPipeline.params().getOutMchId();
160 161 OnlinePayment payment = OnlinePayment.builder().outMchId(outMchId).tradeId(trade.getTradeId())
161   - .type(TradeType.TRADE).paymentId(paymentId).channelId(pipeline.supportedChannel())
162   - .payType(PaymentType.DIRECT).pipelineId(pipeline.pipelineId()).goods(trade.getGoods())
  162 + .type(TradeType.TRADE).paymentId(paymentId).channelId(cardPipeline.supportedChannel())
  163 + .payType(PaymentType.DIRECT).pipelineId(cardPipeline.pipelineId()).goods(trade.getGoods())
163 164 .amount(trade.getAmount()).payerId(response.getPayerId())
164 165 .finishTime(response.getWhen()).outTradeNo(response.getOutTradeNo())
165 166 .outPayType(OutPaymentType.DILICARD).state(response.getState()).notifyUrl(trade.getNotifyUrl())
166 167 .description(response.getMessage()).version(0).createdTime(now).modifiedTime(now).build();
167 168 onlinePaymentDao.insertOnlinePayment(payment);
168 169  
169   - // 只有成功才修改交易订单状态,此时交易订单仍然可以继续支付
170   - if (response.getState() == PaymentState.SUCCESS) {
171   - TradeStateDTO tradeStateDTO = TradeStateDTO.of(trade.getTradeId(), TradeState.SUCCESS,
172   - trade.getVersion(), now);
173   - tradeAssistantService.proceedTradeOrder(tradeStateDTO);
174   - // 支付成功才通知业务系统支付结果,失败可以再次进行支付
175   - OnlinePaymentResult paymentResult = new OnlinePaymentResult(trade.getTradeId(), paymentId,
176   - response.getState().getCode(), trade.getOutTradeNo(), OutPaymentType.DILICARD.getCode(),
177   - response.getPayerId(), response.getWhen(), response.getMessage());
178   - paymentResultManager.notifyPaymentResult(trade.getNotifyUrl(), paymentResult);
179   - }
  170 + TradeStateDTO tradeStateDTO = TradeStateDTO.of(trade.getTradeId(), TradeState.SUCCESS,
  171 + trade.getVersion(), now);
  172 + tradeAssistantService.proceedTradeOrder(tradeStateDTO);
  173 + // 通知业务系统支付结果,
  174 + OnlinePaymentResult paymentResult = new OnlinePaymentResult(trade.getTradeId(), paymentId,
  175 + response.getState().getCode(), trade.getOutTradeNo(), OutPaymentType.DILICARD.getCode(),
  176 + response.getPayerId(), response.getWhen(), response.getMessage());
  177 + paymentResultManager.notifyPaymentResult(trade.getNotifyUrl(), paymentResult, 500);
180 178 }
181 179 return response;
182 180 } else {
... ... @@ -205,10 +203,10 @@ public class CashierPaymentServiceImpl implements ICashierPaymentService {
205 203 public void notifyPaymentResponse(OnlinePaymentResponse response) {
206 204 OnlinePayment payment = tradeAssistantService.findByPaymentId(response.getPaymentId());
207 205 if (PaymentState.isFinished(payment.getState())) {
208   - LOG.warn("Duplicate process payment result notification for {}:{}", payment.getPaymentId(), response.getState());
  206 + LOG.warn("Duplicate process payment response: [{}:{}]", payment.getPaymentId(), response.getState());
209 207 return;
210 208 }
211   - LOG.info("Processing payment result notification: [{},{}]", payment.getPaymentId(), response.getState());
  209 + LOG.info("Processing payment response: [{},{}]", payment.getPaymentId(), response.getState());
212 210  
213 211 if (PaymentState.isFinished(response.getState().getCode())) {
214 212 LocalDateTime now = LocalDateTime.now();
... ... @@ -224,14 +222,14 @@ public class CashierPaymentServiceImpl implements ICashierPaymentService {
224 222 tradeAssistantService.proceedOnlinePayment(paymentDTO);
225 223  
226 224 // 交易订单未完成,支付订单成功支付时处理交易订单,并通知业务系统支付结果;如果支付订单支付失败,收银台交易订单可以继续支付
227   - if (TradeState.PENDING.equalTo(trade.getState()) && PaymentState.SUCCESS == response.getState()) {
  225 + if (PaymentState.SUCCESS == response.getState() && TradeState.PENDING.equalTo(trade.getState())) {
228 226 TradeStateDTO tradeStateDTO = TradeStateDTO.of(trade.getTradeId(), TradeState.SUCCESS, trade.getVersion(), now);
229 227 tradeAssistantService.proceedTradeOrder(tradeStateDTO);
230   -
  228 + LOG.debug("Accomplish cashier order[{}] successfully", trade.getTradeId());
231 229 OnlinePaymentResult paymentResult = OnlinePaymentResult.of(trade.getTradeId(), payment.getPaymentId(),
232 230 response.getState(), trade.getOutTradeNo(), response.getOutPayType(), response.getPayerId(),
233 231 response.getWhen() != null ? response.getWhen() : now, response.getMessage());
234   - paymentResultManager.notifyPaymentResult(trade.getNotifyUrl(), paymentResult);
  232 + paymentResultManager.notifyPaymentResult(trade.getNotifyUrl(), paymentResult, 500);
235 233 }
236 234 } finally {
237 235 if (lock.isHeldByCurrentThread()) {
... ... @@ -280,6 +278,7 @@ public class CashierPaymentServiceImpl implements ICashierPaymentService {
280 278  
281 279 @Override
282 280 public OnlinePaymentResult queryPaymentState(String paymentId, String mode) {
  281 + // TODO: 支付成功后返回支付成功的页面地址
283 282 OnlinePayment payment = tradeAssistantService.findByPaymentId(paymentId);
284 283 TradeOrder trade = tradeAssistantService.findByTradeId(payment.getTradeId());
285 284  
... ...
scripts/cashier-data.sql 0 → 100644
  1 +INSERT INTO upay_merchant(mch_id, name, param, address, linkman, telephone, state, created_time, modified_time)
  2 +VALUES (1001, '安徽省中瑞农副产品有限责任公司', '{"cashier":{"miniProUrl": "https://cashier.pay.gszdtop.com/merchant/mp"}}', '安徽省濉溪县南环路中瑞农产品批发市场', '赵静', '0561-6863420', 1, now(), now());
0 3 \ No newline at end of file
... ...