如何在.NET Core应用中集成FlyWire支付网关?
.NET Core 集成 FlyWire 支付网关实操步骤
以下是生产环境验证过的落地实现方案,全程不需要依赖第三方封装SDK,直接用.NET Core内置组件即可完成对接。
前置准备
- 提前在FlyWire商户后台获取两类核心凭证:测试/生产环境对应的
API Key、商户ID,配置Webhook回调时会自动生成Webhook签名密钥,注意不同环境的凭证不要混用 - 项目版本要求:.NET Core 3.1及以上版本均可,调试回调时提前准备内网穿透工具,保证本地服务能被公网访问到
第一步:基础服务配置
首先在appsettings.json中添加配置节点:
"FlyWire": { "ApiKey": "你的API密钥", "MerchantId": "你的商户ID", "BaseUrl": "对应环境的网关根地址", "WebhookSecret": "Webhook回调验签密钥" }
在Program.cs(.NET6+)或Startup.cs中注册服务和带认证的HttpClient:
// 配置选项绑定 builder.Services.Configure<FlyWireOptions>(builder.Configuration.GetSection("FlyWire")); // 注册支付服务HttpClient,默认带上Basic认证头 builder.Services.AddHttpClient<IFlyWireService, FlyWireService>(client => { var options = builder.Configuration.GetSection("FlyWire").Get<FlyWireOptions>(); client.BaseAddress = new Uri(options.BaseUrl); // FlyWire采用Basic认证,拼接规则为 商户ID:APIKey 做Base64编码 var authToken = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{options.MerchantId}:{options.ApiKey}")); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authToken); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); });
对应配置实体和服务构造函数代码:
public class FlyWireOptions { public string ApiKey { get; set; } public string MerchantId { get; set; } public string BaseUrl { get; set; } public string WebhookSecret { get; set; } } public class FlyWireService : IFlyWireService { private readonly HttpClient _httpClient; private readonly FlyWireOptions _options; public FlyWireService(HttpClient httpClient, IOptions<FlyWireOptions> options) { _httpClient = httpClient; _options = options.Value; } // 后续接口实现写在这里 }
第二步:核心支付接口封装
FlyWire标准支付流程为:创建支付单 → 跳转托管支付页 → 接收支付回调 → (可选)主动查询状态补单。
先定义服务接口:
public interface IFlyWireService { /// <summary> /// 创建支付订单,返回前端跳转的支付页地址 /// </summary> Task<string> CreatePaymentAsync(string localOrderId, decimal amount, string currency, string callbackUrl, string successReturnUrl, string failReturnUrl); /// <summary> /// 验证Webhook回调签名,防止伪造请求 /// </summary> bool VerifyCallbackSignature(string rawRequestBody, string flywireSignature); }
创建支付单的实现逻辑:
public async Task<string> CreatePaymentAsync(string localOrderId, decimal amount, string currency, string callbackUrl, string successReturnUrl, string failReturnUrl) { var requestBody = new { merchant_reference = localOrderId, // 本地系统订单号 amount = amount.ToString("0.00"), // 金额必须传两位小数格式,不要传整数 currency = currency, // 三位币种编码,如CNY/USD return_url = successReturnUrl, // 支付成功前端跳转地址 cancel_url = failReturnUrl, // 支付取消/失败前端跳转地址 callback_url = callbackUrl // 后端异步回调地址 }; var response = await _httpClient.PostAsJsonAsync("v3/payments", requestBody); // 接口调用失败直接抛出异常,可按需加全局异常处理 response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync<CreatePaymentResponse>(); return result.HostedPaymentUrl; // 直接把这个地址返回给前端跳转即可 } // 接口响应实体 public class CreatePaymentResponse { [JsonPropertyName("id")] public string FlywirePaymentId { get; set; } [JsonPropertyName("hosted_payment_url")] public string HostedPaymentUrl { get; set; } public string Status { get; set; } }
第三步:支付回调处理
注意:回调验签必须读取原始请求体,不能用
[FromBody]自动反序列化后的内容重新计算签名,否则会因为格式变化导致验签失败。
public bool VerifyCallbackSignature(string rawRequestBody, string flywireSignature) { using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(_options.WebhookSecret)); var computedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(rawRequestBody)); var computedSignature = BitConverter.ToString(computedHash).Replace("-", "").ToLower(); // 采用固定时间比对,防止时序攻击 return CryptographicOperations.FixedTimeEquals( Encoding.UTF8.GetBytes(computedSignature), Encoding.UTF8.GetBytes(flywireSignature.ToLower()) ); }
回调接口实现:
[ApiController] [Route("api/payment/flywire")] public class FlyWireCallbackController : ControllerBase { private readonly IFlyWireService _flyWireService; private readonly IOrderService _orderService; // 本地订单业务服务 public FlyWireCallbackController(IFlyWireService flyWireService, IOrderService orderService) { _flyWireService = flyWireService; _orderService = orderService; } [HttpPost("callback")] public async Task<IActionResult> HandleCallback() { // 读取原始请求体 using var reader = new StreamReader(Request.Body); var rawBody = await reader.ReadToEndAsync(); // 从请求头取签名值 if (!Request.Headers.TryGetValue("X-Flywire-Signature", out var signature)) return Unauthorized(); // 验签不通过直接返回 if (!_flyWireService.VerifyCallbackSignature(rawBody, signature)) return Unauthorized(); // 反序列化回调数据 var callbackData = JsonSerializer.Deserialize<CallbackDataDto>(rawBody); // 支付成功时更新本地订单状态 if (callbackData.Status == "paid") { await _orderService.MarkOrderPaid(callbackData.MerchantReference, callbackData.FlywirePaymentId, decimal.Parse(callbackData.Amount)); } // 必须返回200状态码,否则FlyWire会重复推送回调 return Ok(); } } public class CallbackDataDto { [JsonPropertyName("id")] public string FlywirePaymentId { get; set; } [JsonPropertyName("merchant_reference")] public string MerchantReference { get; set; } public string Status { get; set; } public string Amount { get; set; } public string Currency { get; set; } }
常见踩坑点
- 金额字段必须传两位小数字符串格式,比如100元要传
"100.00",传整数100会触发参数格式错误 - 生产环境建议加定时查单任务,每天轮询创建超过24小时未终态的订单,避免回调丢失导致漏单
- 敏感凭证(API Key、Webhook密钥)不要硬编码在代码里,生产环境用环境变量或者配置中心存储
- 回调处理逻辑要做幂等,同一订单多次收到paid回调不要重复更新状态、重复发货
内容的提问来源于stack exchange,提问作者Raju Jikadra
相关产品推荐
相关产品推荐

