net5 接入 paypal支付 v2

paypal支付流程

1、创建订单(Checkout) => 获取订单中返回的rel=approve的连接,这是需要用户授权的账单 => 用户授权后paypal回调CHECKOUT.ORDER.APPROVED事件,账单授权后服务端发起扣款请求(Capture)=> paypal回调CAPTURE.ORDER.COMPLETE事件 => 完成

具体可参考以下代码 paypal sdk地址 https://github.com/paypal/Checkout-NET-SDK

using Castle.Core.Internal;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Payment.Paypal.CaptureOrder;
using PayPalCheckoutSdk.Core;
using PayPalCheckoutSdk.Orders;
using PayPalCheckoutSdk.Payments;
using PayPalHttp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Util;

namespace Payment.Paypal
{
    /// <summary>
    /// paypal支付帮助类
    /// </summary>
    public class PaypalHelper
    {
        private static IConfiguration _configuration;
        private ILogger<PaypalHelper> _logger;
        private IHttpContextAccessor _httpContextAccessor;
        private static string _clientId;
        private static string _clientSecret;

        public PaypalHelper(ILogger<PaypalHelper> logger, IConfiguration configuration, IHttpContextAccessor httpContextAccessor)
        {
            _configuration = configuration;
            _clientId = configuration["Payments:Paypal:ClientId"];
            _clientSecret = configuration["Payments:Paypal:ClientSecret"];
            _logger = logger;
            _httpContextAccessor = httpContextAccessor;
        }

        //paypal client
        private static HttpClient GetClient()
        {
            var isSandboxEnvironment = _configuration["Payments:Paypal:IsSandboxEnvironment"];
            if (isSandboxEnvironment.IsNullOrEmpty())
                throw new Exception("paypal环境未配置,请配置环境");

            PayPalEnvironment environment;
            if (isSandboxEnvironment.ParseToBool())
                environment = new SandboxEnvironment(_clientId, _clientSecret);
            else
                environment = new LiveEnvironment(_clientId, _clientSecret);

            return new PayPalHttpClient(environment);
        }

        /// <summary>
        /// 1.创建订单(https://developer.paypal.com/docs/api/orders/v2/#orders_create)
        /// </summary>
        public async Task<string> CreateOrderAsync(string tradeNo, decimal amount, string returnUrl)
        {
            var rate = _configuration["Rates:CNY:USD"].ParseToDecimal();
            PayPalHttp.HttpResponse response;
            var order = new OrderRequest()
            {
                CheckoutPaymentIntent = "CAPTURE",
                PurchaseUnits = new List<PurchaseUnitRequest>()
                {
                    new PurchaseUnitRequest()
                    {
                        ReferenceId = tradeNo,
                        AmountWithBreakdown = new AmountWithBreakdown
                        {
                            Value = Math.Round(amount * rate, 2).ToString(),
                            CurrencyCode = "USD"
                        }
                    }
                },
                ApplicationContext = new ApplicationContext { ReturnUrl = returnUrl }
            };

            // Call API with your client and get a response for your call
            var request = new OrdersCreateRequest();
            request.Prefer("return=representation");
            request.RequestBody(order);
            response = await GetClient().Execute(request);
            if (response.StatusCode == HttpStatusCode.Created)
            {
                var result = response.Result<Order>();

                return result.Links.FirstOrDefault(p => p.Rel == "approve").Href;
            }
            _logger.LogError($"创建paypal订单失败:{JsonConvert.SerializeObject(response.Result<Order>())}");
            return string.Empty;
        }

        /// <summary>
        /// 2.捕获订单(https://developer.paypal.com/docs/api/orders/v2/#orders_capture)
        /// </summary>
        public async Task<bool> CaptureOrderAsync(string orderId)
        {
            if (!string.IsNullOrEmpty(orderId))
            {
                var request = new OrdersCaptureRequest(orderId);
                request.Prefer("return=representation");
                request.RequestBody(new OrderActionRequest());
                var response = await GetClient().Execute(request);
                if (response.StatusCode == HttpStatusCode.Created)
                {
                    var result = response.Result<Order>();
                    return true;
                }
                _logger.LogError($"捕获paypal订单失败:{JsonConvert.SerializeObject(response.Result<Order>())}");
            }
            _logger.LogError($"捕获paypal订单失败:根据tradeNo查询orderId无数据 tradeNo:{orderId} orderId:{orderId}");
            return false;
        }

        /// <summary>
        /// 3.退款(https://developer.paypal.com/docs/api/payments/v2/#refunds)
        /// </summary>
        public async Task RefundOrderAsync(string captureId)
        {
            var request = new CapturesRefundRequest(captureId);
            request.Prefer("return=representation");
            var response = await GetClient().Execute(request);
            if (response.StatusCode == HttpStatusCode.Created)
            {
                var result = response.Result<PayPalCheckoutSdk.Payments.Refund>();
            }
        }

