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

C#集成PayPal:支付成功后如何等待接收支付响应

非ASP.NET场景下PayPal支付结果接收实现方案

你当前使用的PayPal Checkout v2 .NET SDK本身不依赖ASP.NET框架,不需要照搬Web框架专属示例,支付结果通过「同步主动查询+异步Webhook兜底」两个机制配合接收即可,具体实现如下:

同步流程:用户支付完成后即时获取结果

  • 你代码中ApplicationContext配置的ReturnUrl是用户在PayPal端完成支付授权后,浏览器自动跳转的地址。该地址不需要是ASP.NET控制器路由,只要是你项目可处理HTTP请求的入口即可:不管是Discord机器人配套的简易HTTP监听端点、桌面程序内嵌的HTTP服务、控制台程序开放的公网接口都能适配。
  • 用户跳回ReturnUrl时,PayPal会在URL查询参数中携带token字段,对应你创建订单时生成的Order ID。绝对不要信任前端传递的「支付成功」标记,拿到Order ID后立刻在服务端调用你已经实现的CaptureOrder(OrderId)方法,主动向PayPal发起扣款请求:当返回结果的Status字段为COMPLETED时,才代表支付实际成功,可以给用户发放对应权益。
  • 你测试代码中用Console.Read()等待用户确认后再调用Capture的逻辑,本质就是模拟同步流程的触发时机,正式环境把人工触发替换成ReturnUrl入口的自动触发即可。
  • 注意把占位的https://www.example.com替换成你自己项目实际的跳转、取消地址。

异步兜底:Webhook通知防止丢单

仅靠同步跳转有概率丢单:比如用户支付完成后直接关闭浏览器、网络中断导致跳转失败,你将无法收到支付结果,必须配置Webhook做兜底:

  • Webhook同样不依赖ASP.NET:只要你有一个公网可访问的HTTP POST接收端点,在PayPal开发者后台配置该端点,订阅PAYMENT.CAPTURE.COMPLETED(扣款成功)、PAYMENT.CAPTURE.DENIED(扣款失败)、PAYMENT.CAPTURE.REFUNDED(退款完成)几个核心事件即可。
  • 收到Webhook推送的POST请求后,先通过SDK内置的签名校验方法确认请求确实来自PayPal,再解析请求中携带的Order ID、Capture ID,和你本地存储的业务订单做匹配,更新订单状态。该机制和同步流程独立,哪怕用户没跳回你的地址,只要PayPal完成扣款就会给你发通知。

现有代码优化点

  • 不要在异步方法中用.Result阻塞获取返回值,会触发死锁,全链路使用await调用异步方法即可,比如测试方法里的CreateOrder(true).Result替换为await CreateOrder(true)。
  • PayPalClient.environment()方法中读取环境变量的键名为空,记得填入对应沙箱/正式环境的Client ID、Client Secret,正式环境将SandboxEnvironment替换为LiveEnvironment实例。
  • 创建订单拿到Order ID后,要和你自身系统的业务订单做绑定持久化存储,不要仅存在内存中,避免服务重启后订单关联丢失。

你当前的核心业务逻辑代码(创建订单、扣款、退款)已经可以直接复用,不需要针对非ASP.NET场景做特殊改造,只要补全上面的结果接收逻辑即可。

现有实现代码参考:

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading.Tasks;
using PayPalCheckoutSdk.Core;
using PayPalCheckoutSdk.Orders;
using PayPalCheckoutSdk.Payments;
using PayPalHttp;

