Commit 1e1c5f65d28fb27e4365558a4e29e8a7c321d8d9
1 parent
b893160f
rcb payment pipeline supported
Showing
20 changed files
with
810 additions
and
34 deletions
cashier-boss/src/main/java/com/diligrp/cashier/boss/Constants.java
| @@ -13,8 +13,8 @@ public final class Constants { | @@ -13,8 +13,8 @@ public final class Constants { | ||
| 13 | public static final String TOKEN_SIGN_ALGORITHM = "HmacSHA256"; | 13 | public static final String TOKEN_SIGN_ALGORITHM = "HmacSHA256"; |
| 14 | // TOKEN的签名长度 | 14 | // TOKEN的签名长度 |
| 15 | public static final int TOKEN_SIGN_LENGTH = 8; | 15 | public static final int TOKEN_SIGN_LENGTH = 8; |
| 16 | - // TOKEN过期时长,单位秒 - 两分钟 | ||
| 17 | - public static final long TOKEN_TIMEOUT_SECONDS = 120; | 16 | + // TOKEN过期时长,单位秒 - 5分钟 |
| 17 | + public static final long TOKEN_TIMEOUT_SECONDS = 5 * 60; | ||
| 18 | 18 | ||
| 19 | public final static String CONTENT_TYPE = "application/json;charset=UTF-8"; | 19 | public final static String CONTENT_TYPE = "application/json;charset=UTF-8"; |
| 20 | 20 |
cashier-boss/src/main/java/com/diligrp/cashier/boss/controller/RcbPaymentController.java
0 → 100644
| 1 | +package com.diligrp.cashier.boss.controller; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.boss.exception.BossServiceException; | ||
| 4 | +import com.diligrp.cashier.pipeline.core.RcbOnlinePipeline; | ||
| 5 | +import com.diligrp.cashier.pipeline.domain.OnlinePaymentResponse; | ||
| 6 | +import com.diligrp.cashier.pipeline.service.IPaymentPipelineManager; | ||
| 7 | +import com.diligrp.cashier.pipeline.type.OutPaymentType; | ||
| 8 | +import com.diligrp.cashier.pipeline.type.PaymentState; | ||
| 9 | +import com.diligrp.cashier.pipeline.util.RcbSignatureUtils; | ||
| 10 | +import com.diligrp.cashier.pipeline.util.RcbStateUtils; | ||
| 11 | +import com.diligrp.cashier.shared.ErrorCode; | ||
| 12 | +import com.diligrp.cashier.shared.util.JsonUtils; | ||
| 13 | +import com.diligrp.cashier.shared.util.ObjectUtils; | ||
| 14 | +import com.diligrp.cashier.trade.model.OnlinePayment; | ||
| 15 | +import com.diligrp.cashier.trade.service.ICashierPaymentService; | ||
| 16 | +import com.diligrp.cashier.trade.service.ITradeAssistantService; | ||
| 17 | +import com.fasterxml.jackson.core.type.TypeReference; | ||
| 18 | +import jakarta.annotation.Resource; | ||
| 19 | +import jakarta.servlet.http.HttpServletRequest; | ||
| 20 | +import org.slf4j.Logger; | ||
| 21 | +import org.slf4j.LoggerFactory; | ||
| 22 | +import org.springframework.http.HttpStatus; | ||
| 23 | +import org.springframework.http.ResponseEntity; | ||
| 24 | +import org.springframework.web.bind.annotation.PathVariable; | ||
| 25 | +import org.springframework.web.bind.annotation.RequestMapping; | ||
| 26 | +import org.springframework.web.bind.annotation.RestController; | ||
| 27 | + | ||
| 28 | +import java.time.Instant; | ||
| 29 | +import java.time.LocalDateTime; | ||
| 30 | +import java.time.ZoneId; | ||
| 31 | +import java.util.Map; | ||
| 32 | + | ||
| 33 | +@RestController | ||
| 34 | +@RequestMapping(value = "/rcb") | ||
| 35 | +public class RcbPaymentController { | ||
| 36 | + | ||
| 37 | + private static final Logger LOG = LoggerFactory.getLogger(RcbPaymentController.class); | ||
| 38 | + | ||
| 39 | + @Resource | ||
| 40 | + private ITradeAssistantService tradeAssistantService; | ||
| 41 | + | ||
| 42 | + @Resource | ||
| 43 | + private ICashierPaymentService cashierPaymentService; | ||
| 44 | + | ||
| 45 | + @Resource | ||
| 46 | + private IPaymentPipelineManager paymentPipelineManager; | ||
| 47 | + | ||
| 48 | + /** | ||
| 49 | + * 支付结果通知 | ||
| 50 | + */ | ||
| 51 | + @RequestMapping(value = "/payment/{paymentId}/notify.do") | ||
| 52 | + public ResponseEntity<?> paymentNotify(HttpServletRequest request, @PathVariable("paymentId") String paymentId) { | ||
| 53 | + String payload = request.getParameter("order"); | ||
| 54 | + LOG.info("Receiving rcb payment pipeline result: {}\n{}", paymentId, payload); | ||
| 55 | + | ||
| 56 | + try { | ||
| 57 | + LocalDateTime when = LocalDateTime.now(); | ||
| 58 | + Map<String, String> params = JsonUtils.fromJsonString(payload, new TypeReference<>(){}); | ||
| 59 | + String sign = params.remove("sign"); | ||
| 60 | + String source = RcbSignatureUtils.map2String(params); | ||
| 61 | + | ||
| 62 | + OnlinePayment payment = tradeAssistantService.findByPaymentId(paymentId); | ||
| 63 | + RcbOnlinePipeline pipeline = paymentPipelineManager.findPipelineById(payment.getPipelineId(), RcbOnlinePipeline.class); | ||
| 64 | + RcbOnlinePipeline.RcbParams config = pipeline.params(); | ||
| 65 | + if (!RcbSignatureUtils.verify(source, config.getKey(), sign)) { | ||
| 66 | + LOG.error("Rcb pipeline data sign verify failed"); | ||
| 67 | + throw new BossServiceException(ErrorCode.UNAUTHORIZED_ACCESS_ERROR, "Data sign verify failed"); | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | +// String paymentId = params.get("outTradeNo"); | ||
| 71 | + PaymentState paymentState = RcbStateUtils.paymentState(params.get("orderStatus")); | ||
| 72 | + String outTradeNo = params.get("cposOrderId"); | ||
| 73 | + String paidTime = params.get("paidTime"); | ||
| 74 | + if (ObjectUtils.isNotEmpty(paidTime)) { | ||
| 75 | + long timestamp = Long.parseLong(paidTime); // ⽀付完成时间戳 | ||
| 76 | + Instant instant = Instant.ofEpochMilli(timestamp); | ||
| 77 | + when = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); | ||
| 78 | + } | ||
| 79 | + OutPaymentType outPayType = RcbStateUtils.outPayType(params.get("tradeChannel")); | ||
| 80 | + | ||
| 81 | + String payerId = params.get("payUserInfo"); | ||
| 82 | + String errorDesc = params.get("errorDesc"); | ||
| 83 | + // String outOrderId = params.get("chnOrderId"); // 第三方支付通道的订单号 | ||
| 84 | + | ||
| 85 | + OnlinePaymentResponse paymentResponse = new OnlinePaymentResponse(paymentId, outTradeNo, outPayType, | ||
| 86 | + payerId, when, paymentState, errorDesc); | ||
| 87 | + cashierPaymentService.notifyPaymentResponse(paymentResponse); | ||
| 88 | + return ResponseEntity.ok("SUCCESS"); | ||
| 89 | + } catch (Exception ex) { | ||
| 90 | + LOG.error("Process rcb payment result exception", ex); | ||
| 91 | + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("FAILED"); | ||
| 92 | + } | ||
| 93 | + } | ||
| 94 | +} |
cashier-boss/src/main/java/com/diligrp/cashier/boss/domain/CashierOrderVO.java
| @@ -11,14 +11,14 @@ public class CashierOrderVO { | @@ -11,14 +11,14 @@ public class CashierOrderVO { | ||
| 11 | private final String userId; | 11 | private final String userId; |
| 12 | // 商品描述 | 12 | // 商品描述 |
| 13 | private final String goods; | 13 | private final String goods; |
| 14 | - // 付款金额-元 | ||
| 15 | - private final String amount; | 14 | + // 付款金额-分 |
| 15 | + private final Long amount; | ||
| 16 | // 页面回调地址 | 16 | // 页面回调地址 |
| 17 | private final String redirectUrl; | 17 | private final String redirectUrl; |
| 18 | // 支付通道 | 18 | // 支付通道 |
| 19 | private final List<PaymentPipeline> pipelines; | 19 | private final List<PaymentPipeline> pipelines; |
| 20 | 20 | ||
| 21 | - public CashierOrderVO(String tradeId, String userId, String goods, String amount, | 21 | + public CashierOrderVO(String tradeId, String userId, String goods, Long amount, |
| 22 | String redirectUrl, List<PaymentPipeline> pipelines) { | 22 | String redirectUrl, List<PaymentPipeline> pipelines) { |
| 23 | this.tradeId = tradeId; | 23 | this.tradeId = tradeId; |
| 24 | this.userId = userId; | 24 | this.userId = userId; |
| @@ -40,7 +40,7 @@ public class CashierOrderVO { | @@ -40,7 +40,7 @@ public class CashierOrderVO { | ||
| 40 | return goods; | 40 | return goods; |
| 41 | } | 41 | } |
| 42 | 42 | ||
| 43 | - public String getAmount() { | 43 | + public Long getAmount() { |
| 44 | return amount; | 44 | return amount; |
| 45 | } | 45 | } |
| 46 | 46 |
cashier-boss/src/main/java/com/diligrp/cashier/boss/service/impl/CashierDeskServiceImpl.java
| @@ -2,8 +2,8 @@ package com.diligrp.cashier.boss.service.impl; | @@ -2,8 +2,8 @@ package com.diligrp.cashier.boss.service.impl; | ||
| 2 | 2 | ||
| 3 | import com.diligrp.cashier.boss.CashierDeskProperties; | 3 | import com.diligrp.cashier.boss.CashierDeskProperties; |
| 4 | import com.diligrp.cashier.boss.Constants; | 4 | import com.diligrp.cashier.boss.Constants; |
| 5 | -import com.diligrp.cashier.boss.domain.CashierOrderVO; | ||
| 6 | import com.diligrp.cashier.boss.domain.CashierOrderToken; | 5 | import com.diligrp.cashier.boss.domain.CashierOrderToken; |
| 6 | +import com.diligrp.cashier.boss.domain.CashierOrderVO; | ||
| 7 | import com.diligrp.cashier.boss.domain.CashierPaymentUrl; | 7 | import com.diligrp.cashier.boss.domain.CashierPaymentUrl; |
| 8 | import com.diligrp.cashier.boss.exception.BossServiceException; | 8 | import com.diligrp.cashier.boss.exception.BossServiceException; |
| 9 | import com.diligrp.cashier.boss.service.ICashierDeskService; | 9 | import com.diligrp.cashier.boss.service.ICashierDeskService; |
| @@ -12,7 +12,6 @@ import com.diligrp.cashier.pipeline.domain.OnlinePaymentStatus; | @@ -12,7 +12,6 @@ import com.diligrp.cashier.pipeline.domain.OnlinePaymentStatus; | ||
| 12 | import com.diligrp.cashier.pipeline.service.IPaymentPipelineManager; | 12 | import com.diligrp.cashier.pipeline.service.IPaymentPipelineManager; |
| 13 | import com.diligrp.cashier.pipeline.type.PaymentState; | 13 | import com.diligrp.cashier.pipeline.type.PaymentState; |
| 14 | import com.diligrp.cashier.shared.ErrorCode; | 14 | import com.diligrp.cashier.shared.ErrorCode; |
| 15 | -import com.diligrp.cashier.shared.util.CurrencyUtils; | ||
| 16 | import com.diligrp.cashier.shared.util.ObjectUtils; | 15 | import com.diligrp.cashier.shared.util.ObjectUtils; |
| 17 | import com.diligrp.cashier.trade.domain.*; | 16 | import com.diligrp.cashier.trade.domain.*; |
| 18 | import com.diligrp.cashier.trade.model.TradeOrder; | 17 | import com.diligrp.cashier.trade.model.TradeOrder; |
| @@ -87,8 +86,7 @@ public class CashierDeskServiceImpl implements ICashierDeskService { | @@ -87,8 +86,7 @@ public class CashierDeskServiceImpl implements ICashierDeskService { | ||
| 87 | List<PaymentPipeline> pipelines = paymentPipelineManager.listPipelines(orderToken.getMchId(), PaymentPipeline.class); | 86 | List<PaymentPipeline> pipelines = paymentPipelineManager.listPipelines(orderToken.getMchId(), PaymentPipeline.class); |
| 88 | List<CashierOrderVO.PaymentPipeline> pipelineList = pipelines.stream().map(pipeline -> | 87 | List<CashierOrderVO.PaymentPipeline> pipelineList = pipelines.stream().map(pipeline -> |
| 89 | new CashierOrderVO.PaymentPipeline(pipeline.pipelineId(), pipeline.supportedChannel())).toList(); | 88 | new CashierOrderVO.PaymentPipeline(pipeline.pipelineId(), pipeline.supportedChannel())).toList(); |
| 90 | - String amount = CurrencyUtils.toNoSymbolCurrency(trade.getMaxAmount()); | ||
| 91 | - return new CashierOrderVO(orderToken.getTradeId(), orderToken.getUserId(), trade.getGoods(), amount, | 89 | + return new CashierOrderVO(orderToken.getTradeId(), orderToken.getUserId(), trade.getGoods(), trade.getMaxAmount(), |
| 92 | orderToken.getRedirectUrl(), pipelineList); | 90 | orderToken.getRedirectUrl(), pipelineList); |
| 93 | } | 91 | } |
| 94 | 92 |
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/Constants.java
| @@ -12,6 +12,8 @@ public final class Constants { | @@ -12,6 +12,8 @@ public final class Constants { | ||
| 12 | public static final String WECHAT_PAYMENT_NOTIFY_URI = "%s/wechat/payment/%s/notify.do"; | 12 | public static final String WECHAT_PAYMENT_NOTIFY_URI = "%s/wechat/payment/%s/notify.do"; |
| 13 | // 微信退款结果通知URI: 参数1-baseUri 参数2-refundId | 13 | // 微信退款结果通知URI: 参数1-baseUri 参数2-refundId |
| 14 | public static final String WECHAT_REFUND_NOTIFY_URI = "%s/wechat/refund/%s/notify.do"; | 14 | public static final String WECHAT_REFUND_NOTIFY_URI = "%s/wechat/refund/%s/notify.do"; |
| 15 | + // 农商行聚合支付结果通知URI | ||
| 16 | + public static final String RCB_PAYMENT_NOTIFY_URI = "%s/rcb/payment/%s/notify.do"; | ||
| 15 | 17 | ||
| 16 | public static final long ONE_MINUTE = 60 * 1000; | 18 | public static final long ONE_MINUTE = 60 * 1000; |
| 17 | 19 |
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/client/CardPaymentHttpClient.java
| @@ -73,12 +73,12 @@ public class CardPaymentHttpClient extends ServiceEndpointSupport { | @@ -73,12 +73,12 @@ public class CardPaymentHttpClient extends ServiceEndpointSupport { | ||
| 73 | Map<String, Object> response = JsonUtils.fromJsonString(result.responseText, new TypeReference<>() {}); | 73 | Map<String, Object> response = JsonUtils.fromJsonString(result.responseText, new TypeReference<>() {}); |
| 74 | if ("200".equals(response.get("code"))) { | 74 | if ("200".equals(response.get("code"))) { |
| 75 | Map<String, Object> data = (Map<String, Object>) response.get("data"); | 75 | Map<String, Object> data = (Map<String, Object>) response.get("data"); |
| 76 | - String outTradeNo = (String) data.get("outTradeNo"); | 76 | + String outTradeNo = (String) data.get("tradeId"); |
| 77 | Map<String, Object> payerId = new LinkedHashMap<>(); | 77 | Map<String, Object> payerId = new LinkedHashMap<>(); |
| 78 | payerId.put("customerId", convertLong(data.get("customerId"))); | 78 | payerId.put("customerId", convertLong(data.get("customerId"))); |
| 79 | payerId.put("accountId", convertLong(data.get("accountId"))); | 79 | payerId.put("accountId", convertLong(data.get("accountId"))); |
| 80 | payerId.put("cardNo", data.get("cardNo")); | 80 | payerId.put("cardNo", data.get("cardNo")); |
| 81 | - payerId.put("name", data.get("name")); | 81 | + payerId.put("name", data.get("customerName")); |
| 82 | return new CardPaymentResponse(request.getPaymentId(), outTradeNo, OutPaymentType.DILICARD, | 82 | return new CardPaymentResponse(request.getPaymentId(), outTradeNo, OutPaymentType.DILICARD, |
| 83 | JsonUtils.toJsonString(payerId), now, PaymentState.SUCCESS, "园区卡支付成功"); | 83 | JsonUtils.toJsonString(payerId), now, PaymentState.SUCCESS, "园区卡支付成功"); |
| 84 | } else { | 84 | } else { |
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/client/RcbOnlineHttpClient.java
0 → 100644
| 1 | + package com.diligrp.cashier.pipeline.client; | ||
| 2 | + | ||
| 3 | + import com.diligrp.cashier.pipeline.domain.*; | ||
| 4 | + import com.diligrp.cashier.pipeline.exception.PaymentPipelineException; | ||
| 5 | + import com.diligrp.cashier.pipeline.type.OutPaymentType; | ||
| 6 | + import com.diligrp.cashier.pipeline.type.PaymentState; | ||
| 7 | + import com.diligrp.cashier.pipeline.util.RcbSignatureUtils; | ||
| 8 | + import com.diligrp.cashier.pipeline.util.RcbStateUtils; | ||
| 9 | + import com.diligrp.cashier.shared.ErrorCode; | ||
| 10 | + import com.diligrp.cashier.shared.exception.ServiceAccessException; | ||
| 11 | + import com.diligrp.cashier.shared.service.ServiceEndpointSupport; | ||
| 12 | + import com.diligrp.cashier.shared.util.DateUtils; | ||
| 13 | + import com.diligrp.cashier.shared.util.JsonUtils; | ||
| 14 | + import com.diligrp.cashier.shared.util.ObjectUtils; | ||
| 15 | + import com.diligrp.cashier.shared.util.RandomUtils; | ||
| 16 | + import com.fasterxml.jackson.core.type.TypeReference; | ||
| 17 | + import jakarta.annotation.Resource; | ||
| 18 | + import org.slf4j.Logger; | ||
| 19 | + import org.slf4j.LoggerFactory; | ||
| 20 | + import org.springframework.dao.DataAccessException; | ||
| 21 | + import org.springframework.data.redis.core.RedisOperations; | ||
| 22 | + import org.springframework.data.redis.core.SessionCallback; | ||
| 23 | + import org.springframework.data.redis.core.StringRedisTemplate; | ||
| 24 | + import org.springframework.lang.NonNull; | ||
| 25 | + | ||
| 26 | + import javax.net.ssl.SSLContext; | ||
| 27 | + import javax.net.ssl.TrustManager; | ||
| 28 | + import javax.net.ssl.X509TrustManager; | ||
| 29 | + import java.security.cert.X509Certificate; | ||
| 30 | + import java.time.Instant; | ||
| 31 | + import java.time.LocalDate; | ||
| 32 | + import java.time.LocalDateTime; | ||
| 33 | + import java.time.ZoneId; | ||
| 34 | + import java.util.*; | ||
| 35 | + import java.util.concurrent.TimeUnit; | ||
| 36 | + | ||
| 37 | + /** | ||
| 38 | + * 安徽农商银行聚合支付客户端 | ||
| 39 | + */ | ||
| 40 | +public class RcbOnlineHttpClient extends ServiceEndpointSupport { | ||
| 41 | + | ||
| 42 | + private static final Logger LOG = LoggerFactory.getLogger(RcbOnlineHttpClient.class); | ||
| 43 | + | ||
| 44 | + // 微信API BASE URL | ||
| 45 | + private static final String WECHAT_BASE_URL = "https://api.weixin.qq.com"; | ||
| 46 | + // code2session接口: 根据登录凭证code获取登录信息 | ||
| 47 | + private static final String CODE_TO_SESSION = "/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code"; | ||
| 48 | + | ||
| 49 | + private static final int STATUS_OK = 200; | ||
| 50 | + | ||
| 51 | + private final String uri; | ||
| 52 | + | ||
| 53 | + private final String merchantNo; | ||
| 54 | + | ||
| 55 | + private final String terminalNo; | ||
| 56 | + | ||
| 57 | + private final String key; | ||
| 58 | + | ||
| 59 | + private final String appId; | ||
| 60 | + | ||
| 61 | + @Resource | ||
| 62 | + private StringRedisTemplate stringRedisTemplate; | ||
| 63 | + | ||
| 64 | + public RcbOnlineHttpClient(String uri, String merchantNo, String terminalNo, String key, String appId) { | ||
| 65 | + this.uri = uri; | ||
| 66 | + this.merchantNo = merchantNo; | ||
| 67 | + this.terminalNo = terminalNo; | ||
| 68 | + this.key = key; | ||
| 69 | + this.appId = appId; | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + public String sendMiniProPrepayRequest(MiniProPrepayRequest request, String notifyUrl) { | ||
| 73 | + Map<String, String> params = new HashMap<>(); | ||
| 74 | + params.put("merchantNo", merchantNo); | ||
| 75 | + params.put("terminalNo", terminalNo); | ||
| 76 | + params.put("batchNo", getBatchNo()); | ||
| 77 | + params.put("traceNo", getTraceNo()); | ||
| 78 | + params.put("outTradeNo", request.getPaymentId()); | ||
| 79 | + params.put("transAmount", String.valueOf(request.getAmount())); | ||
| 80 | + params.put("appid", appId); | ||
| 81 | + params.put("openId", request.getOpenId()); | ||
| 82 | + params.put("timeExpire", "5"); // 5分钟 | ||
| 83 | + params.put("tradeChannel", "01"); // 微信小程序 | ||
| 84 | + params.put("notifyUrl", notifyUrl); | ||
| 85 | + params.put("nonceStr", RandomUtils.randomString(32)); | ||
| 86 | + params.put("sign", RcbSignatureUtils.sign(params, key)); | ||
| 87 | + | ||
| 88 | + String payload = JsonUtils.toJsonString(params); | ||
| 89 | + LOG.debug("Sending rcb minipro prepay request: {}", payload); | ||
| 90 | + HttpResult result = send(uri + "/cposp/pay/unifiedorder", payload); | ||
| 91 | + if (result.statusCode != STATUS_OK) { | ||
| 92 | + LOG.error("Failed to send rcb minipro prepay, statusCode: {}", result.statusCode); | ||
| 93 | + throw new PaymentPipelineException(ErrorCode.SYSTEM_UNKNOWN_ERROR, "调用小程序预支付接口失败: " + result.statusCode); | ||
| 94 | + } | ||
| 95 | + LOG.debug("Received rcb mini pro prepay response: {}", result.responseText); | ||
| 96 | + | ||
| 97 | + Map<String, String> data = JsonUtils.fromJsonString(result.responseText, new TypeReference<>() {}); | ||
| 98 | + String resultCode = data.get("resultCode"); | ||
| 99 | + String resultMessage = data.get("resultMessage"); | ||
| 100 | + if ("00".equals(resultCode)) { | ||
| 101 | + String signature = data.remove("sign"); | ||
| 102 | + if (!RcbSignatureUtils.verify(data, key, signature)) { | ||
| 103 | + throw new PaymentPipelineException(ErrorCode.OPERATION_NOT_ALLOWED, "小程序预支付请求数据验签失败"); | ||
| 104 | + } | ||
| 105 | + return data.get("payInfo"); | ||
| 106 | + } else { | ||
| 107 | + LOG.error("Failed to send minipro prepay request, errorCode: {}, resultMessage: {}", resultCode, resultMessage); | ||
| 108 | + throw new PaymentPipelineException(ErrorCode.SERVICE_ACCESS_ERROR, "调用小程序预支付接口失败: " + resultMessage); | ||
| 109 | + } | ||
| 110 | + } | ||
| 111 | + | ||
| 112 | + public OnlinePaymentResponse queryPrepayResponse(OnlinePrepayOrder request) { | ||
| 113 | + Map<String, String> params = new LinkedHashMap<>(); | ||
| 114 | + params.put("merchantNo", merchantNo); | ||
| 115 | + params.put("terminalNo", terminalNo); | ||
| 116 | + params.put("batchNo", getBatchNo()); | ||
| 117 | + params.put("traceNo", getTraceNo()); | ||
| 118 | + params.put("outTradeNo", request.getPaymentId()); | ||
| 119 | + params.put("nonceStr", RandomUtils.randomString(32)); | ||
| 120 | + | ||
| 121 | + params.put("sign", RcbSignatureUtils.sign(params, key)); | ||
| 122 | + String payload = JsonUtils.toJsonString(params); | ||
| 123 | + LOG.debug("Sending rcb order query request: {}", payload); | ||
| 124 | + HttpResult result = send(uri + "/cposp/pay/orderQuery", payload); | ||
| 125 | + if (result.statusCode != STATUS_OK) { | ||
| 126 | + LOG.error("Failed to query rcb order, statusCode: {}", result.statusCode); | ||
| 127 | + throw new PaymentPipelineException(ErrorCode.SYSTEM_UNKNOWN_ERROR, "查询支付状态失败: " + result.statusCode); | ||
| 128 | + } | ||
| 129 | + LOG.debug("Received rcb order query response: {}", result.responseText); | ||
| 130 | + | ||
| 131 | + Map<String, String> data = JsonUtils.fromJsonString(result.responseText, new TypeReference<>() {}); | ||
| 132 | + String signature = data.remove("sign"); | ||
| 133 | + if (!RcbSignatureUtils.verify(data, key, signature)) { | ||
| 134 | + throw new PaymentPipelineException(ErrorCode.OPERATION_NOT_ALLOWED, "查询支付状态数据验签失败"); | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | + String resultCode = data.get("resultCode"); | ||
| 138 | + String resultMessage = data.get("resultMessage"); | ||
| 139 | + if ("00".equals(resultCode)) { | ||
| 140 | + String orderStatus = data.get("orderStatus"); | ||
| 141 | + String errorDesc = data.get("errorDesc"); | ||
| 142 | + String outTradeNo = data.get("cposOrderId"); | ||
| 143 | + OutPaymentType paymentType = RcbStateUtils.outPayType(data.get("tradeChannel")); | ||
| 144 | + LocalDateTime when = LocalDateTime.now().withNano(0); | ||
| 145 | + // 不能使用paidTime或transTime, paidTime只有年月日,transTime是订单发起时间 | ||
| 146 | + // 当前未设计存储支付时间字段(采用记录修改时间作为支付时间),因此采用当前时间作为记录修改时间 | ||
| 147 | + String timeEnd = data.get("timeEnd"); | ||
| 148 | + if (ObjectUtils.isNotEmpty(timeEnd)) { | ||
| 149 | + long timestamp = Long.parseLong(timeEnd); // ⽀付完成时间戳 | ||
| 150 | + Instant instant = Instant.ofEpochMilli(timestamp); | ||
| 151 | + when = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); | ||
| 152 | + } | ||
| 153 | + String payUserInfo = data.get("payUserInfo"); | ||
| 154 | + // 第三方支付通道的订单编号,比如:微信订单 | ||
| 155 | + // String outOrderId = data.get("chnOrderId"); | ||
| 156 | + return new OnlinePaymentResponse(request.getPaymentId(), outTradeNo, paymentType, | ||
| 157 | + payUserInfo, when, RcbStateUtils.paymentState(orderStatus), errorDesc); | ||
| 158 | + } else { | ||
| 159 | + LOG.error("Failed to query rcb order, errorCode: {}, resultMessage: {}", resultCode, resultMessage); | ||
| 160 | + throw new PaymentPipelineException(ErrorCode.SERVICE_ACCESS_ERROR, "调用支付结果查询接口失败"); | ||
| 161 | + } | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + public void closePrepayOrder(OnlinePrepayOrder request) { | ||
| 165 | + Map<String, String> params = new HashMap<>(); | ||
| 166 | + params.put("merchantNo", merchantNo); | ||
| 167 | + params.put("terminalNo", terminalNo); | ||
| 168 | + params.put("batchNo", getBatchNo()); | ||
| 169 | + params.put("traceNo", getTraceNo()); | ||
| 170 | + params.put("outTradeNo", request.getPaymentId()); | ||
| 171 | + params.put("nonceStr", RandomUtils.randomString(32)); | ||
| 172 | + params.put("sign", RcbSignatureUtils.sign(params, key)); | ||
| 173 | + | ||
| 174 | + String payload = JsonUtils.toJsonString(params); | ||
| 175 | + LOG.info("Sending close rcb order request: {}", payload); | ||
| 176 | + HttpResult result = send(uri + "/cposp/pay/closeOrder", payload); | ||
| 177 | + if (result.statusCode != STATUS_OK) { | ||
| 178 | + LOG.error("Failed to close rcb order, statusCode: {}", result.statusCode); | ||
| 179 | + throw new PaymentPipelineException(ErrorCode.SYSTEM_UNKNOWN_ERROR, "关闭支付订单失败: " + result.statusCode); | ||
| 180 | + } | ||
| 181 | + LOG.info("Received close rcb order response: {}", result.responseText); | ||
| 182 | + | ||
| 183 | + Map<String, String> data = JsonUtils.fromJsonString(result.responseText, new TypeReference<>() {}); | ||
| 184 | + String resultCode = data.get("resultCode"); | ||
| 185 | + String resultMessage = data.get("resultMessage"); | ||
| 186 | + if (!"00".equals(resultCode)) { | ||
| 187 | + LOG.error("Failed to close rcb order, errorCode: {}, resultMessage: {}", resultCode, resultMessage); | ||
| 188 | + throw new PaymentPipelineException(ErrorCode.SERVICE_ACCESS_ERROR, "关闭支付订单失败"); | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + String signature = data.remove("sign"); | ||
| 192 | + if (!RcbSignatureUtils.verify(data, key, signature)) { | ||
| 193 | + throw new PaymentPipelineException(ErrorCode.OPERATION_NOT_ALLOWED, "关闭订单数据验签失败"); | ||
| 194 | + } | ||
| 195 | + } | ||
| 196 | + | ||
| 197 | + public OnlineRefundResponse sendRefundRequest(OnlineRefundRequest request) { | ||
| 198 | + Map<String, String> params = new HashMap<>(); | ||
| 199 | + params.put("merchantNo", merchantNo); | ||
| 200 | + params.put("terminalNo", terminalNo); | ||
| 201 | + params.put("batchNo", getBatchNo()); | ||
| 202 | + params.put("traceNo", getTraceNo()); | ||
| 203 | + params.put("mchtRefundNo", request.getRefundId()); | ||
| 204 | + params.put("outTradeNo", request.getPaymentId()); | ||
| 205 | + params.put("refundAmount", String.valueOf(request.getAmount())); | ||
| 206 | + params.put("nonceStr", RandomUtils.randomString(32)); | ||
| 207 | + params.put("sign", RcbSignatureUtils.sign(params, key)); | ||
| 208 | + | ||
| 209 | + LocalDateTime now = LocalDateTime.now(); | ||
| 210 | + String payload = JsonUtils.toJsonString(params); | ||
| 211 | + LOG.debug("Sending refund request: {}", payload); | ||
| 212 | + HttpResult result = send(uri + "/cposp/pay/refund", payload); | ||
| 213 | + if (result.statusCode != STATUS_OK) { | ||
| 214 | + LOG.error("Failed to refund rcb order, statusCode: {}", result.statusCode); | ||
| 215 | + throw new PaymentPipelineException(ErrorCode.SYSTEM_UNKNOWN_ERROR, "发送退款请求失败: " + result.statusCode); | ||
| 216 | + } | ||
| 217 | + LOG.debug("Received rcb order refund response: {}", result.responseText); | ||
| 218 | + | ||
| 219 | + Map<String, String> data = JsonUtils.fromJsonString(result.responseText, new TypeReference<>() {}); | ||
| 220 | + String resultCode = data.get("resultCode"); | ||
| 221 | + String resultMessage = data.get("resultMessage"); | ||
| 222 | + if (!"00".equals(resultCode)) { | ||
| 223 | + LOG.error("Failed to refund, errorCode: {}, resultMessage: {}", resultCode, resultMessage); | ||
| 224 | + throw new PaymentPipelineException(ErrorCode.SERVICE_ACCESS_ERROR, "发送退款请求失败"); | ||
| 225 | + } | ||
| 226 | + | ||
| 227 | + String signature = data.remove("sign"); | ||
| 228 | + if (!RcbSignatureUtils.verify(data, key, signature)) { | ||
| 229 | + throw new PaymentPipelineException(ErrorCode.OPERATION_NOT_ALLOWED, "退款接口数据验签失败"); | ||
| 230 | + } | ||
| 231 | + String refundStatus = data.get("refundStatus"); | ||
| 232 | + String cposRefundOrderId = data.get("cposRefundOrderId"); | ||
| 233 | + PaymentState refundState = RcbStateUtils.refundState(refundStatus); | ||
| 234 | + return new OnlineRefundResponse(request.getRefundId(), cposRefundOrderId, now, refundState, RcbStateUtils.refundInfo(refundState)); | ||
| 235 | + } | ||
| 236 | + | ||
| 237 | + /** | ||
| 238 | + * 各交易终端,每⽇第⼀笔交易时,需要通过签到,获取批次号等信息。 | ||
| 239 | + * | ||
| 240 | + * @return 批次号 | ||
| 241 | + */ | ||
| 242 | + protected String getBatchNo() { | ||
| 243 | + try { | ||
| 244 | + String key = "rcb:online:batchNo" + DateUtils.formatDate(LocalDate.now(), DateUtils.YYYYMMDD); | ||
| 245 | + String batchNo = stringRedisTemplate.opsForValue().get(key); | ||
| 246 | + if (batchNo != null) { | ||
| 247 | + return batchNo; | ||
| 248 | + } | ||
| 249 | + | ||
| 250 | + String payload = String.format("{\"merchantNo\": \"%s\", \"terminalNo\": \"%s\"}", merchantNo, terminalNo); | ||
| 251 | + LOG.debug("Sending signIn request: {}", payload); | ||
| 252 | + HttpResult result = send(uri + "/cposp/pay/signIn", payload); | ||
| 253 | + if (result.statusCode != STATUS_OK) { | ||
| 254 | + LOG.error("Failed to get rcb batch no, statusCode: {}", result.statusCode); | ||
| 255 | + throw new PaymentPipelineException(ErrorCode.SYSTEM_UNKNOWN_ERROR, "获取签到批次号: " + result.statusCode); | ||
| 256 | + } | ||
| 257 | + LOG.debug("Received rcb signIn response: {}", result.responseText); | ||
| 258 | + | ||
| 259 | + Map<String, String> data = JsonUtils.fromJsonString(result.responseText, new TypeReference<>() {}); | ||
| 260 | + String resultCode = data.get("resultCode"); | ||
| 261 | + String resultMessage = data.get("resultMessage"); | ||
| 262 | + if ("00".equals(resultCode)) { | ||
| 263 | + batchNo = data.get("batchNo"); | ||
| 264 | + stringRedisTemplate.opsForValue().set(key, batchNo, 36 * 60 * 60, TimeUnit.SECONDS); | ||
| 265 | + return batchNo; | ||
| 266 | + } else { | ||
| 267 | + LOG.error("Failed to rcb sign in, errorCode: {}, resultMessage: {}", resultCode, resultMessage); | ||
| 268 | + throw new PaymentPipelineException(ErrorCode.SERVICE_ACCESS_ERROR, "签到接口调用失败"); | ||
| 269 | + } | ||
| 270 | + } catch (ServiceAccessException | PaymentPipelineException rex) { | ||
| 271 | + throw rex; | ||
| 272 | + } catch (Exception ex) { | ||
| 273 | + LOG.error("Failed to get rcb sign in batchNo", ex); | ||
| 274 | + throw new PaymentPipelineException(ErrorCode.SERVICE_ACCESS_ERROR, "获取签到批次号失败"); | ||
| 275 | + } | ||
| 276 | + } | ||
| 277 | + | ||
| 278 | + protected String getTraceNo() { | ||
| 279 | + try { | ||
| 280 | + String key = "rcb:online:traceNo" + DateUtils.formatDate(LocalDate.now(), DateUtils.YYYYMMDD); | ||
| 281 | + StringBuilder traceNo = new StringBuilder(); | ||
| 282 | + | ||
| 283 | + List<Object> results = stringRedisTemplate.executePipelined(new SessionCallback<Object>() { | ||
| 284 | + @Override | ||
| 285 | + public Object execute(@NonNull RedisOperations operations) throws DataAccessException { | ||
| 286 | + operations.opsForValue().increment(key); | ||
| 287 | + operations.expire(key, 36 * 60 * 60, TimeUnit.SECONDS); | ||
| 288 | + return null; | ||
| 289 | + } | ||
| 290 | + }); | ||
| 291 | + | ||
| 292 | + traceNo.append(results.getFirst()); | ||
| 293 | + int length = traceNo.length(); | ||
| 294 | + if (length < 6) { | ||
| 295 | + for (int i = length; i < 6; i++) { | ||
| 296 | + traceNo.insert(0, "0"); | ||
| 297 | + } | ||
| 298 | + } | ||
| 299 | + return traceNo.toString(); | ||
| 300 | + } catch (Exception ex) { | ||
| 301 | + LOG.error("Failed to get traceNo", ex); | ||
| 302 | + throw new PaymentPipelineException(ErrorCode.SERVICE_ACCESS_ERROR, "获取系统跟踪号失败"); | ||
| 303 | + } | ||
| 304 | + } | ||
| 305 | + | ||
| 306 | + protected Optional<javax.net.ssl.SSLContext> buildSSLContext() { | ||
| 307 | + SSLContext sslContext = null; | ||
| 308 | + try { | ||
| 309 | + TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[] { | ||
| 310 | + new X509TrustManager() { | ||
| 311 | + @Override | ||
| 312 | + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) { | ||
| 313 | + } | ||
| 314 | + | ||
| 315 | + @Override | ||
| 316 | + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) { | ||
| 317 | + } | ||
| 318 | + | ||
| 319 | + @Override | ||
| 320 | + public X509Certificate[] getAcceptedIssuers() { | ||
| 321 | + return new X509Certificate[0]; | ||
| 322 | + } | ||
| 323 | + } | ||
| 324 | + }; | ||
| 325 | + sslContext = SSLContext.getInstance("SSL"); | ||
| 326 | + sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); | ||
| 327 | + } catch (Exception ex) { | ||
| 328 | + LOG.error("Build SSLContext failed", ex); | ||
| 329 | + } | ||
| 330 | + return Optional.ofNullable(sslContext); | ||
| 331 | + } | ||
| 332 | + | ||
| 333 | + private static class AuthorizationSession { | ||
| 334 | + // 用户唯一标识 | ||
| 335 | + private String openid; | ||
| 336 | + // 会话密钥 | ||
| 337 | + private String session_key; | ||
| 338 | + // 用户在开放平台的唯一标识 | ||
| 339 | + private String unionid; | ||
| 340 | + // 错误码 | ||
| 341 | + private Integer errcode; | ||
| 342 | + // 错误信息 | ||
| 343 | + private String errmsg; | ||
| 344 | + | ||
| 345 | + public String getOpenid() { | ||
| 346 | + return openid; | ||
| 347 | + } | ||
| 348 | + | ||
| 349 | + public void setOpenid(String openid) { | ||
| 350 | + this.openid = openid; | ||
| 351 | + } | ||
| 352 | + | ||
| 353 | + public String getSession_key() { | ||
| 354 | + return session_key; | ||
| 355 | + } | ||
| 356 | + | ||
| 357 | + public void setSession_key(String session_key) { | ||
| 358 | + this.session_key = session_key; | ||
| 359 | + } | ||
| 360 | + | ||
| 361 | + public String getUnionid() { | ||
| 362 | + return unionid; | ||
| 363 | + } | ||
| 364 | + | ||
| 365 | + public void setUnionid(String unionid) { | ||
| 366 | + this.unionid = unionid; | ||
| 367 | + } | ||
| 368 | + | ||
| 369 | + public Integer getErrcode() { | ||
| 370 | + return errcode; | ||
| 371 | + } | ||
| 372 | + | ||
| 373 | + public void setErrcode(Integer errcode) { | ||
| 374 | + this.errcode = errcode; | ||
| 375 | + } | ||
| 376 | + | ||
| 377 | + public String getErrmsg() { | ||
| 378 | + return errmsg; | ||
| 379 | + } | ||
| 380 | + | ||
| 381 | + public void setErrmsg(String errmsg) { | ||
| 382 | + this.errmsg = errmsg; | ||
| 383 | + } | ||
| 384 | + } | ||
| 385 | +} |
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/client/WechatDirectHttpClient.java
| @@ -53,8 +53,8 @@ public class WechatDirectHttpClient extends WechatHttpClient { | @@ -53,8 +53,8 @@ public class WechatDirectHttpClient extends WechatHttpClient { | ||
| 53 | * Native支付下单, 返回二维码链接 | 53 | * Native支付下单, 返回二维码链接 |
| 54 | */ | 54 | */ |
| 55 | @Override | 55 | @Override |
| 56 | - public NativePrepayResponse sendNativePrepayRequest(NativePrepayRequest request, String notifyUri) throws Exception { | ||
| 57 | - String payload = nativePrepayRequest(request, notifyUri); | 56 | + public NativePrepayResponse sendNativePrepayRequest(NativePrepayRequest request, String notifyUrl) throws Exception { |
| 57 | + String payload = nativePrepayRequest(request, notifyUrl); | ||
| 58 | // 获取认证信息和签名信息 | 58 | // 获取认证信息和签名信息 |
| 59 | String authorization = WechatSignatureUtils.authorization(wechatConfig.getMchId(), WechatConstants.HTTP_POST, | 59 | String authorization = WechatSignatureUtils.authorization(wechatConfig.getMchId(), WechatConstants.HTTP_POST, |
| 60 | NATIVE_PREPAY, payload, wechatConfig.getPrivateKey(), wechatConfig.getSerialNo()); | 60 | NATIVE_PREPAY, payload, wechatConfig.getPrivateKey(), wechatConfig.getSerialNo()); |
| @@ -81,8 +81,8 @@ public class WechatDirectHttpClient extends WechatHttpClient { | @@ -81,8 +81,8 @@ public class WechatDirectHttpClient extends WechatHttpClient { | ||
| 81 | * 小程序支付下单 | 81 | * 小程序支付下单 |
| 82 | */ | 82 | */ |
| 83 | @Override | 83 | @Override |
| 84 | - public String sendMiniProPrepayRequest(MiniProPrepayRequest request, String notifyUri) throws Exception { | ||
| 85 | - String payload = miniProPrepayRequest(request, notifyUri); | 84 | + public String sendMiniProPrepayRequest(MiniProPrepayRequest request, String notifyUrl) throws Exception { |
| 85 | + String payload = miniProPrepayRequest(request, notifyUrl); | ||
| 86 | // 获取认证信息和签名信息 | 86 | // 获取认证信息和签名信息 |
| 87 | String authorization = WechatSignatureUtils.authorization(wechatConfig.getMchId(), WechatConstants.HTTP_POST, JSAPI_PREPAY, | 87 | String authorization = WechatSignatureUtils.authorization(wechatConfig.getMchId(), WechatConstants.HTTP_POST, JSAPI_PREPAY, |
| 88 | payload, wechatConfig.getPrivateKey(), wechatConfig.getSerialNo()); | 88 | payload, wechatConfig.getPrivateKey(), wechatConfig.getSerialNo()); |
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/client/WechatPartnerHttpClient.java
| @@ -54,8 +54,8 @@ public class WechatPartnerHttpClient extends WechatHttpClient { | @@ -54,8 +54,8 @@ public class WechatPartnerHttpClient extends WechatHttpClient { | ||
| 54 | * Native支付下单, 返回二维码链接 | 54 | * Native支付下单, 返回二维码链接 |
| 55 | */ | 55 | */ |
| 56 | @Override | 56 | @Override |
| 57 | - public NativePrepayResponse sendNativePrepayRequest(NativePrepayRequest request, String notifyUri) throws Exception { | ||
| 58 | - String payload = nativePrepayRequest(request, notifyUri); | 57 | + public NativePrepayResponse sendNativePrepayRequest(NativePrepayRequest request, String notifyUrl) throws Exception { |
| 58 | + String payload = nativePrepayRequest(request, notifyUrl); | ||
| 59 | // 获取认证信息和签名信息 | 59 | // 获取认证信息和签名信息 |
| 60 | String authorization = WechatSignatureUtils.authorization(wechatConfig.getMchId(), WechatConstants.HTTP_POST, NATIVE_PREPAY, | 60 | String authorization = WechatSignatureUtils.authorization(wechatConfig.getMchId(), WechatConstants.HTTP_POST, NATIVE_PREPAY, |
| 61 | payload, wechatConfig.getPrivateKey(), wechatConfig.getSerialNo()); | 61 | payload, wechatConfig.getPrivateKey(), wechatConfig.getSerialNo()); |
| @@ -82,8 +82,8 @@ public class WechatPartnerHttpClient extends WechatHttpClient { | @@ -82,8 +82,8 @@ public class WechatPartnerHttpClient extends WechatHttpClient { | ||
| 82 | * 小程序支付下单, 返回prepay_id | 82 | * 小程序支付下单, 返回prepay_id |
| 83 | */ | 83 | */ |
| 84 | @Override | 84 | @Override |
| 85 | - public String sendMiniProPrepayRequest(MiniProPrepayRequest request, String notifyUri) throws Exception { | ||
| 86 | - String payload = miniProPrepayRequest(request, notifyUri); | 85 | + public String sendMiniProPrepayRequest(MiniProPrepayRequest request, String notifyUrl) throws Exception { |
| 86 | + String payload = miniProPrepayRequest(request, notifyUrl); | ||
| 87 | // 获取认证信息和签名信息 | 87 | // 获取认证信息和签名信息 |
| 88 | String authorization = WechatSignatureUtils.authorization(wechatConfig.getMchId(), WechatConstants.HTTP_POST, JSAPI_PREPAY, | 88 | String authorization = WechatSignatureUtils.authorization(wechatConfig.getMchId(), WechatConstants.HTTP_POST, JSAPI_PREPAY, |
| 89 | payload, wechatConfig.getPrivateKey(), wechatConfig.getSerialNo()); | 89 | payload, wechatConfig.getPrivateKey(), wechatConfig.getSerialNo()); |
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/core/DiliCardPipeline.java
| @@ -27,8 +27,8 @@ public class DiliCardPipeline extends PaymentPipeline<DiliCardPipeline.CardParam | @@ -27,8 +27,8 @@ public class DiliCardPipeline extends PaymentPipeline<DiliCardPipeline.CardParam | ||
| 27 | public DiliCardPipeline(long mchId, long pipelineId, String name, String uri, String params) throws Exception { | 27 | public DiliCardPipeline(long mchId, long pipelineId, String name, String uri, String params) throws Exception { |
| 28 | super(mchId, pipelineId, name, uri, params); | 28 | super(mchId, pipelineId, name, uri, params); |
| 29 | CardParams config = params(); | 29 | CardParams config = params(); |
| 30 | - AssertUtils.notNull(config.getOutMchId(), "园区卡支付缺少参数配置: outMchId"); | ||
| 31 | - AssertUtils.notNull(config.getAccountId(), "园区卡支付缺少参数配置: accountId"); | 30 | + checkParam(config.getOutMchId(), "outMchId"); |
| 31 | + checkParam(config.getAccountId(), "accountId"); | ||
| 32 | 32 | ||
| 33 | this.strategy = new DefaultTimeStrategy(); | 33 | this.strategy = new DefaultTimeStrategy(); |
| 34 | this.client = new CardPaymentHttpClient(uri, config.getOutMchId(), config.getAccountId()); | 34 | this.client = new CardPaymentHttpClient(uri, config.getOutMchId(), config.getAccountId()); |
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/core/PaymentPipeline.java
| @@ -7,6 +7,7 @@ import com.diligrp.cashier.shared.util.ObjectUtils; | @@ -7,6 +7,7 @@ import com.diligrp.cashier.shared.util.ObjectUtils; | ||
| 7 | 7 | ||
| 8 | import java.lang.reflect.Constructor; | 8 | import java.lang.reflect.Constructor; |
| 9 | import java.lang.reflect.InvocationTargetException; | 9 | import java.lang.reflect.InvocationTargetException; |
| 10 | +import java.util.Objects; | ||
| 10 | 11 | ||
| 11 | /** | 12 | /** |
| 12 | * 所有支付通道的基类, 目前分两类支付通道: 在线支付通道、园区卡支付通道 | 13 | * 所有支付通道的基类, 目前分两类支付通道: 在线支付通道、园区卡支付通道 |
| @@ -65,12 +66,18 @@ public abstract class PaymentPipeline<T extends PaymentPipeline.PipelineParams> | @@ -65,12 +66,18 @@ public abstract class PaymentPipeline<T extends PaymentPipeline.PipelineParams> | ||
| 65 | return params; | 66 | return params; |
| 66 | } | 67 | } |
| 67 | 68 | ||
| 68 | - protected void checkParam(String label, String value) { | 69 | + protected void checkParam(String value, String label) { |
| 69 | if (ObjectUtils.isEmpty(value)) { | 70 | if (ObjectUtils.isEmpty(value)) { |
| 70 | throw new PaymentPipelineException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, String.format("支付通道缺少参数配置: %s", label)); | 71 | throw new PaymentPipelineException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, String.format("支付通道缺少参数配置: %s", label)); |
| 71 | } | 72 | } |
| 72 | } | 73 | } |
| 73 | 74 | ||
| 75 | + protected void checkParam(Object value, String label) { | ||
| 76 | + if (Objects.isNull(value)) { | ||
| 77 | + throw new PaymentPipelineException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, String.format("支付通道缺少参数配置: %s", label)); | ||
| 78 | + } | ||
| 79 | + } | ||
| 80 | + | ||
| 74 | protected static abstract class PipelineParams { | 81 | protected static abstract class PipelineParams { |
| 75 | public PipelineParams(String params) { | 82 | public PipelineParams(String params) { |
| 76 | parseParams(params); | 83 | parseParams(params); |
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/core/RcbOnlinePipeline.java
0 → 100644
| 1 | +package com.diligrp.cashier.pipeline.core; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.pipeline.Constants; | ||
| 4 | +import com.diligrp.cashier.pipeline.client.RcbOnlineHttpClient; | ||
| 5 | +import com.diligrp.cashier.pipeline.domain.*; | ||
| 6 | +import com.diligrp.cashier.pipeline.exception.PaymentPipelineException; | ||
| 7 | +import com.diligrp.cashier.pipeline.type.ChannelType; | ||
| 8 | +import com.diligrp.cashier.pipeline.type.PaymentState; | ||
| 9 | +import com.diligrp.cashier.shared.ErrorCode; | ||
| 10 | +import com.diligrp.cashier.shared.util.ObjectUtils; | ||
| 11 | + | ||
| 12 | +import java.time.LocalDateTime; | ||
| 13 | +import java.util.HashMap; | ||
| 14 | +import java.util.Map; | ||
| 15 | +import java.util.StringTokenizer; | ||
| 16 | + | ||
| 17 | +/** | ||
| 18 | + * 农商行聚合支付通道 | ||
| 19 | + */ | ||
| 20 | +public class RcbOnlinePipeline extends OnlinePipeline<RcbOnlinePipeline.RcbParams> { | ||
| 21 | + | ||
| 22 | + private final ScanTimeStrategy strategy; | ||
| 23 | + | ||
| 24 | + private final RcbOnlineHttpClient client; | ||
| 25 | + | ||
| 26 | + public RcbOnlinePipeline(long mchId, long pipelineId, String name, String uri, String params) throws Exception { | ||
| 27 | + super(mchId, pipelineId, name, uri, params); | ||
| 28 | + if (ObjectUtils.isEmpty(params)) { | ||
| 29 | + throw new PaymentPipelineException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "支付通道未进行参数配置"); | ||
| 30 | + } | ||
| 31 | + RcbParams config = params(); | ||
| 32 | + | ||
| 33 | + checkParam(config.getMerchantNo(), "merchantNo"); | ||
| 34 | + checkParam(config.getTerminalNo(), "terminalNo"); | ||
| 35 | + checkParam(config.getAppId(), "appId"); | ||
| 36 | + checkParam(config.getAppSecret(), "appSecret"); | ||
| 37 | + checkParam(config.getKey(), "key"); | ||
| 38 | + checkParam(config.getNotifyUrl(), "notifyUrl"); | ||
| 39 | + this.client = new RcbOnlineHttpClient(uri, config.getMerchantNo(), config.getTerminalNo(), config.getKey(), config.getAppId()); | ||
| 40 | + this.strategy = new DefaultTimeStrategy(); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + /** | ||
| 44 | + * 小程序支付 | ||
| 45 | + */ | ||
| 46 | + public MiniProPrepayResponse sendMiniProPrepayRequest(MiniProPrepayRequest request) { | ||
| 47 | + String notifyUri = String.format(Constants.RCB_PAYMENT_NOTIFY_URI, params().getNotifyUrl(), request.getPaymentId()); | ||
| 48 | + request.put(Constants.PARAM_MCH_ID, params().getMerchantNo()); | ||
| 49 | + String payInfo = client.sendMiniProPrepayRequest(request, notifyUri); | ||
| 50 | + // 解析农商行小程序支付信息 | ||
| 51 | + StringTokenizer tokenizer = new StringTokenizer(payInfo, ";", false); | ||
| 52 | + Map<String, String> result = new HashMap<>(8); | ||
| 53 | + while (tokenizer.hasMoreTokens()) { | ||
| 54 | + String token = tokenizer.nextToken(); | ||
| 55 | + int index = token.indexOf("="); | ||
| 56 | + if (index > 0) { | ||
| 57 | + String key = token.substring (0, index).trim (); | ||
| 58 | + String value = token.substring (index + 1).trim (); | ||
| 59 | + result.put(key, value); | ||
| 60 | + } | ||
| 61 | + } | ||
| 62 | + String prepayId = result.get("tradeNO"); | ||
| 63 | + String timeStamp = result.get("timeStamp"); | ||
| 64 | + String nonceStr = result.get("nonceStr"); | ||
| 65 | +// String packet = result.get("body"); // prepay_id=<prepayId> | ||
| 66 | + String signType = result.get("signType"); | ||
| 67 | + String paySign = result.get("paySign"); | ||
| 68 | + return MiniProPrepayResponse.of(request.getPaymentId(), prepayId, prepayId, timeStamp, nonceStr, signType, paySign); | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + @Override | ||
| 72 | + public OnlinePaymentResponse queryPrepayResponse(OnlinePrepayOrder request) { | ||
| 73 | + return client.queryPrepayResponse(request); | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + @Override | ||
| 77 | + public void closePrepayOrder(OnlinePrepayOrder request) { | ||
| 78 | + client.closePrepayOrder(request); | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + @Override | ||
| 82 | + public OnlineRefundResponse sendRefundRequest(OnlineRefundRequest request) { | ||
| 83 | + return client.sendRefundRequest(request); | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | + @Override | ||
| 87 | + public OnlineRefundResponse queryRefundResponse(OnlineRefundOrder request) { | ||
| 88 | + return new OnlineRefundResponse(request.getRefundId(), request.getOutTradeNo(), LocalDateTime.now(), | ||
| 89 | + PaymentState.PROCESSING, "支付通道不支持退款状态查询"); | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + @Override | ||
| 93 | + public ScanTimeStrategy getTimeStrategy() { | ||
| 94 | + return strategy; | ||
| 95 | + } | ||
| 96 | + | ||
| 97 | + @Override | ||
| 98 | + public ChannelType supportedChannel() { | ||
| 99 | + return ChannelType.RCB; | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + @Override | ||
| 103 | + public Class<RcbParams> paramClass() { | ||
| 104 | + return RcbParams.class; | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + public static class RcbParams extends PaymentPipeline.PipelineParams { | ||
| 108 | + public RcbParams(String params) { | ||
| 109 | + super(params); | ||
| 110 | + } | ||
| 111 | + | ||
| 112 | + private String merchantNo; | ||
| 113 | + | ||
| 114 | + private String terminalNo; | ||
| 115 | + | ||
| 116 | + private String appId; | ||
| 117 | + | ||
| 118 | + private String appSecret; | ||
| 119 | + | ||
| 120 | + private String key; | ||
| 121 | + | ||
| 122 | + private String notifyUrl; | ||
| 123 | + | ||
| 124 | + public String getMerchantNo() { | ||
| 125 | + return merchantNo; | ||
| 126 | + } | ||
| 127 | + | ||
| 128 | + public void setMerchantNo(String merchantNo) { | ||
| 129 | + this.merchantNo = merchantNo; | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + public String getTerminalNo() { | ||
| 133 | + return terminalNo; | ||
| 134 | + } | ||
| 135 | + | ||
| 136 | + public void setTerminalNo(String terminalNo) { | ||
| 137 | + this.terminalNo = terminalNo; | ||
| 138 | + } | ||
| 139 | + | ||
| 140 | + public String getAppId() { | ||
| 141 | + return appId; | ||
| 142 | + } | ||
| 143 | + | ||
| 144 | + public void setAppId(String appId) { | ||
| 145 | + this.appId = appId; | ||
| 146 | + } | ||
| 147 | + | ||
| 148 | + public String getAppSecret() { | ||
| 149 | + return appSecret; | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + public void setAppSecret(String appSecret) { | ||
| 153 | + this.appSecret = appSecret; | ||
| 154 | + } | ||
| 155 | + | ||
| 156 | + public String getKey() { | ||
| 157 | + return key; | ||
| 158 | + } | ||
| 159 | + | ||
| 160 | + public void setKey(String key) { | ||
| 161 | + this.key = key; | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + public String getNotifyUrl() { | ||
| 165 | + return notifyUrl; | ||
| 166 | + } | ||
| 167 | + | ||
| 168 | + public void setNotifyUrl(String notifyUrl) { | ||
| 169 | + this.notifyUrl = notifyUrl; | ||
| 170 | + } | ||
| 171 | + } | ||
| 172 | +} |
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/core/WechatPartnerPipeline.java
| @@ -32,9 +32,9 @@ public class WechatPartnerPipeline extends WechatPipeline<WechatPartnerPipeline. | @@ -32,9 +32,9 @@ public class WechatPartnerPipeline extends WechatPipeline<WechatPartnerPipeline. | ||
| 32 | public WechatPartnerPipeline(long mchId, long pipelineId, String name, String uri, String params) throws Exception { | 32 | public WechatPartnerPipeline(long mchId, long pipelineId, String name, String uri, String params) throws Exception { |
| 33 | super(mchId, pipelineId, name, uri, params); | 33 | super(mchId, pipelineId, name, uri, params); |
| 34 | PartnerWechatParams config = params(); | 34 | PartnerWechatParams config = params(); |
| 35 | - AssertUtils.notEmpty(config.getSubMchId(), "微信支付缺少参数配置: subMchId"); | ||
| 36 | - AssertUtils.notEmpty(config.getSubAppId(), "微信支付缺少参数配置: subAppId"); | ||
| 37 | - AssertUtils.notEmpty(config.getAppSecret(), "微信支付缺少参数配置: appSecret"); | 35 | + checkParam(config.getSubMchId(), "subMchId"); |
| 36 | + checkParam(config.getSubAppId(), "subAppId"); | ||
| 37 | + checkParam(config.getAppSecret(), "appSecret"); | ||
| 38 | this.strategy = new DefaultTimeStrategy(); | 38 | this.strategy = new DefaultTimeStrategy(); |
| 39 | } | 39 | } |
| 40 | 40 |
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/core/WechatPipeline.java
| @@ -10,7 +10,6 @@ import com.diligrp.cashier.pipeline.type.ChannelType; | @@ -10,7 +10,6 @@ import com.diligrp.cashier.pipeline.type.ChannelType; | ||
| 10 | import com.diligrp.cashier.pipeline.util.WechatConstants; | 10 | import com.diligrp.cashier.pipeline.util.WechatConstants; |
| 11 | import com.diligrp.cashier.shared.ErrorCode; | 11 | import com.diligrp.cashier.shared.ErrorCode; |
| 12 | import com.diligrp.cashier.shared.security.RsaCipher; | 12 | import com.diligrp.cashier.shared.security.RsaCipher; |
| 13 | -import com.diligrp.cashier.shared.util.AssertUtils; | ||
| 14 | import org.slf4j.Logger; | 13 | import org.slf4j.Logger; |
| 15 | import org.slf4j.LoggerFactory; | 14 | import org.slf4j.LoggerFactory; |
| 16 | 15 | ||
| @@ -31,7 +30,7 @@ public abstract class WechatPipeline<T extends WechatPipeline.WechatParams> exte | @@ -31,7 +30,7 @@ public abstract class WechatPipeline<T extends WechatPipeline.WechatParams> exte | ||
| 31 | public WechatPipeline(long mchId, long pipelineId, String name, String uri, String params) throws Exception { | 30 | public WechatPipeline(long mchId, long pipelineId, String name, String uri, String params) throws Exception { |
| 32 | super(mchId, pipelineId, name, uri, params); | 31 | super(mchId, pipelineId, name, uri, params); |
| 33 | WechatParams config = params(); | 32 | WechatParams config = params(); |
| 34 | - AssertUtils.notEmpty(config.getNotifyUrl(), "微信支付缺少参数配置: notifyUrl"); | 33 | + checkParam(config.getNotifyUrl(), "notifyUrl"); |
| 35 | } | 34 | } |
| 36 | 35 | ||
| 37 | /** | 36 | /** |
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/service/impl/PaymentPipelineManagerImpl.java
| @@ -60,6 +60,10 @@ public class PaymentPipelineManagerImpl extends LifeCycle implements IPaymentPip | @@ -60,6 +60,10 @@ public class PaymentPipelineManagerImpl extends LifeCycle implements IPaymentPip | ||
| 60 | DiliCardPipeline paymentPipeline = new DiliCardPipeline(pipeline.getMchId(), pipeline.getPipelineId(), | 60 | DiliCardPipeline paymentPipeline = new DiliCardPipeline(pipeline.getMchId(), pipeline.getPipelineId(), |
| 61 | pipeline.getName(), pipeline.getUri(), pipeline.getParam()); | 61 | pipeline.getName(), pipeline.getUri(), pipeline.getParam()); |
| 62 | registerPaymentPipeline(paymentPipeline); | 62 | registerPaymentPipeline(paymentPipeline); |
| 63 | + } else if (ChannelType.RCB.equalTo(pipeline.getChannelId())) { | ||
| 64 | + RcbOnlinePipeline paymentPipeline = new RcbOnlinePipeline(pipeline.getMchId(), pipeline.getPipelineId(), | ||
| 65 | + pipeline.getName(), pipeline.getUri(), pipeline.getParam()); | ||
| 66 | + registerPaymentPipeline(paymentPipeline); | ||
| 63 | } else { | 67 | } else { |
| 64 | LOG.warn("Ignore payment pipeline configuration: {}", pipeline.getName()); | 68 | LOG.warn("Ignore payment pipeline configuration: {}", pipeline.getName()); |
| 65 | } | 69 | } |
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/service/impl/WechatPaymentServiceImpl.java
| 1 | package com.diligrp.cashier.pipeline.service.impl; | 1 | package com.diligrp.cashier.pipeline.service.impl; |
| 2 | 2 | ||
| 3 | import com.diligrp.cashier.pipeline.client.WechatHttpClient; | 3 | import com.diligrp.cashier.pipeline.client.WechatHttpClient; |
| 4 | +import com.diligrp.cashier.pipeline.core.OnlinePipeline; | ||
| 5 | +import com.diligrp.cashier.pipeline.core.RcbOnlinePipeline; | ||
| 6 | +import com.diligrp.cashier.pipeline.core.WechatDirectPipeline; | ||
| 4 | import com.diligrp.cashier.pipeline.core.WechatPartnerPipeline; | 7 | import com.diligrp.cashier.pipeline.core.WechatPartnerPipeline; |
| 5 | -import com.diligrp.cashier.pipeline.core.WechatPipeline; | ||
| 6 | import com.diligrp.cashier.pipeline.domain.wechat.UploadShippingRequest; | 8 | import com.diligrp.cashier.pipeline.domain.wechat.UploadShippingRequest; |
| 7 | import com.diligrp.cashier.pipeline.domain.wechat.WechatAccessToken; | 9 | import com.diligrp.cashier.pipeline.domain.wechat.WechatAccessToken; |
| 10 | +import com.diligrp.cashier.pipeline.exception.PaymentPipelineException; | ||
| 8 | import com.diligrp.cashier.pipeline.service.IPaymentPipelineManager; | 11 | import com.diligrp.cashier.pipeline.service.IPaymentPipelineManager; |
| 9 | import com.diligrp.cashier.pipeline.service.IWechatPaymentService; | 12 | import com.diligrp.cashier.pipeline.service.IWechatPaymentService; |
| 13 | +import com.diligrp.cashier.shared.ErrorCode; | ||
| 10 | import jakarta.annotation.Resource; | 14 | import jakarta.annotation.Resource; |
| 11 | import org.springframework.stereotype.Service; | 15 | import org.springframework.stereotype.Service; |
| 12 | 16 | ||
| @@ -16,19 +20,26 @@ public class WechatPaymentServiceImpl implements IWechatPaymentService { | @@ -16,19 +20,26 @@ public class WechatPaymentServiceImpl implements IWechatPaymentService { | ||
| 16 | @Resource | 20 | @Resource |
| 17 | private IPaymentPipelineManager paymentPipelineManager; | 21 | private IPaymentPipelineManager paymentPipelineManager; |
| 18 | 22 | ||
| 23 | + private WechatHttpClient wechatHttpClient = new WechatHttpClient(null, null); | ||
| 24 | + | ||
| 19 | /** | 25 | /** |
| 20 | * 根据登陆凭证code获取openId | 26 | * 根据登陆凭证code获取openId |
| 21 | */ | 27 | */ |
| 22 | @Override | 28 | @Override |
| 23 | public String openIdByCode(Long pipelineId, String code) { | 29 | public String openIdByCode(Long pipelineId, String code) { |
| 24 | - WechatPipeline<?> pipeline = paymentPipelineManager.findPipelineById(pipelineId, WechatPipeline.class); | 30 | + OnlinePipeline<?> pipeline = paymentPipelineManager.findPipelineById(pipelineId, OnlinePipeline.class); |
| 25 | if (pipeline instanceof WechatPartnerPipeline partnerPipeline) { | 31 | if (pipeline instanceof WechatPartnerPipeline partnerPipeline) { |
| 26 | // 服务商模式下使用WechatParams配置的信息获取openId | 32 | // 服务商模式下使用WechatParams配置的信息获取openId |
| 27 | WechatPartnerPipeline.PartnerWechatParams params = partnerPipeline.params(); | 33 | WechatPartnerPipeline.PartnerWechatParams params = partnerPipeline.params(); |
| 28 | - return pipeline.getClient().loginAuthorization(params.getSubAppId(), params.getAppSecret(), code); | ||
| 29 | - } else { | 34 | + return partnerPipeline.getClient().loginAuthorization(params.getSubAppId(), params.getAppSecret(), code); |
| 35 | + } else if (pipeline instanceof WechatDirectPipeline directPipeline) { | ||
| 30 | // 直连模式直接使用WebConfig配置的信息获取openId | 36 | // 直连模式直接使用WebConfig配置的信息获取openId |
| 31 | - return pipeline.getClient().loginAuthorization(code); | 37 | + return directPipeline.getClient().loginAuthorization(code); |
| 38 | + } else if (pipeline instanceof RcbOnlinePipeline rcbOnlinePipeline) { | ||
| 39 | + RcbOnlinePipeline.RcbParams params = rcbOnlinePipeline.params(); | ||
| 40 | + return wechatHttpClient.loginAuthorization(params.getAppId(), params.getAppSecret(), code); | ||
| 41 | + } else { | ||
| 42 | + throw new PaymentPipelineException(ErrorCode.OPERATION_NOT_ALLOWED, "该支付通道不支持此操作"); | ||
| 32 | } | 43 | } |
| 33 | } | 44 | } |
| 34 | 45 |
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/util/RcbSignatureUtils.java
0 → 100644
| 1 | +package com.diligrp.cashier.pipeline.util; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.security.HexUtils; | ||
| 4 | +import com.diligrp.cashier.shared.util.ObjectUtils; | ||
| 5 | + | ||
| 6 | +import java.nio.charset.StandardCharsets; | ||
| 7 | +import java.security.MessageDigest; | ||
| 8 | +import java.security.NoSuchAlgorithmException; | ||
| 9 | +import java.util.Comparator; | ||
| 10 | +import java.util.Map; | ||
| 11 | +import java.util.TreeMap; | ||
| 12 | +import java.util.stream.Collectors; | ||
| 13 | + | ||
| 14 | +/** | ||
| 15 | + * 安徽农商行的聚合支付文档中并未提到验签功能 | ||
| 16 | + */ | ||
| 17 | +public final class RcbSignatureUtils { | ||
| 18 | + public static String sign(Map<String, String> params, String key) { | ||
| 19 | + String data = map2String(params).concat("&key=").concat(key); | ||
| 20 | + MessageDigest md5; | ||
| 21 | + try { | ||
| 22 | + md5 = MessageDigest.getInstance("MD5"); | ||
| 23 | + md5.update(data.getBytes(StandardCharsets.UTF_8)); | ||
| 24 | + return HexUtils.encodeHexStr(md5.digest(), false); | ||
| 25 | + } catch (NoSuchAlgorithmException ex) { | ||
| 26 | + // Never happened | ||
| 27 | + throw new RuntimeException(ex); | ||
| 28 | + } | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + public static boolean verify(Map<String, String> params, String key, String signature) { | ||
| 32 | + String data = sign(params, key); | ||
| 33 | + return data.equals(signature); | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + public static boolean verify(String params, String key, String signature) { | ||
| 37 | + String data = params.concat("&key=").concat(key); | ||
| 38 | + MessageDigest md5 = null; | ||
| 39 | + try { | ||
| 40 | + md5 = MessageDigest.getInstance("MD5"); | ||
| 41 | + md5.update(data.getBytes(StandardCharsets.UTF_8)); | ||
| 42 | + String toBeSigned = HexUtils.encodeHexStr(md5.digest(), false); | ||
| 43 | + return toBeSigned.equals(signature); | ||
| 44 | + } catch (NoSuchAlgorithmException ex) { | ||
| 45 | + // Never happened | ||
| 46 | + throw new RuntimeException(ex); | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + public static String map2String(Map<String, String> params) { | ||
| 52 | + TreeMap<String, String> data = new TreeMap<>(Comparator.naturalOrder()); | ||
| 53 | + data.putAll(params); | ||
| 54 | + return data.entrySet().stream().filter(e -> ObjectUtils.isNotEmpty(e.getValue())) | ||
| 55 | + .map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&", "", "")); | ||
| 56 | + } | ||
| 57 | +} |
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/util/RcbStateUtils.java
0 → 100644
| 1 | +package com.diligrp.cashier.pipeline.util; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.pipeline.type.OutPaymentType; | ||
| 4 | +import com.diligrp.cashier.pipeline.type.PaymentState; | ||
| 5 | + | ||
| 6 | +public final class RcbStateUtils { | ||
| 7 | + public static PaymentState paymentState(String orderStatus) { | ||
| 8 | + return switch (orderStatus) { | ||
| 9 | + case "3", "6" -> PaymentState.SUCCESS; // 3-交易成功; 6-交易成功但有退款 | ||
| 10 | + case "1", "2" -> PaymentState.PROCESSING; | ||
| 11 | + default -> PaymentState.FAILED; | ||
| 12 | + }; | ||
| 13 | + } | ||
| 14 | + | ||
| 15 | + public static PaymentState refundState(String orderStatus) { | ||
| 16 | + return switch (orderStatus) { | ||
| 17 | + case "01" -> PaymentState.SUCCESS; | ||
| 18 | + case "02" -> PaymentState.FAILED; | ||
| 19 | + default -> PaymentState.PROCESSING; | ||
| 20 | + }; | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + public static String refundInfo(PaymentState refundState) { | ||
| 24 | + return switch (refundState) { | ||
| 25 | + case SUCCESS -> "退款成功"; | ||
| 26 | + case FAILED -> "退款失败"; | ||
| 27 | + default -> "退款处理中"; | ||
| 28 | + }; | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + public static OutPaymentType outPayType(String tradeChannel) { | ||
| 32 | + return switch (tradeChannel) { | ||
| 33 | + case "01" -> OutPaymentType.WXPAY; // 微信 | ||
| 34 | + case "02" -> OutPaymentType.ALIPAY; // 支付宝 | ||
| 35 | + case "04" -> OutPaymentType.UPAY; // 银联 | ||
| 36 | + default -> OutPaymentType.NOP; // 未知支付方式 | ||
| 37 | + }; | ||
| 38 | + } | ||
| 39 | +} |
cashier-shared/src/main/java/com/diligrp/cashier/shared/util/DateUtils.java
| @@ -16,6 +16,12 @@ import java.util.Date; | @@ -16,6 +16,12 @@ import java.util.Date; | ||
| 16 | */ | 16 | */ |
| 17 | public class DateUtils { | 17 | public class DateUtils { |
| 18 | 18 | ||
| 19 | + public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; | ||
| 20 | + | ||
| 21 | + public static final String YYYY_MM_DD = "yyyy-MM-dd"; | ||
| 22 | + | ||
| 23 | + public static final String YYYYMMDD = "yyyyMMdd"; | ||
| 24 | + | ||
| 19 | public static String formatDateTime(LocalDateTime when, String format) { | 25 | public static String formatDateTime(LocalDateTime when, String format) { |
| 20 | if (ObjectUtils.isNull(when)) { | 26 | if (ObjectUtils.isNull(when)) { |
| 21 | return null; | 27 | return null; |
scripts/cashier-data.sql
| @@ -6,6 +6,8 @@ INSERT INTO `dili_cashier`.upay_payment_pipeline(mch_id, pipeline_id, channel_id | @@ -6,6 +6,8 @@ INSERT INTO `dili_cashier`.upay_payment_pipeline(mch_id, pipeline_id, channel_id | ||
| 6 | VALUES (1001, 10011, 10, 10, '中瑞微信服务商支付通道', 'https://api.mch.weixin.qq.com', '{"subMchId":"1679224186", "subAppId":"wxad27b69b888b6dc9", "appSecret":"9c254c0ab932b3c30292a05679a688f7", "notifyUrl": "https://cashier.test.gszdtop.com"}', 1, now(), now()); | 6 | VALUES (1001, 10011, 10, 10, '中瑞微信服务商支付通道', 'https://api.mch.weixin.qq.com', '{"subMchId":"1679224186", "subAppId":"wxad27b69b888b6dc9", "appSecret":"9c254c0ab932b3c30292a05679a688f7", "notifyUrl": "https://cashier.test.gszdtop.com"}', 1, now(), now()); |
| 7 | INSERT INTO `dili_cashier`.upay_wechat_param(pipeline_id, mch_id, app_id, app_secret, serial_no, private_key, wechat_serial_no, wechat_public_key, api_v3_key, type, created_time) | 7 | INSERT INTO `dili_cashier`.upay_wechat_param(pipeline_id, mch_id, app_id, app_secret, serial_no, private_key, wechat_serial_no, wechat_public_key, api_v3_key, type, created_time) |
| 8 | VALUES(10011, '1679223106', 'wxca99d56a6ab15f29', '9c254c0ab932b3c30292a05679a688f7', '60C2877836D1D618D2E40186995BB00299D92F44','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', 'RSfFvEBBQiHz8GZyDcP2eSUlZJgKjdxk', 2, now()); | 8 | VALUES(10011, '1679223106', 'wxca99d56a6ab15f29', '9c254c0ab932b3c30292a05679a688f7', '60C2877836D1D618D2E40186995BB00299D92F44','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', 'RSfFvEBBQiHz8GZyDcP2eSUlZJgKjdxk', 2, now()); |
| 9 | --- 根据环境配置不同的uri | 9 | + |
| 10 | +INSERT INTO `dili_cashier`.upay_payment_pipeline(mch_id, pipeline_id, channel_id, type, name, uri, param, state, created_time, modified_time) | ||
| 11 | +VALUES (1001, 10014, 29, 2, '中瑞农商行聚合支付通道', 'https://epos.ahrcu.com:3443', '{"merchantNo": "94734065411016B", "terminalNo": "19A03301", "appId": "wx6f15ee0bd788c744", "appSecret": "4ab4152adf21da99c629fe7f3ce6b571", "key": "45913E42F86C18F512BEF54C2F0FE5BF", "notifyUrl": "https://cashier.test.gszdtop.com"}', 1, now(), now()); | ||
| 10 | INSERT INTO `dili_cashier`.upay_payment_pipeline(mch_id, pipeline_id, channel_id, type, name, uri, param, state, created_time, modified_time) | 12 | INSERT INTO `dili_cashier`.upay_payment_pipeline(mch_id, pipeline_id, channel_id, type, name, uri, param, state, created_time, modified_time) |
| 11 | VALUES (1001, 10012, 19, 2, '中瑞园区卡支付通道', 'http://gateway.dev.nong12.com/pay-service', '{"outMchId": 9, "accountId": 118924}', 1, now(), now()); | 13 | VALUES (1001, 10012, 19, 2, '中瑞园区卡支付通道', 'http://gateway.dev.nong12.com/pay-service', '{"outMchId": 9, "accountId": 118924}', 1, now(), now()); |