        /// <summary>
        /// 支付回调验签,此方法参考paypal sdk netframework版本的实现 https://github.com/paypal/sdk-core-dotnet
        /// </summary>
        public async Task<bool> VerifySig(string requestBody = "")
        {
            var _webHookId = _configuration["Payments:Paypal:WebHookId"];
            var transmissionId = _httpContextAccessor.HttpContext.Request.Headers["Paypal-Transmission-Id"][0];
            var transmissionTime = _httpContextAccessor.HttpContext.Request.Headers["Paypal-Transmission-Time"][0];
            var algo = _httpContextAccessor.HttpContext.Request.Headers["Paypal-Auth-Algo"][0].Replace("withRSA", "");
            var certUrl = _httpContextAccessor.HttpContext.Request.Headers["Paypal-Cert-Url"][0];
            var sig = _httpContextAccessor.HttpContext.Request.Headers["Paypal-Transmission-Sig"][0];

            if (string.IsNullOrEmpty(requestBody))
            {
                using var reader = new StreamReader(_httpContextAccessor.HttpContext.Request.Body);
                requestBody = await reader.ReadToEndAsync();
            }


            var cert = CertificateManager.GetCertificateFromUrl(certUrl);
            var crc32 = Crc32.ComputeChecksum(requestBody);
            var str = $"{transmissionId}|{transmissionTime}|{_webHookId}|{crc32}";
            var bytes = Encoding.UTF8.GetBytes(str);

            return cert.GetRSAPublicKey().VerifyData(bytes, Convert.FromBase64String(sig), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        }

        /// <summary>
        /// 回调事件处理
        /// </summary>
        /// <param name="caFunc">账单授权完成处理逻辑</param>
        /// <param name="ccFunc">支付完成处理逻辑</param>
        /// <returns></returns>
        public async Task<bool> CallbackEventHandle(Func<CheckoutApproved, bool> caFunc, Func<CaptureCompleted, bool> ccFunc)
        {
            using var reader = new StreamReader(_httpContextAccessor.HttpContext.Request.Body);
            var requestBody = await reader.ReadToEndAsync();
            //验签
            var valid = await VerifySig(requestBody);
            if (valid)
            {
                var eventType = JsonConvert.DeserializeObject<BaseEventType>(requestBody);
                switch (eventType.event_type)
                {
                    //账单已授权
                    case "CHECKOUT.ORDER.APPROVED":
                        var checkoutApproved = JsonConvert.DeserializeObject<CheckoutApproved>(requestBody);
                        return caFunc.Invoke(checkoutApproved);
                    //支付已完成
                    case "PAYMENT.CAPTURE.COMPLETED":
                        var captureCompleted = JsonConvert.DeserializeObject<CaptureCompleted>(requestBody);
                        return ccFunc.Invoke(captureCompleted);
                }
            }
            _logger.LogError($"验签失败");
            return false;
        }
    }
}