namespace Discord.Modules
{
    public class PaypalPayment
    {
        private static OrderRequest BuildRequestBody()
        {
            OrderRequest orderRequest = new OrderRequest()
            {
                CheckoutPaymentIntent = "CAPTURE",

                ApplicationContext = new ApplicationContext
                {
                    BrandName = "EXAMPLE INC",
                    LandingPage = "BILLING",
                    CancelUrl = "https://www.example.com",
                    ReturnUrl = "https://www.example.com",
                    UserAction = "CONTINUE",
                    ShippingPreference = "SET_PROVIDED_ADDRESS"
                },
                PurchaseUnits = new List<PurchaseUnitRequest>
                {
                    new PurchaseUnitRequest{
                        ReferenceId =  "PUHF",
                        Description = "Sporting Goods",
                        CustomId = "CUST-HighFashions",
                        SoftDescriptor = "HighFashions",
                        AmountWithBreakdown = new AmountWithBreakdown
                        {
                            CurrencyCode = "USD",
                            Value = "220.00",
                            AmountBreakdown = new AmountBreakdown
                            {
                                ItemTotal = new PayPalCheckoutSdk.Orders.Money
                                {
                                    CurrencyCode = "USD",
                                    Value = "180.00"
                                },
                                Shipping = new PayPalCheckoutSdk.Orders.Money
                                {
                                    CurrencyCode = "USD",
                                    Value = "20.00"
                                },
                                Handling = new PayPalCheckoutSdk.Orders.Money
                                {
                                    CurrencyCode = "USD",
                                    Value = "10.00"
                                },
                                TaxTotal = new PayPalCheckoutSdk.Orders.Money
                                {
                                    CurrencyCode = "USD",
                                    Value = "20.00"
                                },
                                ShippingDiscount = new PayPalCheckoutSdk.Orders.Money
                                {
                                    CurrencyCode = "USD",
                                    Value = "10.00"
                                }
                            }
                        },
                        Items = new List<Item>
                        {
                            new Item
                            {
                                Name = "T-shirt",
                                Description = "Green XL",
                                Sku = "sku01",
                                UnitAmount = new PayPalCheckoutSdk.Orders.Money
                                {
                                    CurrencyCode = "USD",
                                    Value = "90.00"
                                },
                                Tax = new PayPalCheckoutSdk.Orders.Money
                                {
                                    CurrencyCode = "USD",
                                    Value = "10.00"
                                },
                                Quantity = "1",
                                Category = "PHYSICAL_GOODS"
                            },
                            new Item
                            {
                                Name = "Shoes",
                                Description = "Running, Size 10.5",
                                Sku = "sku02",
                                UnitAmount = new PayPalCheckoutSdk.Orders.Money
                                {
                                    CurrencyCode = "USD",
                                    Value = "45.00"
                                },
                                Tax = new PayPalCheckoutSdk.Orders.Money
                                {
                                    CurrencyCode = "USD",
                                    Value = "5.00"
                                },
                                Quantity = "2",
                                Category = "PHYSICAL_GOODS"
                            }
                        },
                        ShippingDetail = new ShippingDetail
                        {
                            Name = new Name
                            {
                                FullName = "John Doe"
                            },
                            AddressPortable = new AddressPortable
                            {
                                AddressLine1 = "123 Townsend St",
                                AddressLine2 = "Floor 6",
                                AdminArea2 = "San Francisco",
                                AdminArea1 = "CA",
                                PostalCode = "94107",
                                CountryCode = "US"
                            }
                        }
                    }
                }
            };

            return orderRequest;
        }


        public async static Task<HttpResponse> CreateOrder(bool debug = false)
        {
            var request = new OrdersCreateRequest();
            request.Headers.Add("prefer", "return=representation");
            request.RequestBody(BuildRequestBody());
            var client = PayPalClient.client();
            var response = await client.Execute(request);

            if (debug)
            {
                var result = response.Result<Order>();
                Console.WriteLine("Status: {0}", result.Status);
                Console.WriteLine("Order Id: {0}", result.Id);
                Console.WriteLine("Intent: {0}", result.CheckoutPaymentIntent);
                Console.WriteLine("Links:");
                foreach (PayPalCheckoutSdk.Orders.LinkDescription link in result.Links)
                {
                    Console.WriteLine("\t{0}: {1}\tCall Type: {2}", link.Rel, link.Href, link.Method);
                }
                AmountWithBreakdown amount = result.PurchaseUnits[0].AmountWithBreakdown;
                Console.WriteLine("Total Amount: {0} {1}", amount.CurrencyCode, amount.Value);
                Console.WriteLine("Response JSON: \n {0}", PayPalClient.ObjectToJSONString(result));
            }

            return response;
        }

