Commit 7ef99d77fc257123c78bc8454f3ba5ba65aba7ae
1 parent
e6fca59a
improve cashier api
Showing
43 changed files
with
1462 additions
and
191 deletions
cashier-boss/src/main/java/com/diligrp/cashier/boss/Constants.java
| @@ -13,4 +13,7 @@ public final class Constants { | @@ -13,4 +13,7 @@ public final class Constants { | ||
| 13 | public static final int TOKEN_SIGN_LENGTH = 8; | 13 | public static final int TOKEN_SIGN_LENGTH = 8; |
| 14 | // TOKEN过期时长,单位秒 | 14 | // TOKEN过期时长,单位秒 |
| 15 | public static final long TOKEN_TIMEOUT_SECONDS = 60; | 15 | public static final long TOKEN_TIMEOUT_SECONDS = 60; |
| 16 | + | ||
| 17 | + public final static String CONTENT_TYPE = "application/json;charset=UTF-8"; | ||
| 18 | + | ||
| 16 | } | 19 | } |
cashier-boss/src/main/java/com/diligrp/cashier/boss/controller/CashierDeskController.java
| @@ -8,12 +8,11 @@ import com.diligrp.cashier.boss.util.CashierOrderConverter; | @@ -8,12 +8,11 @@ import com.diligrp.cashier.boss.util.CashierOrderConverter; | ||
| 8 | import com.diligrp.cashier.pipeline.domain.OnlinePaymentStatus; | 8 | import com.diligrp.cashier.pipeline.domain.OnlinePaymentStatus; |
| 9 | import com.diligrp.cashier.shared.domain.Message; | 9 | import com.diligrp.cashier.shared.domain.Message; |
| 10 | import com.diligrp.cashier.shared.util.AssertUtils; | 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; | 11 | +import com.diligrp.cashier.trade.domain.*; |
| 14 | import jakarta.annotation.Resource; | 12 | import jakarta.annotation.Resource; |
| 15 | import org.springframework.web.bind.annotation.RequestBody; | 13 | import org.springframework.web.bind.annotation.RequestBody; |
| 16 | import org.springframework.web.bind.annotation.RequestMapping; | 14 | import org.springframework.web.bind.annotation.RequestMapping; |
| 15 | +import org.springframework.web.bind.annotation.RequestParam; | ||
| 17 | import org.springframework.web.bind.annotation.RestController; | 16 | import org.springframework.web.bind.annotation.RestController; |
| 18 | 17 | ||
| 19 | @RestController | 18 | @RestController |
| @@ -43,6 +42,11 @@ public class CashierDeskController { | @@ -43,6 +42,11 @@ public class CashierDeskController { | ||
| 43 | return Message.success(paymentUrl); | 42 | return Message.success(paymentUrl); |
| 44 | } | 43 | } |
| 45 | 44 | ||
| 45 | + @RequestMapping("/orderInfo") | ||
| 46 | + public Message<?> orderInfo(@RequestParam("token") String token) { | ||
| 47 | + return Message.success(cashierDeskService.getCashierOrderByToken(token)); | ||
| 48 | + } | ||
| 49 | + | ||
| 46 | @RequestMapping("/orderPayment") | 50 | @RequestMapping("/orderPayment") |
| 47 | public Message<?> orderPayment(@RequestBody CashierPayment request) { | 51 | public Message<?> orderPayment(@RequestBody CashierPayment request) { |
| 48 | // 基本参数校验 | 52 | // 基本参数校验 |
| @@ -52,4 +56,36 @@ public class CashierDeskController { | @@ -52,4 +56,36 @@ public class CashierDeskController { | ||
| 52 | OnlinePaymentStatus paymentStatus = cashierDeskService.doPayment(request); | 56 | OnlinePaymentStatus paymentStatus = cashierDeskService.doPayment(request); |
| 53 | return Message.success(paymentStatus); | 57 | return Message.success(paymentStatus); |
| 54 | } | 58 | } |
| 59 | + | ||
| 60 | + @RequestMapping("/orderClose") | ||
| 61 | + public Message<?> orderPayment(@RequestParam("paymentId") String paymentId) { | ||
| 62 | + cashierDeskService.closePrepayOrder(paymentId); | ||
| 63 | + return Message.success(); | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + @RequestMapping(value = "/paymentState") | ||
| 67 | + public Message<?> paymentState(@RequestParam("paymentId") String paymentId, | ||
| 68 | + @RequestParam(name = "mode", required = false) String mode) { | ||
| 69 | + | ||
| 70 | + OnlinePaymentResult response = cashierDeskService.queryPaymentState(paymentId, mode); | ||
| 71 | + return Message.success(response); | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + @RequestMapping(value = "/orderRefund.do") | ||
| 75 | + public Message<?> requestRefund(@RequestBody OnlineRefundDTO request) { | ||
| 76 | + AssertUtils.notEmpty(request.getTradeId(), "tradeId missed"); | ||
| 77 | + AssertUtils.notNull(request.getAmount(), "amount missed"); | ||
| 78 | + AssertUtils.isTrue(request.getAmount() > 0, "Invalid amount"); | ||
| 79 | + | ||
| 80 | + OnlineRefundResult response = cashierDeskService.sendRefundRequest(request); | ||
| 81 | + return Message.success(response); | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + @RequestMapping(value = "/refundState") | ||
| 85 | + public Message<?> refundState(@RequestParam("refundId") String refundId, | ||
| 86 | + @RequestParam(name = "mode", required = false) String mode) { | ||
| 87 | + | ||
| 88 | + OnlineRefundResult response = cashierDeskService.queryRefundState(refundId, mode); | ||
| 89 | + return Message.success(response); | ||
| 90 | + } | ||
| 55 | } | 91 | } |
cashier-boss/src/main/java/com/diligrp/cashier/boss/controller/WechatPaymentController.java
0 → 100644
| 1 | +package com.diligrp.cashier.boss.controller; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.boss.exception.BossServiceException; | ||
| 4 | +import com.diligrp.cashier.boss.util.HttpUtils; | ||
| 5 | +import com.diligrp.cashier.pipeline.core.WechatPartnerPipeline; | ||
| 6 | +import com.diligrp.cashier.pipeline.core.WechatPipeline; | ||
| 7 | +import com.diligrp.cashier.pipeline.domain.OnlinePaymentResponse; | ||
| 8 | +import com.diligrp.cashier.pipeline.domain.OnlineRefundResponse; | ||
| 9 | +import com.diligrp.cashier.pipeline.domain.wechat.*; | ||
| 10 | +import com.diligrp.cashier.pipeline.service.IPaymentPipelineManager; | ||
| 11 | +import com.diligrp.cashier.pipeline.service.IWechatPaymentService; | ||
| 12 | +import com.diligrp.cashier.pipeline.type.OutPaymentType; | ||
| 13 | +import com.diligrp.cashier.pipeline.type.PaymentState; | ||
| 14 | +import com.diligrp.cashier.pipeline.util.WechatConstants; | ||
| 15 | +import com.diligrp.cashier.pipeline.util.WechatSignatureUtils; | ||
| 16 | +import com.diligrp.cashier.pipeline.util.WechatStateUtils; | ||
| 17 | +import com.diligrp.cashier.shared.ErrorCode; | ||
| 18 | +import com.diligrp.cashier.shared.domain.Message; | ||
| 19 | +import com.diligrp.cashier.shared.util.DateUtils; | ||
| 20 | +import com.diligrp.cashier.shared.util.JsonUtils; | ||
| 21 | +import com.diligrp.cashier.trade.model.OnlinePayment; | ||
| 22 | +import com.diligrp.cashier.trade.service.ICashierPaymentService; | ||
| 23 | +import com.diligrp.cashier.trade.service.ITradeAssistantService; | ||
| 24 | +import jakarta.annotation.Resource; | ||
| 25 | +import jakarta.servlet.http.HttpServletRequest; | ||
| 26 | +import org.slf4j.Logger; | ||
| 27 | +import org.slf4j.LoggerFactory; | ||
| 28 | +import org.springframework.http.HttpStatus; | ||
| 29 | +import org.springframework.http.ResponseEntity; | ||
| 30 | +import org.springframework.web.bind.annotation.PathVariable; | ||
| 31 | +import org.springframework.web.bind.annotation.RequestMapping; | ||
| 32 | +import org.springframework.web.bind.annotation.RequestParam; | ||
| 33 | +import org.springframework.web.bind.annotation.RestController; | ||
| 34 | + | ||
| 35 | +import java.time.LocalDateTime; | ||
| 36 | + | ||
| 37 | +@RestController | ||
| 38 | +@RequestMapping(value = "/wechat") | ||
| 39 | +public class WechatPaymentController { | ||
| 40 | + | ||
| 41 | + private static final Logger LOG = LoggerFactory.getLogger(WechatPaymentController.class); | ||
| 42 | + | ||
| 43 | + @Resource | ||
| 44 | + private IWechatPaymentService wechatPaymentService; | ||
| 45 | + | ||
| 46 | + @Resource | ||
| 47 | + private ITradeAssistantService tradeAssistantService; | ||
| 48 | + | ||
| 49 | + @Resource | ||
| 50 | + private ICashierPaymentService cashierPaymentService; | ||
| 51 | + | ||
| 52 | + @Resource | ||
| 53 | + private IPaymentPipelineManager paymentPipelineManager; | ||
| 54 | + | ||
| 55 | + @RequestMapping(value = "/payment/openId.do") | ||
| 56 | + public Message<?> openId(@RequestParam("pipelineId") Long pipelineId, @RequestParam("code") String code) { | ||
| 57 | + String openId = wechatPaymentService.openIdByCode(pipelineId, code); | ||
| 58 | + return Message.success(openId); | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + @RequestMapping(value = "/payment/deliver.do") | ||
| 62 | + public Message<?> deliverGoods(@RequestParam("paymentId") String paymentId, | ||
| 63 | + @RequestParam("logisticsType") Integer logisticsType) { | ||
| 64 | + OnlinePayment payment = tradeAssistantService.findByPaymentId(paymentId); | ||
| 65 | + if (!PaymentState.SUCCESS.equalTo(payment.getState())) { | ||
| 66 | + throw new BossServiceException(ErrorCode.INVALID_OBJECT_STATE, "微信订单未完成支付,不能进行发货操作"); | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + UploadShippingRequest request = UploadShippingRequest.of(payment.getOutTradeNo(), logisticsType, | ||
| 70 | + payment.getGoods(), payment.getPayerId()); | ||
| 71 | + wechatPaymentService.deliverGoods(payment.getPipelineId(), request); | ||
| 72 | + return Message.success(); | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + /** | ||
| 76 | + * 微信支付结果通知 | ||
| 77 | + */ | ||
| 78 | + @RequestMapping(value = "/payment/{paymentId}/notify.do") | ||
| 79 | + public ResponseEntity<NotifyResult> paymentNotify(HttpServletRequest request, @PathVariable("paymentId") String paymentId) { | ||
| 80 | + LOG.info("Receiving wechat payment result notify for {}", paymentId); | ||
| 81 | + String payload = HttpUtils.httpBody(request); | ||
| 82 | + | ||
| 83 | + try { | ||
| 84 | + OnlinePayment payment = tradeAssistantService.findByPaymentId(paymentId); | ||
| 85 | + WechatPipeline pipeline = paymentPipelineManager.findPipelineById(payment.getPipelineId(), WechatPipeline.class); | ||
| 86 | + if (dataVerify(request, pipeline, payload)) { | ||
| 87 | + WechatNotifyResponse response = JsonUtils.fromJsonString(payload, WechatNotifyResponse.class); | ||
| 88 | + OnlinePaymentResponse paymentResponse = paymentResponse(pipeline, response); | ||
| 89 | + if (WechatConstants.NOTIFY_EVENT_TYPE.equals(response.getEvent_type())) { | ||
| 90 | + cashierPaymentService.notifyPaymentResponse(paymentResponse); | ||
| 91 | + } | ||
| 92 | + return ResponseEntity.ok(NotifyResult.success()); | ||
| 93 | + } else { | ||
| 94 | + LOG.error("Wechat payment result notify data verify failed"); | ||
| 95 | + return ResponseEntity.badRequest().body(NotifyResult.failure("Data verify failed")); | ||
| 96 | + } | ||
| 97 | + } catch (Exception ex) { | ||
| 98 | + LOG.error("Process wechat payment result notify exception", ex); | ||
| 99 | + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) | ||
| 100 | + .body(NotifyResult.failure("Process wechat payment result notify exception")); | ||
| 101 | + } | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + /*@RequestMapping(value = "/refund/{refundId}/notify.do") | ||
| 105 | + public ResponseEntity<NotifyResult> refundNotify(HttpServletRequest request, @PathVariable("refundId") String refundId) { | ||
| 106 | + LOG.info("Receiving wechat refund result notify for {}", refundId); | ||
| 107 | + String payload = HttpUtils.httpBody(request); | ||
| 108 | + | ||
| 109 | + try { | ||
| 110 | + OnlinePayment refund = onlinePaymentService.findByRefundId(refundId); | ||
| 111 | + WechatPipeline pipeline = paymentPipelineManager.findPipelineById(refund.getPipelineId(), WechatPipeline.class); | ||
| 112 | + | ||
| 113 | + if (dataVerify(request, pipeline, payload)) { | ||
| 114 | + WechatNotifyResponse notifyResponse = JsonUtils.fromJsonString(payload, WechatNotifyResponse.class); | ||
| 115 | + if (WechatConstants.REFUND_EVENT_TYPE.equals(notifyResponse.getEvent_type())) { | ||
| 116 | + OnlineRefundResponse refundResponse = refundResponse(pipeline, notifyResponse); | ||
| 117 | + onlinePaymentService.notifyRefundResult(refundResponse); | ||
| 118 | + } | ||
| 119 | + return ResponseEntity.ok(NotifyResult.success()); | ||
| 120 | + } else { | ||
| 121 | + LOG.error("Wechat refund result notify data verify failed"); | ||
| 122 | + return ResponseEntity.badRequest().body(NotifyResult.failure("Data verify failed")); | ||
| 123 | + } | ||
| 124 | + } catch (Exception ex) { | ||
| 125 | + LOG.error("Process wechat refund result notify exception", ex); | ||
| 126 | + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) | ||
| 127 | + .body(NotifyResult.failure("Process wechat refund result notify exception")); | ||
| 128 | + } | ||
| 129 | + }*/ | ||
| 130 | + | ||
| 131 | + private OnlinePaymentResponse paymentResponse(WechatPipeline pipeline, WechatNotifyResponse notifyResponse) throws Exception { | ||
| 132 | + WechatNotifyResponse.Resource resource = notifyResponse.getResource(); | ||
| 133 | + String payload = WechatSignatureUtils.decrypt(resource.getCiphertext(), resource.getNonce(), | ||
| 134 | + resource.getAssociated_data(), pipeline.getClient().getWechatConfig().getApiV3Key()); | ||
| 135 | + if (pipeline instanceof WechatPartnerPipeline) { | ||
| 136 | + PartnerPaymentResponse response = JsonUtils.fromJsonString(payload, PartnerPaymentResponse.class); | ||
| 137 | + LocalDateTime when = DateUtils.parseDateTime(response.getSuccess_time(), WechatConstants.RFC3339_FORMAT); | ||
| 138 | + String payer = response.getPayer() == null ? null : response.getPayer().getSp_openid(); | ||
| 139 | + PaymentState paymentState = WechatStateUtils.getPaymentState(response.getTrade_state()); | ||
| 140 | + return new OnlinePaymentResponse(response.getOut_trade_no(), response.getTransaction_id(), OutPaymentType.WXPAY, | ||
| 141 | + payer, when, paymentState, response.getTrade_state_desc()); | ||
| 142 | + } else { | ||
| 143 | + DirectPaymentResponse response = JsonUtils.fromJsonString(payload, DirectPaymentResponse.class); | ||
| 144 | + LocalDateTime when = DateUtils.parseDateTime(response.getSuccess_time(), WechatConstants.RFC3339_FORMAT); | ||
| 145 | + String payer = response.getPayer() == null ? null : response.getPayer().getOpenid(); | ||
| 146 | + PaymentState paymentState = WechatStateUtils.getPaymentState(response.getTrade_state()); | ||
| 147 | + return new OnlinePaymentResponse(response.getOut_trade_no(), response.getTransaction_id(), OutPaymentType.WXPAY, | ||
| 148 | + payer, when, paymentState, response.getTrade_state_desc()); | ||
| 149 | + } | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + private OnlineRefundResponse refundResponse(WechatPipeline pipeline, WechatNotifyResponse notifyResponse) throws Exception { | ||
| 153 | + WechatNotifyResponse.Resource resource = notifyResponse.getResource(); | ||
| 154 | + String payload = WechatSignatureUtils.decrypt(resource.getCiphertext(), resource.getNonce(), | ||
| 155 | + resource.getAssociated_data(), pipeline.getClient().getWechatConfig().getApiV3Key()); | ||
| 156 | + RefundNotifyResponse response = JsonUtils.fromJsonString(payload, RefundNotifyResponse.class); | ||
| 157 | + LocalDateTime when = DateUtils.parseDateTime(response.getSuccess_time(), WechatConstants.RFC3339_FORMAT); | ||
| 158 | + PaymentState refundState = WechatStateUtils.getRefundState(response.getRefund_status()); | ||
| 159 | + return OnlineRefundResponse.of(response.getOut_refund_no(), response.getRefund_id(), when, | ||
| 160 | + refundState.getCode(), response.getRefund_status()); | ||
| 161 | + } | ||
| 162 | + | ||
| 163 | + private boolean dataVerify(HttpServletRequest request, WechatPipeline pipeline, String payload) { | ||
| 164 | + String serialNo = request.getHeader(WechatConstants.HEADER_SERIAL_NO); | ||
| 165 | + String timestamp = request.getHeader(WechatConstants.HEADER_TIMESTAMP); | ||
| 166 | + String nonce = request.getHeader(WechatConstants.HEADER_NONCE); | ||
| 167 | + String sign = request.getHeader(WechatConstants.HEADER_SIGNATURE); | ||
| 168 | + | ||
| 169 | + try { | ||
| 170 | + return pipeline.getClient().dataVerify(serialNo, timestamp, nonce, sign, payload); | ||
| 171 | + } catch (Exception ex) { | ||
| 172 | + LOG.error("Wechat result notify data verify failed", ex); | ||
| 173 | + return false; | ||
| 174 | + } | ||
| 175 | + } | ||
| 176 | + | ||
| 177 | + public static class NotifyResult { | ||
| 178 | + private String code; | ||
| 179 | + private String message; | ||
| 180 | + | ||
| 181 | + public static NotifyResult success() { | ||
| 182 | + NotifyResult result = new NotifyResult(); | ||
| 183 | + result.code = "SUCCESS"; | ||
| 184 | + result.message = "SUCCESS"; | ||
| 185 | + return result; | ||
| 186 | + } | ||
| 187 | + | ||
| 188 | + public static NotifyResult failure(String message) { | ||
| 189 | + NotifyResult result = new NotifyResult(); | ||
| 190 | + result.code = "FAILED"; | ||
| 191 | + result.message = message; | ||
| 192 | + return result; | ||
| 193 | + } | ||
| 194 | + | ||
| 195 | + public String getCode() { | ||
| 196 | + return code; | ||
| 197 | + } | ||
| 198 | + | ||
| 199 | + public String getMessage() { | ||
| 200 | + return message; | ||
| 201 | + } | ||
| 202 | + } | ||
| 203 | + | ||
| 204 | +} |
cashier-boss/src/main/java/com/diligrp/cashier/boss/domain/CashierOrderInfo.java
0 → 100644
| 1 | +package com.diligrp.cashier.boss.domain; | ||
| 2 | + | ||
| 3 | +import java.util.List; | ||
| 4 | + | ||
| 5 | +public class CashierOrderInfo { | ||
| 6 | + // 业务系统用户标识 | ||
| 7 | + private final String userId; | ||
| 8 | + // 支付通道 | ||
| 9 | + private final List<PaymentPipeline> pipelines; | ||
| 10 | + | ||
| 11 | + public CashierOrderInfo(String userId, List<PaymentPipeline> pipelines) { | ||
| 12 | + this.userId = userId; | ||
| 13 | + this.pipelines = pipelines; | ||
| 14 | + } | ||
| 15 | + | ||
| 16 | + public String getUserId() { | ||
| 17 | + return userId; | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + public List<PaymentPipeline> getPipelines() { | ||
| 21 | + return pipelines; | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + public static class PaymentPipeline { | ||
| 25 | + // 支付通道 | ||
| 26 | + private final Long pipelineId; | ||
| 27 | + // 支付渠道 | ||
| 28 | + private final Integer channelId; | ||
| 29 | + | ||
| 30 | + public PaymentPipeline(Long pipelineId, Integer channelId) { | ||
| 31 | + this.pipelineId = pipelineId; | ||
| 32 | + this.channelId = channelId; | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + public Long getPipelineId() { | ||
| 36 | + return pipelineId; | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + public Integer getChannelId() { | ||
| 40 | + return channelId; | ||
| 41 | + } | ||
| 42 | + } | ||
| 43 | +} |
cashier-boss/src/main/java/com/diligrp/cashier/boss/domain/CashierOrderToken.java
| @@ -60,7 +60,7 @@ public class CashierOrderToken { | @@ -60,7 +60,7 @@ public class CashierOrderToken { | ||
| 60 | return Base62Cipher.decodeLong(payload); | 60 | return Base62Cipher.decodeLong(payload); |
| 61 | } | 61 | } |
| 62 | 62 | ||
| 63 | - public static CashierOrderToken decode(String payload) { | 63 | + public static CashierOrderToken decodeCashierOrder(String payload) { |
| 64 | if (payload != null) { | 64 | if (payload != null) { |
| 65 | return JsonUtils.fromJsonString(payload, CashierOrderToken.class); | 65 | return JsonUtils.fromJsonString(payload, CashierOrderToken.class); |
| 66 | } | 66 | } |
cashier-boss/src/main/java/com/diligrp/cashier/boss/service/ICashierDeskService.java
| 1 | package com.diligrp.cashier.boss.service; | 1 | package com.diligrp.cashier.boss.service; |
| 2 | 2 | ||
| 3 | +import com.diligrp.cashier.boss.domain.CashierOrderInfo; | ||
| 3 | import com.diligrp.cashier.boss.domain.CashierPaymentUrl; | 4 | import com.diligrp.cashier.boss.domain.CashierPaymentUrl; |
| 4 | import com.diligrp.cashier.pipeline.domain.OnlinePaymentStatus; | 5 | 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; | 6 | +import com.diligrp.cashier.trade.domain.*; |
| 8 | 7 | ||
| 9 | public interface ICashierDeskService { | 8 | public interface ICashierDeskService { |
| 10 | /** | 9 | /** |
| @@ -17,10 +16,36 @@ public interface ICashierDeskService { | @@ -17,10 +16,36 @@ public interface ICashierDeskService { | ||
| 17 | CashierPaymentUrl doSubmit(Merchant merchant, CashierOrder order); | 16 | CashierPaymentUrl doSubmit(Merchant merchant, CashierOrder order); |
| 18 | 17 | ||
| 19 | /** | 18 | /** |
| 19 | + * 根据收银台TOKEN信息获取订单信息 | ||
| 20 | + */ | ||
| 21 | + CashierOrderInfo getCashierOrderByToken(String token); | ||
| 22 | + | ||
| 23 | + /** | ||
| 20 | * 提交收银台支付 | 24 | * 提交收银台支付 |
| 21 | * | 25 | * |
| 22 | * @param payment - 支付信息 | 26 | * @param payment - 支付信息 |
| 23 | * @return 支付状态 | 27 | * @return 支付状态 |
| 24 | */ | 28 | */ |
| 25 | OnlinePaymentStatus doPayment(CashierPayment payment); | 29 | OnlinePaymentStatus doPayment(CashierPayment payment); |
| 30 | + | ||
| 31 | + /** | ||
| 32 | + * 关闭预支付订单 | ||
| 33 | + */ | ||
| 34 | + void closePrepayOrder(String paymentId); | ||
| 35 | + | ||
| 36 | + /** | ||
| 37 | + * 查询支付状态 | ||
| 38 | + */ | ||
| 39 | + OnlinePaymentResult queryPaymentState(String paymentId, String mode); | ||
| 40 | + | ||
| 41 | + /** | ||
| 42 | + * 交易退款 | ||
| 43 | + */ | ||
| 44 | + OnlineRefundResult sendRefundRequest(OnlineRefundDTO request); | ||
| 45 | + | ||
| 46 | + /** | ||
| 47 | + * 查询退款状态 | ||
| 48 | + */ | ||
| 49 | + OnlineRefundResult queryRefundState(String refundId, String mode); | ||
| 50 | + | ||
| 26 | } | 51 | } |
cashier-boss/src/main/java/com/diligrp/cashier/boss/service/impl/CashierDeskServiceImpl.java
| @@ -2,19 +2,26 @@ package com.diligrp.cashier.boss.service.impl; | @@ -2,19 +2,26 @@ 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.CashierOrderInfo; | ||
| 5 | import com.diligrp.cashier.boss.domain.CashierOrderToken; | 6 | import com.diligrp.cashier.boss.domain.CashierOrderToken; |
| 6 | import com.diligrp.cashier.boss.domain.CashierPaymentUrl; | 7 | import com.diligrp.cashier.boss.domain.CashierPaymentUrl; |
| 8 | +import com.diligrp.cashier.boss.exception.BossServiceException; | ||
| 7 | import com.diligrp.cashier.boss.service.ICashierDeskService; | 9 | import com.diligrp.cashier.boss.service.ICashierDeskService; |
| 10 | +import com.diligrp.cashier.pipeline.core.PaymentPipeline; | ||
| 8 | import com.diligrp.cashier.pipeline.domain.OnlinePaymentStatus; | 11 | 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; | 12 | +import com.diligrp.cashier.pipeline.service.IPaymentPipelineManager; |
| 13 | +import com.diligrp.cashier.shared.ErrorCode; | ||
| 14 | +import com.diligrp.cashier.shared.util.ObjectUtils; | ||
| 15 | +import com.diligrp.cashier.trade.domain.*; | ||
| 16 | +import com.diligrp.cashier.trade.model.TradeOrder; | ||
| 13 | import com.diligrp.cashier.trade.service.ICashierPaymentService; | 17 | import com.diligrp.cashier.trade.service.ICashierPaymentService; |
| 18 | +import com.diligrp.cashier.trade.service.ITradeAssistantService; | ||
| 19 | +import com.diligrp.cashier.trade.type.TradeState; | ||
| 14 | import jakarta.annotation.Resource; | 20 | import jakarta.annotation.Resource; |
| 15 | import org.springframework.data.redis.core.StringRedisTemplate; | 21 | import org.springframework.data.redis.core.StringRedisTemplate; |
| 16 | import org.springframework.stereotype.Service; | 22 | import org.springframework.stereotype.Service; |
| 17 | 23 | ||
| 24 | +import java.util.List; | ||
| 18 | import java.util.concurrent.TimeUnit; | 25 | import java.util.concurrent.TimeUnit; |
| 19 | 26 | ||
| 20 | @Service("cashierDeskService") | 27 | @Service("cashierDeskService") |
| @@ -24,6 +31,12 @@ public class CashierDeskServiceImpl implements ICashierDeskService { | @@ -24,6 +31,12 @@ public class CashierDeskServiceImpl implements ICashierDeskService { | ||
| 24 | private ICashierPaymentService cashierPaymentService; | 31 | private ICashierPaymentService cashierPaymentService; |
| 25 | 32 | ||
| 26 | @Resource | 33 | @Resource |
| 34 | + private ITradeAssistantService tradeAssistantService; | ||
| 35 | + | ||
| 36 | + @Resource | ||
| 37 | + private IPaymentPipelineManager paymentPipelineManager; | ||
| 38 | + | ||
| 39 | + @Resource | ||
| 27 | private CashierDeskProperties cashierDeskProperties; | 40 | private CashierDeskProperties cashierDeskProperties; |
| 28 | 41 | ||
| 29 | @Resource | 42 | @Resource |
| @@ -54,6 +67,29 @@ public class CashierDeskServiceImpl implements ICashierDeskService { | @@ -54,6 +67,29 @@ public class CashierDeskServiceImpl implements ICashierDeskService { | ||
| 54 | return new CashierPaymentUrl(tradeId, paymentUrl); | 67 | return new CashierPaymentUrl(tradeId, paymentUrl); |
| 55 | } | 68 | } |
| 56 | 69 | ||
| 70 | + @Override | ||
| 71 | + public CashierOrderInfo getCashierOrderByToken(String token) { | ||
| 72 | + CashierOrderToken.decode(token, cashierDeskProperties.getSecretKey()); | ||
| 73 | + String tokenKey = String.format(Constants.TOKEN_REDIS_KEY, token); | ||
| 74 | + String payload = stringRedisTemplate.opsForValue().get(tokenKey); | ||
| 75 | + if (ObjectUtils.isEmpty(payload)) { | ||
| 76 | + throw new BossServiceException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "收银台订单超时过期,不能支付"); | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + CashierOrderToken orderToken = CashierOrderToken.decodeCashierOrder(payload); | ||
| 80 | + TradeOrder trade = tradeAssistantService.findByTradeId(orderToken.getTradeId()); | ||
| 81 | + if (TradeState.isFinished(trade.getState())) { | ||
| 82 | + throw new BossServiceException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "收银台订单已完成,不能进行支付"); | ||
| 83 | + } | ||
| 84 | + List<PaymentPipeline> pipelines = paymentPipelineManager.listPipelines(orderToken.getMchId(), PaymentPipeline.class); | ||
| 85 | + if (pipelines.isEmpty()) { | ||
| 86 | + throw new BossServiceException(ErrorCode.ILLEGAL_ARGUMENT_ERROR, "商户无可用的支付通道"); | ||
| 87 | + } | ||
| 88 | + List<CashierOrderInfo.PaymentPipeline> pipelineList = pipelines.stream().map(pipeline -> | ||
| 89 | + new CashierOrderInfo.PaymentPipeline(pipeline.pipelineId(), pipeline.supportedChannel().getCode())).toList(); | ||
| 90 | + return new CashierOrderInfo(orderToken.getUserId(), pipelineList); | ||
| 91 | + } | ||
| 92 | + | ||
| 57 | /** | 93 | /** |
| 58 | * 提交收银台支付 | 94 | * 提交收银台支付 |
| 59 | * | 95 | * |
| @@ -62,10 +98,31 @@ public class CashierDeskServiceImpl implements ICashierDeskService { | @@ -62,10 +98,31 @@ public class CashierDeskServiceImpl implements ICashierDeskService { | ||
| 62 | */ | 98 | */ |
| 63 | @Override | 99 | @Override |
| 64 | public OnlinePaymentStatus doPayment(CashierPayment payment) { | 100 | 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); | 101 | + OnlinePaymentStatus paymentStatus = cashierPaymentService.doPayment(payment); |
| 102 | + // 只要提交了收银台支付,无论是否支付成功,都使token失效,收银台页面将无法重新打开 | ||
| 103 | + String token = CashierOrderToken.encode(Long.valueOf(payment.getTradeId()), cashierDeskProperties.getSecretKey()); | ||
| 104 | + String tokenKey = String.format(Constants.TOKEN_REDIS_KEY, token); | ||
| 105 | + stringRedisTemplate.delete(tokenKey); | ||
| 106 | + return paymentStatus; | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + @Override | ||
| 110 | + public void closePrepayOrder(String paymentId) { | ||
| 111 | + cashierPaymentService.closePrepayOrder(paymentId); | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + @Override | ||
| 115 | + public OnlinePaymentResult queryPaymentState(String paymentId, String mode) { | ||
| 116 | + return cashierPaymentService.queryPaymentState(paymentId, mode); | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + @Override | ||
| 120 | + public OnlineRefundResult sendRefundRequest(OnlineRefundDTO request) { | ||
| 121 | + return cashierPaymentService.sendRefundRequest(request); | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + @Override | ||
| 125 | + public OnlineRefundResult queryRefundState(String refundId, String mode) { | ||
| 126 | + return cashierPaymentService.queryRefundState(refundId, mode); | ||
| 70 | } | 127 | } |
| 71 | } | 128 | } |
cashier-boss/src/main/java/com/diligrp/cashier/boss/util/HttpUtils.java
0 → 100644
| 1 | +package com.diligrp.cashier.boss.util; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.boss.Constants; | ||
| 4 | +import jakarta.servlet.http.HttpServletRequest; | ||
| 5 | +import jakarta.servlet.http.HttpServletResponse; | ||
| 6 | +import org.slf4j.Logger; | ||
| 7 | +import org.slf4j.LoggerFactory; | ||
| 8 | + | ||
| 9 | +import java.io.BufferedReader; | ||
| 10 | +import java.io.IOException; | ||
| 11 | +import java.nio.charset.StandardCharsets; | ||
| 12 | + | ||
| 13 | +/** | ||
| 14 | + * HTTP工具类 | ||
| 15 | + */ | ||
| 16 | +public final class HttpUtils { | ||
| 17 | + | ||
| 18 | + private static final Logger LOG = LoggerFactory.getLogger(HttpUtils.class); | ||
| 19 | + | ||
| 20 | + public static String httpBody(HttpServletRequest request) { | ||
| 21 | + StringBuilder payload = new StringBuilder(); | ||
| 22 | + try { | ||
| 23 | + String line; | ||
| 24 | + BufferedReader reader = request.getReader(); | ||
| 25 | + while ((line = reader.readLine()) != null) { | ||
| 26 | + payload.append(line); | ||
| 27 | + } | ||
| 28 | + } catch (IOException iex) { | ||
| 29 | + LOG.error("Failed to extract http body", iex); | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + return payload.toString(); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + public static void sendResponse(HttpServletResponse response, String payload) { | ||
| 36 | + try { | ||
| 37 | + response.setContentType(Constants.CONTENT_TYPE); | ||
| 38 | + byte[] responseBytes = payload.getBytes(StandardCharsets.UTF_8); | ||
| 39 | + response.setContentLength(responseBytes.length); | ||
| 40 | + response.getOutputStream().write(responseBytes); | ||
| 41 | + response.flushBuffer(); | ||
| 42 | + } catch (IOException iex) { | ||
| 43 | + LOG.error("Failed to write data packet back"); | ||
| 44 | + } | ||
| 45 | + } | ||
| 46 | +} |
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/client/WechatDirectHttpClient.java
| @@ -4,6 +4,7 @@ import com.diligrp.cashier.pipeline.domain.*; | @@ -4,6 +4,7 @@ import com.diligrp.cashier.pipeline.domain.*; | ||
| 4 | import com.diligrp.cashier.pipeline.domain.wechat.ErrorMessage; | 4 | import com.diligrp.cashier.pipeline.domain.wechat.ErrorMessage; |
| 5 | import com.diligrp.cashier.pipeline.domain.wechat.WechatConfig; | 5 | import com.diligrp.cashier.pipeline.domain.wechat.WechatConfig; |
| 6 | import com.diligrp.cashier.pipeline.exception.PaymentPipelineException; | 6 | import com.diligrp.cashier.pipeline.exception.PaymentPipelineException; |
| 7 | +import com.diligrp.cashier.pipeline.type.OutPaymentType; | ||
| 7 | import com.diligrp.cashier.pipeline.type.PaymentState; | 8 | import com.diligrp.cashier.pipeline.type.PaymentState; |
| 8 | import com.diligrp.cashier.pipeline.util.WechatConstants; | 9 | import com.diligrp.cashier.pipeline.util.WechatConstants; |
| 9 | import com.diligrp.cashier.pipeline.util.WechatSignatureUtils; | 10 | import com.diligrp.cashier.pipeline.util.WechatSignatureUtils; |
| @@ -130,8 +131,8 @@ public class WechatDirectHttpClient extends WechatHttpClient { | @@ -130,8 +131,8 @@ public class WechatDirectHttpClient extends WechatHttpClient { | ||
| 130 | Map<String, Object> payer = (Map<String, Object>) response.get("payer"); | 131 | Map<String, Object> payer = (Map<String, Object>) response.get("payer"); |
| 131 | String openId = payer == null ? null : (String) payer.get("openid"); | 132 | String openId = payer == null ? null : (String) payer.get("openid"); |
| 132 | PaymentState state = WechatStateUtils.getPaymentState((String) response.get("trade_state")); | 133 | PaymentState state = WechatStateUtils.getPaymentState((String) response.get("trade_state")); |
| 133 | - return OnlinePaymentResponse.of((String) response.get("out_trade_no"), (String) response.get("transaction_id"), | ||
| 134 | - openId, when, state, (String) response.get("trade_state_desc")); | 134 | + return new OnlinePaymentResponse((String) response.get("out_trade_no"), (String) response.get("transaction_id"), |
| 135 | + OutPaymentType.WXPAY, openId, when, state, (String) response.get("trade_state_desc")); | ||
| 135 | } else { | 136 | } else { |
| 136 | LOG.info("Wechat query transaction status failed: {}", result.statusCode); | 137 | LOG.info("Wechat query transaction status failed: {}", result.statusCode); |
| 137 | ErrorMessage message = JsonUtils.fromJsonString(result.responseText, ErrorMessage.class); | 138 | ErrorMessage message = JsonUtils.fromJsonString(result.responseText, ErrorMessage.class); |
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/client/WechatPartnerHttpClient.java
| @@ -5,6 +5,7 @@ import com.diligrp.cashier.pipeline.domain.*; | @@ -5,6 +5,7 @@ import com.diligrp.cashier.pipeline.domain.*; | ||
| 5 | import com.diligrp.cashier.pipeline.domain.wechat.ErrorMessage; | 5 | import com.diligrp.cashier.pipeline.domain.wechat.ErrorMessage; |
| 6 | import com.diligrp.cashier.pipeline.domain.wechat.WechatConfig; | 6 | import com.diligrp.cashier.pipeline.domain.wechat.WechatConfig; |
| 7 | import com.diligrp.cashier.pipeline.exception.PaymentPipelineException; | 7 | import com.diligrp.cashier.pipeline.exception.PaymentPipelineException; |
| 8 | +import com.diligrp.cashier.pipeline.type.OutPaymentType; | ||
| 8 | import com.diligrp.cashier.pipeline.type.PaymentState; | 9 | import com.diligrp.cashier.pipeline.type.PaymentState; |
| 9 | import com.diligrp.cashier.pipeline.util.WechatConstants; | 10 | import com.diligrp.cashier.pipeline.util.WechatConstants; |
| 10 | import com.diligrp.cashier.pipeline.util.WechatSignatureUtils; | 11 | import com.diligrp.cashier.pipeline.util.WechatSignatureUtils; |
| @@ -134,8 +135,8 @@ public class WechatPartnerHttpClient extends WechatHttpClient { | @@ -134,8 +135,8 @@ public class WechatPartnerHttpClient extends WechatHttpClient { | ||
| 134 | Map<String, Object> payer = (Map<String, Object>) response.get("payer"); | 135 | Map<String, Object> payer = (Map<String, Object>) response.get("payer"); |
| 135 | String openId = payer == null ? null : (String) payer.get("sp_openid"); // 获取服务商APPID下的openId,而非子商户APPID下的openId | 136 | String openId = payer == null ? null : (String) payer.get("sp_openid"); // 获取服务商APPID下的openId,而非子商户APPID下的openId |
| 136 | PaymentState state = WechatStateUtils.getPaymentState((String) response.get("trade_state")); | 137 | PaymentState state = WechatStateUtils.getPaymentState((String) response.get("trade_state")); |
| 137 | - return OnlinePaymentResponse.of((String) response.get("out_trade_no"), (String) response.get("transaction_id"), | ||
| 138 | - openId, when, state, (String) response.get("trade_state_desc")); | 138 | + return new OnlinePaymentResponse((String) response.get("out_trade_no"), (String) response.get("transaction_id"), |
| 139 | + OutPaymentType.WXPAY, openId, when, state, (String) response.get("trade_state_desc")); | ||
| 139 | } else { | 140 | } else { |
| 140 | LOG.info("Wechat query transaction status failed: {}", result.statusCode); | 141 | LOG.info("Wechat query transaction status failed: {}", result.statusCode); |
| 141 | ErrorMessage message = JsonUtils.fromJsonString(result.responseText, ErrorMessage.class); | 142 | ErrorMessage message = JsonUtils.fromJsonString(result.responseText, ErrorMessage.class); |
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/core/DiliCardPipeline.java
| @@ -15,7 +15,7 @@ import org.slf4j.LoggerFactory; | @@ -15,7 +15,7 @@ import org.slf4j.LoggerFactory; | ||
| 15 | /** | 15 | /** |
| 16 | * 地利园区卡支付通道模型 | 16 | * 地利园区卡支付通道模型 |
| 17 | */ | 17 | */ |
| 18 | -public class DiliCardPipeline extends OnlinePipeline<DiliCardPipeline.CardParams> { | 18 | +public class DiliCardPipeline extends PaymentPipeline<DiliCardPipeline.CardParams> { |
| 19 | 19 | ||
| 20 | private static final Logger LOG = LoggerFactory.getLogger(DiliCardPipeline.class); | 20 | private static final Logger LOG = LoggerFactory.getLogger(DiliCardPipeline.class); |
| 21 | 21 |
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/domain/OnlinePaymentResponse.java
| @@ -18,21 +18,6 @@ public class OnlinePaymentResponse extends OnlinePaymentStatus { | @@ -18,21 +18,6 @@ public class OnlinePaymentResponse extends OnlinePaymentStatus { | ||
| 18 | // 交易备注 | 18 | // 交易备注 |
| 19 | private final String message; | 19 | private final String message; |
| 20 | 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 | - } | ||
| 25 | - | ||
| 26 | - public static OnlinePaymentResponse of(String paymentId, String outTradeNo, String payerId, | ||
| 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 | - } | ||
| 35 | - | ||
| 36 | public OnlinePaymentResponse(String paymentId, String outTradeNo, OutPaymentType outPayType, | 21 | public OnlinePaymentResponse(String paymentId, String outTradeNo, OutPaymentType outPayType, |
| 37 | String payerId, LocalDateTime when, PaymentState state, String message) { | 22 | String payerId, LocalDateTime when, PaymentState state, String message) { |
| 38 | super(paymentId, outTradeNo, state); | 23 | super(paymentId, outTradeNo, state); |
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/domain/card/CardPaymentResponse.java
| @@ -8,7 +8,8 @@ import java.time.LocalDateTime; | @@ -8,7 +8,8 @@ import java.time.LocalDateTime; | ||
| 8 | 8 | ||
| 9 | public class CardPaymentResponse extends OnlinePaymentResponse { | 9 | public class CardPaymentResponse extends OnlinePaymentResponse { |
| 10 | 10 | ||
| 11 | - public CardPaymentResponse(String paymentId, String outTradeNo, OutPaymentType outPayType, String payerId, LocalDateTime when, PaymentState state, String message) { | 11 | + public CardPaymentResponse(String paymentId, String outTradeNo, OutPaymentType outPayType, String payerId, |
| 12 | + LocalDateTime when, PaymentState state, String message) { | ||
| 12 | super(paymentId, outTradeNo, outPayType, payerId, when, state, message); | 13 | super(paymentId, outTradeNo, outPayType, payerId, when, state, message); |
| 13 | } | 14 | } |
| 14 | } | 15 | } |
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/service/IWechatPaymentService.java
0 → 100644
| 1 | +package com.diligrp.cashier.pipeline.service; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.pipeline.domain.wechat.UploadShippingRequest; | ||
| 4 | + | ||
| 5 | +public interface IWechatPaymentService { | ||
| 6 | + /** | ||
| 7 | + * 根据登陆凭证code获取openId | ||
| 8 | + */ | ||
| 9 | + String openIdByCode(Long pipelineId, String code); | ||
| 10 | + | ||
| 11 | + /** | ||
| 12 | + * 通知微信发货 | ||
| 13 | + * 服务商模式下,解决买家微信付款成功,卖家微信商户号无法收到钱,需要去自己的微信后台操作一下【发货】 | ||
| 14 | + */ | ||
| 15 | + void deliverGoods(Long pipelineId, UploadShippingRequest request); | ||
| 16 | +} |
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/service/impl/WechatPaymentServiceImpl.java
0 → 100644
| 1 | +package com.diligrp.cashier.pipeline.service.impl; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.pipeline.client.WechatHttpClient; | ||
| 4 | +import com.diligrp.cashier.pipeline.core.WechatPartnerPipeline; | ||
| 5 | +import com.diligrp.cashier.pipeline.core.WechatPipeline; | ||
| 6 | +import com.diligrp.cashier.pipeline.domain.wechat.UploadShippingRequest; | ||
| 7 | +import com.diligrp.cashier.pipeline.domain.wechat.WechatAccessToken; | ||
| 8 | +import com.diligrp.cashier.pipeline.service.IPaymentPipelineManager; | ||
| 9 | +import com.diligrp.cashier.pipeline.service.IWechatPaymentService; | ||
| 10 | +import jakarta.annotation.Resource; | ||
| 11 | +import org.springframework.stereotype.Service; | ||
| 12 | + | ||
| 13 | +@Service("wechatPaymentService") | ||
| 14 | +public class WechatPaymentServiceImpl implements IWechatPaymentService { | ||
| 15 | + | ||
| 16 | + @Resource | ||
| 17 | + private IPaymentPipelineManager paymentPipelineManager; | ||
| 18 | + | ||
| 19 | + /** | ||
| 20 | + * 根据登陆凭证code获取openId | ||
| 21 | + */ | ||
| 22 | + @Override | ||
| 23 | + public String openIdByCode(Long pipelineId, String code) { | ||
| 24 | + WechatPipeline pipeline = paymentPipelineManager.findPipelineById(pipelineId, WechatPipeline.class); | ||
| 25 | + return pipeline.getClient().loginAuthorization(code); | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + /** | ||
| 29 | + * 通知微信发货 | ||
| 30 | + * 服务商模式下,解决买家微信付款成功,卖家微信商户号无法收到钱,需要去自己的微信后台操作一下【发货】 | ||
| 31 | + */ | ||
| 32 | + @Override | ||
| 33 | + public void deliverGoods(Long pipelineId, UploadShippingRequest request) { | ||
| 34 | + WechatPartnerPipeline pipeline = paymentPipelineManager.findPipelineById(pipelineId, WechatPartnerPipeline.class); | ||
| 35 | + // 获取微信接口登录凭证,并调用微信发货信息录入接口 | ||
| 36 | + WechatHttpClient client = pipeline.getClient(); | ||
| 37 | + WechatAccessToken accessToken = client.getAccessToken(); | ||
| 38 | + client.sendUploadShippingRequest(request, accessToken.getToken()); | ||
| 39 | + } | ||
| 40 | +} |
cashier-pipeline/src/main/java/com/diligrp/cashier/pipeline/type/PaymentState.java
| @@ -46,14 +46,6 @@ public enum PaymentState implements IEnumType { | @@ -46,14 +46,6 @@ public enum PaymentState implements IEnumType { | ||
| 46 | return Arrays.asList(PaymentState.values()); | 46 | return Arrays.asList(PaymentState.values()); |
| 47 | } | 47 | } |
| 48 | 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) { | 49 | public static boolean isFinished(int code) { |
| 58 | return PaymentState.SUCCESS.equalTo(code) || PaymentState.FAILED.equalTo(code); | 50 | return PaymentState.SUCCESS.equalTo(code) || PaymentState.FAILED.equalTo(code); |
| 59 | } | 51 | } |
cashier-shared/src/main/java/com/diligrp/cashier/shared/spi/IPaymentEventListener.java
| 1 | package com.diligrp.cashier.shared.spi; | 1 | package com.diligrp.cashier.shared.spi; |
| 2 | 2 | ||
| 3 | -@FunctionalInterface | ||
| 4 | public interface IPaymentEventListener { | 3 | public interface IPaymentEventListener { |
| 5 | 4 | ||
| 6 | void onEvent(PaymentEvent event); | 5 | void onEvent(PaymentEvent event); |
| 6 | + | ||
| 7 | + void onEvent(RefundEvent event); | ||
| 7 | } | 8 | } |
cashier-shared/src/main/java/com/diligrp/cashier/shared/spi/PaymentEvent.java
| @@ -3,8 +3,10 @@ package com.diligrp.cashier.shared.spi; | @@ -3,8 +3,10 @@ package com.diligrp.cashier.shared.spi; | ||
| 3 | import java.time.LocalDateTime; | 3 | import java.time.LocalDateTime; |
| 4 | 4 | ||
| 5 | public class PaymentEvent { | 5 | public class PaymentEvent { |
| 6 | - // 支付ID | 6 | + // 交易号 |
| 7 | private final String tradeId; | 7 | private final String tradeId; |
| 8 | + // 支付ID | ||
| 9 | + private final String paymentId; | ||
| 8 | // 支付状态 | 10 | // 支付状态 |
| 9 | private final Integer state; | 11 | private final Integer state; |
| 10 | // 业务系统订单号 | 12 | // 业务系统订单号 |
| @@ -18,8 +20,10 @@ public class PaymentEvent { | @@ -18,8 +20,10 @@ public class PaymentEvent { | ||
| 18 | // 交易描述 | 20 | // 交易描述 |
| 19 | private final String message; | 21 | private final String message; |
| 20 | 22 | ||
| 21 | - public PaymentEvent(String tradeId, int state, String outTradeNo, Integer outPayType, String payerId, LocalDateTime when, String message) { | 23 | + public PaymentEvent(String tradeId, String paymentId, int state, String outTradeNo, Integer outPayType, |
| 24 | + String payerId, LocalDateTime when, String message) { | ||
| 22 | this.tradeId = tradeId; | 25 | this.tradeId = tradeId; |
| 26 | + this.paymentId = paymentId; | ||
| 23 | this.state = state; | 27 | this.state = state; |
| 24 | this.outTradeNo = outTradeNo; | 28 | this.outTradeNo = outTradeNo; |
| 25 | this.outPayType = outPayType; | 29 | this.outPayType = outPayType; |
| @@ -32,6 +36,10 @@ public class PaymentEvent { | @@ -32,6 +36,10 @@ public class PaymentEvent { | ||
| 32 | return tradeId; | 36 | return tradeId; |
| 33 | } | 37 | } |
| 34 | 38 | ||
| 39 | + public String getPaymentId() { | ||
| 40 | + return paymentId; | ||
| 41 | + } | ||
| 42 | + | ||
| 35 | public Integer getState() { | 43 | public Integer getState() { |
| 36 | return state; | 44 | return state; |
| 37 | } | 45 | } |
cashier-shared/src/main/java/com/diligrp/cashier/shared/spi/RefundEvent.java
0 → 100644
| 1 | +package com.diligrp.cashier.shared.spi; | ||
| 2 | + | ||
| 3 | +import java.time.LocalDateTime; | ||
| 4 | + | ||
| 5 | +public class RefundEvent { | ||
| 6 | + // 退款单号 | ||
| 7 | + private final String refundId; | ||
| 8 | + // 原支付ID | ||
| 9 | + private final String tradeId; | ||
| 10 | + // 支付状态 | ||
| 11 | + private final Integer state; | ||
| 12 | + // 发生时间 | ||
| 13 | + private final LocalDateTime when; | ||
| 14 | + // 交易描述 | ||
| 15 | + private final String message; | ||
| 16 | + | ||
| 17 | + public RefundEvent(String refundId, String tradeId, int state, LocalDateTime when, String message) { | ||
| 18 | + this.refundId = refundId; | ||
| 19 | + this.tradeId = tradeId; | ||
| 20 | + this.state = state; | ||
| 21 | + this.when = when; | ||
| 22 | + this.message = message; | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + public String getRefundId() { | ||
| 26 | + return refundId; | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + public String getTradeId() { | ||
| 30 | + return tradeId; | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + public Integer getState() { | ||
| 34 | + return state; | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + public LocalDateTime getWhen() { | ||
| 38 | + return when; | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + public String getMessage() { | ||
| 42 | + return message; | ||
| 43 | + } | ||
| 44 | +} |
cashier-trade/src/main/java/com/diligrp/cashier/trade/Constants.java
| @@ -2,6 +2,15 @@ package com.diligrp.cashier.trade; | @@ -2,6 +2,15 @@ package com.diligrp.cashier.trade; | ||
| 2 | 2 | ||
| 3 | public final class Constants { | 3 | public final class Constants { |
| 4 | 4 | ||
| 5 | + // 支付通道延时队列 | ||
| 6 | + public static final String PAYMENT_DELAY_QUEUE = "cashier.payment.delayQueue"; | ||
| 7 | + | ||
| 8 | + // 支付通道延时交换机 | ||
| 9 | + public static final String PAYMENT_DELAY_EXCHANGE = "cashier.payment.delayExchange"; | ||
| 10 | + | ||
| 11 | + // 支付通道延时路由KEY | ||
| 12 | + public static final String PAYMENT_DELAY_KEY = "cashier.payment.delayKey"; | ||
| 13 | + | ||
| 5 | // 默认订单超时时间-秒, 十分钟 | 14 | // 默认订单超时时间-秒, 十分钟 |
| 6 | public static final int DEFAULT_ORDER_TIMEOUT_SECONDS = 10 * 60 * 1000; | 15 | public static final int DEFAULT_ORDER_TIMEOUT_SECONDS = 10 * 60 * 1000; |
| 7 | 16 | ||
| @@ -14,10 +23,4 @@ public final class Constants { | @@ -14,10 +23,4 @@ public final class Constants { | ||
| 14 | // 支付订单分布式锁超时时长-秒 | 23 | // 支付订单分布式锁超时时长-秒 |
| 15 | public static final int TRADE_LOCK_TIMEOUT_SECONDS = 15 * 1000; | 24 | public static final int TRADE_LOCK_TIMEOUT_SECONDS = 15 * 1000; |
| 16 | 25 | ||
| 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 | } | 26 | } |
cashier-trade/src/main/java/com/diligrp/cashier/trade/TradeConfiguration.java
| @@ -2,11 +2,45 @@ package com.diligrp.cashier.trade; | @@ -2,11 +2,45 @@ package com.diligrp.cashier.trade; | ||
| 2 | 2 | ||
| 3 | import com.diligrp.cashier.shared.mybatis.MybatisMapperSupport; | 3 | import com.diligrp.cashier.shared.mybatis.MybatisMapperSupport; |
| 4 | import org.mybatis.spring.annotation.MapperScan; | 4 | import org.mybatis.spring.annotation.MapperScan; |
| 5 | +import org.springframework.amqp.core.Binding; | ||
| 6 | +import org.springframework.amqp.core.BindingBuilder; | ||
| 7 | +import org.springframework.amqp.core.CustomExchange; | ||
| 8 | +import org.springframework.amqp.core.Queue; | ||
| 9 | +import org.springframework.context.annotation.Bean; | ||
| 5 | import org.springframework.context.annotation.ComponentScan; | 10 | import org.springframework.context.annotation.ComponentScan; |
| 6 | import org.springframework.context.annotation.Configuration; | 11 | import org.springframework.context.annotation.Configuration; |
| 7 | 12 | ||
| 13 | +import java.util.HashMap; | ||
| 14 | +import java.util.Map; | ||
| 15 | + | ||
| 8 | @Configuration | 16 | @Configuration |
| 9 | @ComponentScan("com.diligrp.cashier.trade") | 17 | @ComponentScan("com.diligrp.cashier.trade") |
| 10 | @MapperScan(basePackages = {"com.diligrp.cashier.trade.dao"}, markerInterface = MybatisMapperSupport.class) | 18 | @MapperScan(basePackages = {"com.diligrp.cashier.trade.dao"}, markerInterface = MybatisMapperSupport.class) |
| 11 | public class TradeConfiguration { | 19 | public class TradeConfiguration { |
| 20 | + /** | ||
| 21 | + * 支付通道MQ延时队列 | ||
| 22 | + * 队列为持久化、非独占式且不自动删除的队列, 利用RabbitMQ延时插件实现延时功能https://github.com/rabbitmq/rabbitmq-delayed-message-exchange | ||
| 23 | + */ | ||
| 24 | + @Bean | ||
| 25 | + public Queue paymentDelayQueue() { | ||
| 26 | + return new Queue(Constants.PAYMENT_DELAY_QUEUE, true, false, false); | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + /** | ||
| 30 | + * 支付通道MQ延时交换机 | ||
| 31 | + */ | ||
| 32 | + @Bean | ||
| 33 | + public CustomExchange paymentDelayExchange() { | ||
| 34 | + Map<String, Object> arguments = new HashMap<>(); | ||
| 35 | + arguments.put("x-delayed-type", "direct"); | ||
| 36 | + return new CustomExchange(Constants.PAYMENT_DELAY_EXCHANGE, "x-delayed-message", true, false, arguments); | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + /** | ||
| 40 | + * 支付通道MQ延时队列和交换机的绑定 | ||
| 41 | + */ | ||
| 42 | + @Bean | ||
| 43 | + public Binding paymentDelayBinding() { | ||
| 44 | + return BindingBuilder.bind(paymentDelayQueue()).to(paymentDelayExchange()).with(Constants.PAYMENT_DELAY_KEY).noargs(); | ||
| 45 | + } | ||
| 12 | } | 46 | } |
cashier-trade/src/main/java/com/diligrp/cashier/trade/dao/IOnlinePaymentDao.java
| @@ -21,5 +21,6 @@ public interface IOnlinePaymentDao extends MybatisMapperSupport { | @@ -21,5 +21,6 @@ public interface IOnlinePaymentDao extends MybatisMapperSupport { | ||
| 21 | 21 | ||
| 22 | int compareAndSetState(PaymentStateDTO paymentDTO); | 22 | int compareAndSetState(PaymentStateDTO paymentDTO); |
| 23 | 23 | ||
| 24 | - List<OnlinePayment> findByTradeId(@Param("tradeId") String tradeId, @Param("state") Integer state); | 24 | + List<OnlinePayment> listOnlinePayments(@Param("tradeId") String tradeId, @Param("type") Integer type, |
| 25 | + @Param("state") Integer state); | ||
| 25 | } | 26 | } |
cashier-trade/src/main/java/com/diligrp/cashier/trade/domain/OnlinePaymentResult.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 | +import com.diligrp.cashier.shared.spi.PaymentEvent; | ||
| 6 | + | ||
| 7 | +import java.time.LocalDateTime; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * 在线支付结果 - 用于业务系统支付结果通知 | ||
| 11 | + */ | ||
| 12 | +public class OnlinePaymentResult extends PaymentEvent { | ||
| 13 | + public static OnlinePaymentResult of(String tradeId, String paymentId, PaymentState state, String outTradeNo, | ||
| 14 | + OutPaymentType outPayType, String payerId, LocalDateTime when, String message) { | ||
| 15 | + Integer outPayTypeCode = outPayType != null ? outPayType.getCode() : null; | ||
| 16 | + return new OnlinePaymentResult(tradeId, paymentId, state.getCode(), outTradeNo, outPayTypeCode, payerId, when, message); | ||
| 17 | + } | ||
| 18 | + | ||
| 19 | + public OnlinePaymentResult(String tradeId, String paymentId, int state, String outTradeNo, Integer outPayType, | ||
| 20 | + String payerId, LocalDateTime when, String message) { | ||
| 21 | + super(tradeId, paymentId, state, outTradeNo, outPayType, payerId, when, message); | ||
| 22 | + } | ||
| 23 | +} |
cashier-trade/src/main/java/com/diligrp/cashier/trade/domain/OnlineRefundDTO.java
0 → 100644
| 1 | +package com.diligrp.cashier.trade.domain; | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * 退款申请 | ||
| 5 | + */ | ||
| 6 | +public class OnlineRefundDTO { | ||
| 7 | + // 原支付号 | ||
| 8 | + private String tradeId; | ||
| 9 | + // 退款金额 | ||
| 10 | + private Long amount; | ||
| 11 | + // 回调地址 | ||
| 12 | + private String notifyUri; | ||
| 13 | + // 退款原因 | ||
| 14 | + private String description; | ||
| 15 | + | ||
| 16 | + public String getTradeId() { | ||
| 17 | + return tradeId; | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + public void setTradeId(String tradeId) { | ||
| 21 | + this.tradeId = tradeId; | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + public Long getAmount() { | ||
| 25 | + return amount; | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + public void setAmount(Long amount) { | ||
| 29 | + this.amount = amount; | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + public String getNotifyUri() { | ||
| 33 | + return notifyUri; | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + public void setNotifyUri(String notifyUri) { | ||
| 37 | + this.notifyUri = notifyUri; | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + public String getDescription() { | ||
| 41 | + return description; | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + public void setDescription(String description) { | ||
| 45 | + this.description = description; | ||
| 46 | + } | ||
| 47 | +} |
cashier-trade/src/main/java/com/diligrp/cashier/trade/domain/OnlineRefundResult.java
0 → 100644
| 1 | +package com.diligrp.cashier.trade.domain; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.spi.RefundEvent; | ||
| 4 | + | ||
| 5 | +import java.time.LocalDateTime; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * 退款结果 | ||
| 9 | + */ | ||
| 10 | +public class OnlineRefundResult extends RefundEvent { | ||
| 11 | + public OnlineRefundResult(String refundId, String tradeId, int state, LocalDateTime when, String message) { | ||
| 12 | + super(refundId, tradeId, state, when, message); | ||
| 13 | + } | ||
| 14 | +} |
cashier-trade/src/main/java/com/diligrp/cashier/trade/domain/PaymentStateDTO.java
| @@ -116,7 +116,7 @@ public class PaymentStateDTO { | @@ -116,7 +116,7 @@ public class PaymentStateDTO { | ||
| 116 | } | 116 | } |
| 117 | 117 | ||
| 118 | public Builder outPayType(OutPaymentType outPayType) { | 118 | public Builder outPayType(OutPaymentType outPayType) { |
| 119 | - PaymentStateDTO.this.outPayType = outPayType.getCode(); | 119 | + PaymentStateDTO.this.outPayType = outPayType != null ? outPayType.getCode() : null; |
| 120 | return this; | 120 | return this; |
| 121 | } | 121 | } |
| 122 | 122 | ||
| @@ -135,6 +135,11 @@ public class PaymentStateDTO { | @@ -135,6 +135,11 @@ public class PaymentStateDTO { | ||
| 135 | return this; | 135 | return this; |
| 136 | } | 136 | } |
| 137 | 137 | ||
| 138 | + public Builder state(Integer state) { | ||
| 139 | + PaymentStateDTO.this.state = state; | ||
| 140 | + return this; | ||
| 141 | + } | ||
| 142 | + | ||
| 138 | public Builder description(String description) { | 143 | public Builder description(String description) { |
| 139 | PaymentStateDTO.this.description = description; | 144 | PaymentStateDTO.this.description = description; |
| 140 | return this; | 145 | return this; |
cashier-trade/src/main/java/com/diligrp/cashier/trade/domain/TaskMessage.java
0 → 100644
| 1 | +package com.diligrp.cashier.trade.domain; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.util.JsonUtils; | ||
| 4 | + | ||
| 5 | +/** | ||
| 6 | + * 异步消息模型 | ||
| 7 | + */ | ||
| 8 | +public class TaskMessage { | ||
| 9 | + // 收银台订单扫描,在固定周期后兜底处理预支付订单; | ||
| 10 | + // 查询交易订单的支付状态,根据支付状态完成交易订单,或关闭交易订单 | ||
| 11 | + public static final int TYPE_CASHIER_ORDER_SCAN = 10; | ||
| 12 | + // 周期性查询退款状态 | ||
| 13 | + public static final int TYPE_CASHIER_REFUND_SCAN = 20; | ||
| 14 | + | ||
| 15 | + // 消息类型 | ||
| 16 | + private int type; | ||
| 17 | + // 消息体 | ||
| 18 | + private String payload; | ||
| 19 | + // 消息参数 | ||
| 20 | + private String params; | ||
| 21 | + | ||
| 22 | + public TaskMessage() { | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + public TaskMessage(int type, String payload, String params) { | ||
| 26 | + this.type = type; | ||
| 27 | + this.payload = payload; | ||
| 28 | + this.params = params; | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + | ||
| 32 | + public int getType() { | ||
| 33 | + return type; | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + public void setType(int type) { | ||
| 37 | + this.type = type; | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + public String getPayload() { | ||
| 41 | + return payload; | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + public void setPayload(String payload) { | ||
| 45 | + this.payload = payload; | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + public String getParams() { | ||
| 49 | + return params; | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + public void setParams(String params) { | ||
| 53 | + this.params = params; | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + public static TaskMessage of(int type, String payload) { | ||
| 57 | + return of(type, payload, null); | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + public static TaskMessage of(int type, String payload, String params) { | ||
| 61 | + return new TaskMessage(type, payload, params); | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + public static TaskMessage fromJson(String json) { | ||
| 65 | + return JsonUtils.fromJsonString(json, TaskMessage.class); | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + @Override | ||
| 69 | + public String toString() { | ||
| 70 | + return JsonUtils.toJsonString(this); | ||
| 71 | + } | ||
| 72 | +} |
cashier-trade/src/main/java/com/diligrp/cashier/trade/domain/TradePaymentResult.java deleted
100644 → 0
| 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
| @@ -19,6 +19,22 @@ public class TradeStateDTO { | @@ -19,6 +19,22 @@ public class TradeStateDTO { | ||
| 19 | // 修改时间 | 19 | // 修改时间 |
| 20 | private LocalDateTime modifiedTime; | 20 | private LocalDateTime modifiedTime; |
| 21 | 21 | ||
| 22 | + public static TradeStateDTO of(String tradeId, TradeState state, Integer version, LocalDateTime modifiedTime) { | ||
| 23 | + return new TradeStateDTO(tradeId, null, state.getCode(), version, modifiedTime); | ||
| 24 | + } | ||
| 25 | + | ||
| 26 | + public static TradeStateDTO of(String tradeId, Long amount, TradeState state, Integer version, LocalDateTime modifiedTime) { | ||
| 27 | + return new TradeStateDTO(tradeId, amount, state.getCode(), version, modifiedTime); | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + public TradeStateDTO(String tradeId, Long amount, Integer state, Integer version, LocalDateTime modifiedTime) { | ||
| 31 | + this.tradeId = tradeId; | ||
| 32 | + this.amount = amount; | ||
| 33 | + this.state = state; | ||
| 34 | + this.version = version; | ||
| 35 | + this.modifiedTime = modifiedTime; | ||
| 36 | + } | ||
| 37 | + | ||
| 22 | public String getTradeId() { | 38 | public String getTradeId() { |
| 23 | return tradeId; | 39 | return tradeId; |
| 24 | } | 40 | } |
| @@ -58,18 +74,4 @@ public class TradeStateDTO { | @@ -58,18 +74,4 @@ public class TradeStateDTO { | ||
| 58 | public void setModifiedTime(LocalDateTime modifiedTime) { | 74 | public void setModifiedTime(LocalDateTime modifiedTime) { |
| 59 | this.modifiedTime = modifiedTime; | 75 | this.modifiedTime = modifiedTime; |
| 60 | } | 76 | } |
| 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 | } | 77 | } |
| 76 | \ No newline at end of file | 78 | \ No newline at end of file |
cashier-trade/src/main/java/com/diligrp/cashier/trade/manager/PaymentResultManager.java
| @@ -5,7 +5,8 @@ import com.diligrp.cashier.shared.service.ThreadPoolService; | @@ -5,7 +5,8 @@ import com.diligrp.cashier.shared.service.ThreadPoolService; | ||
| 5 | import com.diligrp.cashier.shared.spi.IPaymentEventListener; | 5 | import com.diligrp.cashier.shared.spi.IPaymentEventListener; |
| 6 | import com.diligrp.cashier.shared.util.JsonUtils; | 6 | import com.diligrp.cashier.shared.util.JsonUtils; |
| 7 | import com.diligrp.cashier.shared.util.ObjectUtils; | 7 | import com.diligrp.cashier.shared.util.ObjectUtils; |
| 8 | -import com.diligrp.cashier.trade.domain.TradePaymentResult; | 8 | +import com.diligrp.cashier.trade.domain.OnlineRefundResult; |
| 9 | +import com.diligrp.cashier.trade.domain.OnlinePaymentResult; | ||
| 9 | import jakarta.annotation.Resource; | 10 | import jakarta.annotation.Resource; |
| 10 | import org.slf4j.Logger; | 11 | import org.slf4j.Logger; |
| 11 | import org.slf4j.LoggerFactory; | 12 | import org.slf4j.LoggerFactory; |
| @@ -25,7 +26,7 @@ public class PaymentResultManager { | @@ -25,7 +26,7 @@ public class PaymentResultManager { | ||
| 25 | /** | 26 | /** |
| 26 | * 通知业务系统在线支付通道处理结果 | 27 | * 通知业务系统在线支付通道处理结果 |
| 27 | */ | 28 | */ |
| 28 | - public void notifyPaymentResult(String uri, TradePaymentResult payload) { | 29 | + public void notifyPaymentResult(String uri, OnlinePaymentResult payload) { |
| 29 | ThreadPoolService.getIoThreadPoll().submit(() -> { | 30 | ThreadPoolService.getIoThreadPoll().submit(() -> { |
| 30 | List<IPaymentEventListener> lifeCycles = eventListeners.stream().toList(); | 31 | List<IPaymentEventListener> lifeCycles = eventListeners.stream().toList(); |
| 31 | for (IPaymentEventListener listener : lifeCycles) { | 32 | for (IPaymentEventListener listener : lifeCycles) { |
| @@ -55,6 +56,39 @@ public class PaymentResultManager { | @@ -55,6 +56,39 @@ public class PaymentResultManager { | ||
| 55 | }); | 56 | }); |
| 56 | } | 57 | } |
| 57 | 58 | ||
| 59 | + /** | ||
| 60 | + * 通知业务系统退款处理结果 | ||
| 61 | + */ | ||
| 62 | + public void notifyRefundResult(String uri, OnlineRefundResult payload) { | ||
| 63 | + ThreadPoolService.getIoThreadPoll().submit(() -> { | ||
| 64 | + List<IPaymentEventListener> lifeCycles = eventListeners.stream().toList(); | ||
| 65 | + for (IPaymentEventListener listener : lifeCycles) { | ||
| 66 | + try { | ||
| 67 | + listener.onEvent(payload); | ||
| 68 | + } catch (Exception ex) { | ||
| 69 | + LOG.error("Failed to notify trade refund result", ex); | ||
| 70 | + } | ||
| 71 | + } | ||
| 72 | + }); | ||
| 73 | + | ||
| 74 | + if (ObjectUtils.isEmpty(uri)) { | ||
| 75 | + return; | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + ThreadPoolService.getIoThreadPoll().submit(() -> { | ||
| 79 | + try { | ||
| 80 | + String body = JsonUtils.toJsonString(payload); | ||
| 81 | + LOG.info("Notifying online trade refund result: {}", body); | ||
| 82 | + ServiceEndpointSupport.HttpResult httpResult = new NotifyHttpClient(uri).send(body); | ||
| 83 | + if (httpResult.statusCode != 200) { | ||
| 84 | + LOG.error("Failed to notify trade refund result"); | ||
| 85 | + } | ||
| 86 | + } catch (Exception ex) { | ||
| 87 | + LOG.error("Failed to notify trade refund result", ex); | ||
| 88 | + } | ||
| 89 | + }); | ||
| 90 | + } | ||
| 91 | + | ||
| 58 | private static class NotifyHttpClient extends ServiceEndpointSupport { | 92 | private static class NotifyHttpClient extends ServiceEndpointSupport { |
| 59 | private final String baseUrl; | 93 | private final String baseUrl; |
| 60 | 94 |
cashier-trade/src/main/java/com/diligrp/cashier/trade/manager/TaskMessageConsumer.java
0 → 100644
| 1 | +package com.diligrp.cashier.trade.manager; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.util.NumberUtils; | ||
| 4 | +import com.diligrp.cashier.trade.Constants; | ||
| 5 | +import com.diligrp.cashier.trade.domain.TaskMessage; | ||
| 6 | +import com.diligrp.cashier.trade.service.ICashierAssistantService; | ||
| 7 | +import jakarta.annotation.Resource; | ||
| 8 | +import org.slf4j.Logger; | ||
| 9 | +import org.slf4j.LoggerFactory; | ||
| 10 | +import org.springframework.amqp.core.Message; | ||
| 11 | +import org.springframework.amqp.core.MessageProperties; | ||
| 12 | +import org.springframework.amqp.rabbit.annotation.RabbitHandler; | ||
| 13 | +import org.springframework.amqp.rabbit.annotation.RabbitListener; | ||
| 14 | +import org.springframework.stereotype.Service; | ||
| 15 | + | ||
| 16 | +import java.nio.charset.StandardCharsets; | ||
| 17 | + | ||
| 18 | +@Service("taskMessageConsumer") | ||
| 19 | +public class TaskMessageConsumer { | ||
| 20 | + | ||
| 21 | + private static final Logger LOG = LoggerFactory.getLogger(TaskMessageConsumer.class); | ||
| 22 | + | ||
| 23 | + @Resource | ||
| 24 | + private ICashierAssistantService cashierAssistantService; | ||
| 25 | + | ||
| 26 | + /** | ||
| 27 | + * 监听支付通道异步处理任务消息 | ||
| 28 | + */ | ||
| 29 | + @RabbitHandler | ||
| 30 | + @RabbitListener(queues = {Constants.PAYMENT_DELAY_QUEUE}) | ||
| 31 | + public void onDelayMessage(Message message) { | ||
| 32 | + byte[] packet = message.getBody(); | ||
| 33 | + MessageProperties properties = message.getMessageProperties(); | ||
| 34 | + String charSet = properties != null && properties.getContentEncoding() != null | ||
| 35 | + ? properties.getContentEncoding() : StandardCharsets.UTF_8.name(); | ||
| 36 | + try { | ||
| 37 | + String body = new String(packet, charSet); | ||
| 38 | + LOG.info("Receiving online pipeline async task request: {}", body); | ||
| 39 | + TaskMessage task = TaskMessage.fromJson(body); | ||
| 40 | + int times = NumberUtils.str2Int(task.getParams(), Integer.MAX_VALUE); | ||
| 41 | + if (task.getType() == TaskMessage.TYPE_CASHIER_ORDER_SCAN) { | ||
| 42 | + cashierAssistantService.scanCashierTradeOrder(task.getPayload(), times); | ||
| 43 | + } else if (task.getType() == TaskMessage.TYPE_CASHIER_REFUND_SCAN) { | ||
| 44 | + cashierAssistantService.scanCashierRefundOrder(task.getPayload(), times); | ||
| 45 | + } else { | ||
| 46 | + LOG.error("Never happened"); | ||
| 47 | + } | ||
| 48 | + } catch (Exception ex) { | ||
| 49 | + LOG.error("Consume online pipeline async message exception", ex); | ||
| 50 | + } | ||
| 51 | + } | ||
| 52 | +} |
cashier-trade/src/main/java/com/diligrp/cashier/trade/manager/TaskMessageSender.java
0 → 100644
| 1 | +package com.diligrp.cashier.trade.manager; | ||
| 2 | + | ||
| 3 | +import com.diligrp.cashier.shared.service.ThreadPoolService; | ||
| 4 | +import com.diligrp.cashier.shared.util.JsonUtils; | ||
| 5 | +import com.diligrp.cashier.trade.Constants; | ||
| 6 | +import com.diligrp.cashier.trade.domain.TaskMessage; | ||
| 7 | +import jakarta.annotation.Resource; | ||
| 8 | +import org.slf4j.Logger; | ||
| 9 | +import org.slf4j.LoggerFactory; | ||
| 10 | +import org.springframework.amqp.core.Message; | ||
| 11 | +import org.springframework.amqp.core.MessageProperties; | ||
| 12 | +import org.springframework.amqp.rabbit.core.RabbitTemplate; | ||
| 13 | +import org.springframework.stereotype.Service; | ||
| 14 | + | ||
| 15 | +import java.nio.charset.StandardCharsets; | ||
| 16 | + | ||
| 17 | +@Service("taskMessageSender") | ||
| 18 | +public class TaskMessageSender { | ||
| 19 | + | ||
| 20 | + private static final Logger LOG = LoggerFactory.getLogger(TaskMessageSender.class); | ||
| 21 | + | ||
| 22 | + private static final long ONE_MINUTE = 60 * 1000; | ||
| 23 | + | ||
| 24 | + private static final long TEN_MINUTES = 10 * ONE_MINUTE; | ||
| 25 | + | ||
| 26 | + private static final long ONE_SECOND = 1000; | ||
| 27 | + | ||
| 28 | + @Resource | ||
| 29 | + private RabbitTemplate rabbitTemplate; | ||
| 30 | + | ||
| 31 | + /** | ||
| 32 | + * 发送延时处理消息 | ||
| 33 | + */ | ||
| 34 | + public void sendDelayTaskMessage(TaskMessage task, long delayInMillis) { | ||
| 35 | + if (delayInMillis < 0) { | ||
| 36 | + LOG.info("No need send scan order message[type={}]", task.getType()); | ||
| 37 | + return; | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + ThreadPoolService.getIoThreadPoll().submit(() -> { | ||
| 41 | + try { | ||
| 42 | + MessageProperties properties = new MessageProperties(); | ||
| 43 | + properties.setContentEncoding(StandardCharsets.UTF_8.name()); | ||
| 44 | + properties.setContentType(MessageProperties.CONTENT_TYPE_BYTES); | ||
| 45 | + // properties.setExpiration(String.valueOf(expiredTime)); | ||
| 46 | + // RabbitMQ延时插件必须设置x-delay的header才能生效 | ||
| 47 | + properties.setHeader("x-delay", String.valueOf(delayInMillis)); | ||
| 48 | + String payload = JsonUtils.toJsonString(task); | ||
| 49 | + Message message = new Message(payload.getBytes(StandardCharsets.UTF_8), properties); | ||
| 50 | + LOG.info("Sending online payment order scan request for {}", task.getPayload()); | ||
| 51 | + rabbitTemplate.send(Constants.PAYMENT_DELAY_EXCHANGE, Constants.PAYMENT_DELAY_KEY, message); | ||
| 52 | + } catch (Exception ex) { | ||
| 53 | + LOG.error("Failed to send online payment order scan request for {}", task.getPayload(), ex); | ||
| 54 | + } | ||
| 55 | + }); | ||
| 56 | + } | ||
| 57 | +} |
cashier-trade/src/main/java/com/diligrp/cashier/trade/model/OnlinePayment.java
| @@ -40,6 +40,8 @@ public class OnlinePayment extends BaseDO { | @@ -40,6 +40,8 @@ public class OnlinePayment extends BaseDO { | ||
| 40 | private Integer outPayType; | 40 | private Integer outPayType; |
| 41 | // 申请状态 | 41 | // 申请状态 |
| 42 | private Integer state; | 42 | private Integer state; |
| 43 | + // 业务回调地址 | ||
| 44 | + private String notifyUrl; | ||
| 43 | // 备注 | 45 | // 备注 |
| 44 | private String description; | 46 | private String description; |
| 45 | 47 | ||
| @@ -163,6 +165,14 @@ public class OnlinePayment extends BaseDO { | @@ -163,6 +165,14 @@ public class OnlinePayment extends BaseDO { | ||
| 163 | this.state = state; | 165 | this.state = state; |
| 164 | } | 166 | } |
| 165 | 167 | ||
| 168 | + public String getNotifyUrl() { | ||
| 169 | + return notifyUrl; | ||
| 170 | + } | ||
| 171 | + | ||
| 172 | + public void setNotifyUrl(String notifyUrl) { | ||
| 173 | + this.notifyUrl = notifyUrl; | ||
| 174 | + } | ||
| 175 | + | ||
| 166 | public String getDescription() { | 176 | public String getDescription() { |
| 167 | return description; | 177 | return description; |
| 168 | } | 178 | } |
| @@ -201,11 +211,21 @@ public class OnlinePayment extends BaseDO { | @@ -201,11 +211,21 @@ public class OnlinePayment extends BaseDO { | ||
| 201 | return this; | 211 | return this; |
| 202 | } | 212 | } |
| 203 | 213 | ||
| 214 | + public Builder channelId(Integer channelType) { | ||
| 215 | + OnlinePayment.this.channelId = channelType; | ||
| 216 | + return this; | ||
| 217 | + } | ||
| 218 | + | ||
| 204 | public Builder payType(PaymentType payType) { | 219 | public Builder payType(PaymentType payType) { |
| 205 | OnlinePayment.this.payType = payType.getCode(); | 220 | OnlinePayment.this.payType = payType.getCode(); |
| 206 | return this; | 221 | return this; |
| 207 | } | 222 | } |
| 208 | 223 | ||
| 224 | + public Builder payType(Integer payType) { | ||
| 225 | + OnlinePayment.this.payType = payType; | ||
| 226 | + return this; | ||
| 227 | + } | ||
| 228 | + | ||
| 209 | public Builder pipelineId(Long pipelineId) { | 229 | public Builder pipelineId(Long pipelineId) { |
| 210 | OnlinePayment.this.pipelineId = pipelineId; | 230 | OnlinePayment.this.pipelineId = pipelineId; |
| 211 | return this; | 231 | return this; |
| @@ -246,11 +266,26 @@ public class OnlinePayment extends BaseDO { | @@ -246,11 +266,26 @@ public class OnlinePayment extends BaseDO { | ||
| 246 | return this; | 266 | return this; |
| 247 | } | 267 | } |
| 248 | 268 | ||
| 269 | + public Builder outPayType(Integer outPayType) { | ||
| 270 | + OnlinePayment.this.outPayType = outPayType; | ||
| 271 | + return this; | ||
| 272 | + } | ||
| 273 | + | ||
| 249 | public Builder state(PaymentState state) { | 274 | public Builder state(PaymentState state) { |
| 250 | OnlinePayment.this.state = state.getCode(); | 275 | OnlinePayment.this.state = state.getCode(); |
| 251 | return this; | 276 | return this; |
| 252 | } | 277 | } |
| 253 | 278 | ||
| 279 | + public Builder state(Integer state) { | ||
| 280 | + OnlinePayment.this.state = state; | ||
| 281 | + return this; | ||
| 282 | + } | ||
| 283 | + | ||
| 284 | + public Builder notifyUrl(String notifyUrl) { | ||
| 285 | + OnlinePayment.this.notifyUrl = notifyUrl; | ||
| 286 | + return this; | ||
| 287 | + } | ||
| 288 | + | ||
| 254 | public Builder description(String description) { | 289 | public Builder description(String description) { |
| 255 | OnlinePayment.this.description = description; | 290 | OnlinePayment.this.description = description; |
| 256 | return this; | 291 | return this; |
cashier-trade/src/main/java/com/diligrp/cashier/trade/service/ICashierAssistantService.java
0 → 100644
cashier-trade/src/main/java/com/diligrp/cashier/trade/service/ICashierPaymentService.java
| 1 | package com.diligrp.cashier.trade.service; | 1 | package com.diligrp.cashier.trade.service; |
| 2 | 2 | ||
| 3 | +import com.diligrp.cashier.pipeline.domain.OnlinePaymentResponse; | ||
| 3 | import com.diligrp.cashier.pipeline.domain.OnlinePaymentStatus; | 4 | 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; | 5 | +import com.diligrp.cashier.pipeline.domain.OnlineRefundResponse; |
| 6 | +import com.diligrp.cashier.trade.domain.*; | ||
| 7 | 7 | ||
| 8 | public interface ICashierPaymentService { | 8 | public interface ICashierPaymentService { |
| 9 | /** | 9 | /** |
| 10 | * 提交收银台订单 | 10 | * 提交收银台订单 |
| 11 | * | 11 | * |
| 12 | - * @param merchant - 接入商户 | 12 | + * @param merchant - 接入商户 |
| 13 | * @param cashierOrder - 订单申请 | 13 | * @param cashierOrder - 订单申请 |
| 14 | * @return 支付ID | 14 | * @return 支付ID |
| 15 | */ | 15 | */ |
| @@ -22,4 +22,38 @@ public interface ICashierPaymentService { | @@ -22,4 +22,38 @@ public interface ICashierPaymentService { | ||
| 22 | * @return 支付状态 | 22 | * @return 支付状态 |
| 23 | */ | 23 | */ |
| 24 | OnlinePaymentStatus doPayment(CashierPayment cashierPayment); | 24 | OnlinePaymentStatus doPayment(CashierPayment cashierPayment); |
| 25 | + | ||
| 26 | + /** | ||
| 27 | + * 支付结果通知 | ||
| 28 | + * | ||
| 29 | + * @param response - 支付结果 | ||
| 30 | + */ | ||
| 31 | + void notifyPaymentResponse(OnlinePaymentResponse response); | ||
| 32 | + | ||
| 33 | + /** | ||
| 34 | + * 关闭预支付订单 | ||
| 35 | + */ | ||
| 36 | + void closePrepayOrder(String paymentId); | ||
| 37 | + | ||
| 38 | + /** | ||
| 39 | + * 查询交易订单状态 | ||
| 40 | + */ | ||
| 41 | + OnlinePaymentResult queryPaymentState(String paymentId, String mode); | ||
| 42 | + | ||
| 43 | + /** | ||
| 44 | + * 支付退款申请 | ||
| 45 | + */ | ||
| 46 | + OnlineRefundResult sendRefundRequest(OnlineRefundDTO request); | ||
| 47 | + | ||
| 48 | + /** | ||
| 49 | + * 退款结果通知 | ||
| 50 | + * | ||
| 51 | + * @param response - 退款结果 | ||
| 52 | + */ | ||
| 53 | + void notifyRefundResult(OnlineRefundResponse response); | ||
| 54 | + | ||
| 55 | + /** | ||
| 56 | + * 查询退款状态 | ||
| 57 | + */ | ||
| 58 | + OnlineRefundResult queryRefundState(String refundId, String mode); | ||
| 25 | } | 59 | } |
cashier-trade/src/main/java/com/diligrp/cashier/trade/service/IPaymentAssistantService.java deleted
100644 → 0
cashier-trade/src/main/java/com/diligrp/cashier/trade/service/ITradeAssistantService.java
| @@ -5,6 +5,9 @@ import com.diligrp.cashier.trade.domain.TradeStateDTO; | @@ -5,6 +5,9 @@ import com.diligrp.cashier.trade.domain.TradeStateDTO; | ||
| 5 | import com.diligrp.cashier.trade.model.OnlinePayment; | 5 | import com.diligrp.cashier.trade.model.OnlinePayment; |
| 6 | import com.diligrp.cashier.trade.model.TradeOrder; | 6 | import com.diligrp.cashier.trade.model.TradeOrder; |
| 7 | 7 | ||
| 8 | +import java.util.List; | ||
| 9 | +import java.util.Optional; | ||
| 10 | + | ||
| 8 | public interface ITradeAssistantService { | 11 | public interface ITradeAssistantService { |
| 9 | 12 | ||
| 10 | /** | 13 | /** |
| @@ -32,4 +35,8 @@ public interface ITradeAssistantService { | @@ -32,4 +35,8 @@ public interface ITradeAssistantService { | ||
| 32 | */ | 35 | */ |
| 33 | void proceedOnlinePayment(PaymentStateDTO paymentDTO); | 36 | void proceedOnlinePayment(PaymentStateDTO paymentDTO); |
| 34 | 37 | ||
| 38 | + /** | ||
| 39 | + * 查询退款订单 | ||
| 40 | + */ | ||
| 41 | + OnlinePayment findByRefundId(String refundId); | ||
| 35 | } | 42 | } |
cashier-trade/src/main/java/com/diligrp/cashier/trade/service/impl/CashierAssistantServiceImpl.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.OnlinePaymentResponse; | ||
| 7 | +import com.diligrp.cashier.pipeline.domain.OnlinePrepayOrder; | ||
| 8 | +import com.diligrp.cashier.pipeline.domain.OnlineRefundOrder; | ||
| 9 | +import com.diligrp.cashier.pipeline.domain.OnlineRefundResponse; | ||
| 10 | +import com.diligrp.cashier.pipeline.service.IPaymentPipelineManager; | ||
| 11 | +import com.diligrp.cashier.pipeline.type.PaymentState; | ||
| 12 | +import com.diligrp.cashier.trade.dao.IOnlinePaymentDao; | ||
| 13 | +import com.diligrp.cashier.trade.domain.TradeStateDTO; | ||
| 14 | +import com.diligrp.cashier.trade.model.OnlinePayment; | ||
| 15 | +import com.diligrp.cashier.trade.model.TradeOrder; | ||
| 16 | +import com.diligrp.cashier.trade.service.ICashierAssistantService; | ||
| 17 | +import com.diligrp.cashier.trade.service.ICashierPaymentService; | ||
| 18 | +import com.diligrp.cashier.trade.type.TradeState; | ||
| 19 | +import com.diligrp.cashier.trade.type.TradeType; | ||
| 20 | +import jakarta.annotation.Resource; | ||
| 21 | +import org.redisson.api.RLock; | ||
| 22 | +import org.redisson.api.RedissonClient; | ||
| 23 | +import org.slf4j.Logger; | ||
| 24 | +import org.slf4j.LoggerFactory; | ||
| 25 | +import org.springframework.stereotype.Service; | ||
| 26 | + | ||
| 27 | +import java.time.LocalDateTime; | ||
| 28 | +import java.util.List; | ||
| 29 | + | ||
| 30 | +@Service("cashierAssistantService") | ||
| 31 | +public class CashierAssistantServiceImpl implements ICashierAssistantService { | ||
| 32 | + | ||
| 33 | + private static final Logger LOG = LoggerFactory.getLogger(CashierAssistantServiceImpl.class); | ||
| 34 | + | ||
| 35 | + @Resource | ||
| 36 | + private IOnlinePaymentDao onlinePaymentDao; | ||
| 37 | + | ||
| 38 | + @Resource | ||
| 39 | + private ICashierPaymentService cashierPaymentService; | ||
| 40 | + | ||
| 41 | + @Resource | ||
| 42 | + private IPaymentPipelineManager paymentPipelineManager; | ||
| 43 | + | ||
| 44 | + @Resource | ||
| 45 | + private TradeAssistantServiceImpl tradeAssistantService; | ||
| 46 | + | ||
| 47 | + @Resource | ||
| 48 | + private RedissonClient redissonClient; | ||
| 49 | + | ||
| 50 | + @Override | ||
| 51 | + public void scanCashierTradeOrder(String tradeId, int times) { | ||
| 52 | + LOG.debug("scanCashierTradeOrder{}: processing cashier order {}", times, tradeId); | ||
| 53 | + String lockKey = String.format(com.diligrp.cashier.trade.Constants.TRADE_LOCK_REDIS_KEY, tradeId); | ||
| 54 | + RLock lock = redissonClient.getLock(lockKey); | ||
| 55 | + try { | ||
| 56 | + lock.lock(); | ||
| 57 | + LocalDateTime now = LocalDateTime.now(); | ||
| 58 | + // 理论上只会存在一条支付中的支付订单 | ||
| 59 | + List<OnlinePayment> onlinePayments = onlinePaymentDao.listOnlinePayments(tradeId, | ||
| 60 | + TradeType.TRADE.getCode(), PaymentState.PROCESSING.getCode()); | ||
| 61 | + // 关闭所有未支付完成的支付订单 | ||
| 62 | + for (OnlinePayment payment : onlinePayments) { | ||
| 63 | + PaymentPipeline<?> pipeline = paymentPipelineManager.findPipelineById(payment.getPipelineId(), PaymentPipeline.class); | ||
| 64 | + // 在线支付通道才能关闭订单, 园区卡支付不支持 | ||
| 65 | + if (pipeline instanceof OnlinePipeline<?> onlinePipeline) { | ||
| 66 | + OnlinePrepayOrder order = new OnlinePrepayOrder(payment.getPaymentId(), payment.getOutTradeNo()); | ||
| 67 | + // 微信服务商模式,还需子商户 | ||
| 68 | + order.attach(Constants.PARAM_MCH_ID, payment.getOutMchId()); | ||
| 69 | + OnlinePaymentResponse response = onlinePipeline.queryPrepayResponse(order); | ||
| 70 | + if (!PaymentState.isFinished(response.getState().getCode())) { | ||
| 71 | + try { | ||
| 72 | + onlinePipeline.closePrepayOrder(order); | ||
| 73 | + LOG.debug("scanCashierTradeOrder: close online prepay order {}", payment.getPaymentId()); | ||
| 74 | + response = new OnlinePaymentResponse(response.getPaymentId(), response.getOutTradeNo(), | ||
| 75 | + response.getOutPayType(), response.getPayerId(), response.getWhen(), | ||
| 76 | + PaymentState.FAILED, "自动关闭超时的支付订单"); | ||
| 77 | + } catch (Exception ex) { | ||
| 78 | + LOG.error("scanOnlinePrepayOrder: close online prepare order exception", ex); | ||
| 79 | + } | ||
| 80 | + } | ||
| 81 | + cashierPaymentService.notifyPaymentResponse(response); | ||
| 82 | + } | ||
| 83 | + } | ||
| 84 | + // 交易订单仍然未完成则关闭交易订单,交易订单不能继续支付 | ||
| 85 | + TradeOrder trade = tradeAssistantService.findByTradeId(tradeId); | ||
| 86 | + if (!TradeState.isFinished(trade.getState())) { | ||
| 87 | + TradeStateDTO tradeStateDTO = TradeStateDTO.of(trade.getTradeId(), TradeState.FAILED, trade.getVersion(), now); | ||
| 88 | + tradeAssistantService.proceedTradeOrder(tradeStateDTO); | ||
| 89 | + } | ||
| 90 | + } finally { | ||
| 91 | + if (lock.isHeldByCurrentThread()) { | ||
| 92 | + lock.unlock(); | ||
| 93 | + } | ||
| 94 | + } | ||
| 95 | + } | ||
| 96 | + | ||
| 97 | + @Override | ||
| 98 | + public void scanCashierRefundOrder(String refundId, int times) { | ||
| 99 | + LOG.debug("scanCashierRefundOrder{}: processing online refund order {}", times, refundId); | ||
| 100 | + OnlinePayment refund = tradeAssistantService.findByRefundId(refundId); | ||
| 101 | + if (PaymentState.isFinished(refund.getState())) { | ||
| 102 | + LOG.debug("scanCashierRefundOrder{}: online refund order {} already accomplished", times, refundId); | ||
| 103 | + return; | ||
| 104 | + } | ||
| 105 | + | ||
| 106 | + OnlinePipeline<?> pipeline = paymentPipelineManager.findPipelineById(refund.getPipelineId(), OnlinePipeline.class); | ||
| 107 | + OnlineRefundOrder order = new OnlineRefundOrder(refundId, refund.getOutTradeNo()); | ||
| 108 | + // 微信服务商模式,还需子商户 | ||
| 109 | + order.attach(Constants.PARAM_MCH_ID, refund.getOutMchId()); | ||
| 110 | + OnlineRefundResponse response = pipeline.queryRefundResponse(order); | ||
| 111 | + cashierPaymentService.notifyRefundResult(response); | ||
| 112 | + } | ||
| 113 | +} |
cashier-trade/src/main/java/com/diligrp/cashier/trade/service/impl/CashierPaymentServiceImpl.java
| 1 | package com.diligrp.cashier.trade.service.impl; | 1 | package com.diligrp.cashier.trade.service.impl; |
| 2 | 2 | ||
| 3 | +import com.diligrp.cashier.assistant.service.KeyGenerator; | ||
| 3 | import com.diligrp.cashier.assistant.service.impl.SnowflakeKeyManager; | 4 | import com.diligrp.cashier.assistant.service.impl.SnowflakeKeyManager; |
| 4 | import com.diligrp.cashier.pipeline.core.DiliCardPipeline; | 5 | import com.diligrp.cashier.pipeline.core.DiliCardPipeline; |
| 5 | import com.diligrp.cashier.pipeline.core.OnlinePipeline; | 6 | import com.diligrp.cashier.pipeline.core.OnlinePipeline; |
| 6 | import com.diligrp.cashier.pipeline.core.PaymentPipeline; | 7 | 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; | 8 | +import com.diligrp.cashier.pipeline.core.WechatPipeline; |
| 9 | +import com.diligrp.cashier.pipeline.domain.*; | ||
| 10 | import com.diligrp.cashier.pipeline.domain.card.CardPaymentRequest; | 10 | import com.diligrp.cashier.pipeline.domain.card.CardPaymentRequest; |
| 11 | import com.diligrp.cashier.pipeline.domain.card.CardPaymentResponse; | 11 | import com.diligrp.cashier.pipeline.domain.card.CardPaymentResponse; |
| 12 | import com.diligrp.cashier.pipeline.service.IPaymentPipelineManager; | 12 | import com.diligrp.cashier.pipeline.service.IPaymentPipelineManager; |
| @@ -20,6 +20,7 @@ import com.diligrp.cashier.trade.dao.IOnlinePaymentDao; | @@ -20,6 +20,7 @@ import com.diligrp.cashier.trade.dao.IOnlinePaymentDao; | ||
| 20 | import com.diligrp.cashier.trade.dao.ITradeOrderDao; | 20 | import com.diligrp.cashier.trade.dao.ITradeOrderDao; |
| 21 | import com.diligrp.cashier.trade.domain.*; | 21 | import com.diligrp.cashier.trade.domain.*; |
| 22 | import com.diligrp.cashier.trade.exception.TradePaymentException; | 22 | import com.diligrp.cashier.trade.exception.TradePaymentException; |
| 23 | +import com.diligrp.cashier.trade.manager.TaskMessageSender; | ||
| 23 | import com.diligrp.cashier.trade.manager.PaymentResultManager; | 24 | import com.diligrp.cashier.trade.manager.PaymentResultManager; |
| 24 | import com.diligrp.cashier.trade.model.OnlinePayment; | 25 | import com.diligrp.cashier.trade.model.OnlinePayment; |
| 25 | import com.diligrp.cashier.trade.model.TradeOrder; | 26 | import com.diligrp.cashier.trade.model.TradeOrder; |
| @@ -33,6 +34,8 @@ import com.diligrp.cashier.trade.util.MiniProPaymentConverter; | @@ -33,6 +34,8 @@ import com.diligrp.cashier.trade.util.MiniProPaymentConverter; | ||
| 33 | import jakarta.annotation.Resource; | 34 | import jakarta.annotation.Resource; |
| 34 | import org.redisson.api.RLock; | 35 | import org.redisson.api.RLock; |
| 35 | import org.redisson.api.RedissonClient; | 36 | import org.redisson.api.RedissonClient; |
| 37 | +import org.slf4j.Logger; | ||
| 38 | +import org.slf4j.LoggerFactory; | ||
| 36 | import org.springframework.stereotype.Service; | 39 | import org.springframework.stereotype.Service; |
| 37 | import org.springframework.transaction.annotation.Transactional; | 40 | import org.springframework.transaction.annotation.Transactional; |
| 38 | 41 | ||
| @@ -43,6 +46,8 @@ import java.util.concurrent.TimeUnit; | @@ -43,6 +46,8 @@ import java.util.concurrent.TimeUnit; | ||
| 43 | @Service("cashierPaymentService") | 46 | @Service("cashierPaymentService") |
| 44 | public class CashierPaymentServiceImpl implements ICashierPaymentService { | 47 | public class CashierPaymentServiceImpl implements ICashierPaymentService { |
| 45 | 48 | ||
| 49 | + private static final Logger LOG = LoggerFactory.getLogger(CashierPaymentServiceImpl.class); | ||
| 50 | + | ||
| 46 | @Resource | 51 | @Resource |
| 47 | private ITradeOrderDao tradeOrderDao; | 52 | private ITradeOrderDao tradeOrderDao; |
| 48 | 53 | ||
| @@ -56,6 +61,9 @@ public class CashierPaymentServiceImpl implements ICashierPaymentService { | @@ -56,6 +61,9 @@ public class CashierPaymentServiceImpl implements ICashierPaymentService { | ||
| 56 | private IPaymentPipelineManager paymentPipelineManager; | 61 | private IPaymentPipelineManager paymentPipelineManager; |
| 57 | 62 | ||
| 58 | @Resource | 63 | @Resource |
| 64 | + private TaskMessageSender taskMessageSender; | ||
| 65 | + | ||
| 66 | + @Resource | ||
| 59 | private PaymentResultManager paymentResultManager; | 67 | private PaymentResultManager paymentResultManager; |
| 60 | 68 | ||
| 61 | @Resource | 69 | @Resource |
| @@ -86,7 +94,9 @@ public class CashierPaymentServiceImpl implements ICashierPaymentService { | @@ -86,7 +94,9 @@ public class CashierPaymentServiceImpl implements ICashierPaymentService { | ||
| 86 | tradeOrderDao.insertTradeOrder(tradeOrder); | 94 | tradeOrderDao.insertTradeOrder(tradeOrder); |
| 87 | 95 | ||
| 88 | // TODO: userId是否需要存储 | 96 | // TODO: userId是否需要存储 |
| 89 | - // TODO: 如果不打开收银台支付,定时关闭订单 | 97 | + // 兜底处理交易订单,根据支付结果选择关闭或完成交易订单 |
| 98 | + TaskMessage message = new TaskMessage(TaskMessage.TYPE_CASHIER_ORDER_SCAN, tradeId, "1"); | ||
| 99 | + taskMessageSender.sendDelayTaskMessage(message, timeout); | ||
| 90 | return tradeId; | 100 | return tradeId; |
| 91 | } | 101 | } |
| 92 | 102 | ||
| @@ -99,77 +109,76 @@ public class CashierPaymentServiceImpl implements ICashierPaymentService { | @@ -99,77 +109,76 @@ public class CashierPaymentServiceImpl implements ICashierPaymentService { | ||
| 99 | @Override | 109 | @Override |
| 100 | @Transactional(rollbackFor = Exception.class) | 110 | @Transactional(rollbackFor = Exception.class) |
| 101 | public OnlinePaymentStatus doPayment(CashierPayment cashierPayment) { | 111 | public OnlinePaymentStatus doPayment(CashierPayment cashierPayment) { |
| 102 | - // TODO: 防重复提交 | 112 | + // TODO: 接口防重复提交 |
| 103 | String lockKey = String.format(Constants.TRADE_LOCK_REDIS_KEY, cashierPayment.getTradeId()); | 113 | String lockKey = String.format(Constants.TRADE_LOCK_REDIS_KEY, cashierPayment.getTradeId()); |
| 104 | RLock lock = redissonClient.getLock(lockKey); | 114 | RLock lock = redissonClient.getLock(lockKey); |
| 105 | try { | 115 | try { |
| 106 | boolean locked = lock.tryLock(Constants.TRADE_LOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS); | 116 | boolean locked = lock.tryLock(Constants.TRADE_LOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS); |
| 107 | if (locked) { | 117 | 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, "该交易订单已经完成,不能进行支付"); | 118 | + TradeOrder trade = tradeAssistantService.findByTradeId(cashierPayment.getTradeId()); |
| 119 | + CashierType cashierType = CashierType.getByCode(trade.getType()); | ||
| 120 | + if (TradeState.isFinished(trade.getState())) { | ||
| 121 | + throw new TradePaymentException(ErrorCode.OPERATION_NOT_ALLOWED, "不能进行支付, 交易订单已经完成"); | ||
| 122 | + } | ||
| 123 | + // 目前只支持小程序收银台,后期将支持其他收银台类型 | ||
| 124 | + if (cashierType != CashierType.MINIPRO) { | ||
| 125 | + throw new TradePaymentException(ErrorCode.OPERATION_NOT_ALLOWED, "系统当前不支持的此收银台类型"); | ||
| 112 | } | 126 | } |
| 113 | // 关闭支付中的支付订单, 避免一个交易订单存在多笔支付订单, 造成重复支付 | 127 | // 关闭支付中的支付订单, 避免一个交易订单存在多笔支付订单, 造成重复支付 |
| 114 | - if (!tradeAssistantService.resetTradeOrder(tradeOrder)) { | ||
| 115 | - throw new TradePaymentException(ErrorCode.OPERATION_NOT_ALLOWED, "存在支付中的支付申请"); | 128 | + if (!tradeAssistantService.resetTradeOrder(trade)) { |
| 129 | + throw new TradePaymentException(ErrorCode.OPERATION_NOT_ALLOWED, "不能进行支付, 交易订单正在支付中"); | ||
| 116 | } | 130 | } |
| 117 | 131 | ||
| 118 | LocalDateTime now = LocalDateTime.now(); | 132 | LocalDateTime now = LocalDateTime.now(); |
| 119 | // 获取支付通道 | 133 | // 获取支付通道 |
| 120 | - PaymentPipeline<?> pipeline = paymentPipelineManager.findPipelineById(cashierPayment.getPipelineId(), PaymentPipeline.class); | 134 | + PaymentPipeline<?> paymentPipeline = paymentPipelineManager.findPipelineById( |
| 135 | + cashierPayment.getPipelineId(), PaymentPipeline.class); | ||
| 121 | String paymentId = snowflakeKeyManager.getKeyGenerator(SnowflakeKey.PAYMENT_ID).nextId(); | 136 | String paymentId = snowflakeKeyManager.getKeyGenerator(SnowflakeKey.PAYMENT_ID).nextId(); |
| 122 | - if (pipeline instanceof DiliCardPipeline cardPipeline) { | 137 | + if (paymentPipeline instanceof OnlinePipeline<?> pipeline) { // 在线支付通道 |
| 138 | + // 在线支付通道: 不同的收银台类型使用不同的支付方式(目前只支持小程序收银台) | ||
| 139 | + // 小程序收银台将使用在线支付通道的小程序支付 | ||
| 140 | + MiniProPrepayRequest request = new MiniProPaymentConverter(trade, paymentId, now).convert(cashierPayment); | ||
| 141 | + MiniProPrepayResponse response = pipeline.sendMiniProPrepayRequest(request); | ||
| 142 | + // 微信服务商模式下outMchId为签约子商户 | ||
| 143 | + String outMchId = request.getString(com.diligrp.cashier.pipeline.Constants.PARAM_MCH_ID); | ||
| 144 | + OnlinePayment payment = OnlinePayment.builder().outMchId(outMchId).tradeId(trade.getTradeId()) | ||
| 145 | + .type(TradeType.TRADE).paymentId(paymentId).channelId(pipeline.supportedChannel()) | ||
| 146 | + .payType(PaymentType.MINI_PRO).pipelineId(pipeline.pipelineId()).goods(trade.getGoods()) | ||
| 147 | + .amount(trade.getAmount()).payerId(request.getOpenId()).outTradeNo(response.getOutTradeNo()) | ||
| 148 | + .state(response.getState()).version(0).createdTime(now).modifiedTime(now).build(); | ||
| 149 | + onlinePaymentDao.insertOnlinePayment(payment); | ||
| 150 | + return response; | ||
| 151 | + } | ||
| 152 | + if (paymentPipeline instanceof DiliCardPipeline pipeline) { // 园区卡支付通道 | ||
| 123 | // 园区卡支付通道: 所有的收银台类型使用的是同一种园区卡支付流程 | 153 | // 园区卡支付通道: 所有的收银台类型使用的是同一种园区卡支付流程 |
| 124 | - // 检查园区卡支付参数 | ||
| 125 | - CardPaymentRequest request = new CardPaymentConverter(tradeOrder, paymentId, now).convert(cashierPayment); | 154 | + CardPaymentRequest request = new CardPaymentConverter(trade, paymentId, now).convert(cashierPayment); |
| 126 | // 修改支付状态为支付中,防止重复支付 | 155 | // 修改支付状态为支付中,防止重复支付 |
| 127 | - CardPaymentResponse response = cardPipeline.sendPaymentRequest(request); | 156 | + CardPaymentResponse response = pipeline.sendPaymentRequest(request); |
| 128 | if (PaymentState.isFinished(response.getState().getCode())) { | 157 | if (PaymentState.isFinished(response.getState().getCode())) { |
| 129 | // 园区卡支付通道outMchId为市场ID | 158 | // 园区卡支付通道outMchId为市场ID |
| 130 | - String outMchId = cardPipeline.params().getOutMchId(); | ||
| 131 | - OnlinePayment payment = OnlinePayment.builder().outMchId(outMchId).tradeId(tradeOrder.getTradeId()) | 159 | + String outMchId = pipeline.params().getOutMchId(); |
| 160 | + OnlinePayment payment = OnlinePayment.builder().outMchId(outMchId).tradeId(trade.getTradeId()) | ||
| 132 | .type(TradeType.TRADE).paymentId(paymentId).channelId(pipeline.supportedChannel()) | 161 | .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()) | 162 | + .payType(PaymentType.DIRECT).pipelineId(pipeline.pipelineId()).goods(trade.getGoods()) |
| 163 | + .amount(trade.getAmount()).payerId(response.getPayerId()) | ||
| 135 | .finishTime(response.getWhen()).outTradeNo(response.getOutTradeNo()) | 164 | .finishTime(response.getWhen()).outTradeNo(response.getOutTradeNo()) |
| 136 | - .outPayType(OutPaymentType.DILICARD).state(response.getState()) | 165 | + .outPayType(OutPaymentType.DILICARD).state(response.getState()).notifyUrl(trade.getNotifyUrl()) |
| 137 | .description(response.getMessage()).version(0).createdTime(now).modifiedTime(now).build(); | 166 | .description(response.getMessage()).version(0).createdTime(now).modifiedTime(now).build(); |
| 138 | onlinePaymentDao.insertOnlinePayment(payment); | 167 | 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 | 168 | ||
| 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, "不支持的收银台类型"); | 169 | + // 只有成功才修改交易订单状态,此时交易订单仍然可以继续支付 |
| 170 | + if (response.getState() == PaymentState.SUCCESS) { | ||
| 171 | + TradeStateDTO tradeStateDTO = TradeStateDTO.of(trade.getTradeId(), TradeState.SUCCESS, | ||
| 172 | + trade.getVersion(), now); | ||
| 173 | + tradeAssistantService.proceedTradeOrder(tradeStateDTO); | ||
| 174 | + // 支付成功才通知业务系统支付结果,失败可以再次进行支付 | ||
| 175 | + OnlinePaymentResult paymentResult = new OnlinePaymentResult(trade.getTradeId(), paymentId, | ||
| 176 | + response.getState().getCode(), trade.getOutTradeNo(), OutPaymentType.DILICARD.getCode(), | ||
| 177 | + response.getPayerId(), response.getWhen(), response.getMessage()); | ||
| 178 | + paymentResultManager.notifyPaymentResult(trade.getNotifyUrl(), paymentResult); | ||
| 179 | + } | ||
| 172 | } | 180 | } |
| 181 | + return response; | ||
| 173 | } else { | 182 | } else { |
| 174 | // 目前只有两类支付通道: CardPipeline和OnlinePipeline, 程序逻辑不应该到达此代码块 | 183 | // 目前只有两类支付通道: CardPipeline和OnlinePipeline, 程序逻辑不应该到达此代码块 |
| 175 | throw new TradePaymentException(ErrorCode.OPERATION_NOT_ALLOWED, "不支持的支付通道类型"); | 184 | throw new TradePaymentException(ErrorCode.OPERATION_NOT_ALLOWED, "不支持的支付通道类型"); |
| @@ -187,4 +196,240 @@ public class CashierPaymentServiceImpl implements ICashierPaymentService { | @@ -187,4 +196,240 @@ public class CashierPaymentServiceImpl implements ICashierPaymentService { | ||
| 187 | } | 196 | } |
| 188 | } | 197 | } |
| 189 | } | 198 | } |
| 199 | + | ||
| 200 | + /** | ||
| 201 | + * 通知支付结果 | ||
| 202 | + */ | ||
| 203 | + @Override | ||
| 204 | + @Transactional(rollbackFor = Exception.class) | ||
| 205 | + public void notifyPaymentResponse(OnlinePaymentResponse response) { | ||
| 206 | + OnlinePayment payment = tradeAssistantService.findByPaymentId(response.getPaymentId()); | ||
| 207 | + if (PaymentState.isFinished(payment.getState())) { | ||
| 208 | + LOG.warn("Duplicate process payment result notification for {}:{}", payment.getPaymentId(), response.getState()); | ||
| 209 | + return; | ||
| 210 | + } | ||
| 211 | + LOG.info("Processing payment result notification: [{},{}]", payment.getPaymentId(), response.getState()); | ||
| 212 | + | ||
| 213 | + if (PaymentState.isFinished(response.getState().getCode())) { | ||
| 214 | + LocalDateTime now = LocalDateTime.now(); | ||
| 215 | + String lockKey = String.format(Constants.TRADE_LOCK_REDIS_KEY, payment.getTradeId()); | ||
| 216 | + RLock lock = redissonClient.getLock(lockKey); | ||
| 217 | + try { | ||
| 218 | + lock.lock(); | ||
| 219 | + TradeOrder trade = tradeAssistantService.findByTradeId(payment.getTradeId()); | ||
| 220 | + PaymentStateDTO paymentDTO = PaymentStateDTO.builder().paymentId(payment.getPaymentId()) | ||
| 221 | + .outTradeNo(response.getOutTradeNo()).outPayType(response.getOutPayType()).payerId(response.getPayerId()) | ||
| 222 | + .finishTime(response.getWhen()).state(response.getState()).description(response.getMessage()) | ||
| 223 | + .version(payment.getVersion()).modifiedTime(now).build(); | ||
| 224 | + tradeAssistantService.proceedOnlinePayment(paymentDTO); | ||
| 225 | + | ||
| 226 | + // 交易订单未完成,支付订单成功支付时处理交易订单,并通知业务系统支付结果;如果支付订单支付失败,收银台交易订单可以继续支付 | ||
| 227 | + if (TradeState.PENDING.equalTo(trade.getState()) && PaymentState.SUCCESS == response.getState()) { | ||
| 228 | + TradeStateDTO tradeStateDTO = TradeStateDTO.of(trade.getTradeId(), TradeState.SUCCESS, trade.getVersion(), now); | ||
| 229 | + tradeAssistantService.proceedTradeOrder(tradeStateDTO); | ||
| 230 | + | ||
| 231 | + OnlinePaymentResult paymentResult = OnlinePaymentResult.of(trade.getTradeId(), payment.getPaymentId(), | ||
| 232 | + response.getState(), trade.getOutTradeNo(), response.getOutPayType(), response.getPayerId(), | ||
| 233 | + response.getWhen() != null ? response.getWhen() : now, response.getMessage()); | ||
| 234 | + paymentResultManager.notifyPaymentResult(trade.getNotifyUrl(), paymentResult); | ||
| 235 | + } | ||
| 236 | + } finally { | ||
| 237 | + if (lock.isHeldByCurrentThread()) { | ||
| 238 | + lock.unlock(); | ||
| 239 | + } | ||
| 240 | + } | ||
| 241 | + } | ||
| 242 | + } | ||
| 243 | + | ||
| 244 | + @Override | ||
| 245 | + @Transactional(rollbackFor = Exception.class) | ||
| 246 | + public void closePrepayOrder(String paymentId) { | ||
| 247 | + OnlinePayment payment = tradeAssistantService.findByPaymentId(paymentId); | ||
| 248 | + if (PaymentState.isFinished(payment.getState())) { | ||
| 249 | + throw new TradePaymentException(ErrorCode.OPERATION_NOT_ALLOWED, "不能关闭预支付订单: 无效的订单状态"); | ||
| 250 | + } | ||
| 251 | + | ||
| 252 | + String lockKey = String.format(Constants.TRADE_LOCK_REDIS_KEY, payment.getTradeId()); | ||
| 253 | + RLock lock = redissonClient.getLock(lockKey); | ||
| 254 | + try { | ||
| 255 | + lock.lock(); | ||
| 256 | + | ||
| 257 | + LocalDateTime now = LocalDateTime.now(); | ||
| 258 | + TradeOrder trade = tradeAssistantService.findByTradeId(payment.getTradeId()); | ||
| 259 | + PaymentPipeline<?> pipeline = paymentPipelineManager.findPipelineById(payment.getPipelineId(), PaymentPipeline.class); | ||
| 260 | + // 只有在线支付通道才可以关闭支付订单,园区卡支付不支持 | ||
| 261 | + if (pipeline instanceof OnlinePipeline<?> onlinePipeline) { | ||
| 262 | + OnlinePrepayOrder order = new OnlinePrepayOrder(paymentId, payment.getOutTradeNo()); | ||
| 263 | + // 用于微信服务商模式下,微信子商户信息 | ||
| 264 | + order.attach(com.diligrp.cashier.pipeline.Constants.PARAM_MCH_ID, payment.getOutMchId()); | ||
| 265 | + onlinePipeline.closePrepayOrder(order); | ||
| 266 | + PaymentStateDTO paymentDTO = PaymentStateDTO.builder().paymentId(payment.getPaymentId()) | ||
| 267 | + .outTradeNo(null).outPayType(null).payerId(null).finishTime(now).state(PaymentState.FAILED) | ||
| 268 | + .description("人工关闭支付订单").version(payment.getVersion()).modifiedTime(now).build(); | ||
| 269 | + tradeAssistantService.proceedOnlinePayment(paymentDTO); | ||
| 270 | + | ||
| 271 | + TradeStateDTO tradeStateDTO = TradeStateDTO.of(trade.getTradeId(), TradeState.CLOSED, trade.getVersion(), now); | ||
| 272 | + tradeAssistantService.proceedTradeOrder(tradeStateDTO); | ||
| 273 | + } | ||
| 274 | + } finally { | ||
| 275 | + if (lock.isHeldByCurrentThread()) { | ||
| 276 | + lock.unlock(); | ||
| 277 | + } | ||
| 278 | + } | ||
| 279 | + } | ||
| 280 | + | ||
| 281 | + @Override | ||
| 282 | + public OnlinePaymentResult queryPaymentState(String paymentId, String mode) { | ||
| 283 | + OnlinePayment payment = tradeAssistantService.findByPaymentId(paymentId); | ||
| 284 | + TradeOrder trade = tradeAssistantService.findByTradeId(payment.getTradeId()); | ||
| 285 | + | ||
| 286 | + LOG.debug("Query online prepay order[{}-{}] state...", paymentId, payment.getState()); | ||
| 287 | + // online模式下,如果本地支付申请没有明确的支付结果,将调用支付通道查询预支付订单状态 | ||
| 288 | + if (!PaymentState.isFinished(payment.getState()) && "online".equalsIgnoreCase(mode)) { | ||
| 289 | + OnlinePrepayOrder order = new OnlinePrepayOrder(paymentId, payment.getOutTradeNo()); | ||
| 290 | + PaymentPipeline<?> paymentPipeline = paymentPipelineManager.findPipelineById(payment.getPipelineId(), PaymentPipeline.class); | ||
| 291 | + if (paymentPipeline instanceof OnlinePipeline<?> pipeline) { | ||
| 292 | + // 用于微信服务商模式下,微信子商户信息 | ||
| 293 | + order.attach(com.diligrp.cashier.pipeline.Constants.PARAM_MCH_ID, payment.getOutMchId()); | ||
| 294 | + OnlinePaymentResponse response = pipeline.queryPrepayResponse(order); | ||
| 295 | + return OnlinePaymentResult.of(trade.getTradeId(), paymentId, response.getState(), trade.getOutTradeNo(), | ||
| 296 | + response.getOutPayType(), response.getPayerId(), response.getWhen(), response.getMessage()); | ||
| 297 | + } else if (paymentPipeline instanceof DiliCardPipeline pipeline) { | ||
| 298 | + // 园区卡支付outMchId为市场ID | ||
| 299 | + order.attach(com.diligrp.cashier.pipeline.Constants.PARAM_MCH_ID, payment.getOutMchId()); | ||
| 300 | + CardPaymentResponse response = pipeline.queryPaymentResponse(order); | ||
| 301 | + return OnlinePaymentResult.of(trade.getTradeId(), paymentId, response.getState(), trade.getOutTradeNo(), | ||
| 302 | + response.getOutPayType(), response.getPayerId(), response.getWhen(), response.getMessage()); | ||
| 303 | + } | ||
| 304 | + } | ||
| 305 | + return new OnlinePaymentResult(trade.getTradeId(), paymentId, payment.getState(), trade.getOutTradeNo(), | ||
| 306 | + payment.getOutPayType(), payment.getPayerId(), payment.getFinishTime(), payment.getDescription()); | ||
| 307 | + /*LOG.debug("Query online trade order[{}] state...", tradeId); | ||
| 308 | + TradeOrder trade = tradeAssistantService.findByTradeId(tradeId); | ||
| 309 | + List<OnlinePayment> onlinePayments = onlinePaymentDao.listOnlinePayments(tradeId, TradeType.TRADE.getCode()); | ||
| 310 | + if (TradeState.SUCCESS.equalTo(trade.getState()) || TradeState.REFUND.equalTo(trade.getState())) { | ||
| 311 | + OnlinePayment payment = onlinePayments.stream().filter(p -> PaymentState.SUCCESS.equalTo(p.getState())) | ||
| 312 | + .findFirst().orElseThrow(() -> new TradePaymentException(ErrorCode.OBJECT_NOT_FOUND, "未找到支付订单")); | ||
| 313 | + return new OnlinePaymentResult(tradeId, payment.getState(), trade.getOutTradeNo(), payment.getOutPayType(), | ||
| 314 | + payment.getPayerId(), payment.getFinishTime(), payment.getDescription()); | ||
| 315 | + } else if (TradeState.FAILED.equalTo(trade.getState()) || TradeState.CLOSED.equalTo(trade.getState())) { | ||
| 316 | + return OnlinePaymentResult.of(tradeId, PaymentState.FAILED, trade.getOutTradeNo(), | ||
| 317 | + null, null, LocalDateTime.now(), "等待支付"); | ||
| 318 | + } else { // 交易订单等待支付 | ||
| 319 | + // 查找交易订单中正在支付中的支付记录 | ||
| 320 | + OnlinePayment payment = onlinePayments.stream().filter(p -> PaymentState.PROCESSING.equalTo(p.getState())) | ||
| 321 | + .findFirst().orElse(null); | ||
| 322 | + if (payment == null) { // 交易订单没有支付记录 | ||
| 323 | + return OnlinePaymentResult.of(tradeId, PaymentState.PENDING, trade.getOutTradeNo(), | ||
| 324 | + null, null, LocalDateTime.now(), "等待支付"); | ||
| 325 | + } | ||
| 326 | + if ("online".equalsIgnoreCase(mode)) { | ||
| 327 | + OnlinePrepayOrder order = new OnlinePrepayOrder(payment.getPaymentId(), payment.getOutTradeNo()); | ||
| 328 | + OnlinePipeline<?> pipeline = paymentPipelineManager.findPipelineById(payment.getPipelineId(), OnlinePipeline.class); | ||
| 329 | + // 用于微信服务商模式下,微信子商户信息 | ||
| 330 | + order.attach(com.diligrp.cashier.pipeline.Constants.PARAM_MCH_ID, payment.getOutMchId()); | ||
| 331 | + OnlinePaymentResponse response = pipeline.queryPrepayResponse(order); | ||
| 332 | + return OnlinePaymentResult.of(tradeId, response.getState(), trade.getOutTradeNo(), | ||
| 333 | + response.getOutPayType(), response.getPayerId(), response.getWhen(), response.getMessage()); | ||
| 334 | + } else { | ||
| 335 | + return new OnlinePaymentResult(tradeId, payment.getState(), trade.getOutTradeNo(), payment.getOutPayType(), | ||
| 336 | + payment.getPayerId(), payment.getFinishTime(), payment.getDescription()); | ||
| 337 | + } | ||
| 338 | + | ||
| 339 | + }*/ | ||
| 340 | + } | ||
| 341 | + | ||
| 342 | + @Override | ||
| 343 | + @Transactional(rollbackFor = Exception.class) | ||
| 344 | + public OnlineRefundResult sendRefundRequest(OnlineRefundDTO request) { | ||
| 345 | + TradeOrder trade = tradeAssistantService.findByTradeId(request.getTradeId()); | ||
| 346 | + if (!TradeState.forRefund(trade.getState())) { | ||
| 347 | + throw new TradePaymentException(ErrorCode.INVALID_OBJECT_STATE, "不能进行交易退款: 无效的交易状态"); | ||
| 348 | + } | ||
| 349 | + if (trade.getAmount() < request.getAmount()) { | ||
| 350 | + throw new TradePaymentException(ErrorCode.OPERATION_NOT_ALLOWED, "申请退费金额超过原支付金额"); | ||
| 351 | + } | ||
| 352 | + // 理论上只会存在一条支付完成的支付订单 | ||
| 353 | + OnlinePayment payment = onlinePaymentDao.listOnlinePayments(trade.getTradeId(), | ||
| 354 | + TradeType.TRADE.getCode(), PaymentState.SUCCESS.getCode()).stream().findFirst() | ||
| 355 | + .orElseThrow(() -> new TradePaymentException(ErrorCode.OPERATION_NOT_ALLOWED, "不能进行交易退款: 无支付信息")); | ||
| 356 | + | ||
| 357 | + LocalDateTime now = LocalDateTime.now().withNano(0); | ||
| 358 | + KeyGenerator refundIdKey = snowflakeKeyManager.getKeyGenerator(SnowflakeKey.PAYMENT_ID); | ||
| 359 | + String refundId = refundIdKey.nextId(); | ||
| 360 | + OnlinePipeline<?> pipeline = paymentPipelineManager.findPipelineById(payment.getPipelineId(), OnlinePipeline.class); | ||
| 361 | + OnlineRefundRequest refundRequest = OnlineRefundRequest.of(refundId, payment.getPaymentId(), payment.getOutTradeNo(), | ||
| 362 | + trade.getMaxAmount(), request.getAmount(), request.getDescription(), now); | ||
| 363 | + refundRequest.attach(com.diligrp.cashier.pipeline.Constants.PARAM_MCH_ID, payment.getOutMchId()); // 用于微信服务商模式下,微信子商户信息 | ||
| 364 | + OnlineRefundResponse response = pipeline.sendRefundRequest(refundRequest); | ||
| 365 | + | ||
| 366 | + OnlinePayment refund = OnlinePayment.builder().outMchId(payment.getOutMchId()).tradeId(payment.getTradeId()) | ||
| 367 | + .type(TradeType.REFUND).paymentId(refundId).channelId(payment.getChannelId()) | ||
| 368 | + .payType(payment.getPayType()).pipelineId(payment.getPipelineId()).goods(payment.getGoods() + "-退款") | ||
| 369 | + .amount(request.getAmount()).objectId(payment.getPaymentId()).payerId(payment.getPayerId()) | ||
| 370 | + .finishTime(response.getWhen()).outTradeNo(response.getOutTradeNo()).outPayType(payment.getOutPayType()) | ||
| 371 | + .state(response.getState()).description(request.getDescription()).version(0).createdTime(now).modifiedTime(now).build(); | ||
| 372 | + onlinePaymentDao.insertOnlinePayment(refund); | ||
| 373 | + | ||
| 374 | + if (PaymentState.SUCCESS.equalTo(response.getState())) { | ||
| 375 | + Long newAmount = trade.getAmount() - refund.getAmount(); | ||
| 376 | + TradeStateDTO tradeState = TradeStateDTO.of(trade.getTradeId(), newAmount, TradeState.REFUND, trade.getVersion(), now); | ||
| 377 | + tradeAssistantService.proceedTradeOrder(tradeState); | ||
| 378 | + } else if (!PaymentState.isFinished(response.getState())) { | ||
| 379 | + // 固定周期后,查询退款状态,根据状态完成退款订单 | ||
| 380 | + TaskMessage message = new TaskMessage(TaskMessage.TYPE_CASHIER_REFUND_SCAN, refundId, "1"); | ||
| 381 | + taskMessageSender.sendDelayTaskMessage(message, trade.getTimeout()); | ||
| 382 | + } | ||
| 383 | + | ||
| 384 | + return new OnlineRefundResult(refundId, payment.getPaymentId(), response.getState(), response.getWhen(), response.getMessage()); | ||
| 385 | + } | ||
| 386 | + | ||
| 387 | + @Override | ||
| 388 | + @Transactional(rollbackFor = Exception.class) | ||
| 389 | + public void notifyRefundResult(OnlineRefundResponse response) { | ||
| 390 | + OnlinePayment refund = tradeAssistantService.findByRefundId(response.getRefundId()); | ||
| 391 | + if (PaymentState.isFinished(refund.getState())) { | ||
| 392 | + LOG.warn("Duplicate process online refund order notification for {}:{}", response.getRefundId(), response.getState()); | ||
| 393 | + return; | ||
| 394 | + } | ||
| 395 | + if (!PaymentState.isFinished(response.getState())) { | ||
| 396 | + LOG.warn("Ignore online refund order notification for {}:{}", response.getRefundId(), response.getState()); | ||
| 397 | + return; | ||
| 398 | + } | ||
| 399 | + LOG.info("Processing online refund order notification for {}:{}", response.getRefundId(), response.getState()); | ||
| 400 | + | ||
| 401 | + LocalDateTime now = LocalDateTime.now(); | ||
| 402 | + TradeOrder trade = tradeAssistantService.findByTradeId(refund.getTradeId()); | ||
| 403 | + PaymentStateDTO refundDTO = PaymentStateDTO.builder().paymentId(refund.getPaymentId()) | ||
| 404 | + .outTradeNo(response.getOutTradeNo()).finishTime(response.getWhen()).state(response.getState()) | ||
| 405 | + .description(response.getMessage()).version(refund.getVersion()).modifiedTime(now).build(); | ||
| 406 | + tradeAssistantService.proceedOnlinePayment(refundDTO); | ||
| 407 | + | ||
| 408 | + if (PaymentState.SUCCESS.equalTo(response.getState())) { | ||
| 409 | + Long newAmount = trade.getAmount() - refund.getAmount(); | ||
| 410 | + TradeStateDTO tradeState = TradeStateDTO.of(trade.getTradeId(), newAmount, TradeState.REFUND, trade.getVersion(), now); | ||
| 411 | + tradeAssistantService.proceedTradeOrder(tradeState); | ||
| 412 | + } | ||
| 413 | + | ||
| 414 | + OnlineRefundResult refundResult = new OnlineRefundResult(response.getRefundId(), refund.getObjectId(), | ||
| 415 | + response.getState(), response.getWhen(), response.getMessage()); | ||
| 416 | + paymentResultManager.notifyRefundResult(refund.getNotifyUrl(), refundResult); | ||
| 417 | + } | ||
| 418 | + | ||
| 419 | + @Override | ||
| 420 | + public OnlineRefundResult queryRefundState(String refundId, String mode) { | ||
| 421 | + OnlinePayment refund = tradeAssistantService.findByRefundId(refundId); | ||
| 422 | + | ||
| 423 | + LOG.debug("Query online refund order[{}-{}] state...", refundId, refund.getState()); | ||
| 424 | + // 微信支付通知较为及时和安全,非特殊情况可使用offline模式;一些本地状态与微信状态不一致的"异常订单"可使用online模式同步状态 | ||
| 425 | + if (!PaymentState.isFinished(refund.getState()) && "online".equalsIgnoreCase(mode)) { | ||
| 426 | + WechatPipeline pipeline = paymentPipelineManager.findPipelineById(refund.getPipelineId(), WechatPipeline.class); | ||
| 427 | + OnlineRefundOrder order = new OnlineRefundOrder(refundId, refund.getOutTradeNo()); | ||
| 428 | + // 用于微信服务商模式下,微信子商户信息 | ||
| 429 | + order.attach(com.diligrp.cashier.pipeline.Constants.PARAM_MCH_ID, refund.getOutMchId()); | ||
| 430 | + OnlineRefundResponse response = pipeline.queryRefundResponse(order); | ||
| 431 | + return new OnlineRefundResult(refundId, refund.getObjectId(), response.getState(), response.getWhen(), response.getMessage()); | ||
| 432 | + } | ||
| 433 | + return new OnlineRefundResult(refundId, refund.getObjectId(), refund.getState(), refund.getFinishTime(), refund.getDescription()); | ||
| 434 | + } | ||
| 190 | } | 435 | } |
cashier-trade/src/main/java/com/diligrp/cashier/trade/service/impl/TradeAssistantServiceImpl.java
| @@ -15,7 +15,7 @@ import com.diligrp.cashier.trade.exception.TradePaymentException; | @@ -15,7 +15,7 @@ import com.diligrp.cashier.trade.exception.TradePaymentException; | ||
| 15 | import com.diligrp.cashier.trade.model.OnlinePayment; | 15 | import com.diligrp.cashier.trade.model.OnlinePayment; |
| 16 | import com.diligrp.cashier.trade.model.TradeOrder; | 16 | import com.diligrp.cashier.trade.model.TradeOrder; |
| 17 | import com.diligrp.cashier.trade.service.ITradeAssistantService; | 17 | import com.diligrp.cashier.trade.service.ITradeAssistantService; |
| 18 | -import com.diligrp.cashier.trade.type.TradeState; | 18 | +import com.diligrp.cashier.trade.type.TradeType; |
| 19 | import jakarta.annotation.Resource; | 19 | import jakarta.annotation.Resource; |
| 20 | import org.slf4j.Logger; | 20 | import org.slf4j.Logger; |
| 21 | import org.slf4j.LoggerFactory; | 21 | import org.slf4j.LoggerFactory; |
| @@ -44,7 +44,7 @@ public class TradeAssistantServiceImpl implements ITradeAssistantService { | @@ -44,7 +44,7 @@ public class TradeAssistantServiceImpl implements ITradeAssistantService { | ||
| 44 | @Override | 44 | @Override |
| 45 | public TradeOrder findByTradeId(String tradeId) { | 45 | public TradeOrder findByTradeId(String tradeId) { |
| 46 | Optional<TradeOrder> tradeOpt = tradeOrderDao.findByTradeId(tradeId); | 46 | Optional<TradeOrder> tradeOpt = tradeOrderDao.findByTradeId(tradeId); |
| 47 | - return tradeOpt.orElseThrow(() -> new TradePaymentException(ErrorCode.OBJECT_NOT_FOUND, "支付订单不存在")); | 47 | + return tradeOpt.orElseThrow(() -> new TradePaymentException(ErrorCode.OBJECT_NOT_FOUND, "交易订单不存在")); |
| 48 | } | 48 | } |
| 49 | 49 | ||
| 50 | @Override | 50 | @Override |
| @@ -58,46 +58,41 @@ public class TradeAssistantServiceImpl implements ITradeAssistantService { | @@ -58,46 +58,41 @@ public class TradeAssistantServiceImpl implements ITradeAssistantService { | ||
| 58 | * 关闭交易订单下状态为支付中的支付订单, 园区卡支付不存在支付中的订单 | 58 | * 关闭交易订单下状态为支付中的支付订单, 园区卡支付不存在支付中的订单 |
| 59 | * | 59 | * |
| 60 | * 独立数据库事务, 避免远程支付通道关单后, 支付订单状态仍然为支付中 | 60 | * 独立数据库事务, 避免远程支付通道关单后, 支付订单状态仍然为支付中 |
| 61 | - * 任何一笔关闭订单失败都应该返回FALSE,且一笔失败不影响其他能正常的关单操作 | ||
| 62 | */ | 61 | */ |
| 63 | @Override | 62 | @Override |
| 64 | @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) | 63 | @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); | 64 | + public boolean resetTradeOrder(TradeOrder trade) { |
| 65 | + // 查询支付订单所有的支付记录, 关闭支付中的支付记录, 忽略支付失败的支付记录, 存在支付成功的支付记录时不允许继续支付 | ||
| 66 | + List<OnlinePayment> onlinePayments = onlinePaymentDao.listOnlinePayments(trade.getTradeId(), TradeType.TRADE.getCode(), null); | ||
| 67 | + for (OnlinePayment payment : onlinePayments) { | ||
| 68 | + if (PaymentState.PENDING.equalTo(payment.getState()) || PaymentState.PROCESSING.equalTo(payment.getState())) { | ||
| 69 | + PaymentPipeline<?> pipeline = paymentPipelineManager.findPipelineById(payment.getPipelineId(), PaymentPipeline.class); | ||
| 77 | if (pipeline instanceof OnlinePipeline<?> onlinePipeline) { | 70 | if (pipeline instanceof OnlinePipeline<?> onlinePipeline) { |
| 78 | LocalDateTime now = LocalDateTime.now(); | 71 | LocalDateTime now = LocalDateTime.now(); |
| 79 | - PaymentStateDTO paymentStateDTO = PaymentStateDTO.builder().paymentId(onlinePayment.getPaymentId()) | 72 | + PaymentStateDTO paymentStateDTO = PaymentStateDTO.builder().paymentId(payment.getPaymentId()) |
| 80 | .finishTime(now).state(PaymentState.FAILED).description("主动关闭支付中的订单") | 73 | .finishTime(now).state(PaymentState.FAILED).description("主动关闭支付中的订单") |
| 81 | - .version(onlinePayment.getVersion()).modifiedTime(now).build(); | 74 | + .version(payment.getVersion()).modifiedTime(now).build(); |
| 82 | proceedOnlinePayment(paymentStateDTO); | 75 | proceedOnlinePayment(paymentStateDTO); |
| 83 | - OnlinePrepayOrder prepayOrder = new OnlinePrepayOrder(onlinePayment.getPaymentId(), onlinePayment.getOutTradeNo()); | 76 | + OnlinePrepayOrder prepayOrder = new OnlinePrepayOrder(payment.getPaymentId(), payment.getOutTradeNo()); |
| 84 | // 微信服务商模式下, outMchId为签约子商户; 园区卡支付时, outMchId为市场ID | 77 | // 微信服务商模式下, outMchId为签约子商户; 园区卡支付时, outMchId为市场ID |
| 85 | - prepayOrder.attach(Constants.PARAM_MCH_ID, onlinePayment.getOutMchId()); | 78 | + prepayOrder.attach(Constants.PARAM_MCH_ID, payment.getOutMchId()); |
| 86 | onlinePipeline.closePrepayOrder(prepayOrder); | 79 | onlinePipeline.closePrepayOrder(prepayOrder); |
| 87 | } | 80 | } |
| 88 | - // 园区卡支付不存在支付中的订单 | ||
| 89 | - } catch (Exception ex) { | ||
| 90 | - result = false; | ||
| 91 | - LOG.error("关闭支付订单失败: {}", onlinePayment.getPaymentId(), ex); | 81 | + // 园区卡支付通道不会存在支付中的记录 |
| 82 | + } else if (PaymentState.SUCCESS.equalTo(payment.getState())) { | ||
| 83 | + return false; | ||
| 84 | + } else if (PaymentState.FAILED.equalTo(payment.getState())) { | ||
| 85 | + continue; | ||
| 92 | } | 86 | } |
| 93 | } | 87 | } |
| 94 | - return result; | 88 | + return true; |
| 95 | } | 89 | } |
| 96 | 90 | ||
| 97 | @Override | 91 | @Override |
| 98 | public OnlinePayment findByPaymentId(String paymentId) { | 92 | public OnlinePayment findByPaymentId(String paymentId) { |
| 99 | - Optional<OnlinePayment> paymentOpt = onlinePaymentDao.findByPaymentId(paymentId); | ||
| 100 | - return paymentOpt.orElseThrow(() -> new TradePaymentException(ErrorCode.OBJECT_NOT_FOUND, "支付订单不存在")); | 93 | + return onlinePaymentDao.findByPaymentId(paymentId) |
| 94 | + .filter(p -> TradeType.TRADE.equalTo(p.getType())) | ||
| 95 | + .orElseThrow(() -> new TradePaymentException(ErrorCode.OBJECT_NOT_FOUND, "支付订单不存在")); | ||
| 101 | } | 96 | } |
| 102 | 97 | ||
| 103 | @Override | 98 | @Override |
| @@ -106,4 +101,11 @@ public class TradeAssistantServiceImpl implements ITradeAssistantService { | @@ -106,4 +101,11 @@ public class TradeAssistantServiceImpl implements ITradeAssistantService { | ||
| 106 | throw new TradePaymentException(ErrorCode.SYSTEM_BUSY_ERROR, ErrorCode.MESSAGE_SYSTEM_BUSY); | 101 | throw new TradePaymentException(ErrorCode.SYSTEM_BUSY_ERROR, ErrorCode.MESSAGE_SYSTEM_BUSY); |
| 107 | } | 102 | } |
| 108 | } | 103 | } |
| 104 | + | ||
| 105 | + @Override | ||
| 106 | + public OnlinePayment findByRefundId(String refundId) { | ||
| 107 | + return onlinePaymentDao.findByPaymentId(refundId) | ||
| 108 | + .filter(p -> TradeType.REFUND.equalTo(p.getType())) | ||
| 109 | + .orElseThrow(() -> new TradePaymentException(ErrorCode.OBJECT_NOT_FOUND, "退款订单不存在")); | ||
| 110 | + } | ||
| 109 | } | 111 | } |
cashier-trade/src/main/java/com/diligrp/cashier/trade/type/TradeState.java
| 1 | package com.diligrp.cashier.trade.type; | 1 | package com.diligrp.cashier.trade.type; |
| 2 | 2 | ||
| 3 | -import com.diligrp.cashier.pipeline.type.PaymentState; | ||
| 4 | import com.diligrp.cashier.shared.type.IEnumType; | 3 | import com.diligrp.cashier.shared.type.IEnumType; |
| 5 | 4 | ||
| 6 | import java.util.Arrays; | 5 | import java.util.Arrays; |
| @@ -15,8 +14,6 @@ public enum TradeState implements IEnumType { | @@ -15,8 +14,6 @@ public enum TradeState implements IEnumType { | ||
| 15 | 14 | ||
| 16 | PENDING("待处理", 1), | 15 | PENDING("待处理", 1), |
| 17 | 16 | ||
| 18 | - PROCESSING("处理中", 2), | ||
| 19 | - | ||
| 20 | SUCCESS("交易成功", 4), | 17 | SUCCESS("交易成功", 4), |
| 21 | 18 | ||
| 22 | REFUND("交易退款", 5), | 19 | REFUND("交易退款", 5), |
| @@ -50,14 +47,6 @@ public enum TradeState implements IEnumType { | @@ -50,14 +47,6 @@ public enum TradeState implements IEnumType { | ||
| 50 | return Arrays.asList(TradeState.values()); | 47 | return Arrays.asList(TradeState.values()); |
| 51 | } | 48 | } |
| 52 | 49 | ||
| 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) { | 50 | public static boolean isFinished(int code) { |
| 62 | return TradeState.SUCCESS.equalTo(code) || TradeState.FAILED.equalTo(code) || | 51 | return TradeState.SUCCESS.equalTo(code) || TradeState.FAILED.equalTo(code) || |
| 63 | TradeState.REFUND.equalTo(code) || TradeState.CLOSED.equalTo(code); | 52 | TradeState.REFUND.equalTo(code) || TradeState.CLOSED.equalTo(code); |
cashier-trade/src/main/resources/com/diligrp/cashier/dao/mapper/IOnlinePaymentDao.xml
| @@ -20,6 +20,7 @@ | @@ -20,6 +20,7 @@ | ||
| 20 | <result column="out_trade_no" property="outTradeNo"/> | 20 | <result column="out_trade_no" property="outTradeNo"/> |
| 21 | <result column="out_pay_type" property="outPayType"/> | 21 | <result column="out_pay_type" property="outPayType"/> |
| 22 | <result column="state" property="state"/> | 22 | <result column="state" property="state"/> |
| 23 | + <result column="notify_url" property="notifyUrl"/> | ||
| 23 | <result column="description" property="description"/> | 24 | <result column="description" property="description"/> |
| 24 | <result column="version" property="version"/> | 25 | <result column="version" property="version"/> |
| 25 | <result column="created_time" property="createdTime"/> | 26 | <result column="created_time" property="createdTime"/> |
| @@ -29,11 +30,11 @@ | @@ -29,11 +30,11 @@ | ||
| 29 | <insert id="insertOnlinePayment" parameterType="com.diligrp.cashier.trade.model.OnlinePayment"> | 30 | <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 | 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 | goods, amount, object_id, payer_id, finish_time, out_trade_no, out_pay_type, |
| 32 | - state, description, version, created_time, modified_time) | 33 | + state, notify_url, description, version, created_time, modified_time) |
| 33 | VALUES | 34 | VALUES |
| 34 | (#{outMchId}, #{tradeId}, #{type}, #{paymentId}, #{channelId}, #{payType}, #{pipelineId}, | 35 | (#{outMchId}, #{tradeId}, #{type}, #{paymentId}, #{channelId}, #{payType}, #{pipelineId}, |
| 35 | #{goods}, #{amount}, #{objectId}, #{payerId}, #{finishTime}, #{outTradeNo}, #{outPayType} | 36 | #{goods}, #{amount}, #{objectId}, #{payerId}, #{finishTime}, #{outTradeNo}, #{outPayType} |
| 36 | - #{state}, #{description}, #{version}, #{createdTime}, #{modifiedTime}) | 37 | + #{state}, #{notifyUrl}, #{description}, #{version}, #{createdTime}, #{modifiedTime}) |
| 37 | </insert> | 38 | </insert> |
| 38 | 39 | ||
| 39 | <select id="findByPaymentId" parameterType="string" resultMap="OnlinePaymentMap"> | 40 | <select id="findByPaymentId" parameterType="string" resultMap="OnlinePaymentMap"> |
| @@ -67,8 +68,11 @@ | @@ -67,8 +68,11 @@ | ||
| 67 | payment_id = #{paymentId} AND version = #{version} | 68 | payment_id = #{paymentId} AND version = #{version} |
| 68 | </update> | 69 | </update> |
| 69 | 70 | ||
| 70 | - <select id="findByTradeId" resultMap="OnlinePaymentMap"> | 71 | + <select id="listOnlinePayments" resultMap="OnlinePaymentMap"> |
| 71 | SELECT * FROM upay_online_payment WHERE trade_id = #{tradeId} | 72 | SELECT * FROM upay_online_payment WHERE trade_id = #{tradeId} |
| 73 | + <if test="type != null"> | ||
| 74 | + AND type = #{type} | ||
| 75 | + </if> | ||
| 72 | <if test="state != null"> | 76 | <if test="state != null"> |
| 73 | AND state = #{state} | 77 | AND state = #{state} |
| 74 | </if> | 78 | </if> |
scripts/dili-cashier.sql
| @@ -26,14 +26,14 @@ CREATE TABLE `upay_trade_order` ( | @@ -26,14 +26,14 @@ CREATE TABLE `upay_trade_order` ( | ||
| 26 | `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', | 26 | `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', |
| 27 | `mch_id` BIGINT NOT NULL COMMENT '商户ID', | 27 | `mch_id` BIGINT NOT NULL COMMENT '商户ID', |
| 28 | `trade_id` VARCHAR(40) NOT NULL COMMENT '交易ID', | 28 | `trade_id` VARCHAR(40) NOT NULL COMMENT '交易ID', |
| 29 | - `type` TINYINT UNSIGNED NOT NULL COMMENT '业务类型', -- 购买会员 | 29 | + `type` TINYINT UNSIGNED NOT NULL COMMENT '订单类型', |
| 30 | `out_trade_no` VARCHAR(40) COMMENT '外部流水号', -- 商户流水号 | 30 | `out_trade_no` VARCHAR(40) COMMENT '外部流水号', -- 商户流水号 |
| 31 | `amount` BIGINT NOT NULL COMMENT '金额-分', | 31 | `amount` BIGINT NOT NULL COMMENT '金额-分', |
| 32 | `max_amount` BIGINT NOT NULL COMMENT '初始金额-分', | 32 | `max_amount` BIGINT NOT NULL COMMENT '初始金额-分', |
| 33 | `goods` VARCHAR(128) NOT NULL COMMENT '商品描述', | 33 | `goods` VARCHAR(128) NOT NULL COMMENT '商品描述', |
| 34 | `timeout` INTEGER UNSIGNED NOT NULL COMMENT '超时间隔时间-秒', | 34 | `timeout` INTEGER UNSIGNED NOT NULL COMMENT '超时间隔时间-秒', |
| 35 | `state` TINYINT UNSIGNED NOT NULL COMMENT '交易状态', | 35 | `state` TINYINT UNSIGNED NOT NULL COMMENT '交易状态', |
| 36 | - `notify_url` VARCHAR(128) COMMENT '业务回调链接', | 36 | + `notify_url` VARCHAR(128) COMMENT '业务回调地址', |
| 37 | `description` VARCHAR(128) COMMENT '交易备注', | 37 | `description` VARCHAR(128) COMMENT '交易备注', |
| 38 | `attach` VARCHAR(255) COMMENT '附加数据', | 38 | `attach` VARCHAR(255) COMMENT '附加数据', |
| 39 | `source` TINYINT UNSIGNED COMMENT '订单来源', | 39 | `source` TINYINT UNSIGNED COMMENT '订单来源', |
| @@ -56,7 +56,7 @@ CREATE TABLE `upay_online_payment` ( | @@ -56,7 +56,7 @@ CREATE TABLE `upay_online_payment` ( | ||
| 56 | `trade_id` VARCHAR(40) NOT NULL COMMENT '交易ID', | 56 | `trade_id` VARCHAR(40) NOT NULL COMMENT '交易ID', |
| 57 | `type` TINYINT UNSIGNED NOT NULL COMMENT '交易类型', | 57 | `type` TINYINT UNSIGNED NOT NULL COMMENT '交易类型', |
| 58 | `payment_id` VARCHAR(40) NOT NULL COMMENT '支付ID', | 58 | `payment_id` VARCHAR(40) NOT NULL COMMENT '支付ID', |
| 59 | - `channel_id` TINYINT UNSIGNED NOT NULL COMMENT '支付通道', | 59 | + `channel_id` TINYINT UNSIGNED NOT NULL COMMENT '支付渠道', |
| 60 | `pay_type` TINYINT UNSIGNED NOT NULL COMMENT '支付方式', -- NATIVE MINIPRO | 60 | `pay_type` TINYINT UNSIGNED NOT NULL COMMENT '支付方式', -- NATIVE MINIPRO |
| 61 | `pipeline_id` BIGINT NOT NULL COMMENT '通道ID', | 61 | `pipeline_id` BIGINT NOT NULL COMMENT '通道ID', |
| 62 | `goods` VARCHAR(128) NOT NULL COMMENT '商品描述', | 62 | `goods` VARCHAR(128) NOT NULL COMMENT '商品描述', |
| @@ -67,6 +67,7 @@ CREATE TABLE `upay_online_payment` ( | @@ -67,6 +67,7 @@ CREATE TABLE `upay_online_payment` ( | ||
| 67 | `out_trade_no` VARCHAR(40) COMMENT '通道流水号', | 67 | `out_trade_no` VARCHAR(40) COMMENT '通道流水号', |
| 68 | `out_pay_type` TINYINT UNSIGNED NOT NULL COMMENT '实际支付方式', -- 银行聚合支付时使用 | 68 | `out_pay_type` TINYINT UNSIGNED NOT NULL COMMENT '实际支付方式', -- 银行聚合支付时使用 |
| 69 | `state` TINYINT UNSIGNED NOT NULL COMMENT '申请状态', | 69 | `state` TINYINT UNSIGNED NOT NULL COMMENT '申请状态', |
| 70 | + `notify_url` VARCHAR(128) COMMENT '业务回调地址', | ||
| 70 | `description` VARCHAR(256) COMMENT '交易备注', | 71 | `description` VARCHAR(256) COMMENT '交易备注', |
| 71 | `version` INTEGER UNSIGNED NOT NULL COMMENT '数据版本号', | 72 | `version` INTEGER UNSIGNED NOT NULL COMMENT '数据版本号', |
| 72 | `created_time` DATETIME COMMENT '创建时间', | 73 | `created_time` DATETIME COMMENT '创建时间', |