 参考paypal sdk netframework的实现 https://github.com/paypal/sdk-core-dotnet

using System;
using System.Collections.Concurrent;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace Payment.Paypal
{
    public static class CertificateManager
    {
        private static ConcurrentDictionary<string, X509Certificate2> certificates;

        /// <summary>
        /// 从url获取证书
        /// </summary>
        public static X509Certificate2 GetCertificateFromUrl(string certUrl)
        {
            if (certificates == null)
                certificates = new ConcurrentDictionary<string, X509Certificate2>();

            if (!certificates.ContainsKey(certUrl))
            {
                var certString = string.Empty;
                using (var client = new WebClient())
                {
                    certString = client.DownloadString(certUrl);
                }

                string[] array = certString.Split(new string[2]
                {
                    "-----BEGIN CERTIFICATE-----",
                    "-----END CERTIFICATE-----"
                }, StringSplitOptions.RemoveEmptyEntries);

                string[] array2 = array;
                for (int i = 0; i < array2.Length; i++)
                {
                    string text2 = array2[i].Trim();
                    if (!string.IsNullOrEmpty(text2))
                    {
                        var x509Certificate = new X509Certificate2(Encoding.UTF8.GetBytes(text2));
                        if (x509Certificate.Verify())
                        {
                            certificates.TryAdd(certUrl, x509Certificate);
                        }
                    }
                }
            }
            return certificates[certUrl];
        }
    }
}

 

参考paypal sdk netframework的实现 https://github.com/paypal/sdk-core-dotnet

using System.Text;

namespace Payment.Paypal
{
    public class Crc32
    {
        private static uint[] table = new uint[256]
        {
            0u,
            1996959894u,
            3993919788u,
            2567524794u,
            124634137u,
            1886057615u,
            3915621685u,
            2657392035u,
            249268274u,
            2044508324u,
            3772115230u,
            2547177864u,
            162941995u,
            2125561021u,
            3887607047u,
            2428444049u,
            498536548u,
            1789927666u,
            4089016648u,
            2227061214u,
            450548861u,
            1843258603u,
            4107580753u,
            2211677639u,
            325883990u,
            1684777152u,
            4251122042u,
            2321926636u,
            335633487u,
            1661365465u,
            4195302755u,
            2366115317u,
            997073096u,
            1281953886u,
            3579855332u,
            2724688242u,
            1006888145u,
            1258607687u,
            3524101629u,
            2768942443u,
            901097722u,
            1119000684u,
            3686517206u,
            2898065728u,
            853044451u,
            1172266101u,
            3705015759u,
            2882616665u,
            651767980u,
            1373503546u,
            3369554304u,
            3218104598u,
            565507253u,
            1454621731u,
            3485111705u,
            3099436303u,
            671266974u,
            1594198024u,
            3322730930u,
            2970347812u,
            795835527u,
            1483230225u,
            3244367275u,
            3060149565u,
            1994146192u,
            31158534u,
            2563907772u,
            4023717930u,
            1907459465u,
            112637215u,
            2680153253u,
            3904427059u,
            2013776290u,
            251722036u,
            2517215374u,
            3775830040u,
            2137656763u,
            141376813u,
            2439277719u,
            3865271297u,
            1802195444u,
            476864866u,
            2238001368u,
            4066508878u,
            1812370925u,
            453092731u,
            2181625025u,
            4111451223u,
            1706088902u,
            314042704u,
            2344532202u,
            4240017532u,
            1658658271u,
            366619977u,
            2362670323u,
            4224994405u,
            1303535960u,
            984961486u,
            2747007092u,
            3569037538u,
            1256170817u,
            1037604311u,
            2765210733u,
            3554079995u,
            1131014506u,
            879679996u,
            2909243462u,
            3663771856u,
            1141124467u,
            855842277u,
            2852801631u,
            3708648649u,
            1342533948u,
            654459306u,
            3188396048u,
            3373015174u,
            1466479909u,
            544179635u,
            3110523913u,
            3462522015u,
            1591671054u,
            702138776u,
            2966460450u,
            3352799412u,
            1504918807u,
            783551873u,
            3082640443u,
            3233442989u,
            3988292384u,
            2596254646u,
            62317068u,
            1957810842u,
            3939845945u,
            2647816111u,
            81470997u,
            1943803523u,
            3814918930u,
            2489596804u,
            225274430u,
            2053790376u,
            3826175755u,
            2466906013u,
            167816743u,
            2097651377u,
            4027552580u,
            2265490386u,
            503444072u,
            1762050814u,
            4150417245u,
            2154129355u,
            426522225u,
            1852507879u,
            4275313526u,
            2312317920u,
            282753626u,
            1742555852u,
            4189708143u,
            2394877945u,
            397917763u,
            1622183637u,
            3604390888u,
            2714866558u,
            953729732u,
            1340076626u,
            3518719985u,
            2797360999u,
            1068828381u,
            1219638859u,
            3624741850u,
            2936675148u,
            906185462u,
            1090812512u,
            3747672003u,
            2825379669u,
            829329135u,
            1181335161u,
            3412177804u,
            3160834842u,
            628085408u,
            1382605366u,
            3423369109u,
            3138078467u,
            570562233u,
            1426400815u,
            3317316542u,
            2998733608u,
            733239954u,
            1555261956u,
            3268935591u,
            3050360625u,
            752459403u,
            1541320221u,
            2607071920u,
            3965973030u,
            1969922972u,
            40735498u,
            2617837225u,
            3943577151u,
            1913087877u,
            83908371u,
            2512341634u,
            3803740692u,
            2075208622u,
            213261112u,
            2463272603u,
            3855990285u,
            2094854071u,
            198958881u,
            2262029012u,
            4057260610u,
            1759359992u,
            534414190u,
            2176718541u,
            4139329115u,
            1873836001u,
            414664567u,
            2282248934u,
            4279200368u,
            1711684554u,
            285281116u,
            2405801727u,
            4167216745u,
            1634467795u,
            376229701u,
            2685067896u,
            3608007406u,
            1308918612u,
            956543938u,
            2808555105u,
            3495958263u,
            1231636301u,
            1047427035u,
            2932959818u,
            3654703836u,
            1088359270u,
            936918000u,
            2847714899u,
            3736837829u,
            1202900863u,
            817233897u,
            3183342108u,
            3401237130u,
            1404277552u,
            615818150u,
            3134207493u,
            3453421203u,
            1423857449u,
            601450431u,
            3009837614u,
            3294710456u,
            1567103746u,
            711928724u,
            3020668471u,
            3272380065u,
            1510334235u,
            755167117u
        };

        public static uint ComputeChecksum(string text)
        {
            uint num = uint.MaxValue;
            byte[] bytes = Encoding.UTF8.GetBytes(text);
            for (int i = 0; i < bytes.Length; i++)
            {
                num = (table[(num ^ bytes[i]) & 0xFF] ^ (num >> 8));
            }

            return ~num;
        }
    }
}

 

posted @ 2021-10-14 18:04  maxwelltsai  阅读(883)  评论(0)    收藏  举报