        public async static Task<HttpResponse> CaptureOrder(string OrderId, bool debug = false)
        {
            var request = new OrdersCaptureRequest(OrderId);
            request.Prefer("return=representation");
            request.RequestBody(new OrderActionRequest());
            var client = PayPalClient.client();
            var response = await client.Execute(request);

            if (debug)
            {
                var result = response.Result<Order>();
                Console.WriteLine("Status: {0}", result.Status);
                Console.WriteLine("Order Id: {0}", result.Id);
                Console.WriteLine("Intent: {0}", result.CheckoutPaymentIntent);
                Console.WriteLine("Links:");
                foreach (PayPalCheckoutSdk.Orders.LinkDescription link in result.Links)
                {
                    Console.WriteLine("\t{0}: {1}\tCall Type: {2}", link.Rel, link.Href, link.Method);
                }
                Console.WriteLine("Capture Ids: ");
                foreach (PurchaseUnit purchaseUnit in result.PurchaseUnits)
                {
                    foreach (PayPalCheckoutSdk.Orders.Capture capture in purchaseUnit.Payments.Captures)
                    {
                        Console.WriteLine("\t {0}", capture.Id);
                    }
                }
                AmountWithBreakdown amount = result.PurchaseUnits[0].AmountWithBreakdown;
                Console.WriteLine("Buyer:");
                Console.WriteLine("\tEmail Address: {0}\n\tName: {1} {2}\n",
                    result.Payer.Email,
                    result.Payer.Name.GivenName,
                    result.Payer.Name.Surname);
                Console.WriteLine("Response JSON:\n{0}", PayPalClient.ObjectToJSONString(result));
            }

            return response;
        }


        public static async Task asdasd()
        {
            Console.WriteLine("Running Capture Intent Flow..");
            var createOrderResponse = CreateOrder(true).Result;
            var createOrderResult = createOrderResponse.Result<Order>();
            Console.WriteLine("Status: {0}", createOrderResult.Status);
            Console.WriteLine("Order Id: {0}", createOrderResult.Id);
            Console.WriteLine("Intent: {0}", createOrderResult.CheckoutPaymentIntent);
            Console.WriteLine("Links:");
            foreach (PayPalCheckoutSdk.Orders.LinkDescription link in createOrderResult.Links)
            {
                Console.WriteLine("\t{0}: {1}\tCall Type: {2}", link.Rel, link.Href, link.Method);
            }
            AmountWithBreakdown amount = createOrderResult.PurchaseUnits[0].AmountWithBreakdown;
            Console.WriteLine("Total Amount: {0} {1}", amount.CurrencyCode, amount.Value);

            Console.WriteLine("Copy approve link and paste it in browser. Login with buyer account and follow the instructions.\nOnce approved hit enter...\n");
            Console.Read();

            Console.WriteLine("Capturing the payment...");
            var captureOrderResponse = CaptureOrder(createOrderResult.Id, true).Result;
            var captureOrderResult = captureOrderResponse.Result<Order>();
            var captureId = "";
            Console.WriteLine("Status: {0}", captureOrderResult.Status);
            Console.WriteLine("Order Id: {0}", captureOrderResult.Id);
            Console.WriteLine("Intent: {0}", captureOrderResult.CheckoutPaymentIntent);
            Console.WriteLine("Links:");
            foreach (PayPalCheckoutSdk.Orders.LinkDescription link in captureOrderResult.Links)
            {
                Console.WriteLine("\t{0}: {1}\tCall Type: {2}", link.Rel, link.Href, link.Method);
            }
            foreach (PurchaseUnit purchaseUnit in captureOrderResult.PurchaseUnits)
            {
                foreach (PayPalCheckoutSdk.Orders.Capture capture in purchaseUnit.Payments.Captures)
                {
                    captureId = capture.Id;
                }
            }
            AmountWithBreakdown captureAmount = captureOrderResult.PurchaseUnits[0].AmountWithBreakdown;
            Console.WriteLine("Buyer:");
            Console.WriteLine("\tEmail Address: {0}\n\tName: {1} {2}",
            captureOrderResult.Payer.Email, captureOrderResult.Payer.Name.GivenName, captureOrderResult.Payer.Name.Surname);

            Console.WriteLine("Refunding the Order....");
            var refundOrderResponse = CapturesRefund(captureId, true).Result;
            var refundOrderResult = refundOrderResponse.Result<PayPalCheckoutSdk.Payments.Refund>();
            Console.WriteLine("Status: {0}", refundOrderResult.Status);
            Console.WriteLine("Refund Id: {0}", refundOrderResult.Id);
            Console.WriteLine("Links:");
            foreach (PayPalCheckoutSdk.Payments.LinkDescription link in refundOrderResult.Links)
            {
                Console.WriteLine("\t{0}: {1}\tCall Type: {2}", link.Rel, link.Href, link.Method);
            }
        }

