如何在 .Net MVC Web App 中集成 Spreedly
结论
Spreedly 文档里的 curl 是 HTTP 请求的命令行示例,并不表示需要在 MVC 页面中运行 curl。在 .NET 中,只要通过 HttpClient 发送相同的 URL、HTTP 方法、认证头和 JSON 请求体,就能完成同样的调用。
通常可以这样集成:
- 浏览器通过 Spreedly Express、Hosted Fields 或当前官方提供的客户端组件采集支付信息。
- Spreedly 把支付信息转换为
payment_method_token。 - 浏览器将 token 提交给 ASP.NET MVC 后端。
- 后端使用
HttpClient和 Spreedly 凭据调用交易 API。
银行卡号不应经过自己的 MVC Controller,Spreedly Access Secret 也不能放在 JavaScript、HTML 或前端配置中。使用 MVC 还是 Web Forms,对 API 调用方式没有本质影响。
如何理解文档中的 curl
假设文档提供了下面这样的请求:
curl https://core.spreedly.com/v1/gateways/GATEWAY_TOKEN/purchase.json \
-u "ENVIRONMENT_KEY:ACCESS_SECRET" \
-H "Content-Type: application/json" \
-d '{
"transaction": {
"payment_method_token": "PAYMENT_METHOD_TOKEN",
"amount": 1999,
"currency_code": "USD"
}
}'
各部分与 .NET 代码的对应关系如下:
| curl 参数 | 含义 | .NET 对应方式 |
|---|---|---|
| URL | API 地址 | HttpRequestMessage.RequestUri |
-u | HTTP Basic Authentication | Authorization 请求头 |
-H | 请求头 | request.Headers 或 request.Content.Headers |
-d | 请求体 | JsonContent.Create(...) |
未显式写出的 POST | 使用 -d 时通常发送 POST | HttpMethod.Post |
转换时不必考虑如何“执行 curl”,只需要还原它所描述的 HTTP 请求。
使用 HttpClient 调用 Spreedly
下面的示例适用于现代 ASP.NET Core MVC。API 地址和字段应以 Spreedly 当前文档中对应操作的说明为准。
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
public sealed class SpreedlyClient
{
private readonly HttpClient _httpClient;
private readonly string _environmentKey;
private readonly string _accessSecret;
public SpreedlyClient(
HttpClient httpClient,
IConfiguration configuration)
{
_httpClient = httpClient;
_environmentKey =
configuration["Spreedly:EnvironmentKey"]
?? throw new InvalidOperationException(
"Missing Spreedly environment key.");
_accessSecret =
configuration["Spreedly:AccessSecret"]
?? throw new InvalidOperationException(
"Missing Spreedly access secret.");
}
public async Task<string> PurchaseAsync(
string gatewayToken,
string paymentMethodToken,
int amount,
string currencyCode,
CancellationToken cancellationToken = default)
{
var url =
$"v1/gateways/{Uri.EscapeDataString(gatewayToken)}/purchase.json";
var payload = new
{
transaction = new
{
payment_method_token = paymentMethodToken,
amount,
currency_code = currencyCode
}
};
using var request = new HttpRequestMessage(HttpMethod.Post, url)
{
Content = JsonContent.Create(payload)
};
var credentials = Convert.ToBase64String(
Encoding.UTF8.GetBytes(
$"{_environmentKey}:{_accessSecret}"));
request.Headers.Authorization =
new AuthenticationHeaderValue("Basic", credentials);
using var response = await _httpClient.SendAsync(
request,
cancellationToken);
var responseBody = await response.Content.ReadAsStringAsync(
cancellationToken);
if (!response.IsSuccessStatusCode)
{
throw new SpreedlyException(
response.StatusCode,
responseBody);
}
return responseBody;
}
}
异常类型可以简单定义为:
using System.Net;
public sealed class SpreedlyException : Exception
{
public HttpStatusCode StatusCode { get; }
public string ResponseBody { get; }
public SpreedlyException(
HttpStatusCode statusCode,
string responseBody)
: base($"Spreedly returned HTTP {(int)statusCode}.")
{
StatusCode = statusCode;
ResponseBody = responseBody;
}
}
注册 HttpClient:
builder.Services.AddHttpClient<SpreedlyClient>(client =>
{
client.BaseAddress = new Uri("https://core.spreedly.com/");
client.Timeout = TimeSpan.FromSeconds(30);
});
配置文件中只保留非敏感的空值占位符:
{
"Spreedly": {
"EnvironmentKey": "",
"AccessSecret": ""
}
}
实际密钥应通过环境变量、Secret Manager 或 Key Vault 等方式注入,不能提交到源代码仓库。
在 MVC Controller 中使用
Controller 应接收 Spreedly 生成的 token,而不是原始银行卡号:
using Microsoft.AspNetCore.Mvc;
public sealed record PaymentRequest(
string PaymentMethodToken,
int Amount,
string CurrencyCode);
[ApiController]
[Route("payments")]
public sealed class PaymentsController : ControllerBase
{
private readonly SpreedlyClient _spreedly;
public PaymentsController(SpreedlyClient spreedly)
{
_spreedly = spreedly;
}
[HttpPost]
public async Task<IActionResult> Create(
[FromBody] PaymentRequest input,
CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(input.PaymentMethodToken))
return BadRequest("Missing payment method token.");
if (input.Amount <= 0)
return BadRequest("Amount must be greater than zero.");
const string gatewayToken = "YOUR_GATEWAY_TOKEN";
try
{
var result = await _spreedly.PurchaseAsync(
gatewayToken,
input.PaymentMethodToken,
input.Amount,
input.CurrencyCode,
cancellationToken);
return Content(result, "application/json");
}
catch (SpreedlyException ex)
{
// 生产环境不要直接向客户端返回可能包含敏感细节的完整响应。
return StatusCode(
StatusCodes.Status502BadGateway,
new { error = "Payment provider rejected the request." });
}
}
}
这里直接返回 JSON 字符串,只是为了让请求流程更清楚。实际项目通常会把 Spreedly 响应反序列化为 DTO,检查交易状态和错误字段,再返回自己的业务响应。
如果使用旧版 ASP.NET MVC
.NET Framework 项目同样可以使用 HttpClient,只是没有 JsonContent.Create,因此需要先序列化 JSON:
using Newtonsoft.Json;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
var payload = new
{
transaction = new
{
payment_method_token = paymentMethodToken,
amount = 1999,
currency_code = "USD"
}
};
var json = JsonConvert.SerializeObject(payload);
using var request = new HttpRequestMessage(
HttpMethod.Post,
"https://core.spreedly.com/v1/gateways/GATEWAY_TOKEN/purchase.json");
request.Content = new StringContent(
json,
Encoding.UTF8,
"application/json");
var credentials = Convert.ToBase64String(
Encoding.UTF8.GetBytes(
environmentKey + ":" + accessSecret));
request.Headers.Authorization =
new AuthenticationHeaderValue("Basic", credentials);
var response = await httpClient.SendAsync(request);
var responseBody = await response.Content.ReadAsStringAsync();
Web Forms 也可以使用同一套服务层代码,无须为了兼容 Spreedly 改用其他调用方式。
支付表单应如何处理
支付表单不应把卡号直接提交到自己的服务器。常见流程是:
支付页面
↓
Spreedly 客户端组件安全采集支付信息
↓
获得 payment_method_token
↓
提交 token 和订单标识到 MVC 后端
↓
后端重新读取并校验订单金额
↓
后端调用 Spreedly API
服务端不能直接信任浏览器提交的金额。客户端可以提交商品或订单标识,但后端应根据数据库中的订单重新计算金额,以免用户通过修改请求内容篡改金额。
订阅需要额外设计
Spreedly 主要提供支付方式 tokenization 和支付网关编排。订阅能否直接通过 Spreedly API 管理,取决于所用产品和账户能力,不能从一次性支付示例中推断。
如果周期扣款由应用自行管理,通常需要:
- 首次支付时按照官方要求保留支付方式。
- 在数据库中保存 Spreedly token,而不是卡号。
- 保存订阅状态、周期、下次扣款时间和业务订单。
- 由后台任务在到期时使用 token 发起交易。
- 根据交易结果更新订阅,并处理失败重试和用户通知。
retain_on_success 等字段是否适用于具体接口,以及它们的准确含义,都应以当前接口文档为准。
需要特别注意的事项
amount通常使用最小货币单位。例如,1999可能表示 USD 19.99。零小数货币等情况需要按照 Spreedly 及所接网关的规则处理。- 不要混用测试环境和生产环境的凭据、网关 token 或支付方式 token。
- 不要记录卡号、CVV、Access Secret 或完整的敏感响应。
- 不要在每次请求时创建一个长期未管理的
HttpClient。ASP.NET Core 应优先使用IHttpClientFactory。 - 不能只检查 HTTP 200。响应体中还可能包含交易结果、网关错误和校验错误,因此必须解析业务状态。
- 请求超时不代表交易一定失败,也不应立即再次扣款。应先查询交易状态,避免重复支付。
- 保存业务订单号时,也要保存 Spreedly 返回的 transaction token,以便对账和排查问题。
- 3-D Secure、SCA、异步交易、回调和重定向流程,需要根据所用网关及 Spreedly 当前文档分别实现。
可以把每个 curl 示例拆成“方法、URL、认证、请求头、请求体”五部分,再逐项映射到 HttpRequestMessage。MVC 和 Web Forms 只是页面框架,不会改变 Spreedly API 的调用方式。
备注:内容仅供参考。
