diff --git a/rc-service/rc-inner-api/order-inner/order-inner-model/src/main/java/com/red/circle/order/inner/model/enums/MonthlyRechargeType.java b/rc-service/rc-inner-api/order-inner/order-inner-model/src/main/java/com/red/circle/order/inner/model/enums/MonthlyRechargeType.java index 9a4717a0..71dd5d4e 100644 --- a/rc-service/rc-inner-api/order-inner/order-inner-model/src/main/java/com/red/circle/order/inner/model/enums/MonthlyRechargeType.java +++ b/rc-service/rc-inner-api/order-inner/order-inner-model/src/main/java/com/red/circle/order/inner/model/enums/MonthlyRechargeType.java @@ -19,6 +19,11 @@ public enum MonthlyRechargeType { */ PAYER_MAX, + /** + * MifaPay + */ + MIFA_PAY, + /** * Airwallex. */ diff --git a/rc-service/rc-service-order/order-adapter/src/main/java/com/red/circle/order/adapter/app/PayServerNoticeController.java b/rc-service/rc-service-order/order-adapter/src/main/java/com/red/circle/order/adapter/app/PayServerNoticeController.java index 23f4d085..50a03275 100644 --- a/rc-service/rc-service-order/order-adapter/src/main/java/com/red/circle/order/adapter/app/PayServerNoticeController.java +++ b/rc-service/rc-service-order/order-adapter/src/main/java/com/red/circle/order/adapter/app/PayServerNoticeController.java @@ -81,6 +81,15 @@ public class PayServerNoticeController extends BaseController { return inAppPurchaseProductService.payerMaxServerNoticeReceivePaymentOld(body); } + /** + * MiFaPay 收款回调地址. + */ + @IgnoreResultResponse + @PostMapping(value = "/mifa_pay/receive_payment", consumes = MediaType.APPLICATION_JSON_VALUE) + public String miFaPayReceivePayment(@RequestBody(required = false) byte[] body) { + return inAppPurchaseProductService.miFaPayServerNoticeReceivePayment(body); + } + /** * payerMax 退款回调地址-自定义. */ diff --git a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/MiFaPayServerNoticeReceivePaymentCmdExe.java b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/MiFaPayServerNoticeReceivePaymentCmdExe.java new file mode 100644 index 00000000..7b6d1738 --- /dev/null +++ b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/MiFaPayServerNoticeReceivePaymentCmdExe.java @@ -0,0 +1,145 @@ +package com.red.circle.order.app.command.pay.web; + +import com.red.circle.component.pay.paymax.PayMaxUtils; +import com.red.circle.order.app.common.InAppPurchaseCommon; +import com.red.circle.order.domain.order.MiFaPayReceivePaymentNotice; +import com.red.circle.order.infra.config.MiFaPayProperties; +import com.red.circle.order.infra.database.mongo.order.InAppPurchaseCollectionReceiptService; +import com.red.circle.order.infra.database.mongo.order.InAppPurchaseDetailsService; +import com.red.circle.order.infra.database.mongo.order.entity.InAppPurchaseDetails; +import com.red.circle.order.infra.database.mongo.order.entity.assist.InAppPurchaseEventNotice; +import com.red.circle.order.inner.model.enums.inapp.InAppPurchaseStatus; +import com.red.circle.tool.core.date.TimestampUtils; +import com.red.circle.tool.core.text.StringUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +/** + * MiFaPay 支付回调通知处理 + * + * @author system + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class MiFaPayServerNoticeReceivePaymentCmdExe { + + private static final String SUCCESS = "SUCCESS"; + private static final String FAIL = "FAIL"; + + private final MiFaPayProperties miFaPayProperties; + private final InAppPurchaseCommon inAppPurchaseCommon; + private final InAppPurchaseDetailsService inAppPurchaseDetailsService; + private final InAppPurchaseCollectionReceiptService inAppPurchaseCollectionReceiptService; + + /** + * 处理MiFaPay支付回调 + * + * @param body 请求体 + * @return SUCCESS 或 FAIL + */ + public String execute(byte[] body) { + // 参数校验 + if (Objects.isNull(body) || body.length == 0) { + log.error("MiFaPay回调参数为空"); + return FAIL; + } + + String bodyParam = new String(body, StandardCharsets.UTF_8); + log.warn("MiFaPay回调通知: body={}", bodyParam); + + // 解析回调数据 + MiFaPayReceivePaymentNotice notice = MiFaPayReceivePaymentNotice.serializable(bodyParam); + if (Objects.isNull(notice) || Objects.isNull(notice.getNoticeData())) { + log.error("MiFaPay回调数据解析失败: {}", bodyParam); + return FAIL; + } + + // 验证签名(对data字段验签) + if (StringUtils.isBlank(notice.getSign()) || StringUtils.isBlank(notice.getData())) { + log.error("MiFaPay回调签名或data为空"); + return FAIL; + } + + boolean validSign = PayMaxUtils.validSignRsa( + notice.getData(), + notice.getSign(), + miFaPayProperties.getPlatformRsaPublicKey() + ); + + if (!validSign) { + log.error("MiFaPay回调验签失败: sign={}, data={}", notice.getSign(), notice.getData()); + return FAIL; + } + + MiFaPayReceivePaymentNotice.MiFaPayNoticeData noticeData = notice.getNoticeData(); + + // 查询订单 + InAppPurchaseDetails order = inAppPurchaseDetailsService.getById(noticeData.getOrderId()); + if (Objects.isNull(order)) { + log.error("MiFaPay回调未找到订单: orderId={}", noticeData.getOrderId()); + return FAIL; + } + + // 检查订单状态,避免重复处理 + if (!Objects.equals(order.getStatus(), InAppPurchaseStatus.CREATE)) { + log.warn("MiFaPay订单已处理: orderId={}, status={}", order.getId(), order.getStatus()); + return SUCCESS; + } + + // 记录回调通知 + inAppPurchaseDetailsService.addPayNotices(order.getId(), + new InAppPurchaseEventNotice() + .setEventId(notice.getSign()) + .setNoticeType("mifapay-" + noticeData.getOrderStatus()) + .setNoticeData(notice) + .setCreateTime(TimestampUtils.now()) + ); + + // 处理支付成功 + if (noticeData.isSuccess()) { + if (!Objects.equals(order.statusEqCreate(), Boolean.TRUE)) { + log.warn("MiFaPay订单状态不是CREATE: orderId={}", order.getId()); + return SUCCESS; + } + + // 收款单 + if (order.receiptTypeEqReceipt()) { + inAppPurchaseCommon.inAppPurchaseReceipt(order); + log.info("MiFaPay收款单处理成功: orderId={}", order.getId()); + return SUCCESS; + } + + // 付款单 + if (order.receiptTypeEqPayment()) { + inAppPurchaseCommon.inAppPurchasePayment(order); + log.info("MiFaPay付款单处理成功: orderId={}", order.getId()); + return SUCCESS; + } + + log.error("MiFaPay订单类型错误: orderId={}", order.getId()); + return FAIL; + } + + // 处理支付失败 + if (noticeData.isFailed()) { + if (order.receiptTypeEqReceipt()) { + inAppPurchaseCollectionReceiptService.updateStatusSuccessFail(order.getId()); + } + inAppPurchaseDetailsService.updateStatusReason( + order.getId(), + InAppPurchaseStatus.FAIL, + String.format("MiFaPay支付失败: %s", noticeData.getOrderStatus()) + ); + log.info("MiFaPay支付失败处理完成: orderId={}", order.getId()); + return SUCCESS; + } + + log.error("MiFaPay未知订单状态: orderId={}, status={}", order.getId(), noticeData.getOrderStatus()); + return SUCCESS; + } +} diff --git a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/strategy/MiFaPayWebPayPlaceAnOrderStrategy.java b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/strategy/MiFaPayWebPayPlaceAnOrderStrategy.java index f72cd8e7..3e498e3c 100644 --- a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/strategy/MiFaPayWebPayPlaceAnOrderStrategy.java +++ b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/strategy/MiFaPayWebPayPlaceAnOrderStrategy.java @@ -104,7 +104,7 @@ public class MiFaPayWebPayPlaceAnOrderStrategy implements PayWebPlaceAnOrderStra .payType(details.getFactoryChannel()) .userIp(details.getRequestIp()) .returnUrl(details.getSuccessRedirectUrl()) - .notifyUrl("https://your-domain.com/api/mifapay/notify") + .notifyUrl(MiFaPayService.NOTIFY_URL) .language("en") .email(memberInfo.getEmail()) .countryCode(replaceCountryCode(details.getCountryCode())) diff --git a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/service/InAppPurchaseProductServiceImpl.java b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/service/InAppPurchaseProductServiceImpl.java index 1e27926a..dfb96168 100644 --- a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/service/InAppPurchaseProductServiceImpl.java +++ b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/service/InAppPurchaseProductServiceImpl.java @@ -28,6 +28,7 @@ import com.red.circle.order.app.command.pay.web.PayWebPlaceAnOrderCmdExe; import com.red.circle.order.app.command.pay.web.PayerMaxServerNoticeReceivePaymentCmdExe; import com.red.circle.order.app.command.pay.web.PayerMaxServerNoticeRefundNoticeCmdExe; import com.red.circle.order.app.command.pay.web.PaynicornServerNoticeCmdExe; +import com.red.circle.order.app.command.pay.web.MiFaPayServerNoticeReceivePaymentCmdExe; import com.red.circle.order.app.command.pay.web.ReceiptPayWebPlaceAnOrderReceiptCmdExe; import com.red.circle.order.app.dto.clientobject.PayMaxResponseCO; import com.red.circle.order.app.dto.clientobject.PayerMaxResponseV2CO; @@ -69,6 +70,7 @@ public class InAppPurchaseProductServiceImpl implements InAppPurchaseProductServ private final ReceiptPayWebPlaceAnOrderReceiptCmdExe receiptPayWebPlaceAnOrderReceiptCmdExe; private final PayerMaxServerNoticeRefundNoticeCmdExe payerMaxServerNoticeRefundNoticeCmdExe; private final PayerMaxServerNoticeReceivePaymentCmdExe payerMaxServerNoticeReceivePaymentCmdExe; + private final MiFaPayServerNoticeReceivePaymentCmdExe miFaPayServerNoticeReceivePaymentCmdExe; private final TaskMqMessage taskMqMessage; @Override @@ -177,4 +179,9 @@ public class InAppPurchaseProductServiceImpl implements InAppPurchaseProductServ clipspayServerNoticeCmdExe.execute(body); } + @Override + public String miFaPayServerNoticeReceivePayment(byte[] body) { + return miFaPayServerNoticeReceivePaymentCmdExe.execute(body); + } + } diff --git a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/service/MiFaPayService.java b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/service/MiFaPayService.java index 7fa41712..40123c51 100644 --- a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/service/MiFaPayService.java +++ b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/service/MiFaPayService.java @@ -25,6 +25,7 @@ public class MiFaPayService { /** * 下单接口路径 */ + public static final String NOTIFY_URL = "https://api.likeichat.com/order/play-server-notice/mifa_pay/receive_payment"; private static final String PLACE_ORDER_PATH = "/paygateway/mbpay/order/en_v2"; private final String active; diff --git a/rc-service/rc-service-order/order-client/src/main/java/com/red/circle/order/app/service/InAppPurchaseProductService.java b/rc-service/rc-service-order/order-client/src/main/java/com/red/circle/order/app/service/InAppPurchaseProductService.java index 642bcf7c..b4287661 100644 --- a/rc-service/rc-service-order/order-client/src/main/java/com/red/circle/order/app/service/InAppPurchaseProductService.java +++ b/rc-service/rc-service-order/order-client/src/main/java/com/red/circle/order/app/service/InAppPurchaseProductService.java @@ -46,4 +46,9 @@ public interface InAppPurchaseProductService { void clipspayServerNotice(Map body); + /** + * MiFaPay支付回调通知 + */ + String miFaPayServerNoticeReceivePayment(byte[] body); + } diff --git a/rc-service/rc-service-order/order-domain/src/main/java/com/red/circle/order/domain/order/MiFaPayReceivePaymentNotice.java b/rc-service/rc-service-order/order-domain/src/main/java/com/red/circle/order/domain/order/MiFaPayReceivePaymentNotice.java new file mode 100644 index 00000000..399f71ee --- /dev/null +++ b/rc-service/rc-service-order/order-domain/src/main/java/com/red/circle/order/domain/order/MiFaPayReceivePaymentNotice.java @@ -0,0 +1,151 @@ +package com.red.circle.order.domain.order; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.red.circle.tool.core.json.JacksonUtils; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * MiFaPay支付回调通知实体 + * + * @author system + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class MiFaPayReceivePaymentNotice implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 内层通知数据(JSON字符串) + */ + private String data; + + /** + * 商户标识 + */ + private String merAccount; + + /** + * 响应签名(平台私钥对 data 签名) + */ + private String sign; + + /** + * 解析后的内层数据 + */ + private MiFaPayNoticeData noticeData; + + /** + * 反序列化 + */ + public static MiFaPayReceivePaymentNotice serializable(String json) { + try { + MiFaPayReceivePaymentNotice notice = JacksonUtils.readValue(json, MiFaPayReceivePaymentNotice.class); + if (notice != null && notice.getData() != null) { + notice.setNoticeData(JacksonUtils.readValue(notice.getData(), MiFaPayNoticeData.class)); + } + return notice; + } catch (Exception e) { + return null; + } + } + + /** + * 内层通知数据结构 + */ + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class MiFaPayNoticeData implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 平台唯一订单号(平台生成,唯一标识) + */ + private String mbOrderId; + + /** + * 商户唯一订单号(与下单时传入的orderId一致,用于匹配商户本地订单) + */ + private String orderId; + + /** + * 支付用户ID(商户系统内用户唯一标识,与下单时传入的memberId一致) + */ + private String memberId; + + /** + * 订单金额(单位:分,如10000=100元,需与商户本地订单金额校验) + */ + private String amount; + + /** + * 交易币种(ISO 4217编码,与下单时传入的currency一致) + */ + private String currency; + + /** + * 下单时间(格式:yyyy-MM-dd HH:mm:ss,与下单接口返回的orderTime一致) + */ + private String orderTime; + + /** + * 支付时间(格式:yyyy-MM-dd HH:mm:ss;orderStatus=FAILED时可为空) + */ + private String payTime; + + /** + * 商品名称(与下单时productInfo中的productName一致) + */ + private String product; + + /** + * 订单状态:SUCCESS=交易成功,FAILED=交易失败 + */ + private String orderStatus; + + /** + * 用户支付类型(与下单时传入的payType一致,未支付时可能为空) + */ + private String payType; + + /** + * 本地金额(单位:分,平台按汇率转换后的本地币种金额) + */ + private String localAmount; + + /** + * 本地交易币种(ISO 4217编码,如HKD/CNY等) + */ + private String localCurrency; + + /** + * 付款人卡片信息(JSON格式) + */ + private String payerInfo; + + /** + * 附加数据(下单时传入则原样返回,未传入则不返回) + */ + private String attach; + + /** + * 判断是否支付成功 + */ + public boolean isSuccess() { + return "SUCCESS".equalsIgnoreCase(orderStatus); + } + + /** + * 判断是否支付失败 + */ + public boolean isFailed() { + return "FAILED".equalsIgnoreCase(orderStatus); + } + } +} diff --git a/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/config/MiFaPayProperties.java b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/config/MiFaPayProperties.java index caf2f103..c36af81d 100644 --- a/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/config/MiFaPayProperties.java +++ b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/config/MiFaPayProperties.java @@ -2,6 +2,7 @@ package com.red.circle.order.infra.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.stereotype.Component; /** @@ -12,6 +13,7 @@ import org.springframework.stereotype.Component; @Data @Component @ConfigurationProperties(prefix = "red-circle.pay.mifa-pay") +@RefreshScope public class MiFaPayProperties { /**