Commit e6fca59aabbbdcdf42a325bdab38c530274d9e7d

Authored by huanggang
1 parent 1b8f43e7

trade payment supported

Showing 77 changed files with 3078 additions and 285 deletions
cashier-assistant/src/main/java/com/diligrp/cashier/assistant/pattern/OptionToken.java
... ... @@ -15,7 +15,7 @@ public class OptionToken extends Token {
15 15  
16 16 @Override
17 17 Converter<SequenceKey> getConverter() {
18   - throw new AssistantServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "Not supported converter");
  18 + throw new AssistantServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "Not supported codec");
19 19 }
20 20  
21 21 public String toString() {
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/BossConfiguration.java
1 1 package com.diligrp.cashier.boss;
2 2  
  3 +import com.diligrp.cashier.shared.mybatis.MybatisMapperSupport;
3 4 import com.diligrp.cashier.shared.service.LifeCycle;
4   -import org.springframework.beans.BeansException;
  5 +import jakarta.annotation.Resource;
  6 +import org.mybatis.spring.annotation.MapperScan;
  7 +import org.springframework.beans.factory.ObjectProvider;
5 8 import org.springframework.boot.ApplicationArguments;
6 9 import org.springframework.boot.ApplicationRunner;
7   -import org.springframework.context.ApplicationContext;
8   -import org.springframework.context.ApplicationContextAware;
  10 +import org.springframework.boot.context.properties.ConfigurationProperties;
  11 +import org.springframework.context.annotation.Bean;
9 12 import org.springframework.context.annotation.ComponentScan;
10 13 import org.springframework.context.annotation.Configuration;
11 14  
12   -import java.util.Map;
  15 +import java.util.List;
13 16  
14 17 @Configuration
15 18 @ComponentScan("com.diligrp.cashier.boss")
16   -public class BossConfiguration implements ApplicationContextAware, ApplicationRunner {
  19 +@MapperScan(basePackages = {"com.diligrp.cashier.boss.dao"}, markerInterface = MybatisMapperSupport.class)
  20 +public class BossConfiguration implements ApplicationRunner {
17 21  
18   - private ApplicationContext applicationContext;
  22 + @Resource
  23 + private ObjectProvider<LifeCycle> lifeCycleProvider;
  24 +
  25 + @Bean
  26 + @ConfigurationProperties("merchant")
  27 + public CashierDeskProperties cashierDeskProperties() {
  28 + return new CashierDeskProperties();
  29 + }
19 30  
20 31 @Override
21 32 public void run(ApplicationArguments args) throws Exception {
22   - Map<String, LifeCycle> beans = applicationContext.getBeansOfType(LifeCycle.class);
23   - for (LifeCycle lifeCycle : beans.values()) {
  33 + List<LifeCycle> lifeCycles = lifeCycleProvider.stream().toList();
  34 + for (LifeCycle lifeCycle : lifeCycles) {
24 35 lifeCycle.start();
25 36 }
26 37 }
27   -
28   - @Override
29   - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
30   - this.applicationContext = applicationContext;
31   - }
32 38 }
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/CashierDeskProperties.java 0 → 100644
  1 +package com.diligrp.cashier.boss;
  2 +
  3 +import com.diligrp.cashier.trade.domain.MerchantParams;
  4 +
  5 +import javax.crypto.spec.SecretKeySpec;
  6 +
  7 +public class CashierDeskProperties {
  8 + // 密钥
  9 + private SecretKeySpec secretKey;
  10 + // 收银台配置
  11 + private MerchantParams.CashierParams cashier;
  12 +
  13 + public SecretKeySpec getSecretKey() {
  14 + return secretKey;
  15 + }
  16 +
  17 + public void setSecretKey(SecretKeySpec secretKey) {
  18 + this.secretKey = secretKey;
  19 + }
  20 +
  21 + public MerchantParams.CashierParams getCashier() {
  22 + return cashier;
  23 + }
  24 +
  25 + public void setCashier(MerchantParams.CashierParams cashier) {
  26 + this.cashier = cashier;
  27 + }
  28 +}
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/CashierServiceBootstrap.java
... ... @@ -4,6 +4,7 @@ import com.diligrp.cashier.assistant.AssistantConfiguration;
4 4 import com.diligrp.cashier.mall.MallConfiguration;
5 5 import com.diligrp.cashier.pipeline.PipelineConfiguration;
6 6 import com.diligrp.cashier.shared.SharedConfiguration;
  7 +import com.diligrp.cashier.trade.TradeConfiguration;
7 8 import org.springframework.boot.SpringApplication;
8 9 import org.springframework.boot.SpringBootConfiguration;
9 10 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
... ... @@ -12,13 +13,12 @@ import org.springframework.context.annotation.Import;
12 13  
13 14 @SpringBootConfiguration
14 15 @EnableAutoConfiguration
15   -@Import({BossConfiguration.class, PipelineConfiguration.class,
16   - AssistantConfiguration.class, SharedConfiguration.class,
17   - MallConfiguration.class
  16 +@Import({BossConfiguration.class, TradeConfiguration.class, PipelineConfiguration.class,
  17 + AssistantConfiguration.class, SharedConfiguration.class, MallConfiguration.class
18 18 })
19 19 @EnableDiscoveryClient
20 20 public class CashierServiceBootstrap {
21 21 public static void main(String[] args) {
22 22 SpringApplication.run(CashierServiceBootstrap.class, args);
23 23 }
24   -}
  24 +}
25 25 \ No newline at end of file
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/Constants.java 0 → 100644
  1 +package com.diligrp.cashier.boss;
  2 +
  3 +public final class Constants {
  4 + // 商户信息缓存Key
  5 + public static final String MERCHANT_REDIS_KEY = "cashier:merchant:%s";
  6 + // TOKEN信息缓存Key
  7 + public static final String TOKEN_REDIS_KEY = "cashier:token:%s";
  8 + // TOKEN参数名称
  9 + public static final String TOKEN_PARAM_NAME = "token";
  10 + // TOKEN签名算法
  11 + public static final String TOKEN_SIGN_ALGORITHM = "HmacSHA256";
  12 + // TOKEN的签名长度
  13 + public static final int TOKEN_SIGN_LENGTH = 8;
  14 + // TOKEN过期时长,单位秒
  15 + public static final long TOKEN_TIMEOUT_SECONDS = 60;
  16 +}
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/controller/CashierDeskController.java 0 → 100644
  1 +package com.diligrp.cashier.boss.controller;
  2 +
  3 +import com.diligrp.cashier.boss.domain.CashierOrderDTO;
  4 +import com.diligrp.cashier.boss.domain.CashierPaymentUrl;
  5 +import com.diligrp.cashier.boss.service.ICashierDeskService;
  6 +import com.diligrp.cashier.boss.service.IMerchantService;
  7 +import com.diligrp.cashier.boss.util.CashierOrderConverter;
  8 +import com.diligrp.cashier.pipeline.domain.OnlinePaymentStatus;
  9 +import com.diligrp.cashier.shared.domain.Message;
  10 +import com.diligrp.cashier.shared.util.AssertUtils;
  11 +import com.diligrp.cashier.trade.domain.CashierOrder;
  12 +import com.diligrp.cashier.trade.domain.CashierPayment;
  13 +import com.diligrp.cashier.trade.domain.Merchant;
  14 +import jakarta.annotation.Resource;
  15 +import org.springframework.web.bind.annotation.RequestBody;
  16 +import org.springframework.web.bind.annotation.RequestMapping;
  17 +import org.springframework.web.bind.annotation.RestController;
  18 +
  19 +@RestController
  20 +@RequestMapping("/payment/cashier")
  21 +public class CashierDeskController {
  22 +
  23 + @Resource
  24 + private IMerchantService merchantService;
  25 +
  26 + @Resource
  27 + private ICashierDeskService cashierDeskService;
  28 +
  29 + @RequestMapping("/submitOrder")
  30 + public Message<?> submitOrder(@RequestBody CashierOrderDTO request) {
  31 + // 基本参数校验
  32 + AssertUtils.notNull(request.getMchId(), "mchId missed");
  33 + AssertUtils.notEmpty(request.getUserId(), "userId missed");
  34 + AssertUtils.notNull(request.getCashierType(), "cashierType missed");
  35 + AssertUtils.notEmpty(request.getGoods(), "goods missed");
  36 + AssertUtils.notNull(request.getAmount(), "amount missed");
  37 + AssertUtils.isTrue(request.getAmount() > 0, "Invalid amount");
  38 + AssertUtils.notEmpty(request.getOutTradeNo(), "outTradeNo missed");
  39 +
  40 + CashierOrder cashierOrder = CashierOrderConverter.INSTANCE.convert(request);
  41 + Merchant merchant = merchantService.loadCashierMerchant(request.getMchId());
  42 + CashierPaymentUrl paymentUrl = cashierDeskService.doSubmit(merchant, cashierOrder);
  43 + return Message.success(paymentUrl);
  44 + }
  45 +
  46 + @RequestMapping("/orderPayment")
  47 + public Message<?> orderPayment(@RequestBody CashierPayment request) {
  48 + // 基本参数校验
  49 + AssertUtils.notEmpty(request.getTradeId(), "paymentId missed");
  50 + AssertUtils.notNull(request.getPipelineId(), "pipelineId missed");
  51 +
  52 + OnlinePaymentStatus paymentStatus = cashierDeskService.doPayment(request);
  53 + return Message.success(paymentStatus);
  54 + }
  55 +}
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/dao/IMerchantDao.java 0 → 100644
  1 +package com.diligrp.cashier.boss.dao;
  2 +
  3 +import com.diligrp.cashier.shared.mybatis.MybatisMapperSupport;
  4 +import com.diligrp.cashier.boss.model.MerchantDO;
  5 +import org.springframework.stereotype.Repository;
  6 +
  7 +import java.util.Optional;
  8 +
  9 +@Repository("merchantDao")
  10 +public interface IMerchantDao extends MybatisMapperSupport {
  11 + Optional<MerchantDO> findByMchId(Long mchId);
  12 +}
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/domain/CashierOrderDTO.java 0 → 100644
  1 +package com.diligrp.cashier.boss.domain;
  2 +
  3 +public class CashierOrderDTO {
  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 description;
  22 + // 附加数据
  23 + private String attach;
  24 +
  25 + public Long getMchId() {
  26 + return mchId;
  27 + }
  28 +
  29 + public void setMchId(Long mchId) {
  30 + this.mchId = mchId;
  31 + }
  32 +
  33 + public String getUserId() {
  34 + return userId;
  35 + }
  36 +
  37 + public void setUserId(String userId) {
  38 + this.userId = userId;
  39 + }
  40 +
  41 + public Integer getCashierType() {
  42 + return cashierType;
  43 + }
  44 +
  45 + public void setCashierType(Integer cashierType) {
  46 + this.cashierType = cashierType;
  47 + }
  48 +
  49 + public String getGoods() {
  50 + return goods;
  51 + }
  52 +
  53 + public void setGoods(String goods) {
  54 + this.goods = goods;
  55 + }
  56 +
  57 + public Long getAmount() {
  58 + return amount;
  59 + }
  60 +
  61 + public void setAmount(Long amount) {
  62 + this.amount = amount;
  63 + }
  64 +
  65 + public Integer getTimeout() {
  66 + return timeout;
  67 + }
  68 +
  69 + public void setTimeout(Integer timeout) {
  70 + this.timeout = timeout;
  71 + }
  72 +
  73 + public String getOutTradeNo() {
  74 + return outTradeNo;
  75 + }
  76 +
  77 + public void setOutTradeNo(String outTradeNo) {
  78 + this.outTradeNo = outTradeNo;
  79 + }
  80 +
  81 + public String getNotifyUrl() {
  82 + return notifyUrl;
  83 + }
  84 +
  85 + public void setNotifyUrl(String notifyUrl) {
  86 + this.notifyUrl = notifyUrl;
  87 + }
  88 +
  89 + public String getDescription() {
  90 + return description;
  91 + }
  92 +
  93 + public void setDescription(String description) {
  94 + this.description = description;
  95 + }
  96 +
  97 + public String getAttach() {
  98 + return attach;
  99 + }
  100 +
  101 + public void setAttach(String attach) {
  102 + this.attach = attach;
  103 + }
  104 +}
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/domain/CashierOrderToken.java 0 → 100644
  1 +package com.diligrp.cashier.boss.domain;
  2 +
  3 +import com.diligrp.cashier.boss.Constants;
  4 +import com.diligrp.cashier.boss.exception.BossServiceException;
  5 +import com.diligrp.cashier.shared.ErrorCode;
  6 +import com.diligrp.cashier.shared.security.Base62Cipher;
  7 +import com.diligrp.cashier.shared.security.HexUtils;
  8 +import com.diligrp.cashier.shared.util.JsonUtils;
  9 +
  10 +import javax.crypto.Mac;
  11 +import javax.crypto.spec.SecretKeySpec;
  12 +import java.nio.charset.StandardCharsets;
  13 +import java.util.Objects;
  14 +
  15 +public class CashierOrderToken {
  16 + // 商户号
  17 + private Long mchId;
  18 + // 交易ID
  19 + private String tradeId;
  20 + // 收银台类型
  21 + private Integer cashierType;
  22 + // 业务系统用户标识
  23 + private String userId;
  24 +
  25 + public static String encode(Long tokenId, SecretKeySpec secretKey) {
  26 + try {
  27 + String payload = Base62Cipher.encodeLong(tokenId);
  28 + Mac mac = Mac.getInstance(Constants.TOKEN_SIGN_ALGORITHM);
  29 + mac.init(secretKey);
  30 +
  31 + byte[] bytes = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
  32 + String signature = HexUtils.encodeHexStr(bytes).substring(0, Constants.TOKEN_SIGN_LENGTH);
  33 + payload = payload + signature;
  34 + return payload;
  35 + } catch (Exception ex) {
  36 + throw new BossServiceException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "Failed to generate order token");
  37 + }
  38 + }
  39 +
  40 + public static long decode(String token, SecretKeySpec secretKey) {
  41 + if (token == null || token.length() <= Constants.TOKEN_SIGN_LENGTH) {
  42 + throw new BossServiceException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "Invalid order token");
  43 + }
  44 +
  45 + String payload = token.substring(0, token.length() - Constants.TOKEN_SIGN_LENGTH);
  46 + String signature = token.substring(token.length() - Constants.TOKEN_SIGN_LENGTH);
  47 + String tokenSign;
  48 + try {
  49 + Mac mac = Mac.getInstance(Constants.TOKEN_SIGN_ALGORITHM);
  50 + mac.init(secretKey);
  51 + byte[] bytes = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
  52 + tokenSign = HexUtils.encodeHexStr(bytes).substring(0, Constants.TOKEN_SIGN_LENGTH);
  53 + } catch (Exception ex) {
  54 + throw new BossServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "Invalid order token");
  55 + }
  56 +
  57 + if (!Objects.equals(signature, tokenSign)) {
  58 + throw new BossServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "Invalid order token signature");
  59 + }
  60 + return Base62Cipher.decodeLong(payload);
  61 + }
  62 +
  63 + public static CashierOrderToken decode(String payload) {
  64 + if (payload != null) {
  65 + return JsonUtils.fromJsonString(payload, CashierOrderToken.class);
  66 + }
  67 + return null;
  68 + }
  69 +
  70 + public CashierOrderToken() {
  71 + }
  72 +
  73 + public CashierOrderToken(Long mchId, String tradeId, Integer cashierType, String userId) {
  74 + this.mchId = mchId;
  75 + this.tradeId = tradeId;
  76 + this.cashierType = cashierType;
  77 + this.userId = userId;
  78 + }
  79 +
  80 + public Long getMchId() {
  81 + return mchId;
  82 + }
  83 +
  84 + public void setMchId(Long mchId) {
  85 + this.mchId = mchId;
  86 + }
  87 +
  88 + public String getTradeId() {
  89 + return tradeId;
  90 + }
  91 +
  92 + public void setTradeId(String tradeId) {
  93 + this.tradeId = tradeId;
  94 + }
  95 +
  96 + public Integer getCashierType() {
  97 + return cashierType;
  98 + }
  99 +
  100 + public void setCashierType(Integer cashierType) {
  101 + this.cashierType = cashierType;
  102 + }
  103 +
  104 + public String getUserId() {
  105 + return userId;
  106 + }
  107 +
  108 + public void setUserId(String userId) {
  109 + this.userId = userId;
  110 + }
  111 +
  112 + @Override
  113 + public String toString() {
  114 + return JsonUtils.toJsonString(this);
  115 + }
  116 +}
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/domain/CashierPaymentUrl.java 0 → 100644
  1 +package com.diligrp.cashier.boss.domain;
  2 +
  3 +/**
  4 + * @param tradeId 交易号
  5 + * @param paymentUrl 收银台支付链接
  6 + */
  7 +public record CashierPaymentUrl(String tradeId, String paymentUrl) {
  8 +}
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/exception/BossServiceException.java 0 → 100644
  1 +package com.diligrp.cashier.boss.exception;
  2 +
  3 +import com.diligrp.cashier.shared.exception.PlatformServiceException;
  4 +
  5 +public class BossServiceException extends PlatformServiceException {
  6 + public BossServiceException(String message) {
  7 + super(message);
  8 + }
  9 +
  10 + public BossServiceException(String code, String message) {
  11 + super(code, message);
  12 + }
  13 +
  14 + public BossServiceException(String message, Throwable ex) {
  15 + super(message, ex);
  16 + }
  17 +}
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/model/MerchantDO.java 0 → 100644
  1 +package com.diligrp.cashier.boss.model;
  2 +
  3 +import com.diligrp.cashier.shared.domain.BaseDO;
  4 +
  5 +import java.time.LocalDateTime;
  6 +
  7 +public class MerchantDO extends BaseDO {
  8 + // 商户ID
  9 + private Long mchId;
  10 + // 商户名称
  11 + private String name;
  12 + // 参数配置
  13 + private String param;
  14 + // 商户地址
  15 + private String address;
  16 + // 联系人
  17 + private String linkman;
  18 + // 手机号
  19 + private String telephone;
  20 + // 商户状态
  21 + private Integer state;
  22 +
  23 + public Long getMchId() {
  24 + return mchId;
  25 + }
  26 +
  27 + public void setMchId(Long mchId) {
  28 + this.mchId = mchId;
  29 + }
  30 +
  31 + public String getName() {
  32 + return name;
  33 + }
  34 +
  35 + public void setName(String name) {
  36 + this.name = name;
  37 + }
  38 +
  39 + public String getParam() {
  40 + return param;
  41 + }
  42 +
  43 + public void setParam(String param) {
  44 + this.param = param;
  45 + }
  46 +
  47 + public String getAddress() {
  48 + return address;
  49 + }
  50 +
  51 + public void setAddress(String address) {
  52 + this.address = address;
  53 + }
  54 +
  55 + public String getLinkman() {
  56 + return linkman;
  57 + }
  58 +
  59 + public void setLinkman(String linkman) {
  60 + this.linkman = linkman;
  61 + }
  62 +
  63 + public String getTelephone() {
  64 + return telephone;
  65 + }
  66 +
  67 + public void setTelephone(String telephone) {
  68 + this.telephone = telephone;
  69 + }
  70 +
  71 + public Integer getState() {
  72 + return state;
  73 + }
  74 +
  75 + public void setState(Integer state) {
  76 + this.state = state;
  77 + }
  78 +
  79 + public static Builder builder() {
  80 + return new MerchantDO().new Builder();
  81 + }
  82 +
  83 + public class Builder {
  84 + public Builder mchId(Long mchId) {
  85 + MerchantDO.this.mchId = mchId;
  86 + return this;
  87 + }
  88 +
  89 + public Builder name(String name) {
  90 + MerchantDO.this.name = name;
  91 + return this;
  92 + }
  93 +
  94 + public Builder param(String param) {
  95 + MerchantDO.this.param = param;
  96 + return this;
  97 + }
  98 +
  99 + public Builder address(String address) {
  100 + MerchantDO.this.address = address;
  101 + return this;
  102 + }
  103 +
  104 + public Builder linkman(String linkman) {
  105 + MerchantDO.this.linkman = linkman;
  106 + return this;
  107 + }
  108 +
  109 + public Builder telephone(String telephone) {
  110 + MerchantDO.this.telephone = telephone;
  111 + return this;
  112 + }
  113 +
  114 + public Builder state(Integer state) {
  115 + MerchantDO.this.state = state;
  116 + return this;
  117 + }
  118 +
  119 + public Builder createdTime(LocalDateTime createdTime) {
  120 + MerchantDO.this.createdTime = createdTime;
  121 + return this;
  122 + }
  123 +
  124 + public Builder modifiedTime(LocalDateTime modifiedTime) {
  125 + MerchantDO.this.modifiedTime = modifiedTime;
  126 + return this;
  127 + }
  128 +
  129 + public MerchantDO build() {
  130 + return MerchantDO.this;
  131 + }
  132 + }
  133 +}
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/service/ICashierDeskService.java 0 → 100644
  1 +package com.diligrp.cashier.boss.service;
  2 +
  3 +import com.diligrp.cashier.boss.domain.CashierPaymentUrl;
  4 +import com.diligrp.cashier.pipeline.domain.OnlinePaymentStatus;
  5 +import com.diligrp.cashier.trade.domain.CashierOrder;
  6 +import com.diligrp.cashier.trade.domain.CashierPayment;
  7 +import com.diligrp.cashier.trade.domain.Merchant;
  8 +
  9 +public interface ICashierDeskService {
  10 + /**
  11 + * 提交收银台订单
  12 + *
  13 + * @param merchant - 接入商户
  14 + * @param order - 交易订单
  15 + * @return 交易订单
  16 + */
  17 + CashierPaymentUrl doSubmit(Merchant merchant, CashierOrder order);
  18 +
  19 + /**
  20 + * 提交收银台支付
  21 + *
  22 + * @param payment - 支付信息
  23 + * @return 支付状态
  24 + */
  25 + OnlinePaymentStatus doPayment(CashierPayment payment);
  26 +}
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/service/IMerchantService.java 0 → 100644
  1 +package com.diligrp.cashier.boss.service;
  2 +
  3 +import com.diligrp.cashier.trade.domain.Merchant;
  4 +
  5 +public interface IMerchantService {
  6 +
  7 + Merchant loadCashierMerchant(Long mchId);
  8 +}
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/service/impl/CashierDeskServiceImpl.java 0 → 100644
  1 +package com.diligrp.cashier.boss.service.impl;
  2 +
  3 +import com.diligrp.cashier.boss.CashierDeskProperties;
  4 +import com.diligrp.cashier.boss.Constants;
  5 +import com.diligrp.cashier.boss.domain.CashierOrderToken;
  6 +import com.diligrp.cashier.boss.domain.CashierPaymentUrl;
  7 +import com.diligrp.cashier.boss.service.ICashierDeskService;
  8 +import com.diligrp.cashier.pipeline.domain.OnlinePaymentStatus;
  9 +import com.diligrp.cashier.trade.domain.CashierOrder;
  10 +import com.diligrp.cashier.trade.domain.CashierPayment;
  11 +import com.diligrp.cashier.trade.domain.Merchant;
  12 +import com.diligrp.cashier.trade.domain.MerchantParams;
  13 +import com.diligrp.cashier.trade.service.ICashierPaymentService;
  14 +import jakarta.annotation.Resource;
  15 +import org.springframework.data.redis.core.StringRedisTemplate;
  16 +import org.springframework.stereotype.Service;
  17 +
  18 +import java.util.concurrent.TimeUnit;
  19 +
  20 +@Service("cashierDeskService")
  21 +public class CashierDeskServiceImpl implements ICashierDeskService {
  22 +
  23 + @Resource
  24 + private ICashierPaymentService cashierPaymentService;
  25 +
  26 + @Resource
  27 + private CashierDeskProperties cashierDeskProperties;
  28 +
  29 + @Resource
  30 + private StringRedisTemplate stringRedisTemplate;
  31 +
  32 + /**
  33 + * 提交收银台订单
  34 + *
  35 + * @param merchant - 接入商户
  36 + * @param order - 交易订单
  37 + * @return 交易订单
  38 + */
  39 + @Override
  40 + public CashierPaymentUrl doSubmit(Merchant merchant, CashierOrder order) {
  41 + String tradeId = cashierPaymentService.doSubmit(merchant, order);
  42 + String token = CashierOrderToken.encode(Long.valueOf(tradeId), cashierDeskProperties.getSecretKey());
  43 + CashierOrderToken orderToken = new CashierOrderToken(merchant.getMchId(), tradeId, order.getType().getCode(), order.getUserId());
  44 +
  45 + String tokenKey = String.format(Constants.TOKEN_REDIS_KEY, token);
  46 + stringRedisTemplate.opsForValue().set(tokenKey, orderToken.toString(), Constants.TOKEN_TIMEOUT_SECONDS, TimeUnit.SECONDS);
  47 + MerchantParams params = merchant.getParams();
  48 + String paymentUrl = switch (order.getType()) {
  49 + case MINIPRO -> String.format("%s?%s=%s", params.getCashier().getMiniProUrl(), Constants.TOKEN_PARAM_NAME, token);
  50 + case H5 -> String.format("%s?%s=%s", params.getCashier().getH5Url(), Constants.TOKEN_PARAM_NAME, token);
  51 + case APP -> String.format("%s?%s=%s", params.getCashier().getAppUrl(), Constants.TOKEN_PARAM_NAME, token);
  52 + default -> String.format("%s?%s=%s", params.getCashier().getPcUrl(), Constants.TOKEN_PARAM_NAME, token);
  53 + };
  54 + return new CashierPaymentUrl(tradeId, paymentUrl);
  55 + }
  56 +
  57 + /**
  58 + * 提交收银台支付
  59 + *
  60 + * @param payment - 支付信息
  61 + * @return 支付状态
  62 + */
  63 + @Override
  64 + public OnlinePaymentStatus doPayment(CashierPayment payment) {
  65 + return cashierPaymentService.doPayment(payment);
  66 + // TODO 是否需要删除token
  67 +// String token = CashierOrderToken.encode(Long.valueOf(payment.getTradeId()), cashierDeskProperties.getSecretKey());
  68 +// String tokenKey = String.format(Constants.TOKEN_REDIS_KEY, token);
  69 +// redisTemplate.delete(tokenKey);
  70 + }
  71 +}
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/service/impl/MerchantServiceImpl.java 0 → 100644
  1 +package com.diligrp.cashier.boss.service.impl;
  2 +
  3 +import com.diligrp.cashier.boss.CashierDeskProperties;
  4 +import com.diligrp.cashier.boss.Constants;
  5 +import com.diligrp.cashier.boss.dao.IMerchantDao;
  6 +import com.diligrp.cashier.boss.exception.BossServiceException;
  7 +import com.diligrp.cashier.boss.service.IMerchantService;
  8 +import com.diligrp.cashier.boss.util.MerchantConverter;
  9 +import com.diligrp.cashier.shared.ErrorCode;
  10 +import com.diligrp.cashier.trade.domain.Merchant;
  11 +import jakarta.annotation.Resource;
  12 +import org.springframework.data.redis.core.StringRedisTemplate;
  13 +import org.springframework.stereotype.Service;
  14 +
  15 +import java.util.Objects;
  16 +
  17 +/**
  18 + * 支付平台接入许可服务
  19 + */
  20 +@Service("merchantService")
  21 +public class MerchantServiceImpl implements IMerchantService {
  22 +
  23 + @Resource
  24 + private IMerchantDao merchantDao;
  25 +
  26 + @Resource
  27 + private CashierDeskProperties cashierDeskProperties;
  28 +
  29 + @Resource
  30 + private StringRedisTemplate stringRedisTemplate;
  31 +
  32 + @Override
  33 + public Merchant loadCashierMerchant(Long mchId) {
  34 + String cacheKey = String.format(Constants.MERCHANT_REDIS_KEY, mchId);
  35 + String payload = stringRedisTemplate.opsForValue().get(cacheKey);
  36 + Merchant merchant = Merchant.decode(payload);
  37 + if (Objects.isNull(merchant)) {
  38 + merchant = merchantDao.findByMchId(mchId).map(new MerchantConverter(cashierDeskProperties)::convert)
  39 + .orElseThrow(() -> new BossServiceException(ErrorCode.OBJECT_NOT_FOUND, "商家信息未注册"));
  40 + stringRedisTemplate.opsForValue().set(cacheKey, merchant.toString());
  41 + }
  42 +
  43 + return merchant;
  44 + }
  45 +}
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/util/CashierOrderConverter.java 0 → 100644
  1 +package com.diligrp.cashier.boss.util;
  2 +
  3 +import com.diligrp.cashier.boss.domain.CashierOrderDTO;
  4 +import com.diligrp.cashier.pipeline.type.CashierType;
  5 +import com.diligrp.cashier.shared.codec.IConverter;
  6 +import com.diligrp.cashier.trade.domain.CashierOrder;
  7 +
  8 +public class CashierOrderConverter implements IConverter<CashierOrderDTO, CashierOrder> {
  9 +
  10 + public static IConverter<CashierOrderDTO, CashierOrder> INSTANCE = new CashierOrderConverter();
  11 +
  12 + @Override
  13 + public CashierOrder convert(CashierOrderDTO 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.setDescription(cashierOrderDTO.getDescription());
  23 + cashierOrder.setAttach(cashierOrderDTO.getAttach());
  24 + return cashierOrder;
  25 + }
  26 +}
