MifaPay回调接口调通
This commit is contained in:
parent
6e80241a59
commit
5ef64ef311
@ -19,6 +19,11 @@ public enum MonthlyRechargeType {
|
||||
*/
|
||||
PAYER_MAX,
|
||||
|
||||
/**
|
||||
* MifaPay
|
||||
*/
|
||||
MIFA_PAY,
|
||||
|
||||
/**
|
||||
* Airwallex.
|
||||
*/
|
||||
|
||||
@ -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 退款回调地址-自定义.
|
||||
*/
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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()))
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -46,4 +46,9 @@ public interface InAppPurchaseProductService {
|
||||
|
||||
void clipspayServerNotice(Map<String, String> body);
|
||||
|
||||
/**
|
||||
* MiFaPay支付回调通知
|
||||
*/
|
||||
String miFaPayServerNoticeReceivePayment(byte[] body);
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user