        public async static Task<HttpResponse> CapturesRefund(string CaptureId, bool debug = false)
        {
            var request = new CapturesRefundRequest(CaptureId);
            request.Prefer("return=representation");
            RefundRequest refundRequest = new RefundRequest()
            {
                Amount = new PayPalCheckoutSdk.Payments.Money
                {
                    Value = "20.00",
                    CurrencyCode = "USD"
                }
            };
            request.RequestBody(refundRequest);
            var response = await PayPalClient.client().Execute(request);

            if (debug)
            {
                var result = response.Result<PayPalCheckoutSdk.Payments.Refund>();
                Console.WriteLine("Status: {0}", result.Status);
                Console.WriteLine("Refund Id: {0}", result.Id);
                Console.WriteLine("Links:");
                foreach (PayPalCheckoutSdk.Payments.LinkDescription link in result.Links)
                {
                    Console.WriteLine("\t{0}: {1}\tCall Type: {2}", link.Rel, link.Href, link.Method);
                }
                Console.WriteLine("Response JSON: \n {0}", PayPalClient.ObjectToJSONString(result));
            }
            return response;
        }

    }

    public class PayPalClient
    {
        /**
            Setting up PayPal environment with credentials with sandbox cerdentails. 
            For Live, this should be LiveEnvironment Instance. 
         */
        public static PayPalEnvironment environment()
        {
            return new SandboxEnvironment(
                 System.Environment.GetEnvironmentVariable("") != null ?
                 System.Environment.GetEnvironmentVariable("") : "",
                System.Environment.GetEnvironmentVariable("") != null ?
                 System.Environment.GetEnvironmentVariable("") : "");
        }

        /**
            Returns PayPalHttpClient instance which can be used to invoke PayPal API's.
         */
        public static HttpClient client()
        {
            return new PayPalHttpClient(environment());
        }

        public static HttpClient client(string refreshToken)
        {
            return new PayPalHttpClient(environment(), refreshToken);
        }

        /**
            This method can be used to Serialize Object to JSON string.
        */
        public static String ObjectToJSONString(Object serializableObject)
        {
            MemoryStream memoryStream = new MemoryStream();
            var writer = JsonReaderWriterFactory.CreateJsonWriter(
                        memoryStream, Encoding.UTF8, true, true, "  ");
            DataContractJsonSerializer ser = new DataContractJsonSerializer(serializableObject.GetType(), new DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true });
            ser.WriteObject(writer, serializableObject);
            memoryStream.Position = 0;
            StreamReader sr = new StreamReader(memoryStream);
            return sr.ReadToEnd();
        }
    }

}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 18:57:33