支付后调用 payment status API 返回 HTTP 400
结论
通常不需要在 Startup 或 appsettings.json 中配置额外的 permission。
CallbackUrl 不是主动调用 payment status API 的页面地址,而是支付流程结束后,支付平台向商户服务器发送回调请求的公网端点。该请求通常使用 POST,并携带用于查询支付结果的 token。出现 HTTP 400,一般是因为回调端点只接受 GET、参数绑定不正确,或者 ASP.NET Core 的防伪令牌校验拒绝了外部请求。
处理流程如下:
- 初始化 Checkout Form。
- 用户完成支付。
- 支付平台向
CallbackUrl发送POST请求。 - 商户从表单中取得
token。 - 使用该
token调用 Checkout Form Retrieve API,查询并校验最终支付状态。
配置回调端点
回调地址必须能从公网访问,并且接受 POST 请求。在 ASP.NET Core 2.1 中,可以直接从表单读取 token:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Iyzipay;
using Iyzipay.Model;
using Iyzipay.Request;
public class PaymentController : Controller
{
private readonly Options _options;
public PaymentController(Options options)
{
_options = options;
}
[HttpPost]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> Callback()
{
string token = Request.Form["token"];
if (string.IsNullOrWhiteSpace(token))
{
return BadRequest("Missing payment token.");
}
var request = new RetrieveCheckoutFormRequest
{
Token = token
};
CheckoutForm checkoutForm =
await CheckoutForm.RetrieveAsync(request, _options);
if (!string.Equals(
checkoutForm.Status,
"success",
System.StringComparison.OrdinalIgnoreCase))
{
return BadRequest(checkoutForm.ErrorMessage);
}
// 还应检查 PaymentStatus、Price、PaidPrice、Currency、
// BasketId、PaymentId 等字段是否符合当前订单。
return Ok();
}
}
也可以使用模型绑定:
public class PaymentCallbackRequest
{
public string Token { get; set; }
}
[HttpPost]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> Callback(
[FromForm] PaymentCallbackRequest callback)
{
if (string.IsNullOrWhiteSpace(callback.Token))
{
return BadRequest("Missing payment token.");
}
var request = new RetrieveCheckoutFormRequest
{
Token = callback.Token
};
CheckoutForm result =
await CheckoutForm.RetrieveAsync(request, _options);
if (!string.Equals(
result.Status,
"success",
System.StringComparison.OrdinalIgnoreCase))
{
return BadRequest(result.ErrorMessage);
}
return Ok();
}
SDK 版本不同,异步方法的签名可能略有差异。如果当前版本没有 RetrieveAsync,可以改用该版本提供的同步 Retrieve 方法。具体类名和方法签名以项目实际安装的 SDK 为准。
检查路由是否匹配
如果回调地址是:
request.CallbackUrl = "https://www.merchant.com/callback";
应用中必须存在与 /callback 匹配的 POST 路由,例如:
[Route("callback")]
public class PaymentCallbackController : Controller
{
[HttpPost]
[IgnoreAntiforgeryToken]
public IActionResult Index([FromForm] string token)
{
if (string.IsNullOrWhiteSpace(token))
{
return BadRequest();
}
// 使用 token 查询支付结果
return Ok();
}
}
如果使用传统 MVC 路由,控制器和 Action 生成的实际路径可能是 /Payment/Callback。这时应将初始化请求改为:
request.CallbackUrl = "https://www.merchant.com/Payment/Callback";
控制器方法不会自动对应 /callback。请核对应用的路由配置和服务器访问日志,确认实际路径。
防伪令牌导致的 HTTP 400
支付平台无法携带商户网站生成的 ASP.NET Core antiforgery token。如果项目全局启用了:
services.AddMvc(options =>
{
options.Filters.Add(
new Microsoft.AspNetCore.Mvc.AutoValidateAntiforgeryTokenAttribute());
});
框架可能在请求进入 Action 前就直接返回 HTTP 400。此时,只需在支付回调 Action 上添加:
[IgnoreAntiforgeryToken]
不要因为一个支付回调就关闭整个站点的防伪保护。虽然回调端点不校验 antiforgery token,但仍须使用支付平台返回的 token 查询服务端支付结果,不能信任浏览器提交的金额或”支付成功”参数。
初始化和查询环境必须一致
初始化 Checkout Form 和查询支付结果时,必须使用同一套 API 凭据和同一环境:
var options = new Options
{
ApiKey = configuration["Iyzipay:ApiKey"],
SecretKey = configuration["Iyzipay:SecretKey"],
BaseUrl = configuration["Iyzipay:BaseUrl"]
};
appsettings.json 可以保存这些配置,但它不是 permission 设置:
{
"Iyzipay": {
"ApiKey": "your-api-key",
"SecretKey": "your-secret-key",
"BaseUrl": "your-api-base-url"
}
}
生产环境中不要将真实密钥提交到源代码仓库。可以使用环境变量、Secret Manager 或专用密钥管理服务。
还需要检查的项目
CallbackUrl必须是支付平台能够访问的公网地址,不能使用localhost或内网地址。- HTTPS 证书必须有效,域名应与证书匹配。
- 回调端点必须允许
POST,不能只声明[HttpGet]。 token通常来自application/x-www-form-urlencoded表单,应使用[FromForm],不能只用[FromBody]解析 JSON。- 不要使用初始化请求中的 token、其他订单的 token 或已经失效的 token 查询结果。
- 初始化和查询必须使用一致的 sandbox/production 环境及凭据。
- 记录响应中的
Status、ErrorCode、ErrorMessage和ErrorGroup。仅凭 HTTP 400 无法确定具体原因。 - 只有服务端确认支付状态、订单标识、金额和币种都符合预期后,才能将订单标记为已支付。
- 回调可能重复到达,因此订单更新逻辑需要具备幂等性。
如果 HTTP 400 出现在回调进入应用之前,先检查路由、请求方法和 antiforgery 配置。如果回调 Action 已经取得 token,但 Retrieve API 返回 400,则应检查 token、API 凭据、请求签名,以及 sandbox/production 环境是否一致。
备注:内容仅供参考。