... ...
cashier-boss/src/main/java/com/diligrp/cashier/boss/util/MerchantConverter.java 0 → 100644
  1 +package com.diligrp.cashier.boss.util;
  2 +
  3 +import com.diligrp.cashier.boss.CashierDeskProperties;
  4 +import com.diligrp.cashier.boss.model.MerchantDO;
  5 +import com.diligrp.cashier.shared.codec.IConverter;
  6 +import com.diligrp.cashier.shared.util.JsonUtils;
  7 +import com.diligrp.cashier.trade.domain.Merchant;
  8 +import com.diligrp.cashier.trade.domain.MerchantParams;
  9 +
  10 +import java.util.Objects;
  11 +
  12 +public class MerchantConverter implements IConverter<MerchantDO, Merchant> {
  13 +
  14 + private final CashierDeskProperties cashierProperties;
  15 +
  16 + public MerchantConverter(CashierDeskProperties cashierProperties) {
  17 + this.cashierProperties = cashierProperties;
  18 + }
  19 +
  20 + @Override
  21 + public Merchant convert(MerchantDO merchantDO) {
  22 + Merchant merchant = new Merchant();
  23 + merchant.setMchId(merchantDO.getMchId());
  24 + merchant.setName(merchantDO.getName());
  25 + if (Objects.nonNull(merchantDO.getParam())) {
  26 + merchant.setParams(JsonUtils.fromJsonString(merchantDO.getParam(), MerchantParams.class));
  27 + } else {
  28 + merchant.setParams(new MerchantParams());
  29 + }
  30 + MerchantParams params = merchant.getParams();
  31 +
  32 + if (Objects.isNull(params.getCashier())) {
  33 + params.setCashier(new MerchantParams.CashierParams());
  34 + }
  35 + params.getCashier().override(cashierProperties.getCashier());
  36 +
  37 + return merchant;
  38 + }
  39 +}
... ...
cashier-boss/src/main/resources/com/diligrp/cashier/dao/mapper/IMerchantDao.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8" ?>
  2 +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  3 + "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  4 +
  5 +<mapper namespace="com.diligrp.cashier.boss.dao.IMerchantDao">
  6 + <resultMap id="MerchantMap" type="com.diligrp.cashier.boss.model.MerchantDO">
  7 + <result column="id" property="id"/>
  8 + <result column="mch_id" property="mchId"/>
  9 + <result column="name" property="name"/>
  10 + <result column="param" property="param"/>
  11 + <result column="address" property="address"/>
  12 + <result column="linkman" property="linkman"/>
  13 + <result column="telephone" property="telephone"/>
  14 + <result column="state" property="state"/>
  15 + <result column="created_time" property="createdTime"/>
  16 + <result column="modified_time" property="modifiedTime"/>
  17 + </resultMap>
  18 +
  19 + <select id="findByMchId" parameterType="long" resultMap="MerchantMap">
  20 + SELECT * FROM upay_merchant WHERE mch_id = #{mchId}
  21 + </select>
  22 +</mapper>
... ...
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/client/CardPaymentHttpClient.java
1 1 package com.diligrp.cashier.pipeline.client;
2 2  
3 3 import com.diligrp.cashier.pipeline.domain.*;
  4 +import com.diligrp.cashier.pipeline.domain.card.CardPaymentRequest;
  5 +import com.diligrp.cashier.pipeline.domain.card.CardPaymentResponse;
