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
相关产品推荐
相关产品推荐

