🔔 异步通知(Webhook Notification)
当订单状态发生变化,尤其是在支付成功后,IntPay 会向商户在统一下单接口中提交的 notifyUrl 发起 服务端回调通知(Webhook)。
若商户系统未正确接收或未按约定返回成功响应,IntPay 将按照重试策略进行延迟重发,以提高通知送达率。
异步通知是商户确认最终支付结果的标准依据。
同步跳转页面(ReturnUrl)仅用于前端展示,不应作为订单最终状态判断依据。
一、通知机制说明
在以下情况下,IntPay 可能会向商户系统发起异步通知:
- 支付成功
- 支付失败
- 订单进入待处理状态
- 订单状态发生后续更新(如退款、拒付等)
处理建议
商户系统收到通知后,应按以下顺序处理:
- 校验请求来源与签名
- 解析通知体
- 根据外层状态码
success与data.status进行业务处理 - 幂等更新本地订单状态
- 返回平台要求的成功响应
因为网络波动可能存在多次发送异步通知,强烈建议商户对
reference或data.id建立幂等控制,避免因重复通知造成重复更新。
二、接口说明
| 项目 | 说明 |
|---|---|
| 请求 URL | 使用统一下单接口中的 notifyUrl 参数 |
| 请求方式 | POST |
| Content-Type | application/json |
| 字符编码 | UTF-8 |
| 签名要求 | 必须验签 |
| 重试机制 | 商户未正确返回成功响应时,平台会延迟重试 |
| 响应机制 | 返回 HTTP 200 OK |
重要说明
notifyUrl必须为平台可公网访问的有效地址- 若
notifyUrl不可达、超时、返回异常状态码或返回格式不符合要求,商户可能无法及时收到支付结果通知 - 商户系统应确保通知接口具备高可用、快速响应与重复处理能力
三、通知参数
3.1 通知示例
json
{
"requestId": "75daa341-66fd-4d87-949b-28f7f0ef6d64",
"success": true,
"data": {
"reference": "SAIDF01239-1230",
"requiresAction": false,
"amount": 100,
"currency": "USD",
"id": "P1231823",
"failureMessage": "fail",
"status": "FAILED"
},
"timestamp": "2025-10-10T16:39:23.765+08:00"
}四、通知处理规则
4.1 顶层处理逻辑
收到通知后,商户应先判断顶层字段 success:
- 当
success = false时,表示本次接口调用失败 - 当
success = true时,表示本次接口调用成功,此时需进一步根据data.status判断订单业务状态
4.2 处理说明
success = false
表示此次通知在接口层未成功执行,通常可能由以下原因导致:
- 参数错误
- 签名错误
- 权限校验失败
- 通道关闭
- 通道限额
- 商户配置异常
此时建议:
- 记录完整通知报文与请求头
- 读取错误信息字段(如返回体中存在
message字段) - 不要直接以此更新订单为支付成功
- 结合日志与平台返回内容排查问题
success = true
表示此次通知请求已成功送达并通过接口层校验。
此时订单的真实业务状态应以 data.status 为准。
五、顶层结构字段说明
| 字段 | 类型 | 示例值 | 说明 |
|---|---|---|---|
| requestId | string | 75daa341-66fd-4d87-949b-28f7f0ef6d64 | 本次通知的唯一追踪 ID,由服务端生成,用于日志追踪、链路排查与问题定位。每次通知通常唯一。 |
| success | boolean | true | 表示本次接口调用是否成功(接口层面)。true 表示通知调用成功并已返回业务结果;false 表示接口调用异常,如签名错误、参数缺失、权限拒绝、通道关闭等。 |
| data | object | {...} | 支付结果详情对象。当 success = true 时,商户应重点解析该对象中的业务字段。 |
| timestamp | string (ISO 8601) | 2025-10-10T16:39:23.765+08:00 | 通知生成时间,采用 ISO 8601 标准格式,包含毫秒与时区偏移。 |
六、data 对象字段说明(支付结果详情)
| 字段 | 类型 | 示例值 | 说明 |
|---|---|---|---|
| id | string | P1231823 | IntPay 平台交易号。用于标识平台侧唯一交易,可用于对账、查询与问题排查。 |
| reference | string | SAIDF01239-1230 | 商户订单号或交易参考号,由商户系统生成,用于关联商户内部订单。 |
| requiresAction | boolean | false | 是否仍需用户执行额外操作。通常用于表示是否需要完成额外验证步骤(如 3DS、OTP 等)。 |
| amount | number | 100 | 支付金额,单位为最小货币单位。例如 USD 下 100 表示 1.00 USD。 |
| currency | string | USD | 支付币种,遵循 ISO 4217 三位字母代码标准。 |
| failureMessage | string | fail | 失败原因描述。当交易失败时返回;成功状态下通常为空、为 null 或被省略。 |
| status | string | FAILED | 当前订单状态枚举值,商户应以此字段作为订单状态更新依据。 |
七、交易状态说明
状态枚举
| 状态值 | 说明 | 商户建议 |
|---|---|---|
PENDING | 待处理。支付请求已创建,正在等待用户操作、发卡行响应或通道处理。 | 订单保持处理中,不要提前判定最终结果。 |
REQUIRES_ACTION | 需要下一步操作。通常表示用户需完成额外验证,如 3D Secure、OTP 或其他身份校验。 | 引导用户继续完成验证流程,并等待后续结果通知。 |
SUCCEEDED | 支付成功。发卡行授权通过,交易已成功完成。 | 将订单更新为成功,进入发货、入账或后续履约流程。 |
FAILED | 支付失败。可能由于发卡行拒付、风控拦截、余额不足、卡信息错误或其他原因导致。 | 将订单更新为失败,并视业务需要记录失败原因。 |
CANCELED | 已取消。订单被商户、用户或系统主动取消,通常未完成实际扣款。 | 将订单更新为已取消,结束本次支付流程。 |
REFUND_PENDING | 退款处理中。退款请求已提交,正在等待银行或通道处理。 | 将订单更新为退款处理中,并等待后续通知。 |
REFUNDED | 已退款。原支付金额已成功退回持卡人账户。 | 将订单更新为已退款,并同步售后或财务状态。 |
REFUND_FAILED | 退款失败。退款请求被通道或银行拒绝,未完成退款。 | 保留原支付成功状态,并记录退款失败原因。 |
CHARGEBACK | 拒付。持卡人已发起争议,款项可能被强制退回。 | 启动拒付处理流程,并同步风控、财务或客服系统。 |
不同支付产品或通道路由下,部分状态可能不会全部出现。
商户系统应至少完整兼容:PENDING、REQUIRES_ACTION、SUCCEEDED、FAILED、CANCELED、REFUNDED。
八、推荐处理逻辑
8.1 业务判断优先级
建议商户按以下优先级处理通知:
- 校验签名是否通过
- 判断
success - 解析
data - 根据
data.status更新订单状态 - 返回成功响应
8.2 推荐伪代码
java
if (!verifySignature(request, rawBody)) {
return fail();
}
WebhookNotify notify = parse(rawBody);
if (!notify.isSuccess()) {
log.warn("Webhook call failed: {}", rawBody);
return ok();
}
String reference = notify.getData().getReference();
String platformId = notify.getData().getId();
String status = notify.getData().getStatus();
if (isAlreadyProcessed(reference, status)) {
return ok();
}
switch (status) {
case "SUCCEEDED":
markOrderSuccess(reference, platformId);
break;
case "FAILED":
markOrderFailed(reference, notify.getData().getFailureMessage());
break;
case "PENDING":
case "REQUIRES_ACTION":
markOrderProcessing(reference);
break;
case "REFUNDED":
markOrderRefunded(reference);
break;
case "CHARGEBACK":
markOrderChargeback(reference);
break;
default:
log.info("Unhandled webhook status: {}", status);
}
return ok();九、响应结果处理说明
商户系统在成功处理通知后,应返回明确且稳定的成功响应。
若未正确返回,IntPay 可能视为通知失败,并在后续进行重试。
建议要求
- 返回 HTTP
200 OK - 响应耗时尽量控制在短时间内
- 先快速落库,再异步执行耗时业务
- 避免在通知接口中执行长事务或外部阻塞调用
推荐实践
- 先应答,后处理
- 幂等更新
- 日志完整
- 失败可重放
若商户系统存在高并发或链路复杂场景,建议将通知消息写入消息队列或事件总线后再异步消费。
十、Java 实体示例
java
import lombok.Data;
@Data
public class IntPayWebhookNotify {
private String requestId;
private Boolean success;
private DataObject data;
private String timestamp;
@Data
public static class DataObject {
private String reference;
private Boolean requiresAction;
private Integer amount;
private String currency;
private String id;
private String failureMessage;
private String status;
}
}十一、通知接收示例(伪代码)
java
@PostMapping("/notify")
public ResponseEntity<String> notifyCallback(
HttpServletRequest request,
@RequestBody String rawBody
) {
// 1. 验签
boolean verified = verifySignature(request, rawBody);
if (!verified) {
return ResponseEntity.status(400).body("invalid signature");
}
// 2. 解析通知
IntPayWebhookNotify notify = JSON.parseObject(rawBody, IntPayWebhookNotify.class);
// 3. 接口层失败,不更新成功订单
if (Boolean.FALSE.equals(notify.getSuccess())) {
log.warn("Webhook call failed: {}", rawBody);
return ResponseEntity.ok("success");
}
// 4. 幂等处理业务状态
IntPayWebhookNotify.DataObject data = notify.getData();
if (data != null) {
String reference = data.getReference();
String status = data.getStatus();
switch (status) {
case "SUCCEEDED":
orderService.markSuccess(reference, data.getId());
break;
case "FAILED":
orderService.markFailed(reference, data.getFailureMessage());
break;
case "PENDING":
case "REQUIRES_ACTION":
orderService.markProcessing(reference);
break;
case "REFUNDED":
orderService.markRefunded(reference);
break;
case "CHARGEBACK":
orderService.markChargeback(reference);
break;
default:
log.info("Unhandled status: {}", status);
}
}
// 5. 返回成功响应,避免平台重试
return ResponseEntity.ok("success");
}十二、常见错误
| 错误场景 | 说明 | 建议 |
|---|---|---|
| 未验签直接处理 | 存在伪造通知风险 | 必须先验签,再更新订单 |
| 使用同步跳转判断最终结果 | ReturnUrl 可能被用户关闭或篡改 | 以 Webhook 为最终依据 |
| 未做幂等控制 | 平台重试可能导致重复更新 | 按 reference / id 做幂等 |
| 接口返回非 200 | 平台会认为通知失败并重试 | 成功处理后统一返回 200 |
| 接口处理过慢 | 可能触发超时或重复通知 | 快速响应,异步处理耗时任务 |
| 忽略处理中状态 | 可能误判订单失败或成功 | 正确兼容 PENDING / REQUIRES_ACTION |
十三、最佳实践
- 异步通知应作为最终支付结果依据
- 通知接口必须支持重复回调与幂等处理
- 建议记录以下内容用于排查:
- 请求头
- 原始请求体
- 验签结果
- 本地订单更新结果
- 响应内容与响应时间
- 建议建立告警机制,监控:
- 验签失败率
- 通知处理失败率
- 通知响应耗时
- 重复通知比例
十四、核心总结
IntPay Webhook 的核心目标,是将订单最终状态以服务端到服务端的方式稳定送达商户系统。
商户应基于 验签、幂等、快速响应、状态驱动 的原则实现通知接收逻辑,
以确保订单状态更新准确、可靠且可追踪。