MifaPay回调接口调通

This commit is contained in:
tianfeng 2026-01-21 18:39:39 +08:00
parent 6e80241a59
commit 5ef64ef311
9 changed files with 326 additions and 1 deletions

View File

@ -19,6 +19,11 @@ public enum MonthlyRechargeType {
*/
PAYER_MAX,
/**
* MifaPay
*/
MIFA_PAY,
/**
* Airwallex.
*/

View File

@ -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 退款回调地址-自定义.
*/

View File

@ -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;
}
}

View File

@ -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()))

View File

@ -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);
}
}

View File

@ -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;

View File

@ -46,4 +46,9 @@ public interface InAppPurchaseProductService {
void clipspayServerNotice(Map<String, String> body);
/**
* MiFaPay支付回调通知
*/
String miFaPayServerNoticeReceivePayment(byte[] body);
}

View File

@ -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:ssorderStatus=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);
}
}
}

View File

@ -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 {
/**