4 6 import com.diligrp.cashier.pipeline.type.PaymentState;
5 7 import com.diligrp.cashier.shared.service.ServiceEndpointSupport;
6 8 import org.slf4j.Logger;
... ... @@ -20,11 +22,11 @@ public class CardPaymentHttpClient extends ServiceEndpointSupport {
20 22 this.baseUri = baseUri;
21 23 }
22 24  
23   - public OnlinePaymentResponse sendPaymentRequest(OnlinePaymentRequest request) {
  25 + public CardPaymentResponse sendPaymentRequest(CardPaymentRequest request) {
24 26 return null;
25 27 }
26 28  
27   - public OnlinePaymentResponse queryPrepayResponse(OnlinePrepayOrder order) {
  29 + public CardPaymentResponse queryPaymentResponse(OnlinePrepayOrder order) {
28 30 return null;
29 31 }
30 32  
... ...
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/client/WechatDirectHttpClient.java
... ... @@ -68,7 +68,7 @@ public class WechatDirectHttpClient extends WechatHttpClient {
68 68 verifyHttpResult(result);
69 69 if (result.statusCode == 200) { // 200 处理成功有返回,204处理成功无返回
70 70 Map<String, Object> response = JsonUtils.fromJsonString(result.responseText, new TypeReference<>() {});
71   - return NativePrepayResponse.of(request.getPaymentId(), null, (String) response.get("code_url"));
  71 + return NativePrepayResponse.of(request.getPaymentId(), (String) response.get("code_url"));
72 72 } else {
73 73 LOG.error("Wechat native prepay failed: {}\n{}", result.statusCode, result.responseText);
74 74 ErrorMessage message = JsonUtils.fromJsonString(result.responseText, ErrorMessage.class);
... ... @@ -131,7 +131,7 @@ public class WechatDirectHttpClient extends WechatHttpClient {
131 131 String openId = payer == null ? null : (String) payer.get("openid");
132 132 PaymentState state = WechatStateUtils.getPaymentState((String) response.get("trade_state"));
133 133 return OnlinePaymentResponse.of((String) response.get("out_trade_no"), (String) response.get("transaction_id"),
134   - openId, when, state.getCode(), (String) response.get("trade_state_desc"));
  134 + openId, when, state, (String) response.get("trade_state_desc"));
135 135 } else {
136 136 LOG.info("Wechat query transaction status failed: {}", result.statusCode);
137 137 ErrorMessage message = JsonUtils.fromJsonString(result.responseText, ErrorMessage.class);
... ...
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/client/WechatPartnerHttpClient.java
... ... @@ -69,7 +69,7 @@ public class WechatPartnerHttpClient extends WechatHttpClient {
69 69 verifyHttpResult(result);
70 70 if (result.statusCode == 200) { // 200 处理成功有返回,204处理成功无返回
71 71 Map<String, Object> response = JsonUtils.fromJsonString(result.responseText, new TypeReference<>() {});
72   - return NativePrepayResponse.of(request.getPaymentId(), null, (String) response.get("code_url"));
  72 + return NativePrepayResponse.of(request.getPaymentId(), (String) response.get("code_url"));
73 73 } else {
74 74 LOG.error("Wechat native prepay failed: {}\n{}", result.statusCode, result.responseText);
75 75 ErrorMessage message = JsonUtils.fromJsonString(result.responseText, ErrorMessage.class);
... ... @@ -135,7 +135,7 @@ public class WechatPartnerHttpClient extends WechatHttpClient {
135 135 String openId = payer == null ? null : (String) payer.get("sp_openid"); // 获取服务商APPID下的openId,而非子商户APPID下的openId
136 136 PaymentState state = WechatStateUtils.getPaymentState((String) response.get("trade_state"));
137 137 return OnlinePaymentResponse.of((String) response.get("out_trade_no"), (String) response.get("transaction_id"),
138   - openId, when, state.getCode(), (String) response.get("trade_state_desc"));
  138 + openId, when, state, (String) response.get("trade_state_desc"));
139 139 } else {
140 140 LOG.info("Wechat query transaction status failed: {}", result.statusCode);
141 141 ErrorMessage message = JsonUtils.fromJsonString(result.responseText, ErrorMessage.class);
... ...
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/core/CardPipeline.java deleted 100644 → 0
1   -package com.diligrp.cashier.pipeline.core;
2   -
3   -import com.diligrp.cashier.pipeline.client.CardPaymentHttpClient;
4   -import com.diligrp.cashier.pipeline.domain.*;
5   -import com.diligrp.cashier.pipeline.exception.PaymentPipelineException;
6   -import com.diligrp.cashier.shared.ErrorCode;
7   -import org.slf4j.Logger;
8   -import org.slf4j.LoggerFactory;
9   -
10   -/**
11   - * 园区卡支付通道抽象模型
12   - */
13   -public abstract class CardPipeline extends PaymentPipeline<CardPipeline.CardParams> {
14   -
15   - private static final Logger LOG = LoggerFactory.getLogger(CardPipeline.class);
16   -
17   - private final ScanTimeStrategy strategy;
18   -
19   - private final CardPaymentHttpClient client;
20   -
21   - public CardPipeline(long mchId, long pipelineId, String name, String uri, String params) throws Exception {
22   - super(mchId, pipelineId, name, uri, params);
23   - this.strategy = new DefaultTimeStrategy();
24   - this.client = new CardPaymentHttpClient(uri);
25   - }
26   -
27   - /**
28   - * 发送预支付订单的支付申请 - 目前针对园区卡扫码支付
29   - */
30   - public OnlinePaymentResponse sendPaymentRequest(OnlinePaymentRequest request) {
31   - try {
32   - return client.sendPaymentRequest(request);
33   - } catch (PaymentPipelineException | IllegalArgumentException pse) {
34   - throw pse;
35   - } catch (Exception ex) {
36   - LOG.error("Send card payment request exception", ex);
37   - throw new PaymentPipelineException(ErrorCode.SYSTEM_UNKNOWN_ERROR, "发起地利支付申请失败");
38   - }
39   - }
40   -
41   - /**
42   - * 查询支付订单状态
43   - */
44   - public OnlinePaymentResponse queryPrepayResponse(OnlinePrepayOrder order) {
45   - try {
46   - return client.queryPrepayResponse(order);
47   - } catch (PaymentPipelineException | IllegalArgumentException pse) {
48   - throw pse;
49   - } catch (Exception ex) {
50   - LOG.error("Query card payment state request exception", ex);
51   - throw new PaymentPipelineException(ErrorCode.SYSTEM_UNKNOWN_ERROR, "查询地利支付状态失败");
52   - }
53   - }
54   -
55   - /**
56   - * 关闭预支付订单
57   - */
58   - public void closePrepayOrder(OnlinePrepayOrder request) {
59   - // 不调用远程关单接口
60   - }
61   -
62   - /**
63   - * 退款申请
64   - */
65   - public OnlineRefundResponse sendRefundRequest(OnlineRefundRequest request) {
66   - try {
67   - return client.sendRefundRequest(request);
68   - } catch (PaymentPipelineException | IllegalArgumentException pse) {
69   - throw pse;
70   - } catch (Exception ex) {
71   - LOG.error("Send card refund request exception", ex);
72   - throw new PaymentPipelineException(ErrorCode.SYSTEM_UNKNOWN_ERROR, "发起微信退款失败");
73   - }
74   - }
75   -
76   - public CardPaymentHttpClient getClient() {
77   - return this.client;
78   - }
79   -
80   - public ScanTimeStrategy getTimeStrategy() {
81   - return strategy;
82   - }
83   -
84   - @Override
85   - public Class<CardParams> paramClass() {
86   - return CardParams.class;
87   - }
88   -
89   - public static class CardParams extends PipelineParams {
90   - public CardParams(String params) {
91   - super(params);
92   - }
93   - }
94   -}
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/core/DiliCardPipeline.java
1 1 package com.diligrp.cashier.pipeline.core;
2 2  
  3 +import com.diligrp.cashier.pipeline.client.CardPaymentHttpClient;
  4 +import com.diligrp.cashier.pipeline.domain.OnlinePrepayOrder;
  5 +import com.diligrp.cashier.pipeline.domain.OnlineRefundRequest;
  6 +import com.diligrp.cashier.pipeline.domain.OnlineRefundResponse;
  7 +import com.diligrp.cashier.pipeline.domain.card.CardPaymentRequest;
  8 +import com.diligrp.cashier.pipeline.domain.card.CardPaymentResponse;
  9 +import com.diligrp.cashier.pipeline.exception.PaymentPipelineException;
3 10 import com.diligrp.cashier.pipeline.type.ChannelType;
  11 +import com.diligrp.cashier.shared.ErrorCode;
  12 +import org.slf4j.Logger;
  13 +import org.slf4j.LoggerFactory;
4 14  
5 15 /**
6   - * 地利园区支付通道模型
  16 + * 地利园区支付通道模型
7 17 */
8   -public class DiliCardPipeline extends CardPipeline {
  18 +public class DiliCardPipeline extends OnlinePipeline<DiliCardPipeline.CardParams> {
  19 +
  20 + private static final Logger LOG = LoggerFactory.getLogger(DiliCardPipeline.class);
  21 +
  22 + private final ScanTimeStrategy strategy;
  23 +
  24 + private final CardPaymentHttpClient client;
9 25  
10 26 public DiliCardPipeline(long mchId, long pipelineId, String name, String uri, String params) throws Exception {
11 27 super(mchId, pipelineId, name, uri, params);
  28 + this.strategy = new DefaultTimeStrategy();
  29 + this.client = new CardPaymentHttpClient(uri);
  30 + }
  31 +
  32 + /**
  33 + * 发送预支付订单的支付申请 - 目前针对园区卡扫码支付
  34 + */
  35 + public CardPaymentResponse sendPaymentRequest(CardPaymentRequest request) {
  36 + try {
  37 + return client.sendPaymentRequest(request);
  38 + } catch (PaymentPipelineException | IllegalArgumentException pse) {
  39 + throw pse;
  40 + } catch (Exception ex) {
  41 + LOG.error("Send card payment request exception", ex);
  42 + throw new PaymentPipelineException(ErrorCode.SYSTEM_UNKNOWN_ERROR, "发起地利支付申请失败");
  43 + }
  44 + }
  45 +
  46 + /**
  47 + * 查询支付订单状态
  48 + */
  49 + public CardPaymentResponse queryPaymentResponse(OnlinePrepayOrder order) {
  50 + try {
  51 + return client.queryPaymentResponse(order);
  52 + } catch (PaymentPipelineException | IllegalArgumentException pse) {
  53 + throw pse;
  54 + } catch (Exception ex) {
  55 + LOG.error("Query card payment state request exception", ex);
  56 + throw new PaymentPipelineException(ErrorCode.SYSTEM_UNKNOWN_ERROR, "查询地利支付状态失败");
  57 + }
  58 + }
  59 +
  60 + /**
  61 + * 关闭预支付订单
  62 + */
  63 + public void closePrepayOrder(OnlinePrepayOrder request) {
  64 + // 不调用远程关单接口
  65 + }
  66 +
  67 + /**
  68 + * 退款申请
  69 + */
  70 + public OnlineRefundResponse sendRefundRequest(OnlineRefundRequest request) {
  71 + try {
  72 + return client.sendRefundRequest(request);
  73 + } catch (PaymentPipelineException | IllegalArgumentException pse) {
  74 + throw pse;
  75 + } catch (Exception ex) {
  76 + LOG.error("Send card refund request exception", ex);
  77 + throw new PaymentPipelineException(ErrorCode.SYSTEM_UNKNOWN_ERROR, "发起微信退款失败");
  78 + }
12 79 }
13 80  
14 81 @Override
15 82 public ChannelType supportedChannel() {
16 83 return ChannelType.DILIPAY;
17 84 }
  85 +
  86 + public ScanTimeStrategy getTimeStrategy() {
  87 + return strategy;
  88 + }
  89 +
  90 + @Override
  91 + public Class<CardParams> paramClass() {
  92 + return CardParams.class;
  93 + }
  94 +
  95 + public static class CardParams extends PipelineParams {
  96 + // 园区卡所属市场
  97 + private String outMchId;
  98 +
  99 + public CardParams(String params) {
  100 + super(params);
  101 + }
  102 +
  103 + public String getOutMchId() {
  104 + return outMchId;
  105 + }
  106 +
  107 + public void setOutMchId(String outMchId) {
  108 + this.outMchId = outMchId;
  109 + }
  110 + }
18 111 }
... ...
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/core/OnlinePipeline.java
... ... @@ -5,9 +5,7 @@ import com.diligrp.cashier.pipeline.exception.PaymentPipelineException;
5 5 import com.diligrp.cashier.shared.ErrorCode;
6 6  
7 7 /**
8   - * 在线支付通道抽象模型
9   - *
10   - * TODO: 此支付通道类是否所有支付通道的基类(微信、支付宝、银行聚合支付),还是仅仅作为银行聚合支付的基类,需要仔细斟酌
  8 + * 在线支付通道抽象模型: 支持微信、支付宝、各类银行聚合支付, 但不包括园区卡支付通道
11 9 */
12 10 public abstract class OnlinePipeline<T extends PaymentPipeline.PipelineParams> extends PaymentPipeline<T> {
13 11  
... ... @@ -23,17 +21,17 @@ public abstract class OnlinePipeline&lt;T extends PaymentPipeline.PipelineParams&gt; e
23 21 }
24 22  
25 23 /**
26   - * 付款码支付 - 商户扫客户付款码
  24 + * 小程序支付
27 25 */
28   - public OnlinePaymentResponse sendQrCodePaymentRequest(OnlinePaymentRequest request) {
29   - throw new PaymentPipelineException(ErrorCode.OPERATION_NOT_ALLOWED, "支付通道暂不支持付款码支付");
  26 + public MiniProPrepayResponse sendMiniProPrepayRequest(MiniProPrepayRequest request) {
  27 + throw new PaymentPipelineException(ErrorCode.OPERATION_NOT_ALLOWED, "支付通道暂不支持小程序支付");
30 28 }
31 29  
32 30 /**
33   - * 小程序支付
  31 + * 直接支付 - 比如: 微信付款码, 园区卡支付等
34 32 */
35   - public MiniProPrepayResponse sendMiniProPrepayRequest(MiniProPrepayRequest request) {
36   - throw new PaymentPipelineException(ErrorCode.OPERATION_NOT_ALLOWED, "支付通道暂不支持小程序支付");
  33 + public OnlinePaymentResponse sendQrCodePaymentRequest(QrCodePaymentRequest request) {
  34 + throw new PaymentPipelineException(ErrorCode.OPERATION_NOT_ALLOWED, "支付通道暂不支持付款码支付");
37 35 }
38 36  
39 37 /**
... ...
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/core/PaymentPipeline.java
... ... @@ -8,6 +8,12 @@ import com.diligrp.cashier.shared.util.ObjectUtils;
8 8 import java.lang.reflect.Constructor;
9 9 import java.lang.reflect.InvocationTargetException;
10 10  
  11 +/**
  12 + * 所有支付通道的基类, 目前分两类支付通道: 在线支付通道、园区卡支付通道
  13 + *
  14 + * 在线支付通道支持: 微信支付、支付宝、各类银行聚合支付
  15 + * 园区卡支付通道是指地利一卡通支付
  16 + */
11 17 public abstract class PaymentPipeline<T extends PaymentPipeline.PipelineParams> implements IPipeline<T> {
12 18 // 通道所属商户
13 19 private final long mchId;
... ...
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/core/WechatPipeline.java
... ... @@ -97,14 +97,6 @@ public abstract class WechatPipeline extends OnlinePipeline&lt;WechatPipeline.Wecha
97 97 }
98 98  
99 99 /**
100   - * 付款码支付 - 商户扫客户付款码
101   - */
102   - @Override
103   - public OnlinePaymentResponse sendQrCodePaymentRequest(OnlinePaymentRequest request) {
104   - return super.sendQrCodePaymentRequest(request);
105   - }
106   -
107   - /**
108 100 * 小程序预支付下单
109 101 */
110 102 public MiniProPrepayResponse sendMiniProPrepayRequest(MiniProPrepayRequest request) {
... ... @@ -129,6 +121,14 @@ public abstract class WechatPipeline extends OnlinePipeline&lt;WechatPipeline.Wecha
129 121 }
130 122  
131 123 /**
  124 + * 付款码支付 - 商户扫客户付款码
  125 + */
  126 + @Override
  127 + public OnlinePaymentResponse sendQrCodePaymentRequest(QrCodePaymentRequest request) {
  128 + return super.sendQrCodePaymentRequest(request);
  129 + }
  130 +
  131 + /**
132 132 * 查询预支付订单状态
133 133 */
134 134 public OnlinePaymentResponse queryPrepayResponse(OnlinePrepayOrder order) {
... ...
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/domain/MiniProPrepayResponse.java
1 1 package com.diligrp.cashier.pipeline.domain;
2 2  
  3 +import com.diligrp.cashier.pipeline.type.PaymentState;
  4 +
3 5 /**
4 6 * 小程序预支付响应
5 7 */
6   -public class MiniProPrepayResponse extends OnlinePrepayResponse {
  8 +public class MiniProPrepayResponse extends OnlinePaymentStatus {
7 9 // 微信预支付ID
8 10 protected String prepayId;
9 11 // 时间戳
... ... @@ -17,16 +19,18 @@ public class MiniProPrepayResponse extends OnlinePrepayResponse {
17 19  
18 20 public static MiniProPrepayResponse of(String paymentId, String outTradeNo, String prepayId, String timeStamp,
19 21 String nonceStr, String signType, String paySign) {
20   - MiniProPrepayResponse response = new MiniProPrepayResponse();
21   - response.paymentId = paymentId;
22   - response.outTradeNo = outTradeNo;
23   - response.prepayId = prepayId;
24   - response.timeStamp = timeStamp;
25   - response.nonceStr = nonceStr;
26   - response.signType = signType;
27   - response.paySign = paySign;
28   -
29   - return response;
  22 + return new MiniProPrepayResponse(paymentId, outTradeNo, PaymentState.PROCESSING, prepayId, timeStamp,
  23 + nonceStr, signType, paySign);
  24 + }
  25 +
  26 + public MiniProPrepayResponse(String paymentId, String outTradeNo, PaymentState state, String prepayId,
  27 + String timeStamp, String nonceStr, String signType, String paySign) {
  28 + super(paymentId, outTradeNo, state);
  29 + this.prepayId = prepayId;
  30 + this.timeStamp = timeStamp;
  31 + this.nonceStr = nonceStr;
  32 + this.signType = signType;
  33 + this.paySign = paySign;
30 34 }
31 35  
32 36 public String getPrepayId() {
... ...
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/domain/NativePrepayResponse.java
1 1 package com.diligrp.cashier.pipeline.domain;
2 2  
  3 +import com.diligrp.cashier.pipeline.type.PaymentState;
  4 +
3 5 /**
4 6 * 扫码预支付响应
5 7 */
6   -public class NativePrepayResponse extends OnlinePrepayResponse {
  8 +public class NativePrepayResponse extends OnlinePaymentStatus {
7 9 // 二维码链接
8   - protected String codeUri;
  10 + protected String codeUrl;
  11 +
  12 + public static NativePrepayResponse of(String paymentId, String codeUrl) {
  13 + return new NativePrepayResponse(paymentId, null, PaymentState.PROCESSING, codeUrl);
  14 + }
9 15  
10   - public static NativePrepayResponse of(String paymentId, String outTradeNo, String codeUri) {
11   - NativePrepayResponse response = new NativePrepayResponse();
12   - response.paymentId = paymentId;
13   - response.outTradeNo = outTradeNo;
14   - response.codeUri = codeUri;
15   - return response;
  16 + public NativePrepayResponse(String paymentId, String outTradeNo, PaymentState state, String codeUrl) {
  17 + super(paymentId, outTradeNo, state);
  18 + this.codeUrl = codeUrl;
16 19 }
17 20  
18   - public String getCodeUri() {
19   - return codeUri;
  21 + public String getCodeUrl() {
  22 + return codeUrl;
20 23 }
21 24 }
... ...
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/domain/OnlinePaymentRequest.java
... ... @@ -10,15 +10,15 @@ import java.util.Map;
10 10 */
11 11 public class OnlinePaymentRequest extends ContainerSupport {
12 12 // 支付ID
13   - protected String paymentId;
  13 + protected final String paymentId;
14 14 // 交易金额 - 分
15   - protected long amount;
  15 + protected final long amount;
16 16 // 商品描述
17   - protected String goods;
  17 + protected final String goods;
18 18 // 交易备注
19   - protected String description;
  19 + protected final String description;
20 20 // 交易时间
21   - protected LocalDateTime when;
  21 + protected final LocalDateTime when;
22 22  
23 23 public OnlinePaymentRequest(String paymentId, long amount, String goods, String description, LocalDateTime when) {
24 24 this.paymentId = paymentId;
... ...
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/domain/OnlinePaymentResponse.java
1 1 package com.diligrp.cashier.pipeline.domain;
2 2  
3   -import com.diligrp.cashier.shared.domain.ContainerSupport;
  3 +import com.diligrp.cashier.pipeline.type.OutPaymentType;
  4 +import com.diligrp.cashier.pipeline.type.PaymentState;
4 5  
5 6 import java.time.LocalDateTime;
6 7  
7 8 /**
8 9 * 在线支付结果领域模型
9 10 */
10   -public class OnlinePaymentResponse extends ContainerSupport {
11   - // 支付ID
12   - private String paymentId;
13   - // 支付通道订单号
14   - private String outTradeNo;
  11 +public class OnlinePaymentResponse extends OnlinePaymentStatus {
  12 + // 实际支付方式-聚合支付时
  13 + private final OutPaymentType outPayType;
15 14 // 支付方Id - 比如微信OpenId
16   - private String payerId;
  15 + private final String payerId;
17 16 // 支付时间
18   - private LocalDateTime when;
19   - // 支付状态
20   - private Integer state;
  17 + private final LocalDateTime when;
21 18 // 交易备注
22   - private String message;
  19 + private final String message;
  20 +
  21 + public static OnlinePaymentResponse of(String paymentId, String outTradeNo, LocalDateTime when,
  22 + PaymentState state, String message) {
  23 + return new OnlinePaymentResponse(paymentId, outTradeNo, null, null, when, state, message);
  24 + }
23 25  
24 26 public static OnlinePaymentResponse of(String paymentId, String outTradeNo, String payerId,
25   - LocalDateTime when, Integer state, String message) {
26   - OnlinePaymentResponse response = new OnlinePaymentResponse();
27   - response.paymentId = paymentId;
28   - response.outTradeNo = outTradeNo;
29   - response.payerId = payerId;
30   - response.when = when;
31   - response.state = state;
32   - response.message = message;
33   - return response;
  27 + LocalDateTime when, PaymentState state, String message) {
  28 + return new OnlinePaymentResponse(paymentId, outTradeNo, null, payerId, when, state, message);
  29 + }
  30 +
  31 + public static OnlinePaymentResponse of(String paymentId, String outTradeNo, OutPaymentType outPayType,
  32 + LocalDateTime when, PaymentState state, String message) {
  33 + return new OnlinePaymentResponse(paymentId, outTradeNo, outPayType, null, when, state, message);
34 34 }
35 35  
36   - public String getPaymentId() {
37   - return paymentId;
  36 + public OnlinePaymentResponse(String paymentId, String outTradeNo, OutPaymentType outPayType,
  37 + String payerId, LocalDateTime when, PaymentState state, String message) {
  38 + super(paymentId, outTradeNo, state);
  39 + this.outPayType = outPayType;
  40 + this.payerId = payerId;
  41 + this.when = when;
  42 + this.message = message;
38 43 }
39 44  
40   - public String getOutTradeNo() {
41   - return outTradeNo;
  45 + public OutPaymentType getOutPayType() {
  46 + return outPayType;
42 47 }
43 48  
44 49 public String getPayerId() {
... ... @@ -49,10 +54,6 @@ public class OnlinePaymentResponse extends ContainerSupport {
49 54 return when;
50 55 }
51 56  
52   - public Integer getState() {
53   - return state;
54   - }
55   -
56 57 public String getMessage() {
57 58 return message;
58 59 }
... ...
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/domain/OnlinePrepayResponse.java renamed to cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/domain/OnlinePaymentStatus.java
1 1 package com.diligrp.cashier.pipeline.domain;
2 2  
  3 +import com.diligrp.cashier.pipeline.type.PaymentState;
  4 +
3 5 /**
4   - * 预支付响应
  6 + * 在线支付状态,所有支付结果的抽象类
5 7 */
6   -public class OnlinePrepayResponse {
  8 +public class OnlinePaymentStatus {
7 9 // 支付订单号
8 10 protected String paymentId;
9 11 // 通道订单号
10 12 protected String outTradeNo;
  13 + // 支付状态
  14 + protected PaymentState state;
  15 +
  16 + public OnlinePaymentStatus(String paymentId, String outTradeNo, PaymentState state) {
  17 + this.paymentId = paymentId;
  18 + this.outTradeNo = outTradeNo;
  19 + this.state = state;
  20 + }
11 21  
12 22 public String getPaymentId() {
13 23 return paymentId;
... ... @@ -16,4 +26,8 @@ public class OnlinePrepayResponse {
16 26 public String getOutTradeNo() {
17 27 return outTradeNo;
18 28 }
  29 +
  30 + public PaymentState getState() {
  31 + return state;
  32 + }
19 33 }
... ...
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/domain/OnlineRefundResponse.java
1 1 package com.diligrp.cashier.pipeline.domain;
2 2  
3   -import com.diligrp.cashier.shared.domain.ContainerSupport;
4   -
5 3 import java.time.LocalDateTime;
6 4  
7 5 /**
8 6 * 退款结果领域模型
9 7 */
10   -public class OnlineRefundResponse extends ContainerSupport {
  8 +public class OnlineRefundResponse {
11 9 // 商户退款单号
12 10 private String refundId;
13 11 // 通道退款订单号
... ...
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/domain/QrCodePaymentRequest.java 0 → 100644
  1 +package com.diligrp.cashier.pipeline.domain;
  2 +
  3 +import java.time.LocalDateTime;
  4 +
  5 +/**
  6 + * 付款码支付
  7 + */
  8 +public class QrCodePaymentRequest extends OnlinePaymentRequest {
  9 + private final String qrCode;
  10 +
  11 + public QrCodePaymentRequest(String paymentId, long amount, String goods, String description, LocalDateTime when, String qrCode) {
  12 + super(paymentId, amount, goods, description, when);
  13 + this.qrCode = qrCode;
  14 + }
  15 +
  16 + public String getQrCode() {
  17 + return qrCode;
  18 + }
  19 +}
... ...
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/domain/card/CardPaymentRequest.java 0 → 100644
  1 +package com.diligrp.cashier.pipeline.domain.card;
  2 +
  3 +import com.diligrp.cashier.pipeline.domain.OnlinePaymentRequest;
  4 +
  5 +import java.time.LocalDateTime;
  6 +
  7 +public class CardPaymentRequest extends OnlinePaymentRequest {
  8 + private final Long accountId;
  9 + private final String cardNo;
  10 +
  11 + public CardPaymentRequest(String paymentId, long amount, String goods, String description, LocalDateTime when,
  12 + Long accountId, String cardNo) {
  13 + super(paymentId, amount, goods, description, when);
  14 + this.accountId = accountId;
  15 + this.cardNo = cardNo;
  16 + }
  17 +
  18 + public Long getAccountId() {
  19 + return accountId;
  20 + }
  21 +
  22 + public String getCardNo() {
  23 + return cardNo;
  24 + }
  25 +}
... ...
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/domain/card/CardPaymentResponse.java 0 → 100644
  1 +package com.diligrp.cashier.pipeline.domain.card;
  2 +
  3 +import com.diligrp.cashier.pipeline.domain.OnlinePaymentResponse;
  4 +import com.diligrp.cashier.pipeline.type.OutPaymentType;
  5 +import com.diligrp.cashier.pipeline.type.PaymentState;
  6 +
  7 +import java.time.LocalDateTime;
  8 +
  9 +public class CardPaymentResponse extends OnlinePaymentResponse {
  10 +
  11 + public CardPaymentResponse(String paymentId, String outTradeNo, OutPaymentType outPayType, String payerId, LocalDateTime when, PaymentState state, String message) {
  12 + super(paymentId, outTradeNo, outPayType, payerId, when, state, message);
  13 + }
  14 +}
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/type/CashierDesk.java renamed to cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/type/CashierType.java
1   -package com.diligrp.cashier.trade.type;
  1 +package com.diligrp.cashier.pipeline.type;
2 2  
3   -import com.diligrp.cashier.shared.ErrorCode;
4 3 import com.diligrp.cashier.shared.type.IEnumType;
5   -import com.diligrp.cashier.trade.exception.TradePaymentException;
6 4  
7 5 import java.util.Arrays;
8 6 import java.util.Objects;
... ... @@ -10,24 +8,23 @@ import java.util.Optional;
10 8 import java.util.stream.Stream;
11 9  
12 10 /**
13   - * 终端来源
  11 + * 收银台类型
14 12 */
15   -public enum CashierDesk implements IEnumType {
16   -
17   - NOP("无收银台", 0),
  13 +public enum CashierType implements IEnumType {
18 14  
19 15 PC("PC收银台", 1),
20 16  
21 17 MINIPRO("小程序收银台", 2),
22 18  
23   - H5("H5收银台", 2),
  19 + H5("H5收银台", 3),
  20 +
  21 + APP("APP收银台", 4);
24 22  
25   - APP("APP收银台", 3);
26 23  
27 24 private final String name;
28 25 private final int code;
29 26  
30   - CashierDesk(String name, int code) {
  27 + CashierType(String name, int code) {
31 28 this.name = name;
32 29 this.code = code;
33 30 }
... ... @@ -36,24 +33,26 @@ public enum CashierDesk implements IEnumType {
36 33 return this.code == code;
37 34 }
38 35  
39   - public static Optional<CashierDesk> getType(int code) {
40   - Stream<CashierDesk> TYPES = Arrays.stream(CashierDesk.values());
  36 + public static Optional<CashierType> getType(int code) {
  37 + Stream<CashierType> TYPES = Arrays.stream(CashierType.values());
41 38 return TYPES.filter(type -> type.getCode() == code).findFirst();
42 39 }
43 40  
44   - public static CashierDesk getByCode(int code) {
45   - return getType(code).orElseThrow(() -> new TradePaymentException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "系统暂不支持此收银台类型"));
  41 + public static CashierType getByCode(int code) {
  42 + return getType(code).orElseThrow(() -> new IllegalArgumentException("系统暂不支持此收银台类型"));
46 43 }
47 44  
48   - public static void validateIfNonNull(Integer code) {
  45 + public static CashierType getIfNonNull(Integer code) {
49 46 if (Objects.nonNull(code)) {
50   - Stream<CashierDesk> TYPES = Arrays.stream(CashierDesk.values());
51   - TYPES.filter(type -> type.getCode() == code).findFirst().orElseThrow(() -> new IllegalArgumentException("Invalid cashier desk"));
  47 + Stream<CashierType> TYPES = Arrays.stream(CashierType.values());
  48 + return TYPES.filter(type -> type.getCode() == code).findFirst()
  49 + .orElseThrow(() -> new IllegalArgumentException("Invalid cashier desk"));
52 50 }
  51 + return null;
53 52 }
54 53  
55 54 public static String getName(int code) {
56   - return getType(code).map(CashierDesk::getName).orElse(null);
  55 + return getType(code).map(CashierType::getName).orElse(null);
57 56 }
58 57  
59 58 @Override
... ...
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/type/ChannelType.java
1 1 package com.diligrp.cashier.pipeline.type;
2 2  
3   -import com.diligrp.cashier.pipeline.exception.PaymentPipelineException;
4 3 import com.diligrp.cashier.shared.ErrorCode;
  4 +import com.diligrp.cashier.shared.exception.PlatformServiceException;
5 5 import com.diligrp.cashier.shared.type.IEnumType;
6 6  
7 7 import java.util.Arrays;
... ... @@ -15,13 +15,11 @@ import java.util.stream.Stream;
15 15 */
16 16 public enum ChannelType implements IEnumType {
17 17  
18   - NOP("未知渠道", 0),
19   -
20 18 WXPAY("微信渠道", 10),
21 19  
22 20 ALIPAY("支付宝渠道", 11),
23 21  
24   - DILIPAY("地利渠道", 18), // 地利园区卡支付
  22 + DILIPAY("地利渠道", 19), // 地利园区卡支付
25 23  
26 24 ICBC("工商银行", 20),
27 25  
... ... @@ -61,7 +59,7 @@ public enum ChannelType implements IEnumType {
61 59 }
62 60  
63 61 public static ChannelType getByCode(int code) {
64   - return getType(code).orElseThrow(() -> new PaymentPipelineException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "系统不支持此支付渠道"));
  62 + return getType(code).orElseThrow(() -> new PlatformServiceException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "系统不支持此支付渠道"));
65 63 }
66 64  
67 65 public static String getName(int code) {
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/type/OutPaymentType.java renamed to cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/type/OutPaymentType.java
1   -package com.diligrp.cashier.trade.type;
  1 +package com.diligrp.cashier.pipeline.type;
2 2  
  3 +import com.diligrp.cashier.pipeline.exception.PaymentPipelineException;
3 4 import com.diligrp.cashier.shared.ErrorCode;
4 5 import com.diligrp.cashier.shared.type.IEnumType;
5   -import com.diligrp.cashier.trade.exception.TradePaymentException;
6 6  
7 7 import java.util.Arrays;
8 8 import java.util.Optional;
9 9 import java.util.stream.Stream;
10 10  
11 11 /**
12   - * 聚合支付实际使用的支付方式
  12 + * 聚合支付通道实际使用的支付方式
13 13 */
14 14 public enum OutPaymentType implements IEnumType {
15   - NOP("其他方式", 0),
16 15  
17 16 WXPAY("微信支付", 10),
18 17  
... ... @@ -22,6 +21,8 @@ public enum OutPaymentType implements IEnumType {
22 21  
23 22 APP("掌银支付", 13),
24 23  
  24 + DILICARD("园区卡支付", 19),
  25 +
25 26 BANKCARD("银行卡支付", 20);
26 27  
27 28 private final String name;
... ... @@ -42,7 +43,7 @@ public enum OutPaymentType implements IEnumType {
42 43 }
43 44  
44 45 public static OutPaymentType getByCode(int code) {
45   - return getType(code).orElseThrow(() -> new TradePaymentException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "系统暂不支持该支付方式"));
  46 + return getType(code).orElseThrow(() -> new PaymentPipelineException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "系统暂不支持该支付方式"));
46 47 }
47 48  
48 49 public static String getName(int code) {
... ...
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/type/PaymentState.java
1 1 package com.diligrp.cashier.pipeline.type;
2 2  
3 3 import com.diligrp.cashier.shared.type.IEnumType;
  4 +import com.fasterxml.jackson.annotation.JsonValue;
4 5  
5 6 import java.util.Arrays;
6 7 import java.util.List;
... ... @@ -45,12 +46,25 @@ public enum PaymentState implements IEnumType {
45 46 return Arrays.asList(PaymentState.values());
46 47 }
47 48  
  49 + public static boolean isPending(int code) {
  50 + return PaymentState.PENDING.equalTo(code);
  51 + }
  52 +
  53 + public static boolean isProcessing(int code) {
  54 + return PaymentState.PROCESSING.equalTo(code);
  55 + }
  56 +
  57 + public static boolean isFinished(int code) {
  58 + return PaymentState.SUCCESS.equalTo(code) || PaymentState.FAILED.equalTo(code);
  59 + }
  60 +
48 61 @Override
49 62 public String getName() {
50 63 return name;
51 64 }
52 65  
53 66 @Override
  67 + @JsonValue
54 68 public int getCode() {
55 69 return code;
56 70 }
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/type/PaymentType.java renamed to cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/type/PaymentType.java
1   -package com.diligrp.cashier.trade.type;
  1 +package com.diligrp.cashier.pipeline.type;
2 2  
3 3 import com.diligrp.cashier.shared.ErrorCode;
  4 +import com.diligrp.cashier.shared.exception.PlatformServiceException;
4 5 import com.diligrp.cashier.shared.type.IEnumType;
5   -import com.diligrp.cashier.trade.exception.TradePaymentException;
6 6  
7 7 import java.util.Arrays;
8 8 import java.util.Optional;
... ... @@ -12,11 +12,14 @@ import java.util.stream.Stream;
12 12 * 支付方式
13 13 */
14 14 public enum PaymentType implements IEnumType {
15   - NATIVE("扫码支付", 1), // 客户扫商户收款码
16   -
17   - QRCODE("付款码支付", 2), // 商户扫客户付款码
18   -
19   - MINI_PRO("小程序支付", 3); // 目前特指微信小程序支付
  15 + // 直接支付, 使用PaymentPipeline.sendPaymentRequest
  16 + DIRECT("直接支付", 0),
  17 + // 客户扫商户收款码, 使用PaymentPipeline.sendNativePrepayRequest
  18 + NATIVE("扫码支付", 1),
  19 + // 商户扫客户付款码, 使用PaymentPipeline.sendQrCodePaymentRequest
  20 + QRCODE("付款码支付", 2),
  21 + // 目前特指微信小程序支付, 使用PaymentPipeline.sendMiniProPrepayRequest
  22 + MINI_PRO("小程序支付", 3);
20 23  
21 24 private final String name;
22 25 private final int code;
... ... @@ -36,7 +39,7 @@ public enum PaymentType implements IEnumType {
36 39 }
37 40  
38 41 public static PaymentType getByCode(int code) {
39   - return getType(code).orElseThrow(() -> new TradePaymentException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "系统不支持此类支付方式"));
  42 + return getType(code).orElseThrow(() -> new PlatformServiceException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "系统不支持此类支付方式"));
40 43 }
41 44  
42 45 public static String getName(int code) {
... ...
cashier-shared/src/main/java/com/diligrp/cashier/shared/SharedConfiguration.java
... ... @@ -15,11 +15,14 @@ import org.springframework.data.redis.connection.RedisConnectionFactory;
15 15 import org.springframework.data.redis.core.RedisTemplate;
16 16 import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
17 17 import org.springframework.data.redis.serializer.StringRedisSerializer;
  18 +import org.springframework.lang.NonNull;
18 19 import org.springframework.stereotype.Component;
19 20 import org.springframework.util.StringUtils;
20 21 import org.springframework.web.servlet.config.annotation.CorsRegistry;
21 22 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
22 23  
  24 +import javax.crypto.spec.SecretKeySpec;
  25 +import java.nio.charset.StandardCharsets;
23 26 import java.security.PrivateKey;
24 27 import java.security.PublicKey;
25 28 import java.text.SimpleDateFormat;
... ... @@ -36,7 +39,7 @@ public class SharedConfiguration {
36 39 public WebMvcConfigurer corsConfigurer() {
37 40 return new WebMvcConfigurer() {
38 41 @Override
39   - public void addCorsMappings(CorsRegistry registry) {
  42 + public void addCorsMappings(@NonNull CorsRegistry registry) {
40 43 registry.addMapping("/**")
41 44 .allowedOriginPatterns("*")
42 45 .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
... ... @@ -71,7 +74,7 @@ public class SharedConfiguration {
71 74 // 不能使用lambda表达式,否则导致springboot启动问题
72 75 return new Converter<String, LocalDateTime>() {
73 76 @Override
74   - public LocalDateTime convert(String source) {
  77 + public LocalDateTime convert(@NonNull String source) {
75 78 try {
76 79 return StringUtils.hasText(source) ? LocalDateTime.parse(source, DateTimeFormatter.ofPattern(Constants.DATE_TIME_FORMAT)) : null;
77 80 } catch (Exception ex) {
... ... @@ -86,7 +89,7 @@ public class SharedConfiguration {
86 89 // 不能使用lambda表达式,否则导致springboot启动问题
87 90 return new Converter<String, LocalDate>() {
88 91 @Override
89   - public LocalDate convert(String source) {
  92 + public LocalDate convert(@NonNull String source) {
90 93 try {
91 94 return StringUtils.hasText(source) ? LocalDate.parse(source, DateTimeFormatter.ofPattern(Constants.DATE_FORMAT)) : null;
92 95 } catch (Exception ex) {
... ... @@ -101,7 +104,7 @@ public class SharedConfiguration {
101 104 // 不能使用lambda表达式,否则导致springboot启动问题
102 105 return new Converter<String, LocalTime>() {
103 106 @Override
104   - public LocalTime convert(String source) {
  107 + public LocalTime convert(@NonNull String source) {
105 108 try {
106 109 return StringUtils.hasText(source) ? LocalTime.parse(source, DateTimeFormatter.ofPattern(Constants.TIME_FORMAT)) : null;
107 110 } catch (Exception ex) {
... ... @@ -116,7 +119,7 @@ public class SharedConfiguration {
116 119 // 不能使用lambda表达式,否则导致springboot启动问题
117 120 return new Converter<String, Date>() {
118 121 @Override
119   - public Date convert(String source) {
  122 + public Date convert(@NonNull String source) {
120 123 try {
121 124 return StringUtils.hasText(source) ? new SimpleDateFormat(Constants.DATE_TIME_FORMAT).parse(source) : null;
122 125 } catch (Exception ex) {
... ... @@ -128,9 +131,9 @@ public class SharedConfiguration {
128 131  
129 132 @Component
130 133 @ConfigurationPropertiesBinding
131   - public class PrivateKeyConverter implements Converter<String, PrivateKey> {
  134 + public static class PrivateKeyConverter implements Converter<String, PrivateKey> {
132 135 @Override
133   - public PrivateKey convert(String source) {
  136 + public PrivateKey convert(@NonNull String source) {
134 137 try {
135 138 return RsaCipher.getPrivateKey(source);
136 139 } catch (Exception ex) {
... ... @@ -141,9 +144,9 @@ public class SharedConfiguration {
141 144  
142 145 @Component
143 146 @ConfigurationPropertiesBinding
144   - public class PublicKeyConverter implements Converter<String, PublicKey> {
  147 + public static class PublicKeyConverter implements Converter<String, PublicKey> {
145 148 @Override
146   - public PublicKey convert(String source) {
  149 + public PublicKey convert(@NonNull String source) {
147 150 try {
148 151 return RsaCipher.getPublicKey(source);
149 152 } catch (Exception ex) {
... ... @@ -151,4 +154,13 @@ public class SharedConfiguration {
151 154 }
152 155 }
153 156 }
  157 +
  158 + @Component
  159 + @ConfigurationPropertiesBinding
  160 + public static class SecretKeyConverter implements Converter<String, SecretKeySpec> {
  161 + @Override
  162 + public SecretKeySpec convert(@NonNull String source) {
  163 + return new SecretKeySpec(source.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
  164 + }
  165 + }
154 166 }
155 167 \ No newline at end of file
... ...
cashier-shared/src/main/java/com/diligrp/cashier/shared/codec/ByteCodec.java 0 → 100644
  1 +package com.diligrp.cashier.shared.codec;
  2 +
  3 +public interface ByteCodec<T> {
  4 +
  5 + T decode(byte[] payload);
  6 +
  7 + byte[] encode(T payload);
  8 +}
... ...
cashier-shared/src/main/java/com/diligrp/cashier/shared/codec/ByteDecoder.java deleted 100644 → 0
1   -package com.diligrp.cashier.shared.codec;
2   -
3   -import java.io.IOException;
4   -
5   -public interface ByteDecoder<T> {
6   - T decode(byte[] payload) throws IOException;
7   -}
cashier-shared/src/main/java/com/diligrp/cashier/shared/codec/ByteEncoder.java deleted 100644 → 0
1   -package com.diligrp.cashier.shared.codec;
2   -
3   -import java.io.IOException;
4   -
5   -public interface ByteEncoder<T> {
6   - byte[] encode(T payload) throws IOException;
7   -}
cashier-shared/src/main/java/com/diligrp/cashier/shared/codec/IConverter.java 0 → 100644
  1 +package com.diligrp.cashier.shared.codec;
  2 +
  3 +import java.util.Objects;
  4 +
  5 +@FunctionalInterface
  6 +public interface IConverter<T, R> {
  7 + R convert(T t);
  8 +
  9 + default <V> IConverter<T, V> andThen(IConverter<? super R, ? extends V> after) {
  10 + Objects.requireNonNull(after);
  11 + return (T t) -> after.convert(convert(t));
  12 + }
  13 +}
... ...
cashier-shared/src/main/java/com/diligrp/cashier/shared/codec/JsonCodec.java 0 → 100644
  1 +package com.diligrp.cashier.shared.codec;
  2 +
  3 +public interface JsonCodec<T> {
  4 +
  5 + T decode(String payload);
  6 +
  7 + String encode(T payload);
  8 +}
... ...
cashier-shared/src/main/java/com/diligrp/cashier/shared/codec/StringCodec.java
... ... @@ -2,32 +2,17 @@ package com.diligrp.cashier.shared.codec;
2 2  
3 3 import java.nio.charset.StandardCharsets;
4 4  
5   -public final class StringCodec {
6   - public static ByteEncoder<String> getEncoder() {
7   - return StringEncoder.INSTANCE;
8   - }
9   -
10   - public static ByteDecoder<String> getDecoder() {
11   - return StringDecoder.INSTANCE;
12   - }
  5 +public final class StringCodec implements ByteCodec<String> {
13 6  
14   - static class StringEncoder implements ByteEncoder<String> {
  7 + public static ByteCodec<String> INSTANCE = new StringCodec();
15 8  
16   - static final ByteEncoder<String> INSTANCE = new StringEncoder();
17   -
18   - @Override
19   - public byte[] encode(String payload) {
20   - return payload.getBytes(StandardCharsets.UTF_8);
21   - }
  9 + @Override
  10 + public String decode(byte[] payload) {
  11 + return new String(payload, StandardCharsets.UTF_8);
22 12 }
23 13  
24   - static class StringDecoder implements ByteDecoder<String> {
25   -
26   - static final ByteDecoder<String> INSTANCE = new StringDecoder();
27   -
28   - @Override
29   - public String decode(byte[] payload) {
30   - return new String(payload, StandardCharsets.UTF_8);
31   - }
  14 + @Override
  15 + public byte[] encode(String payload) {
  16 + return payload.getBytes(StandardCharsets.UTF_8);
32 17 }
33 18 }
... ...
cashier-shared/src/main/java/com/diligrp/cashier/shared/security/Base62Cipher.java 0 → 100644
  1 +package com.diligrp.cashier.shared.security;
  2 +
  3 +public final class Base62Cipher {
  4 +
  5 + private static final char[] BASE62_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
  6 +
  7 + private static final int BASE = 62;
  8 +
  9 + public static String encodeLong(long data) {
  10 + if (data < 0) {
  11 + throw new IllegalArgumentException("cannot be negative");
  12 + }
  13 +
  14 + if (data == 0) {
  15 + return String.valueOf(BASE62_CHARS[0]);
  16 + }
  17 +
  18 + StringBuilder sb = new StringBuilder();
  19 + while (data > 0) {
  20 + int remainder = (int) (data % BASE);
  21 + sb.append(BASE62_CHARS[remainder]);
  22 + data = data / BASE;
  23 + }
  24 +
  25 + // 反转字符串(因为取余是从低位到高位,需要反转恢复顺序)
  26 + return sb.reverse().toString();
  27 + }
  28 +
  29 + public static long decodeLong(String payload) {
  30 + if (payload == null || payload.isEmpty()) {
  31 + throw new IllegalArgumentException("Invalid base62 data");
  32 + }
  33 +
  34 + long result = 0;
  35 + for (char c : payload.toCharArray()) {
  36 + if (result > (Long.MAX_VALUE - getCharIndex(c)) / BASE) {
  37 + throw new NumberFormatException("Invalid base62 data: overflow long");
  38 + }
  39 + result = result * BASE + getCharIndex(c);
  40 + }
  41 + return result;
  42 + }
  43 +
  44 + private static int getCharIndex(char c) {
  45 + if (c >= '0' && c <= '9') {
  46 + return c - '0';
  47 + } else if (c >= 'a' && c <= 'z') {
  48 + return 10 + (c - 'a');
  49 + } else if (c >= 'A' && c <= 'Z') {
  50 + return 36 + (c - 'A');
  51 + } else {
  52 + throw new IllegalArgumentException("invalid base62 data:" + c);
  53 + }
  54 + }
  55 +}
... ...
cashier-shared/src/main/java/com/diligrp/cashier/shared/spi/IPaymentEventListener.java 0 → 100644
  1 +package com.diligrp.cashier.shared.spi;
  2 +
  3 +@FunctionalInterface
  4 +public interface IPaymentEventListener {
  5 +
  6 + void onEvent(PaymentEvent event);
  7 +}
... ...
cashier-shared/src/main/java/com/diligrp/cashier/shared/spi/PaymentEvent.java 0 → 100644
  1 +package com.diligrp.cashier.shared.spi;
  2 +
  3 +import java.time.LocalDateTime;
  4 +
  5 +public class PaymentEvent {
  6 + // 支付ID
  7 + private final String tradeId;
  8 + // 支付状态
  9 + private final Integer state;
  10 + // 业务系统订单号
  11 + private final String outTradeNo;
  12 + // 实际支付方式
  13 + private final Integer outPayType;
  14 + // 支付人信息
  15 + private final String payerId;
  16 + // 发生时间
  17 + private final LocalDateTime when;
  18 + // 交易描述
  19 + private final String message;
  20 +
  21 + public PaymentEvent(String tradeId, int state, String outTradeNo, Integer outPayType, String payerId, LocalDateTime when, String message) {
  22 + this.tradeId = tradeId;
  23 + this.state = state;
  24 + this.outTradeNo = outTradeNo;
  25 + this.outPayType = outPayType;
  26 + this.payerId = payerId;
  27 + this.when = when;
  28 + this.message = message;
  29 + }
  30 +
  31 + public String getTradeId() {
  32 + return tradeId;
  33 + }
  34 +
  35 + public Integer getState() {
  36 + return state;
  37 + }
  38 +
  39 + public String getOutTradeNo() {
  40 + return outTradeNo;
  41 + }
  42 +
  43 + public Integer getOutPayType() {
  44 + return outPayType;
  45 + }
  46 +
  47 + public String getPayerId() {
  48 + return payerId;
  49 + }
  50 +
  51 + public LocalDateTime getWhen() {
  52 + return when;
  53 + }
  54 +
  55 + public String getMessage() {
  56 + return message;
  57 + }
  58 +}
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/Constants.java 0 → 100644
  1 +package com.diligrp.cashier.trade;
  2 +
  3 +public final class Constants {
  4 +
  5 + // 默认订单超时时间-秒, 十分钟
  6 + public static final int DEFAULT_ORDER_TIMEOUT_SECONDS = 10 * 60 * 1000;
  7 +
  8 + // 最小订单超时时间-秒, 一分钟
  9 + public static final int MIN_ORDER_TIMEOUT_SECONDS = 60 * 1000;
  10 +
  11 + // 支付订单分布式锁
  12 + public static final String TRADE_LOCK_REDIS_KEY = "cashier:lock:trade:%s";
  13 +
  14 + // 支付订单分布式锁超时时长-秒
  15 + public static final int TRADE_LOCK_TIMEOUT_SECONDS = 15 * 1000;
  16 +
  17 + // 微信支付openId参数
  18 + private static final String PARAM_OPEN_ID = "openId";
  19 +
  20 + // 微信服务商模式下mchId子商户号参数
  21 + public static final String PARAM_MCH_ID = "mchId";
  22 +
  23 +}
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/TradeConfiguration.java
1 1 package com.diligrp.cashier.trade;
2 2  
  3 +import com.diligrp.cashier.shared.mybatis.MybatisMapperSupport;
  4 +import org.mybatis.spring.annotation.MapperScan;
3 5 import org.springframework.context.annotation.ComponentScan;
4 6 import org.springframework.context.annotation.Configuration;
5 7  
6 8 @Configuration
7 9 @ComponentScan("com.diligrp.cashier.trade")
  10 +@MapperScan(basePackages = {"com.diligrp.cashier.trade.dao"}, markerInterface = MybatisMapperSupport.class)
8 11 public class TradeConfiguration {
9 12 }
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/dao/IOnlinePaymentDao.java 0 → 100644
  1 +package com.diligrp.cashier.trade.dao;
  2 +
  3 +import com.diligrp.cashier.shared.mybatis.MybatisMapperSupport;
  4 +import com.diligrp.cashier.trade.domain.PaymentStateDTO;
  5 +import com.diligrp.cashier.trade.model.OnlinePayment;
  6 +import org.apache.ibatis.annotations.Param;
  7 +import org.springframework.stereotype.Repository;
  8 +
  9 +import java.util.List;
  10 +import java.util.Optional;
  11 +
  12 +/**
  13 + * 支付申请数据访问层
  14 + */
  15 +@Repository("onlinePaymentDao")
  16 +public interface IOnlinePaymentDao extends MybatisMapperSupport {
  17 +
  18 + void insertOnlinePayment(OnlinePayment payment);
  19 +
  20 + Optional<OnlinePayment> findByPaymentId(String paymentId);
  21 +
  22 + int compareAndSetState(PaymentStateDTO paymentDTO);
  23 +
  24 + List<OnlinePayment> findByTradeId(@Param("tradeId") String tradeId, @Param("state") Integer state);
  25 +}
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/dao/ITradeOrderDao.java 0 → 100644
  1 +package com.diligrp.cashier.trade.dao;
  2 +
  3 +import com.diligrp.cashier.shared.mybatis.MybatisMapperSupport;
  4 +import com.diligrp.cashier.trade.domain.TradeStateDTO;
  5 +import com.diligrp.cashier.trade.model.TradeOrder;
  6 +import org.apache.ibatis.annotations.Param;
  7 +import org.springframework.stereotype.Repository;
  8 +
  9 +import java.util.Optional;
  10 +
  11 +/**
  12 + * 交易订单数据访问层
  13 + */
  14 +@Repository("tradeOrderDao")
  15 +public interface ITradeOrderDao extends MybatisMapperSupport {
  16 + void insertTradeOrder(TradeOrder tradeOrder);
  17 +
  18 + Optional<TradeOrder> findByTradeId(String tradeId);
  19 +
  20 + Optional<TradeOrder> findByOutTradeNo(@Param("mchId") Long mchId, @Param("outTradeNo") String outTradeNo);
  21 +
  22 + int compareAndSetState(TradeStateDTO tradeState);
  23 +}
0 24 \ No newline at end of file
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/domain/CashierOrder.java 0 → 100644
  1 +package com.diligrp.cashier.trade.domain;
  2 +
  3 +import com.diligrp.cashier.pipeline.type.CashierType;
  4 +
  5 +public class CashierOrder {
  6 + // 业务系统用户标识
  7 + private String userId;
  8 + // 收银台类型 - H5收银台等
  9 + private CashierType type;
  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 description;
  22 + // 附加数据
  23 + private String attach;
  24 +
  25 + public String getUserId() {
  26 + return userId;
  27 + }
  28 +
  29 + public void setUserId(String userId) {
  30 + this.userId = userId;
  31 + }
  32 +
  33 + public CashierType getType() {
  34 + return type;
  35 + }
  36 +
  37 + public void setType(CashierType type) {
  38 + this.type = type;
  39 + }
  40 +
  41 + public String getGoods() {
  42 + return goods;
  43 + }
  44 +
  45 + public void setGoods(String goods) {
  46 + this.goods = goods;
  47 + }
  48 +
  49 + public Long getAmount() {
  50 + return amount;
  51 + }
  52 +
  53 + public void setAmount(Long amount) {
  54 + this.amount = amount;
  55 + }
  56 +
  57 + public Integer getTimeout() {
  58 + return timeout;
  59 + }
  60 +
  61 + public void setTimeout(Integer timeout) {
  62 + this.timeout = timeout;
  63 + }
  64 +
  65 + public String getOutTradeNo() {
  66 + return outTradeNo;
  67 + }
  68 +
  69 + public void setOutTradeNo(String outTradeNo) {
  70 + this.outTradeNo = outTradeNo;
  71 + }
  72 +
  73 + public String getNotifyUrl() {
  74 + return notifyUrl;
  75 + }
  76 +
  77 + public void setNotifyUrl(String notifyUrl) {
  78 + this.notifyUrl = notifyUrl;
  79 + }
  80 +
  81 + public String getDescription() {
  82 + return description;
  83 + }
  84 +
  85 + public void setDescription(String description) {
  86 + this.description = description;
  87 + }
  88 +
  89 + public String getAttach() {
  90 + return attach;
  91 + }
  92 +
  93 + public void setAttach(String attach) {
  94 + this.attach = attach;
  95 + }
  96 +}
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/domain/CashierPayment.java 0 → 100644
  1 +package com.diligrp.cashier.trade.domain;
  2 +
  3 +import java.util.Map;
  4 +
  5 +public class CashierPayment {
  6 + // 支付号
  7 + private String tradeId;
  8 + // 选择的支付通道
  9 + private Long pipelineId;
  10 + // 用户标识
  11 + private Long userId;
  12 + // 支付参数
  13 + private Map<String, Object> params;
  14 +
  15 + public String getTradeId() {
  16 + return tradeId;
  17 + }
  18 +
  19 + public void setTradeId(String tradeId) {
  20 + this.tradeId = tradeId;
  21 + }
  22 +
  23 + public Long getPipelineId() {
  24 + return pipelineId;
  25 + }
  26 +
  27 + public void setPipelineId(Long pipelineId) {
  28 + this.pipelineId = pipelineId;
  29 + }
  30 +
  31 + public Long getUserId() {
  32 + return userId;
  33 + }
  34 +
  35 + public void setUserId(Long userId) {
  36 + this.userId = userId;
  37 + }
  38 +
  39 + public Map<String, Object> getParams() {
  40 + return params;
  41 + }
  42 +
  43 + public void setParams(Map<String, Object> params) {
  44 + this.params = params;
  45 + }
  46 +}
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/domain/Merchant.java 0 → 100644
  1 +package com.diligrp.cashier.trade.domain;
  2 +
  3 +import com.diligrp.cashier.shared.util.JsonUtils;
  4 +
  5 +public class Merchant {
  6 + // 商户号
  7 + private Long mchId;
  8 + // 商户名称
  9 + private String name;
  10 + // 商户参数
  11 + private MerchantParams params;
  12 +
  13 + public static Merchant of(Long mchId, String name, MerchantParams params) {
  14 + Merchant merchant = new Merchant();
  15 + merchant.mchId = mchId;
  16 + merchant.name = name;
  17 + merchant.params = params;
  18 + return merchant;
  19 + }
  20 +
  21 + public static Merchant decode(String payload) {
  22 + if (payload != null) {
  23 + return JsonUtils.fromJsonString(payload, Merchant.class);
  24 + }
  25 + return null;
  26 + }
  27 +
  28 + public Long getMchId() {
  29 + return mchId;
  30 + }
  31 +
  32 + public void setMchId(Long mchId) {
  33 + this.mchId = mchId;
  34 + }
  35 +
  36 + public String getName() {
  37 + return name;
  38 + }
  39 +
  40 + public void setName(String name) {
  41 + this.name = name;
  42 + }
  43 +
  44 + public MerchantParams getParams() {
  45 + return params;
  46 + }
  47 +
  48 + public void setParams(MerchantParams params) {
  49 + this.params = params;
  50 + }
  51 +
  52 + @Override
  53 + public String toString() {
  54 + return JsonUtils.toJsonString(this);
  55 + }
  56 +}
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/domain/MerchantParams.java 0 → 100644
  1 +package com.diligrp.cashier.trade.domain;
  2 +
  3 +import com.diligrp.cashier.shared.util.JsonUtils;
  4 +
  5 +import java.util.Objects;
  6 +
  7 +public class MerchantParams {
  8 + // 收银台配置
  9 + private CashierParams cashier;
  10 + // TODO: 支付结果页面-跳转大润发使用
  11 +
  12 + public static MerchantParams decode(String params) {
  13 + return JsonUtils.fromJsonString(params, MerchantParams.class);
  14 + }
  15 +
  16 + public CashierParams getCashier() {
  17 + return cashier;
  18 + }
  19 +
  20 + public void setCashier(CashierParams cashier) {
  21 + this.cashier = cashier;
  22 + }
  23 +
  24 + public static class CashierParams {
  25 + // PC收银台地址
  26 + private String pcUrl;
  27 + // 小程序收银台地址
  28 + private String miniProUrl;
  29 + // H5收银台地址
  30 + private String h5Url;
  31 + // 小程序收银台地址
  32 + private String appUrl;
  33 +
  34 + public void override(CashierParams params) {
  35 + if (Objects.isNull(this.pcUrl)) {
  36 + this.pcUrl = params.getPcUrl();
  37 + }
  38 + if (Objects.isNull(this.miniProUrl)) {
  39 + this.miniProUrl = params.getMiniProUrl();
  40 + }
  41 + if (Objects.isNull(this.h5Url)) {
  42 + this.h5Url = params.getH5Url();
  43 + }
  44 + if (Objects.isNull(this.appUrl)) {
  45 + this.appUrl = params.getAppUrl();
  46 + }
  47 + }
  48 +
  49 + public String getPcUrl() {
  50 + return pcUrl;
  51 + }
  52 +
  53 + public void setPcUrl(String pcUrl) {
  54 + this.pcUrl = pcUrl;
  55 + }
  56 +
  57 + public String getMiniProUrl() {
  58 + return miniProUrl;
  59 + }
  60 +
  61 + public void setMiniProUrl(String miniProUrl) {
  62 + this.miniProUrl = miniProUrl;
  63 + }
  64 +
  65 + public String getH5Url() {
  66 + return h5Url;
  67 + }
  68 +
  69 + public void setH5Url(String h5Url) {
  70 + this.h5Url = h5Url;
  71 + }
  72 +
  73 + public String getAppUrl() {
  74 + return appUrl;
  75 + }
  76 +
  77 + public void setAppUrl(String appUrl) {
  78 + this.appUrl = appUrl;
  79 + }
  80 + }
  81 +}
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/domain/PaymentStateDTO.java 0 → 100644
  1 +package com.diligrp.cashier.trade.domain;
  2 +
  3 +import com.diligrp.cashier.pipeline.type.OutPaymentType;
  4 +import com.diligrp.cashier.pipeline.type.PaymentState;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +/**
  9 + * 支付订单状态处理模型
  10 + */
  11 +public class PaymentStateDTO {
  12 + // 支付ID
  13 + private String paymentId;
  14 + // 通道流水号
  15 + private String outTradeNo;
  16 + // 实际支付方式
  17 + private Integer outPayType;
  18 + // 支付方ID - 如:微信的openId
  19 + private String payerId;
  20 + // 支付时间
  21 + private LocalDateTime finishTime;
  22 + // 订单状态
  23 + private Integer state;
  24 + // 交易描述
  25 + private String description;
  26 + // 数据版本
  27 + private Integer version;
  28 + // 修改时间
  29 + private LocalDateTime modifiedTime;
  30 +
  31 + public String getPaymentId() {
  32 + return paymentId;
  33 + }
  34 +
  35 + public void setPaymentId(String paymentId) {
  36 + this.paymentId = paymentId;
  37 + }
  38 +
  39 + public String getOutTradeNo() {
  40 + return outTradeNo;
  41 + }
  42 +
  43 + public void setOutTradeNo(String outTradeNo) {
  44 + this.outTradeNo = outTradeNo;
  45 + }
  46 +
  47 + public Integer getOutPayType() {
  48 + return outPayType;
  49 + }
  50 +
  51 + public void setOutPayType(Integer outPayType) {
  52 + this.outPayType = outPayType;
  53 + }
  54 +
  55 + public String getPayerId() {
  56 + return payerId;
  57 + }
  58 +
  59 + public void setPayerId(String payerId) {
  60 + this.payerId = payerId;
  61 + }
  62 +
  63 + public LocalDateTime getFinishTime() {
  64 + return finishTime;
  65 + }
  66 +
  67 + public void setFinishTime(LocalDateTime finishTime) {
  68 + this.finishTime = finishTime;
  69 + }
  70 +
  71 + public Integer getState() {
  72 + return state;
  73 + }
  74 +
  75 + public void setState(Integer state) {
  76 + this.state = state;
  77 + }
  78 +
  79 + public String getDescription() {
  80 + return description;
  81 + }
  82 +
  83 + public void setDescription(String description) {
  84 + this.description = description;
  85 + }
  86 +
  87 + public Integer getVersion() {
  88 + return version;
  89 + }
  90 +
  91 + public void setVersion(Integer version) {
  92 + this.version = version;
  93 + }
  94 +
  95 + public LocalDateTime getModifiedTime() {
  96 + return modifiedTime;
  97 + }
  98 +
  99 + public void setModifiedTime(LocalDateTime modifiedTime) {
  100 + this.modifiedTime = modifiedTime;
  101 + }
  102 +
  103 + public static Builder builder() {
  104 + return new PaymentStateDTO().new Builder();
  105 + }
  106 +
  107 + public class Builder {
  108 + public Builder paymentId(String paymentId) {
  109 + PaymentStateDTO.this.paymentId = paymentId;
  110 + return this;
  111 + }
  112 +
  113 + public Builder outTradeNo(String outTradeNo) {
  114 + PaymentStateDTO.this.outTradeNo = outTradeNo;
  115 + return this;
  116 + }
  117 +
  118 + public Builder outPayType(OutPaymentType outPayType) {
  119 + PaymentStateDTO.this.outPayType = outPayType.getCode();
  120 + return this;
  121 + }
  122 +
  123 + public Builder payerId(String payerId) {
  124 + PaymentStateDTO.this.payerId = payerId;
  125 + return this;
  126 + }
  127 +
  128 + public Builder finishTime(LocalDateTime finishTime) {
  129 + PaymentStateDTO.this.finishTime = finishTime;
  130 + return this;
  131 + }
  132 +
  133 + public Builder state(PaymentState state) {
  134 + PaymentStateDTO.this.state = state.getCode();
  135 + return this;
  136 + }
  137 +
  138 + public Builder description(String description) {
  139 + PaymentStateDTO.this.description = description;
  140 + return this;
  141 + }
  142 +
  143 + public Builder version(Integer version) {
  144 + PaymentStateDTO.this.version = version;
  145 + return this;
  146 + }
  147 +
  148 + public Builder modifiedTime(LocalDateTime modifiedTime) {
  149 + PaymentStateDTO.this.modifiedTime = modifiedTime;
  150 + return this;
  151 + }
  152 +
  153 + public PaymentStateDTO build() {
  154 + return PaymentStateDTO.this;
  155 + }
  156 + }
  157 +}
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/domain/TradePaymentResult.java 0 → 100644
  1 +package com.diligrp.cashier.trade.domain;
  2 +
  3 +import com.diligrp.cashier.shared.spi.PaymentEvent;
  4 +
  5 +import java.time.LocalDateTime;
  6 +
  7 +/**
  8 + * 在线支付结果 - 用于业务系统支付结果通知
  9 + */
  10 +public class TradePaymentResult extends PaymentEvent {
  11 + public TradePaymentResult(String tradeId, int state, String outTradeNo, Integer outPayType, String payerId,
  12 + LocalDateTime when, String message) {
  13 + super(tradeId, state, outTradeNo, outPayType, payerId, when, message);
  14 + }
  15 +}
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/domain/TradeStateDTO.java 0 → 100644
  1 +package com.diligrp.cashier.trade.domain;
  2 +
  3 +import com.diligrp.cashier.trade.type.TradeState;
  4 +
  5 +import java.time.LocalDateTime;
  6 +
  7 +/**
  8 + * 交易状态数据传输对象
  9 + */
  10 +public class TradeStateDTO {
  11 + // 交易ID
  12 + private String tradeId;
  13 + // 金额
  14 + private Long amount;
  15 + // 状态
  16 + private Integer state;
  17 + // 数据版本
  18 + private Integer version;
  19 + // 修改时间
  20 + private LocalDateTime modifiedTime;
  21 +
  22 + public String getTradeId() {
  23 + return tradeId;
  24 + }
  25 +
  26 + public void setTradeId(String tradeId) {
  27 + this.tradeId = tradeId;
  28 + }
  29 +
  30 + public Long getAmount() {
  31 + return amount;
  32 + }
  33 +
  34 + public void setAmount(Long amount) {
  35 + this.amount = amount;
  36 + }
  37 +
  38 + public Integer getState() {
  39 + return state;
  40 + }
  41 +
  42 + public void setState(Integer state) {
  43 + this.state = state;
  44 + }
  45 +
  46 + public Integer getVersion() {
  47 + return version;
  48 + }
  49 +
  50 + public void setVersion(Integer version) {
  51 + this.version = version;
  52 + }
  53 +
  54 + public LocalDateTime getModifiedTime() {
  55 + return modifiedTime;
  56 + }
  57 +
  58 + public void setModifiedTime(LocalDateTime modifiedTime) {
  59 + this.modifiedTime = modifiedTime;
  60 + }
  61 +
  62 + public static TradeStateDTO of(String tradeId, TradeState state, Integer version, LocalDateTime modifiedTime) {
  63 + return of(tradeId, null, state, version, modifiedTime);
  64 + }
  65 +
  66 + public static TradeStateDTO of(String tradeId, Long amount, TradeState state, Integer version, LocalDateTime modifiedTime) {
  67 + TradeStateDTO tradeStateDTO = new TradeStateDTO();
  68 + tradeStateDTO.tradeId = tradeId;
  69 + tradeStateDTO.amount = amount;
  70 + tradeStateDTO.state = state.getCode();
  71 + tradeStateDTO.version = version;
  72 + tradeStateDTO.modifiedTime = modifiedTime;
  73 + return tradeStateDTO;
  74 + }
  75 +}
0 76 \ No newline at end of file
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/manager/PaymentResultManager.java 0 → 100644
  1 +package com.diligrp.cashier.trade.manager;
  2 +
  3 +import com.diligrp.cashier.shared.service.ServiceEndpointSupport;
  4 +import com.diligrp.cashier.shared.service.ThreadPoolService;
  5 +import com.diligrp.cashier.shared.spi.IPaymentEventListener;
  6 +import com.diligrp.cashier.shared.util.JsonUtils;
  7 +import com.diligrp.cashier.shared.util.ObjectUtils;
  8 +import com.diligrp.cashier.trade.domain.TradePaymentResult;
  9 +import jakarta.annotation.Resource;
  10 +import org.slf4j.Logger;
  11 +import org.slf4j.LoggerFactory;
  12 +import org.springframework.beans.factory.ObjectProvider;
  13 +import org.springframework.stereotype.Service;
  14 +
  15 +import java.util.List;
  16 +
  17 +@Service("paymentResultManager")
  18 +public class PaymentResultManager {
  19 +
  20 + private static final Logger LOG = LoggerFactory.getLogger(PaymentResultManager.class);
  21 +
  22 + @Resource
  23 + private ObjectProvider<IPaymentEventListener> eventListeners;
  24 +
  25 + /**
  26 + * 通知业务系统在线支付通道处理结果
  27 + */
  28 + public void notifyPaymentResult(String uri, TradePaymentResult payload) {
  29 + ThreadPoolService.getIoThreadPoll().submit(() -> {
  30 + List<IPaymentEventListener> lifeCycles = eventListeners.stream().toList();
  31 + for (IPaymentEventListener listener : lifeCycles) {
  32 + try {
  33 + listener.onEvent(payload);
  34 + } catch (Exception ex) {
  35 + LOG.error("Failed to notify trade payment result", ex);
  36 + }
  37 + }
  38 + });
  39 +
  40 + if (ObjectUtils.isEmpty(uri)) {
  41 + return;
  42 + }
  43 +
  44 + ThreadPoolService.getIoThreadPoll().submit(() -> {
  45 + try {
  46 + String body = JsonUtils.toJsonString(payload);
  47 + LOG.info("Notifying online trade payment result: {}", body);
  48 + ServiceEndpointSupport.HttpResult httpResult = new NotifyHttpClient(uri).send(body);
  49 + if (httpResult.statusCode != 200) {
  50 + LOG.error("Failed to notify trade payment result");
  51 + }
  52 + } catch (Exception ex) {
  53 + LOG.error("Failed to notify trade payment result", ex);
  54 + }
  55 + });
  56 + }
  57 +
  58 + private static class NotifyHttpClient extends ServiceEndpointSupport {
  59 + private final String baseUrl;
  60 +
  61 + public NotifyHttpClient(String baseUrl) {
  62 + this.baseUrl = baseUrl;
  63 + }
  64 +
  65 + public HttpResult send(String body) {
  66 + return send(baseUrl, body);
  67 + }
  68 + }
  69 +}
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/model/OnlinePayment.java 0 → 100644
  1 +package com.diligrp.cashier.trade.model;
  2 +
  3 +import com.diligrp.cashier.pipeline.type.ChannelType;
  4 +import com.diligrp.cashier.pipeline.type.OutPaymentType;
  5 +import com.diligrp.cashier.pipeline.type.PaymentState;
  6 +import com.diligrp.cashier.pipeline.type.PaymentType;
  7 +import com.diligrp.cashier.shared.domain.BaseDO;
  8 +import com.diligrp.cashier.trade.type.TradeType;
  9 +
  10 +import java.time.LocalDateTime;
  11 +
  12 +public class OnlinePayment extends BaseDO {
  13 + // 外部商户号
  14 + private String outMchId;
  15 + // 订单ID
  16 + private String tradeId;
  17 + // 交易类型
  18 + private Integer type;
  19 + // 支付ID
  20 + private String paymentId;
  21 + // 支付通道
  22 + private Integer channelId;
  23 + // 支付方式
  24 + private Integer payType;
  25 + // 客户ID
  26 + private Long pipelineId;
  27 + // 微信商品描述
  28 + private String goods;
  29 + // 申请金额-分
  30 + private Long amount;
  31 + // 操作对象-比如:prepareId,二维码,或退款时的原单号
  32 + private String objectId;
  33 + // 支付方-如微信openId
  34 + private String payerId;
  35 + // 支付时间
  36 + private LocalDateTime finishTime;
  37 + // 通道流水号
  38 + private String outTradeNo;
  39 + // 实际支付方式-聚合支付通道使用
  40 + private Integer outPayType;
  41 + // 申请状态
  42 + private Integer state;
  43 + // 备注
  44 + private String description;
  45 +
  46 + public String getOutMchId() {
  47 + return outMchId;
  48 + }
  49 +
  50 + public void setOutMchId(String outMchId) {
  51 + this.outMchId = outMchId;
  52 + }
  53 +
  54 + public String getTradeId() {
  55 + return tradeId;
  56 + }
  57 +
  58 + public void setTradeId(String tradeId) {
  59 + this.tradeId = tradeId;
  60 + }
  61 +
  62 + public Integer getType() {
  63 + return type;
  64 + }
  65 +
  66 + public void setType(Integer type) {
  67 + this.type = type;
  68 + }
  69 +
  70 + public String getPaymentId() {
  71 + return paymentId;
  72 + }
  73 +
  74 + public void setPaymentId(String paymentId) {
  75 + this.paymentId = paymentId;
  76 + }
  77 +
  78 + public Integer getChannelId() {
  79 + return channelId;
  80 + }
  81 +
  82 + public void setChannelId(Integer channelId) {
  83 + this.channelId = channelId;
  84 + }
  85 +
  86 + public Integer getPayType() {
  87 + return payType;
  88 + }
  89 +
  90 + public void setPayType(Integer payType) {
  91 + this.payType = payType;
  92 + }
  93 +
  94 + public Long getPipelineId() {
  95 + return pipelineId;
  96 + }
  97 +
  98 + public void setPipelineId(Long pipelineId) {
  99 + this.pipelineId = pipelineId;
  100 + }
  101 +
  102 + public String getGoods() {
  103 + return goods;
  104 + }
  105 +
  106 + public void setGoods(String goods) {
  107 + this.goods = goods;
  108 + }
  109 +
  110 + public Long getAmount() {
  111 + return amount;
  112 + }
  113 +
  114 + public void setAmount(Long amount) {
  115 + this.amount = amount;
  116 + }
  117 +
  118 + public String getObjectId() {
  119 + return objectId;
  120 + }
  121 +
  122 + public void setObjectId(String objectId) {
  123 + this.objectId = objectId;
  124 + }
  125 +
  126 + public String getPayerId() {
  127 + return payerId;
  128 + }
  129 +
  130 + public void setPayerId(String payerId) {
  131 + this.payerId = payerId;
  132 + }
  133 +
  134 + public LocalDateTime getFinishTime() {
  135 + return finishTime;
  136 + }
  137 +
  138 + public void setFinishTime(LocalDateTime finishTime) {
  139 + this.finishTime = finishTime;
  140 + }
  141 +
  142 + public String getOutTradeNo() {
  143 + return outTradeNo;
  144 + }
  145 +
  146 + public void setOutTradeNo(String outTradeNo) {
  147 + this.outTradeNo = outTradeNo;
  148 + }
  149 +
  150 + public Integer getOutPayType() {
  151 + return outPayType;
  152 + }
  153 +
  154 + public void setOutPayType(Integer outPayType) {
  155 + this.outPayType = outPayType;
  156 + }
  157 +
  158 + public Integer getState() {
  159 + return state;
  160 + }
  161 +
  162 + public void setState(Integer state) {
  163 + this.state = state;
  164 + }
  165 +
  166 + public String getDescription() {
  167 + return description;
  168 + }
  169 +
  170 + public void setDescription(String description) {
  171 + this.description = description;
  172 + }
  173 +
  174 + public static Builder builder() {
  175 + return new OnlinePayment().new Builder();
  176 + }
  177 +
  178 + public class Builder {
  179 + public Builder outMchId(String outMchId) {
  180 + OnlinePayment.this.outMchId = outMchId;
  181 + return this;
  182 + }
  183 +
  184 + public Builder tradeId(String tradeId) {
  185 + OnlinePayment.this.tradeId = tradeId;
  186 + return this;
  187 + }
  188 +
  189 + public Builder type(TradeType type) {
  190 + OnlinePayment.this.type = type.getCode();
  191 + return this;
  192 + }
  193 +
  194 + public Builder paymentId(String paymentId) {
  195 + OnlinePayment.this.paymentId = paymentId;
  196 + return this;
  197 + }
  198 +
  199 + public Builder channelId(ChannelType channelType) {
  200 + OnlinePayment.this.channelId = channelType.getCode();
  201 + return this;
  202 + }
  203 +
  204 + public Builder payType(PaymentType payType) {
  205 + OnlinePayment.this.payType = payType.getCode();
  206 + return this;
  207 + }
  208 +
  209 + public Builder pipelineId(Long pipelineId) {
  210 + OnlinePayment.this.pipelineId = pipelineId;
  211 + return this;
  212 + }
  213 +
  214 + public Builder goods(String goods) {
  215 + OnlinePayment.this.goods = goods;
  216 + return this;
  217 + }
  218 +
  219 + public Builder amount(Long amount) {
  220 + OnlinePayment.this.amount = amount;
  221 + return this;
  222 + }
  223 +
  224 + public Builder objectId(String objectId) {
  225 + OnlinePayment.this.objectId = objectId;
  226 + return this;
  227 + }
  228 +
  229 + public Builder payerId(String payerId) {
  230 + OnlinePayment.this.payerId = payerId;
  231 + return this;
  232 + }
  233 +
  234 + public Builder finishTime(LocalDateTime payTime) {
  235 + OnlinePayment.this.finishTime = payTime;
  236 + return this;
  237 + }
  238 +
  239 + public Builder outTradeNo(String outTradeNo) {
  240 + OnlinePayment.this.outTradeNo = outTradeNo;
  241 + return this;
  242 + }
  243 +
  244 + public Builder outPayType(OutPaymentType outPayType) {
  245 + OnlinePayment.this.outPayType = outPayType.getCode();
  246 + return this;
  247 + }
  248 +
  249 + public Builder state(PaymentState state) {
  250 + OnlinePayment.this.state = state.getCode();
  251 + return this;
  252 + }
  253 +
  254 + public Builder description(String description) {
  255 + OnlinePayment.this.description = description;
  256 + return this;
  257 + }
  258 +
  259 + public Builder version(Integer version) {
  260 + OnlinePayment.this.version = version;
  261 + return this;
  262 + }
  263 +
  264 + public Builder createdTime(LocalDateTime createdTime) {
  265 + OnlinePayment.this.createdTime = createdTime;
  266 + return this;
  267 + }
  268 +
  269 + public Builder modifiedTime(LocalDateTime modifiedTime) {
  270 + OnlinePayment.this.modifiedTime = modifiedTime;
  271 + return this;
  272 + }
  273 +
  274 + public OnlinePayment build() {
  275 + return OnlinePayment.this;
  276 + }
  277 + }
  278 +}
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/model/TradeOrder.java 0 → 100644
  1 +package com.diligrp.cashier.trade.model;
  2 +
  3 +import com.diligrp.cashier.shared.domain.BaseDO;
  4 +
  5 +import java.time.LocalDateTime;
  6 +
  7 +/**
  8 + * 交易订单数据模型
  9 + */
  10 +public class TradeOrder extends BaseDO {
  11 + // 商户ID
  12 + private Long mchId;
  13 + // 交易ID
  14 + private String tradeId;
  15 + // 交易类型
  16 + private Integer type;
  17 + // 外部流水号
  18 + private String outTradeNo;
  19 + // 金额-分
  20 + private Long amount;
  21 + // 初始金额-分
  22 + private Long maxAmount;
  23 + // 商品描述
  24 + private String goods;
  25 + // 订单超时时间-秒
  26 + private Integer timeout;
  27 + // 交易状态
  28 + private Integer state;
  29 + // 业务回调地址
  30 + private String notifyUrl;
  31 + // 交易备注
  32 + private String description;
  33 + // 附加数据
  34 + private String attach;
  35 + // 收银台来源
  36 + private Integer source;
  37 +
  38 + public Long getMchId() {
  39 + return mchId;
  40 + }
  41 +
  42 + public void setMchId(Long mchId) {
  43 + this.mchId = mchId;
  44 + }
  45 +
  46 + public String getTradeId() {
  47 + return tradeId;
  48 + }
  49 +
  50 + public void setTradeId(String tradeId) {
  51 + this.tradeId = tradeId;
  52 + }
  53 +
  54 + public Integer getType() {
  55 + return type;
  56 + }
  57 +
  58 + public void setType(Integer type) {
  59 + this.type = type;
  60 + }
  61 +
  62 + public String getOutTradeNo() {
  63 + return outTradeNo;
  64 + }
  65 +
  66 + public void setOutTradeNo(String outTradeNo) {
  67 + this.outTradeNo = outTradeNo;
  68 + }
  69 +
  70 + public Long getAmount() {
  71 + return amount;
  72 + }
  73 +
  74 + public void setAmount(Long amount) {
  75 + this.amount = amount;
  76 + }
  77 +
  78 + public Long getMaxAmount() {
  79 + return maxAmount;
  80 + }
  81 +
  82 + public void setMaxAmount(Long maxAmount) {
  83 + this.maxAmount = maxAmount;
  84 + }
  85 +
  86 + public String getGoods() {
  87 + return goods;
  88 + }
  89 +
  90 + public void setGoods(String goods) {
  91 + this.goods = goods;
  92 + }
  93 +
  94 + public Integer getTimeout() {
  95 + return timeout;
  96 + }
  97 +
  98 + public void setTimeout(Integer timeout) {
  99 + this.timeout = timeout;
  100 + }
  101 +
  102 + public Integer getState() {
  103 + return state;
  104 + }
  105 +
  106 + public void setState(Integer state) {
  107 + this.state = state;
  108 + }
  109 +
  110 + public String getNotifyUrl() {
  111 + return notifyUrl;
  112 + }
  113 +
  114 + public void setNotifyUrl(String notifyUrl) {
  115 + this.notifyUrl = notifyUrl;
  116 + }
  117 +
  118 + public String getDescription() {
  119 + return description;
  120 + }
  121 +
  122 + public void setDescription(String description) {
  123 + this.description = description;
  124 + }
  125 +
  126 + public String getAttach() {
  127 + return attach;
  128 + }
  129 +
  130 + public void setAttach(String attach) {
  131 + this.attach = attach;
  132 + }
  133 +
  134 + public Integer getSource() {
  135 + return source;
  136 + }
  137 +
  138 + public void setSource(Integer source) {
  139 + this.source = source;
  140 + }
  141 +
  142 + public static Builder builder() {
  143 + return new TradeOrder().new Builder();
  144 + }
  145 +
  146 + public class Builder {
  147 + public Builder mchId(Long mchId) {
  148 + TradeOrder.this.mchId = mchId;
  149 + return this;
  150 + }
  151 +
  152 + public Builder tradeId(String tradeId) {
  153 + TradeOrder.this.tradeId = tradeId;
  154 + return this;
  155 + }
  156 +
  157 + public Builder type(Integer type) {
  158 + TradeOrder.this.type = type;
  159 + return this;
  160 + }
  161 +
  162 + public Builder outTradeNo(String outTradeNo) {
  163 + TradeOrder.this.outTradeNo = outTradeNo;
  164 + return this;
  165 + }
  166 +
  167 + public Builder amount(Long amount) {
  168 + TradeOrder.this.amount = amount;
  169 + return this;
  170 + }
  171 +
  172 + public Builder maxAmount(Long maxAmount) {
  173 + TradeOrder.this.maxAmount = maxAmount;
  174 + return this;
  175 + }
  176 +
  177 + public Builder goods(String goods) {
  178 + TradeOrder.this.goods = goods;
  179 + return this;
  180 + }
  181 +
  182 + public Builder timeout(Integer timeout) {
  183 + TradeOrder.this.timeout = timeout;
  184 + return this;
  185 + }
  186 +
  187 + public Builder state(Integer state) {
  188 + TradeOrder.this.state = state;
  189 + return this;
  190 + }
  191 +
  192 + public Builder notifyUrl(String notifyUrl) {
  193 + TradeOrder.this.notifyUrl = notifyUrl;
  194 + return this;
  195 + }
  196 +
  197 + public Builder description(String description) {
  198 + TradeOrder.this.description = description;
  199 + return this;
  200 + }
  201 +
  202 + public Builder attach(String attach) {
  203 + TradeOrder.this.attach = attach;
  204 + return this;
  205 + }
  206 +
  207 + public Builder source(Integer source) {
  208 + TradeOrder.this.source = source;
  209 + return this;
  210 + }
  211 +
  212 + public Builder version(Integer version) {
  213 + TradeOrder.this.version = version;
  214 + return this;
  215 + }
  216 +
  217 + public Builder createdTime(LocalDateTime createdTime) {
  218 + TradeOrder.this.createdTime = createdTime;
  219 + return this;
  220 + }
  221 +
  222 + public Builder modifiedTime(LocalDateTime modifiedTime) {
  223 + TradeOrder.this.modifiedTime = modifiedTime;
  224 + return this;
  225 + }
  226 +
  227 + public TradeOrder build() {
  228 + return TradeOrder.this;
  229 + }
  230 + }
  231 +}
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/service/ICashierPaymentService.java 0 → 100644
  1 +package com.diligrp.cashier.trade.service;
  2 +
  3 +import com.diligrp.cashier.pipeline.domain.OnlinePaymentStatus;
  4 +import com.diligrp.cashier.trade.domain.CashierOrder;
  5 +import com.diligrp.cashier.trade.domain.CashierPayment;
  6 +import com.diligrp.cashier.trade.domain.Merchant;
  7 +
  8 +public interface ICashierPaymentService {
  9 + /**
  10 + * 提交收银台订单
  11 + *
  12 + * @param merchant - 接入商户
  13 + * @param cashierOrder - 订单申请
  14 + * @return 支付ID
  15 + */
  16 + String doSubmit(Merchant merchant, CashierOrder cashierOrder);
  17 +
  18 + /**
  19 + * 收银台支付
  20 + *
  21 + * @param cashierPayment - 支付信息
  22 + * @return 支付状态
  23 + */
  24 + OnlinePaymentStatus doPayment(CashierPayment cashierPayment);
  25 +}
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/service/IPaymentAssistantService.java 0 → 100644
  1 +package com.diligrp.cashier.trade.service;
  2 +
  3 +public interface IPaymentAssistantService {
  4 +}
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/service/ITradeAssistantService.java 0 → 100644
  1 +package com.diligrp.cashier.trade.service;
  2 +
  3 +import com.diligrp.cashier.trade.domain.PaymentStateDTO;
  4 +import com.diligrp.cashier.trade.domain.TradeStateDTO;
  5 +import com.diligrp.cashier.trade.model.OnlinePayment;
  6 +import com.diligrp.cashier.trade.model.TradeOrder;
  7 +
  8 +public interface ITradeAssistantService {
  9 +
  10 + /**
  11 + * 查询交易订单
  12 + */
  13 + TradeOrder findByTradeId(String tradeId);
  14 +
  15 + /**
  16 + * 交易订单状态处理
  17 + */
  18 + void proceedTradeOrder(TradeStateDTO tradeStateDTO);
  19 +
  20 + /**
  21 + * 关闭交易订单下状态为支付中的支付订单
  22 + */
  23 + boolean resetTradeOrder(TradeOrder tradeOrder);
  24 +
  25 + /**
  26 + * 查询支付订单
  27 + */
  28 + OnlinePayment findByPaymentId(String paymentId);
  29 +
  30 + /**
  31 + * 支付订单状态处理
  32 + */
  33 + void proceedOnlinePayment(PaymentStateDTO paymentDTO);
  34 +
  35 +}
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/service/impl/CashierPaymentServiceImpl.java 0 → 100644
  1 +package com.diligrp.cashier.trade.service.impl;
  2 +
  3 +import com.diligrp.cashier.assistant.service.impl.SnowflakeKeyManager;
  4 +import com.diligrp.cashier.pipeline.core.DiliCardPipeline;
  5 +import com.diligrp.cashier.pipeline.core.OnlinePipeline;
  6 +import com.diligrp.cashier.pipeline.core.PaymentPipeline;
  7 +import com.diligrp.cashier.pipeline.domain.MiniProPrepayRequest;
  8 +import com.diligrp.cashier.pipeline.domain.MiniProPrepayResponse;
  9 +import com.diligrp.cashier.pipeline.domain.OnlinePaymentStatus;
  10 +import com.diligrp.cashier.pipeline.domain.card.CardPaymentRequest;
  11 +import com.diligrp.cashier.pipeline.domain.card.CardPaymentResponse;
  12 +import com.diligrp.cashier.pipeline.service.IPaymentPipelineManager;
  13 +import com.diligrp.cashier.pipeline.type.CashierType;
  14 +import com.diligrp.cashier.pipeline.type.OutPaymentType;
  15 +import com.diligrp.cashier.pipeline.type.PaymentState;
  16 +import com.diligrp.cashier.pipeline.type.PaymentType;
  17 +import com.diligrp.cashier.shared.ErrorCode;
  18 +import com.diligrp.cashier.trade.Constants;
  19 +import com.diligrp.cashier.trade.dao.IOnlinePaymentDao;
  20 +import com.diligrp.cashier.trade.dao.ITradeOrderDao;
  21 +import com.diligrp.cashier.trade.domain.*;
  22 +import com.diligrp.cashier.trade.exception.TradePaymentException;
  23 +import com.diligrp.cashier.trade.manager.PaymentResultManager;
  24 +import com.diligrp.cashier.trade.model.OnlinePayment;
  25 +import com.diligrp.cashier.trade.model.TradeOrder;
  26 +import com.diligrp.cashier.trade.service.ICashierPaymentService;
  27 +import com.diligrp.cashier.trade.service.ITradeAssistantService;
  28 +import com.diligrp.cashier.trade.type.SnowflakeKey;
  29 +import com.diligrp.cashier.trade.type.TradeState;
  30 +import com.diligrp.cashier.trade.type.TradeType;
  31 +import com.diligrp.cashier.trade.util.CardPaymentConverter;
  32 +import com.diligrp.cashier.trade.util.MiniProPaymentConverter;
  33 +import jakarta.annotation.Resource;
  34 +import org.redisson.api.RLock;
  35 +import org.redisson.api.RedissonClient;
  36 +import org.springframework.stereotype.Service;
  37 +import org.springframework.transaction.annotation.Transactional;
  38 +
  39 +import java.time.LocalDateTime;
  40 +import java.util.Objects;
  41 +import java.util.concurrent.TimeUnit;
  42 +
  43 +@Service("cashierPaymentService")
  44 +public class CashierPaymentServiceImpl implements ICashierPaymentService {
  45 +
  46 + @Resource
  47 + private ITradeOrderDao tradeOrderDao;
  48 +
  49 + @Resource
  50 + private IOnlinePaymentDao onlinePaymentDao;
  51 +
  52 + @Resource
  53 + private ITradeAssistantService tradeAssistantService;
  54 +
  55 + @Resource
  56 + private IPaymentPipelineManager paymentPipelineManager;
  57 +
  58 + @Resource
  59 + private PaymentResultManager paymentResultManager;
  60 +
  61 + @Resource
  62 + private SnowflakeKeyManager snowflakeKeyManager;
  63 +
  64 + @Resource
  65 + private RedissonClient redissonClient;
  66 +
  67 + /**
  68 + * 提交收银台订单
  69 + *
  70 + * @param merchant - 接入商户
  71 + * @param cashierOrder - 订单申请
  72 + * @return 支付ID
  73 + */
  74 + @Override
  75 + @Transactional(rollbackFor = Exception.class)
  76 + public String doSubmit(Merchant merchant, CashierOrder cashierOrder) {
  77 + LocalDateTime now = LocalDateTime.now();
  78 + String tradeId = snowflakeKeyManager.getKeyGenerator(SnowflakeKey.TRADE_ID).nextId();
  79 + int timeout = Objects.isNull(cashierOrder.getTimeout()) ? Constants.DEFAULT_ORDER_TIMEOUT_SECONDS : cashierOrder.getTimeout();
  80 + timeout = Math.max(timeout, Constants.MIN_ORDER_TIMEOUT_SECONDS);
  81 + TradeOrder tradeOrder = TradeOrder.builder().mchId(merchant.getMchId()).tradeId(tradeId)
  82 + .type(cashierOrder.getType().getCode()).outTradeNo(cashierOrder.getOutTradeNo()).amount(cashierOrder.getAmount())
  83 + .maxAmount(cashierOrder.getAmount()).goods(cashierOrder.getGoods()).timeout(timeout).state(TradeState.PENDING.getCode())
  84 + .notifyUrl(cashierOrder.getNotifyUrl()).description(cashierOrder.getDescription()).attach(cashierOrder.getAttach())
  85 + .source(0).version(0).createdTime(now).modifiedTime(now).build();
  86 + tradeOrderDao.insertTradeOrder(tradeOrder);
  87 +
  88 + // TODO: userId是否需要存储
  89 + // TODO: 如果不打开收银台支付,定时关闭订单
  90 + return tradeId;
  91 + }
  92 +
  93 + /**
  94 + * 收银台支付
  95 + *
  96 + * @param cashierPayment - 支付信息
  97 + * @return 支付状态
  98 + */
  99 + @Override
  100 + @Transactional(rollbackFor = Exception.class)
  101 + public OnlinePaymentStatus doPayment(CashierPayment cashierPayment) {
  102 + // TODO: 防重复提交
  103 + String lockKey = String.format(Constants.TRADE_LOCK_REDIS_KEY, cashierPayment.getTradeId());
  104 + RLock lock = redissonClient.getLock(lockKey);
  105 + try {
  106 + boolean locked = lock.tryLock(Constants.TRADE_LOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
  107 + if (locked) {
  108 + TradeOrder tradeOrder = tradeAssistantService.findByTradeId(cashierPayment.getTradeId());
  109 + CashierType cashierType = CashierType.getByCode(tradeOrder.getType());
  110 + if (TradeState.isFinished(tradeOrder.getState())) {
  111 + throw new TradePaymentException(ErrorCode.OPERATION_NOT_ALLOWED, "该交易订单已经完成,不能进行支付");
  112 + }
  113 + // 关闭支付中的支付订单, 避免一个交易订单存在多笔支付订单, 造成重复支付
  114 + if (!tradeAssistantService.resetTradeOrder(tradeOrder)) {
  115 + throw new TradePaymentException(ErrorCode.OPERATION_NOT_ALLOWED, "存在支付中的支付申请");
  116 + }
  117 +
  118 + LocalDateTime now = LocalDateTime.now();
  119 + // 获取支付通道
  120 + PaymentPipeline<?> pipeline = paymentPipelineManager.findPipelineById(cashierPayment.getPipelineId(), PaymentPipeline.class);
  121 + String paymentId = snowflakeKeyManager.getKeyGenerator(SnowflakeKey.PAYMENT_ID).nextId();
  122 + if (pipeline instanceof DiliCardPipeline cardPipeline) {
  123 + // 园区卡支付通道: 所有的收银台类型使用的是同一种园区卡支付流程
  124 + // 检查园区卡支付参数
  125 + CardPaymentRequest request = new CardPaymentConverter(tradeOrder, paymentId, now).convert(cashierPayment);
  126 + // 修改支付状态为支付中,防止重复支付
  127 + CardPaymentResponse response = cardPipeline.sendPaymentRequest(request);
  128 + if (PaymentState.isFinished(response.getState().getCode())) {
  129 + // 园区卡支付通道outMchId为市场ID
  130 + String outMchId = cardPipeline.params().getOutMchId();
  131 + OnlinePayment payment = OnlinePayment.builder().outMchId(outMchId).tradeId(tradeOrder.getTradeId())
  132 + .type(TradeType.TRADE).paymentId(paymentId).channelId(pipeline.supportedChannel())
  133 + .payType(PaymentType.DIRECT).pipelineId(pipeline.pipelineId()).goods(tradeOrder.getGoods())
  134 + .amount(tradeOrder.getAmount()).payerId(response.getPayerId())
  135 + .finishTime(response.getWhen()).outTradeNo(response.getOutTradeNo())
  136 + .outPayType(OutPaymentType.DILICARD).state(response.getState())
  137 + .description(response.getMessage()).version(0).createdTime(now).modifiedTime(now).build();
  138 + onlinePaymentDao.insertOnlinePayment(payment);
  139 + }
  140 + if (response.getState() == PaymentState.SUCCESS) {
  141 + TradeStateDTO tradeStateDTO = TradeStateDTO.of(tradeOrder.getTradeId(), TradeState.SUCCESS,
  142 + tradeOrder.getVersion(), now);
  143 + tradeAssistantService.proceedTradeOrder(tradeStateDTO);
  144 + // 通知业务系统支付结果
  145 + TradePaymentResult paymentResult = new TradePaymentResult(tradeOrder.getTradeId(),
  146 + response.getState().getCode(), tradeOrder.getOutTradeNo(), OutPaymentType.DILICARD.getCode(),
  147 + response.getPayerId(), response.getWhen(), response.getMessage());
  148 + paymentResultManager.notifyPaymentResult(tradeOrder.getNotifyUrl(), paymentResult);
  149 + }
  150 + return response;
  151 + } else if (pipeline instanceof OnlinePipeline<?> onlinePipeline) {
  152 + // 在线支付通道: 不同的收银台类型使用的支付方式不同
  153 + if (cashierType == CashierType.MINIPRO) {
  154 + // 小程序支付收银台使用小程序支付
  155 + MiniProPrepayRequest request = new MiniProPaymentConverter(tradeOrder, paymentId, now).convert(cashierPayment);
  156 + MiniProPrepayResponse response = onlinePipeline.sendMiniProPrepayRequest(request);
  157 + // 微信服务商模式下outMchId为签约子商户
  158 + String outMchId = request.getString(com.diligrp.cashier.pipeline.Constants.PARAM_MCH_ID);
  159 + OnlinePayment payment = OnlinePayment.builder().outMchId(outMchId).tradeId(tradeOrder.getTradeId())
  160 + .type(TradeType.TRADE).paymentId(paymentId).channelId(pipeline.supportedChannel())
  161 + .payType(PaymentType.MINI_PRO).pipelineId(pipeline.pipelineId()).goods(tradeOrder.getGoods())
  162 + .amount(tradeOrder.getAmount()).payerId(request.getOpenId()).outTradeNo(response.getOutTradeNo())
  163 + .state(response.getState()).version(0).createdTime(now).modifiedTime(now).build();
  164 + onlinePaymentDao.insertOnlinePayment(payment);
  165 +
  166 + TradeStateDTO tradeStateDTO = TradeStateDTO.of(tradeOrder.getTradeId(), TradeState.PROCESSING,
  167 + tradeOrder.getVersion(), now);
  168 + tradeAssistantService.proceedTradeOrder(tradeStateDTO);
  169 + return response;
  170 + } else {
  171 + throw new TradePaymentException(ErrorCode.OPERATION_NOT_ALLOWED, "不支持的收银台类型");
  172 + }
  173 + } else {
  174 + // 目前只有两类支付通道: CardPipeline和OnlinePipeline, 程序逻辑不应该到达此代码块
  175 + throw new TradePaymentException(ErrorCode.OPERATION_NOT_ALLOWED, "不支持的支付通道类型");
  176 + }
  177 + } else {
  178 + throw new TradePaymentException(ErrorCode.SYSTEM_BUSY_ERROR, ErrorCode.MESSAGE_SYSTEM_BUSY);
  179 + }
  180 + } catch (InterruptedException ex) {
  181 + // 重新设置中断标识
  182 + Thread.currentThread().interrupt();
  183 + throw new TradePaymentException(ErrorCode.SYSTEM_BUSY_ERROR, ErrorCode.MESSAGE_SYSTEM_BUSY);
  184 + } finally {
  185 + if (lock.isHeldByCurrentThread()) {
  186 + lock.unlock();
  187 + }
  188 + }
  189 + }
  190 +}
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/service/impl/TradeAssistantServiceImpl.java 0 → 100644
  1 +package com.diligrp.cashier.trade.service.impl;
  2 +
  3 +import com.diligrp.cashier.pipeline.Constants;
  4 +import com.diligrp.cashier.pipeline.core.OnlinePipeline;
  5 +import com.diligrp.cashier.pipeline.core.PaymentPipeline;
  6 +import com.diligrp.cashier.pipeline.domain.OnlinePrepayOrder;
  7 +import com.diligrp.cashier.pipeline.service.IPaymentPipelineManager;
  8 +import com.diligrp.cashier.pipeline.type.PaymentState;
  9 +import com.diligrp.cashier.shared.ErrorCode;
  10 +import com.diligrp.cashier.trade.dao.IOnlinePaymentDao;
  11 +import com.diligrp.cashier.trade.dao.ITradeOrderDao;
  12 +import com.diligrp.cashier.trade.domain.PaymentStateDTO;
  13 +import com.diligrp.cashier.trade.domain.TradeStateDTO;
  14 +import com.diligrp.cashier.trade.exception.TradePaymentException;
  15 +import com.diligrp.cashier.trade.model.OnlinePayment;
  16 +import com.diligrp.cashier.trade.model.TradeOrder;
  17 +import com.diligrp.cashier.trade.service.ITradeAssistantService;
  18 +import com.diligrp.cashier.trade.type.TradeState;
  19 +import jakarta.annotation.Resource;
  20 +import org.slf4j.Logger;
  21 +import org.slf4j.LoggerFactory;
  22 +import org.springframework.stereotype.Service;
  23 +import org.springframework.transaction.annotation.Propagation;
  24 +import org.springframework.transaction.annotation.Transactional;
  25 +
  26 +import java.time.LocalDateTime;
  27 +import java.util.List;
  28 +import java.util.Optional;
  29 +
  30 +@Service("tradeAssistantService")
  31 +public class TradeAssistantServiceImpl implements ITradeAssistantService {
  32 +
  33 + private static final Logger LOG = LoggerFactory.getLogger(TradeAssistantServiceImpl.class);
  34 +
  35 + @Resource
  36 + private ITradeOrderDao tradeOrderDao;
  37 +
  38 + @Resource
  39 + private IOnlinePaymentDao onlinePaymentDao;
  40 +
  41 + @Resource
  42 + private IPaymentPipelineManager paymentPipelineManager;
  43 +
  44 + @Override
  45 + public TradeOrder findByTradeId(String tradeId) {
  46 + Optional<TradeOrder> tradeOpt = tradeOrderDao.findByTradeId(tradeId);
  47 + return tradeOpt.orElseThrow(() -> new TradePaymentException(ErrorCode.OBJECT_NOT_FOUND, "支付订单不存在"));
  48 + }
  49 +
  50 + @Override
  51 + public void proceedTradeOrder(TradeStateDTO tradeStateDTO) {
  52 + if (tradeOrderDao.compareAndSetState(tradeStateDTO) == 0) {
  53 + throw new TradePaymentException(ErrorCode.SYSTEM_BUSY_ERROR, ErrorCode.MESSAGE_SYSTEM_BUSY);
  54 + }
  55 + }
  56 +
  57 + /**
  58 + * 关闭交易订单下状态为支付中的支付订单, 园区卡支付不存在支付中的订单
  59 + *
  60 + * 独立数据库事务, 避免远程支付通道关单后, 支付订单状态仍然为支付中
  61 + * 任何一笔关闭订单失败都应该返回FALSE,且一笔失败不影响其他能正常的关单操作
  62 + */
  63 + @Override
  64 + @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
  65 + public boolean resetTradeOrder(TradeOrder tradeOrder) {
  66 + // 只有处理中的交易订单存在状态为支付中的支付订单
  67 + if (!TradeState.isProcessing(tradeOrder.getState())) {
  68 + return true;
  69 + }
  70 +
  71 + boolean result = true;
  72 + // 理论上只会存在一条支付中的支付订单
  73 + List<OnlinePayment> onlinePayments = onlinePaymentDao.findByTradeId(tradeOrder.getTradeId(), PaymentState.PROCESSING.getCode());
  74 + for (OnlinePayment onlinePayment : onlinePayments) {
  75 + try {
  76 + PaymentPipeline<?> pipeline = paymentPipelineManager.findPipelineById(onlinePayment.getPipelineId(), PaymentPipeline.class);
  77 + if (pipeline instanceof OnlinePipeline<?> onlinePipeline) {
  78 + LocalDateTime now = LocalDateTime.now();
  79 + PaymentStateDTO paymentStateDTO = PaymentStateDTO.builder().paymentId(onlinePayment.getPaymentId())
  80 + .finishTime(now).state(PaymentState.FAILED).description("主动关闭支付中的订单")
  81 + .version(onlinePayment.getVersion()).modifiedTime(now).build();
  82 + proceedOnlinePayment(paymentStateDTO);
  83 + OnlinePrepayOrder prepayOrder = new OnlinePrepayOrder(onlinePayment.getPaymentId(), onlinePayment.getOutTradeNo());
  84 + // 微信服务商模式下, outMchId为签约子商户; 园区卡支付时, outMchId为市场ID
  85 + prepayOrder.attach(Constants.PARAM_MCH_ID, onlinePayment.getOutMchId());
  86 + onlinePipeline.closePrepayOrder(prepayOrder);
  87 + }
  88 + // 园区卡支付不存在支付中的订单
  89 + } catch (Exception ex) {
  90 + result = false;
  91 + LOG.error("关闭支付订单失败: {}", onlinePayment.getPaymentId(), ex);
  92 + }
  93 + }
  94 + return result;
  95 + }
  96 +
  97 + @Override
  98 + public OnlinePayment findByPaymentId(String paymentId) {
  99 + Optional<OnlinePayment> paymentOpt = onlinePaymentDao.findByPaymentId(paymentId);
  100 + return paymentOpt.orElseThrow(() -> new TradePaymentException(ErrorCode.OBJECT_NOT_FOUND, "支付订单不存在"));
  101 + }
  102 +
  103 + @Override
  104 + public void proceedOnlinePayment(PaymentStateDTO paymentDTO) {
  105 + if (onlinePaymentDao.compareAndSetState(paymentDTO) == 0) {
  106 + throw new TradePaymentException(ErrorCode.SYSTEM_BUSY_ERROR, ErrorCode.MESSAGE_SYSTEM_BUSY);
  107 + }
  108 + }
  109 +}
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/type/SourceType.java 0 → 100644
  1 +package com.diligrp.cashier.trade.type;
  2 +
  3 +import com.diligrp.cashier.shared.type.IEnumType;
  4 +
  5 +import java.util.Arrays;
  6 +import java.util.List;
  7 +import java.util.Optional;
  8 +import java.util.stream.Stream;
  9 +
  10 +/**
  11 + * 订单来源
  12 + */
  13 +public enum SourceType implements IEnumType {
  14 +
  15 + PC("PC端", 1),
  16 +
  17 + MINIPRO("小程序端", 2),
  18 +
  19 + APP("APP端", 3),
  20 +
  21 + ATM("自助机", 4);
  22 +
  23 + private final String name;
  24 + private final int code;
  25 +
  26 + SourceType(String name, int code) {
  27 + this.name = name;
  28 + this.code = code;
  29 + }
  30 +
  31 + public boolean equalTo(int code) {
  32 + return this.code == code;
  33 + }
  34 +
  35 + public static Optional<SourceType> getType(int code) {
  36 + Stream<SourceType> TYPES = Arrays.stream(SourceType.values());
  37 + return TYPES.filter(type -> type.getCode() == code).findFirst();
  38 + }
  39 +
  40 + public static String getName(int code) {
  41 + return getType(code).map(SourceType::getName).orElse(null);
  42 + }
  43 +
  44 + public static List<SourceType> getTypes() {
  45 + return Arrays.asList(SourceType.values());
  46 + }
  47 +
  48 + @Override
  49 + public String getName() {
  50 + return name;
  51 + }
  52 +
  53 + @Override
  54 + public int getCode() {
  55 + return code;
  56 + }
  57 +
  58 + @Override
  59 + public String toString() {
  60 + return name;
  61 + }
  62 +}
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/type/TradeState.java
1 1 package com.diligrp.cashier.trade.type;
2 2  
  3 +import com.diligrp.cashier.pipeline.type.PaymentState;
3 4 import com.diligrp.cashier.shared.type.IEnumType;
4 5  
5 6 import java.util.Arrays;
... ... @@ -14,6 +15,8 @@ public enum TradeState implements IEnumType {
14 15  
15 16 PENDING("待处理", 1),
16 17  
  18 + PROCESSING("处理中", 2),
  19 +
17 20 SUCCESS("交易成功", 4),
18 21  
19 22 REFUND("交易退款", 5),
... ... @@ -47,6 +50,19 @@ public enum TradeState implements IEnumType {
47 50 return Arrays.asList(TradeState.values());
48 51 }
49 52  
  53 + public static boolean isPending(int code) {
  54 + return TradeState.PENDING.equalTo(code);
  55 + }
  56 +
  57 + public static boolean isProcessing(int code) {
  58 + return TradeState.PROCESSING.equalTo(code);
  59 + }
  60 +
  61 + public static boolean isFinished(int code) {
  62 + return TradeState.SUCCESS.equalTo(code) || TradeState.FAILED.equalTo(code) ||
  63 + TradeState.REFUND.equalTo(code) || TradeState.CLOSED.equalTo(code);
  64 + }
  65 +
50 66 /**
51 67 * 交易订单是否允许退款; 允许多次交易退款
52 68 *
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/util/CardPaymentConverter.java 0 → 100644
  1 +package com.diligrp.cashier.trade.util;
  2 +
  3 +import com.diligrp.cashier.pipeline.domain.card.CardPaymentRequest;
  4 +import com.diligrp.cashier.shared.codec.IConverter;
  5 +import com.diligrp.cashier.trade.domain.CashierPayment;
  6 +import com.diligrp.cashier.trade.model.OnlinePayment;
  7 +import com.diligrp.cashier.trade.model.TradeOrder;
  8 +import org.springframework.util.Assert;
  9 +
  10 +import java.time.LocalDateTime;
  11 +import java.util.Map;
  12 +
  13 +public class CardPaymentConverter implements IConverter<CashierPayment, CardPaymentRequest> {
  14 +
  15 + private static final String PARAM_ACCOUNT_ID = "accountId";
  16 +
  17 + private static final String PARAM_CARD_NO = "cardNo";
  18 +
  19 + private final TradeOrder tradeOrder;
  20 +
  21 + private final String paymentId;
  22 +
  23 + private final LocalDateTime when;
  24 +
  25 + public CardPaymentConverter(TradeOrder tradeOrder, String paymentId, LocalDateTime when) {
  26 + this.tradeOrder = tradeOrder;
  27 + this.paymentId = paymentId;
  28 + this.when = when;
  29 + }
  30 +
  31 + @Override
  32 + public CardPaymentRequest convert(CashierPayment payment) {
  33 + Map<String, Object> params = payment.getParams();
  34 + Assert.notNull(params, "params missed");
  35 + Long accountId = (Long) payment.getParams().get(PARAM_ACCOUNT_ID);
  36 + String cardNo = (String) params.get(PARAM_CARD_NO);
  37 + Assert.notNull(accountId, "params.accountId missed");
  38 + Assert.notNull(cardNo, "params.cardNo missed");
  39 +
  40 + return new CardPaymentRequest(paymentId, tradeOrder.getMaxAmount(),
  41 + tradeOrder.getGoods(), tradeOrder.getDescription(), when, accountId, cardNo);
  42 + }
  43 +}
... ...
cashier-trade/src/main/java/com/diligrp/cashier/trade/util/MiniProPaymentConverter.java 0 → 100644
  1 +package com.diligrp.cashier.trade.util;
  2 +
  3 +import com.diligrp.cashier.pipeline.Constants;
  4 +import com.diligrp.cashier.pipeline.domain.MiniProPrepayRequest;
  5 +import com.diligrp.cashier.shared.codec.IConverter;
  6 +import com.diligrp.cashier.trade.domain.CashierPayment;
  7 +import com.diligrp.cashier.trade.model.TradeOrder;
  8 +import org.springframework.util.Assert;
  9 +
  10 +import java.time.LocalDateTime;
  11 +import java.util.Map;
  12 +
  13 +public class MiniProPaymentConverter implements IConverter<CashierPayment, MiniProPrepayRequest> {
  14 +
  15 + private final TradeOrder tradeOrder;
  16 +
  17 + private final String paymentId;
  18 +
  19 + private final LocalDateTime when;
  20 +
  21 + public MiniProPaymentConverter(TradeOrder tradeOrder, String paymentId, LocalDateTime when) {
  22 + this.tradeOrder = tradeOrder;
  23 + this.paymentId = paymentId;
  24 + this.when = when;
  25 + }
  26 +
  27 + @Override
  28 + public MiniProPrepayRequest convert(CashierPayment payment) {
  29 + Map<String, Object> params = payment.getParams();
  30 + Assert.notNull(params, "params missed");
  31 + String openId = (String) params.get(Constants.PARAM_OPEN_ID);
  32 + Assert.notNull(openId, "params.openId missed");
  33 +
  34 + MiniProPrepayRequest prepayRequest = new MiniProPrepayRequest(paymentId, tradeOrder.getMaxAmount(),
  35 + tradeOrder.getGoods(), tradeOrder.getDescription(), when, openId);
  36 + prepayRequest.putParams(params);
  37 + return prepayRequest;
  38 + }
  39 +}
... ...
cashier-trade/src/main/resources/com/diligrp/cashier/dao/mapper/IOnlinePaymentDao.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8" ?>
  2 +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  3 + "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  4 +
  5 +<mapper namespace="com.diligrp.cashier.trade.dao.IOnlinePaymentDao">
  6 + <resultMap id="OnlinePaymentMap" type="com.diligrp.cashier.trade.model.OnlinePayment">
  7 + <id column="id" property="id"/>
  8 + <result column="out_mch_id" property="outMchId"/>
  9 + <result column="trade_id" property="tradeId"/>
  10 + <result column="type" property="type"/>
  11 + <result column="payment_id" property="paymentId"/>
  12 + <result column="channel_id" property="channelId"/>
  13 + <result column="pay_type" property="payType"/>
  14 + <result column="pipeline_id" property="pipelineId"/>
  15 + <result column="goods" property="goods"/>
  16 + <result column="amount" property="amount"/>
  17 + <result column="object_id" property="objectId"/>
  18 + <result column="payer_id" property="payerId"/>
  19 + <result column="finish_time" property="finishTime"/>
  20 + <result column="out_trade_no" property="outTradeNo"/>
  21 + <result column="out_pay_type" property="outPayType"/>
  22 + <result column="state" property="state"/>
  23 + <result column="description" property="description"/>
  24 + <result column="version" property="version"/>
  25 + <result column="created_time" property="createdTime"/>
  26 + <result column="modified_time" property="modifiedTime"/>
  27 + </resultMap>
  28 +
  29 + <insert id="insertOnlinePayment" parameterType="com.diligrp.cashier.trade.model.OnlinePayment">
  30 + INSERT INTO upay_online_payment(out_mch_id, trade_id, type, payment_id, channel_id, pay_type, pipeline_id,
  31 + goods, amount, object_id, payer_id, finish_time, out_trade_no, out_pay_type,
  32 + state, description, version, created_time, modified_time)
  33 + VALUES
  34 + (#{outMchId}, #{tradeId}, #{type}, #{paymentId}, #{channelId}, #{payType}, #{pipelineId},
  35 + #{goods}, #{amount}, #{objectId}, #{payerId}, #{finishTime}, #{outTradeNo}, #{outPayType}
  36 + #{state}, #{description}, #{version}, #{createdTime}, #{modifiedTime})
  37 + </insert>
  38 +
  39 + <select id="findByPaymentId" parameterType="string" resultMap="OnlinePaymentMap">
  40 + SELECT * FROM upay_online_payment WHERE payment_id = #{paymentId}
  41 + </select>
  42 +
  43 + <update id="compareAndSetState" parameterType="com.diligrp.cashier.trade.domain.PaymentStateDTO">
  44 + UPDATE upay_online_payment SET version = version + 1
  45 + <if test="outTradeNo != null">
  46 + , out_trade_no = #{outTradeNo}
  47 + </if>
  48 + <if test="outPayType != null">
  49 + , out_pay_type = #{outPayType}
  50 + </if>
  51 + <if test="payerId != null">
  52 + , payer_id = #{payerId}
  53 + </if>
  54 + <if test="finishTime != null">
  55 + , finish_time = #{finishTime}
  56 + </if>
  57 + <if test="state != null">
  58 + , state = #{state}
  59 + </if>
  60 + <if test="description != null">
  61 + , description = #{description}
  62 + </if>
  63 + <if test="modifiedTime != null">
  64 + , modified_time = #{modifiedTime}
  65 + </if>
  66 + WHERE
  67 + payment_id = #{paymentId} AND version = #{version}
  68 + </update>
  69 +
  70 + <select id="findByTradeId" resultMap="OnlinePaymentMap">
  71 + SELECT * FROM upay_online_payment WHERE trade_id = #{tradeId}
  72 + <if test="state != null">
  73 + AND state = #{state}
  74 + </if>
  75 + ORDER BY id
  76 + </select>
  77 +</mapper>
... ...
cashier-trade/src/main/resources/com/diligrp/cashier/dao/mapper/ITradeOrderDao.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8" ?>
  2 +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  3 + "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  4 +
  5 +<mapper namespace="com.diligrp.cashier.trade.dao.ITradeOrderDao">
  6 + <resultMap id="TradeOrderMap" type="com.diligrp.cashier.trade.model.TradeOrder">
  7 + <id column="id" property="id"/>
  8 + <result column="mch_id" property="mchId"/>
  9 + <result column="trade_id" property="tradeId"/>
  10 + <result column="type" property="type"/>
  11 + <result column="out_trade_no" property="outTradeNo"/>
  12 + <result column="amount" property="amount"/>
  13 + <result column="max_amount" property="maxAmount"/>
  14 + <result column="goods" property="goods"/>
  15 + <result column="timeout" property="timeout"/>
  16 + <result column="state" property="state"/>
  17 + <result column="notify_url" property="notifyUrl"/>
  18 + <result column="description" property="description"/>
  19 + <result column="attach" property="attach"/>
  20 + <result column="source" property="source"/>
  21 + <result column="version" property="version"/>
  22 + <result column="created_time" property="createdTime"/>
  23 + <result column="modified_time" property="modifiedTime"/>
  24 + </resultMap>
  25 +
  26 + <insert id="insertTradeOrder" parameterType="com.diligrp.cashier.trade.model.TradeOrder">
  27 + INSERT INTO upay_trade_order(mch_id, trade_id, type, out_trade_no, amount, max_amount, goods, timeout,
  28 + state, notify_url, description, attach, source, version, created_time, modified_time)
  29 + VALUES (#{mchId}, #{tradeId}, #{type}, #{outTradeNo}, #{amount}, #{maxAmount}, #{goods}, #{timeout},
  30 + #{state}, #{notifyUrl}, #{description}, #{attach}, #{source}, #{version}, #{createdTime}, #{modifiedTime})
  31 + </insert>
  32 +
  33 + <select id="findByTradeId" resultMap="TradeOrderMap">
  34 + SELECT * FROM upay_trade_order WHERE trade_id = #{tradeId}
  35 + </select>
  36 +
  37 + <select id="findByOutTradeNo" resultMap="TradeOrderMap">
  38 + SELECT * FROM upay_trade_order WHERE mch_id = #{mchId} AND out_trade_no = #{outTradeNo}
  39 + </select>
  40 +
  41 + <update id="compareAndSetState">
  42 + UPDATE upay_trade_order SET version = version + 1
  43 + <if test="amount != null">
  44 + , amount = #{amount}
  45 + </if>
  46 + <if test="state != null">
  47 + , state = #{state}
  48 + </if>
  49 + <if test="modifiedTime != null">
  50 + , modified_time = #{modifiedTime}
  51 + </if>
  52 + WHERE
  53 + trade_id = #{tradeId} AND version = #{version}
  54 + </update>
  55 +</mapper>
... ...
scripts/dili-cashier.sql
... ... @@ -7,7 +7,6 @@ CREATE TABLE `upay_merchant` (
7 7 `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
8 8 `mch_id` BIGINT NOT NULL COMMENT '商户ID',
9 9 `name` VARCHAR(60) NOT NULL COMMENT '商户名称',
10   - `access_token` VARCHAR(40) NOT NULL COMMENT '访问令牌',
11 10 `param` JSON COMMENT '参数配置',
12 11 `address` VARCHAR(128) COMMENT '商户地址',
13 12 `linkman` VARCHAR(40) COMMENT '联系人',
... ... @@ -27,17 +26,17 @@ CREATE TABLE `upay_trade_order` (
27 26 `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
28 27 `mch_id` BIGINT NOT NULL COMMENT '商户ID',
29 28 `trade_id` VARCHAR(40) NOT NULL COMMENT '交易ID',
30   - `type` TINYINT UNSIGNED NOT NULL COMMENT '交易类型', -- 购买会员
31   --- `channel_id` TINYINT UNSIGNED NOT NULL COMMENT '支付渠道',
  29 + `type` TINYINT UNSIGNED NOT NULL COMMENT '业务类型', -- 购买会员
32 30 `out_trade_no` VARCHAR(40) COMMENT '外部流水号', -- 商户流水号
33 31 `amount` BIGINT NOT NULL COMMENT '金额-分',
34 32 `max_amount` BIGINT NOT NULL COMMENT '初始金额-分',
35   - `goods` VARCHAR(128) COMMENT '商品描述',
36   - `order_timeout` INTEGER UNSIGNED NOT NULL COMMENT '超时间隔时间-秒',
  33 + `goods` VARCHAR(128) NOT NULL COMMENT '商品描述',
  34 + `timeout` INTEGER UNSIGNED NOT NULL COMMENT '超时间隔时间-秒',
37 35 `state` TINYINT UNSIGNED NOT NULL COMMENT '交易状态',
  36 + `notify_url` VARCHAR(128) COMMENT '业务回调链接',
38 37 `description` VARCHAR(128) COMMENT '交易备注',
39 38 `attach` VARCHAR(255) COMMENT '附加数据',
40   - `cashier_desk` TINYINT UNSIGNED NOT NULL COMMENT '收银台类型',
  39 + `source` TINYINT UNSIGNED COMMENT '订单来源',
41 40 `version` INTEGER UNSIGNED NOT NULL COMMENT '数据版本号',
42 41 `created_time` DATETIME COMMENT '创建时间',
43 42 `modified_time` DATETIME COMMENT '修改时间',
... ... @@ -68,7 +67,6 @@ CREATE TABLE `upay_online_payment` (
68 67 `out_trade_no` VARCHAR(40) COMMENT '通道流水号',
69 68 `out_pay_type` TINYINT UNSIGNED NOT NULL COMMENT '实际支付方式', -- 银行聚合支付时使用
70 69 `state` TINYINT UNSIGNED NOT NULL COMMENT '申请状态',
71   - `notify_uri` VARCHAR(128) COMMENT '业务回调链接',
72 70 `description` VARCHAR(256) COMMENT '交易备注',
73 71 `version` INTEGER UNSIGNED NOT NULL COMMENT '数据版本号',
74 72 `created_time` DATETIME COMMENT '创建时间',
... ...