You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PayPal V2 API集成:ASP.Net C#后端安全获取自有订单号

解决方案

以下是几个满足你需求的可行方案,无需在前端暴露明文订单号,也不依赖Session:

1. 使用HttpOnly Cookie存储订单号

当用户进入支付页面时,后端生成订单号后,将其存入HttpOnly、Secure的Cookie中(确保前端JS无法读取或修改)。用户点击PayPal按钮发起请求时,浏览器会自动携带该Cookie,后端直接从Cookie中提取订单号。

代码示例:

  • 后端生成订单号并设置Cookie(在支付页的Action中):
public ActionResult Checkout()
{
    string orderNumber = GenerateUniqueOrderNumber(); // 自定义生成订单号的逻辑
    // 设置HttpOnly、Secure Cookie,有效期设为支付会话时长(比如30分钟)
    Response.Cookies.Append("OrderNumber", orderNumber, new CookieOptions
    {
        HttpOnly = true,
        Secure = Request.IsSecureConnection, // 生产环境强制HTTPS
        Expires = DateTime.Now.AddMinutes(30),
        SameSite = SameSiteMode.Strict // 防止CSRF
    });
    return View();
}
  • 后端创建PayPal订单的接口中读取Cookie:
[HttpPost]
public async Task<IActionResult> CreatePayPalOrder()
{
    if (Request.Cookies.TryGetValue("OrderNumber", out string orderNumber))
    {
        // 验证订单号有效性(比如查询数据库确认存在且未支付)
        if (IsValidOrder(orderNumber))
        {
            // 调用PayPal V2 API创建订单的逻辑
            var paypalOrder = await CreatePayPalOrderApi(orderNumber);
            return Json(new { id = paypalOrder.Id });
        }
    }
    return BadRequest("无效的订单信息");
}
  • 前端保持原fetch请求(无需传参数):
return fetch('/demo/checkout/api/paypal/order/create/', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    }
});

注意:若用户回退重新下单,后端生成新订单号时需覆盖原有Cookie值,确保Cookie始终对应当前页面的有效订单。

2. 加密订单号并通过POST请求体传递

后端在渲染支付页时,生成订单号并对其进行加密(使用对称加密算法,如AES),将加密后的令牌嵌入页面的隐藏字段中。用户点击PayPal按钮时,前端将该加密令牌通过POST请求体发送给后端,后端解密后得到真实订单号。

代码示例:

  • 后端渲染页面时生成加密令牌:
public ActionResult Checkout()
{
    string orderNumber = GenerateUniqueOrderNumber();
    string encryptedToken = EncryptOrderNumber(orderNumber); // 自定义AES加密方法
    ViewBag.EncryptedOrderToken = encryptedToken;
    return View();
}

// 示例AES加密方法(需确保密钥安全存储)
private string EncryptOrderNumber(string orderNumber)
{
    byte[] key = Encoding.UTF8.GetBytes("YourSecureKeyHere32Bytes"); // 32字节密钥(AES-256)
    byte[] iv = Encoding.UTF8.GetBytes("YourIVHere16Bytes"); // 16字节IV
    using (Aes aes = Aes.Create())
    {
        aes.Key = key;
        aes.IV = iv;
        ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
        using (MemoryStream ms = new MemoryStream())
        {
            using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
            {
                using (StreamWriter sw = new StreamWriter(cs))
                {
                    sw.Write(orderNumber);
                }
                return Convert.ToBase64String(ms.ToArray());
            }
        }
    }
}
  • 前端页面添加隐藏字段并在请求中发送:
<input type="hidden" id="encryptedOrderToken" value="@ViewBag.EncryptedOrderToken" />
const token = document.getElementById('encryptedOrderToken').value;
return fetch('/demo/checkout/api/paypal/order/create/', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({ token: token })
});
  • 后端解密并验证订单号:
[HttpPost]
public async Task<IActionResult> CreatePayPalOrder([FromBody] TokenRequest request)
{
    string orderNumber = DecryptOrderToken(request.Token); // 自定义解密方法
    if (IsValidOrder(orderNumber))
    {
        var paypalOrder = await CreatePayPalOrderApi(orderNumber);
        return Json(new { id = paypalOrder.Id });
    }
    return BadRequest("无效的订单信息");
}

// 示例AES解密方法
private string DecryptOrderToken(string token)
{
    byte[] key = Encoding.UTF8.GetBytes("YourSecureKeyHere32Bytes");
    byte[] iv = Encoding.UTF8.GetBytes("YourIVHere16Bytes");
    byte[] cipherText = Convert.FromBase64String(token);
    using (Aes aes = Aes.Create())
    {
        aes.Key = key;
        aes.IV = iv;
        ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
        using (MemoryStream ms = new MemoryStream(cipherText))
        {
            using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
            {
                using (StreamReader sr = new StreamReader(cs))
                {
                    return sr.ReadToEnd();
                }
            }
        }
    }
}

// 用于接收请求的模型
public class TokenRequest
{
    public string Token { get; set; }
}

3. 关联登录用户身份获取订单号

如果用户已登录,后端生成订单号时直接与用户ID绑定(存储到数据库)。当用户点击PayPal按钮发起请求时,后端通过当前登录用户的身份(如User.Identity.Name或UserId)查询数据库中该用户的最新未支付订单号,无需前端传递任何标识。

代码示例:

  • 后端生成订单并关联用户:
public ActionResult Checkout()
{
    if (User.Identity.IsAuthenticated)
    {
        string userId = User.FindFirst(ClaimTypes.NameIdentifier).Value;
        string orderNumber = GenerateUniqueOrderNumber();
        // 将订单号与用户ID关联存入数据库(标记为未支付)
        SaveOrderToDb(orderNumber, userId, OrderStatus.Unpaid);
        return View();
    }
    return RedirectToAction("Login");
}
  • 后端创建PayPal订单时查询用户的未支付订单:
[HttpPost]
public async Task<IActionResult> CreatePayPalOrder()
{
    if (User.Identity.IsAuthenticated)
    {
        string userId = User.FindFirst(ClaimTypes.NameIdentifier).Value;
        // 查询该用户最新的未支付订单号
        string orderNumber = GetLatestUnpaidOrder(userId);
        if (orderNumber != null)
        {
            var paypalOrder = await CreatePayPalOrderApi(orderNumber);
            return Json(new { id = paypalOrder.Id });
        }
    }
    return BadRequest("未找到有效订单");
}

注意:需处理用户同时存在多个未支付订单的情况,比如限制每个用户只能有一个未支付订单,或在查询时明确取当前会话对应的订单。


内容的提问来源于stack exchange,提问作者Programmer Joe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 05:22:54