在本教程中,我将向您展示如何使用 PayPal rest api和 C# 进行付款。他们为不同语言(如 Ruby、node .js、python、php)提供的所有库都非常相似,因此这里的所有概念都适用于所有库。
项目设置
首先,我在 Visual Studio 2015 中创建了一个 MVC 项目:File > New > Project,然后选择ASP.NET Application。
选择ASP.NET 5 Web Application Template,它使用新的 MVC 6。如果您熟悉它,它类似于 MVC 5。
正如您在下面的照片中看到的,我在解决方案中添加了一些文件和文件夹。需要注意的主要两点是:
在参考文献中,我已经删除了目标DNX Core 5.0,它允许我们在mac OS X 或 Linux 中运行这个项目,但是我们需要的 PayPal 库还没有更新。
我添加了文件夹“服务”,我将在其中包装 PayPal 调用的逻辑,这样我们就可以让控制器保持简洁。
使用 NuGet 安装 PayPal SDK。右键单击解决方案的名称并选择Manage NuGet Packages,然后搜索“PayPal”并安装它。
创建一个贝宝应用
要将我们的应用程序与 PayPal 集成,我们需要导航到PayPal Developers,然后在 REST API 应用程序下,单击Create App。
为您的应用命名并选择与该应用关联的沙盒开发者帐户。出于测试目的,我们可以导航到http://sandbox.paypal.com并使用沙盒登录详细信息登录以查看测试的 PayPal 帐户和交易。
单击Create App后,我们会看到带有 Client ID 和 Secret 令牌的确认屏幕。
将 clientId 和 clientSecret 令牌复制到appsettings.json,如下面的屏幕截图所示:
测试付款
PayPal 提供了一个沙盒环境进行测试。您可以从那里创建测试买家和卖家账户。注册后,您将在沙盒中拥有一个与您的开发者帐户绑定的企业帐户。
要创建新的测试帐户,请登录到开发人员站点,然后单击 仪表板选项卡并导航到沙箱 > 帐户。如果有的话,您可以在这里查看测试账户列表:
如果您还没有创建测试帐户,请继续并单击右上角的创建帐户, 以创建至少一个测试个人帐户和一个测试企业帐户。
创建测试帐户后,您可以通过www.sandbox.paypal.com 使用您在之前表格中分配给每个帐户的测试电子邮件地址和密码登录。这对于测试当您使用“个人测试帐户”购买东西时资金是否转移到您的“测试业务帐户”非常有用。现在您已准备好开始与 PayPal 集成并测试资金是否从一个帐户转移到另一个帐户。
单一的贝宝付款
PayPal 提供不同的付款方式。您可以使用***直接付款,这意味着您的客户不会看到 PayPal 登录页面或摘要——这一切都发生在您的网站上。您需要为此符合 PCI,我建议使用 Stripe,因为您只需要使用他们的javascript库的 SSL。另一方面,要通过 PayPal 付款,需要三个步骤:
指定付款信息以创建付款。
通过将您的客户重定向到 PayPal 以批准交易来获得付款批准。
在 PayPal 将您的客户重定向回您的网站后,执行付款以获取资金。
在我的 MVC 项目的services文件夹中,我创建了 PayPalPaymentService 类,并在其中添加了以下方法:
public static Payment CreatePayment(string baseUrl, string intent) { // ### Api Context // Pass in a `APIContext` object to authenticate // the call and to send a unique request id // (that ensures idempotency). The SDK generates // a request id if you do not pass one explicitly. var apiContext = PayPalConfiguration.GetAPIContext(); // Payment Resource var payment = new Payment() { intent = intent, // `sale` or `authorize` payer = new Payer() { payment_method = "paypal" }, transactions = GetTransactionsList(), redirect_urls = GetReturnUrls(baseUrl, intent) }; // Create a payment using a valid APIContext var createdPayment = payment.Create(apiContext); return createdPayment; } private static List<Transaction> GetTransactionsList() { // A transaction defines the contract of a payment // what is the payment for and who is fulfilling it. var transactionList = new List<Transaction>(); // The Payment creation API requires a list of Transaction; // add the created Transaction to a List transactionList.Add(new Transaction() { description = "Transaction description.", invoice_number = GetRandomInvoiceNumber(), amount = new Amount() { currency = "USD", total = "100.00", // Total must be equal to sum of shipping, tax and subtotal. details = new Details() // Details: Let's you specify details of a payment amount. { tax = "15", shipping = "10", subtotal = "75" } }, item_list = new ItemList() { items = new List<Item>() { new Item() { name = "Item Name", currency = "USD", price = "15", quantity = "5", sku = "sku" } } } }); return transactionList; } private static RedirectUrls GetReturnUrls(string baseUrl, string intent) { var returnUrl = intent == "sale" ? "/Home/PaymentSuccessful" : "/Home/AuthorizeSuccessful"; // Redirect URLS // These URLs will determine how the user is redirected from PayPal // once they have either approved or canceled the payment. return new RedirectUrls() { cancel_url = baseUrl + "/Home/PaymentCancelled", return_url = baseUrl + returnUrl }; } public static Payment ExecutePayment(string paymentId, string payerId) { // ### Api Context // Pass in a `APIContext` object to authenticate // the call and to send a unique request id // (that ensures idempotency). The SDK generates // a request id if you do not pass one explicitly. var apiContext = PayPalConfiguration.GetAPIContext(); var paymentExecution = new PaymentExecution() { payer_id = payerId }; var payment = new Payment() { id = paymentId }; // Execute the payment. var executedPayment = payment.Execute(apiContext, paymentExecution); return executedPayment; }
此调用中传递了一些参数:
Intent:三个可能的值:“sale”表示立即付款,“authorize”表示授权付款以稍后获取,“order”表示创建订单。当您获得稍后付款的授权时,您有 3 天的保证,但您最多可以尝试在 29 天后提取付款。
付款人:此付款的资金来源,使用的付款方式 - 贝宝钱包付款,***直接借记卡或直接***。
交易:这用于指定付款金额,并可选择指定要支付的项目。如果需要,您还可以指定小计、运费和税金。
重定向 URL: 指定 PayPal 在交易后将您的客户重定向到的 URL,以便您可以更新数据库并显示确认消息。
可以从您的控制器中使用以前的功能,如下所示:
public IActionResult CreatePayment() { var payment = PayPalPaymentService.CreatePayment(GetBaseUrl(), "sale"); return Redirect(payment.GetApprovalUrl()); } public IActionResult PaymentCancelled() { // TODO: Handle cancelled payment return RedirectToAction("Error"); } public IActionResult PaymentSuccessful(string paymentId, string token, string PayerID) { // Execute Payment var payment = PayPalPaymentService.ExecutePayment(paymentId, PayerID); return View(); }
如您所见,我创建了三个动作:
CreatePayment:这是触发付款的操作。它调用 PayPal 来创建付款,然后将用户重定向到 PayPal 以批准交易。
PaymentSuccessful:这是 PayPal 在成功付款后将我们的客户重定向回来的操作。此时我们可以执行付款以将资金转入我们的商家帐户。
PaymentCancelled:如果用户取消批准过程,此操作是从 PayPal 重定向用户的地方。此时,您可能希望让客户选择再试一次或与您联系。
授权付款以稍后捕获
这种情况与前一种情况非常相似。如果您尝试预订尚不可用的产品,则可能需要使用此方法。获得这笔款项的步骤是:
授权付款:此调用的“intent”参数应为“authorize”。
获取付款:请记住,授权最多可以保证 3 天,但您可以尝试获取最长 29 天的付款。
为了实现这种类型的支付,我只在类 PayPalPaymentService 中添加了一个新方法来捕获支付:
public static Capture CapturePayment(string paymentId) { var apiContext = PayPalConfiguration.GetAPIContext(); var payment = Payment.Get(apiContext, paymentId); var auth = payment.transactions[0].related_resources[0].authorization; // Specify an amount to capture. By setting 'is_final_capture' to true, all remaining funds held by the authorization will be released from the funding instrument. var capture = new Capture() { amount = new Amount() { currency = "USD", total = "4.54" }, is_final_capture = true }; // Capture an authorized payment by posting to // URI v1/payments/authorization/{authorization_id}/capture var responseCapture = auth.Capture(apiContext, capture); return responseCapture; }
然后在 HomeController 中,我添加了两个新动作来显示这种付款方式:
public IActionResult AuthorizePayment() { var payment = PayPalPaymentService.CreatePayment(GetBaseUrl(), "authorize"); return Redirect(payment.GetApprovalUrl()); } public IActionResult AuthorizeSuccessful(string paymentId, string token, string PayerID) { // Capture Payment var capture = PayPalPaymentService.CapturePayment(paymentId); return View(); }
AuthorizePayment是触发支付的动作。它与前面的“CreatePayment”函数非常相似,但在这种情况下,我们将“authorize”作为意图参数传递。
AuthorizeSuccessful是您的客户在成功批准 PayPal 付款后将被重定向的操作。此时我正在捕获付款,但您可以将 paymentId 保存在您的数据库中,并在需要时捕获付款。
在这些代码示例中,为简单起见,我对支付变量值进行了硬编码。在您的实际应用程序中,您可能会将它们包装在将所有这些值作为变量的方法中,以便可以动态设置和重用所有内容。
订阅
这在 PayPal 中称为“计费计划”——您可以创建定期付款计划,并通过创建计费协议为您的客户订阅计费计划。使用 PayPal REST API,您可以创建、更新或删除计费计划;如果您想构建一个管理面板来为您的业务管理这些事情,那么您可能会使用它。
为您的客户创建经常性费用的步骤是:
创建一个计费计划 并 激活它。创建计费计划后,它处于已创建状态。它需要通过发出 PATCH 请求来激活。
创建计费协议 并 执行它:对创建计费协议调用的响应包括到approval_url 和execute_url 的链接。我们需要获得计费协议的批准,然后执行计费协议。
计费计划
创建计费计划
创建一个定义计费周期的计费计划。这是我们创建计划所需传递的参数的摘要。
名称:计费方案的名称。
描述:计费计划的描述。
类型: 对于一定数量的定期付款,允许的值为“FIXED”,对于在手动取消之前重复的计划,允许值为“INFINITE”。
Merchant Preferences:这是一个指定首选项的对象,例如设置费用、支付失败的最大尝试次数、返回 URL、取消 URL、通知 URL,PayPal 将在付款后重定向用户。
付款定义:此计划的付款定义数组。通常这个数组会有一个或两个支付定义。如果我们想提供免费试用或以折扣价试用,那么我们设置了两个付款定义。第一个定义是试用期,第二个定义是定期付款。付款定义的属性是名称、类型(试用或常规)、频率(日、周、月、年)、频率间隔(如果我们将频率设置为 'WEEK' 并将频率间隔设置为 '1',我们是定义每周付款),向客户收取的金额, 周期是总付款次数。收费模式是为计划的金额指定额外的运费和税金。
这是显示如何创建计费计划的代码片段:
// Define the plan and attach the payment definitions and merchant preferences. // More Information: https://developer.paypal.com/webapps/developer/docs/api/#create-a-plan var billingPlan = new Plan { name = "Tuts+ Plus", description = "Monthly plan for courses.", type = "fixed", // Define the merchant preferences. // More Information: https://developer.paypal.com/webapps/developer/docs/api/#merchantpreferences-object merchant_preferences = new MerchantPreferences() { setup_fee = GetCurrency("0"), // $0 return_url = "returnURL", // Retrieve from config cancel_url = "cancelURL", // Retrieve from config auto_bill_amount = "YES", initial_fail_amount_action = "CONTINUE", max_fail_attempts = "0" }, payment_definitions = new List<PaymentDefinition> { // Define a trial plan that will only charge $9.99 for the first // month. After that, the standard plan will take over for the // remaining 11 months of the year. new PaymentDefinition() { name = "Trial Plan", type = "TRIAL", frequency = "MONTH", frequency_interval = "1", amount = GetCurrency("0"), // Free for the 1st month cycles = "1", charge_models = new List<ChargeModel> { new ChargeModel() { type = "TAX", amount = GetCurrency("1.65") // If we need to charge Tax }, new ChargeModel() { type = "SHIPPING", amount = GetCurrency("9.99") // If we need to charge for Shipping } } }, // Define the standard payment plan. It will represent a monthly // plan for $19.99 USD that charges once month for 11 months. new PaymentDefinition { name = "Standard Plan", type = "REGULAR", frequency = "MONTH", frequency_interval = "1", amount = GetCurrency("15.00"), // > NOTE: For `IFNINITE` type plans, `cycles` should be 0 for a `REGULAR` `PaymentDefinition` object. cycles = "11", charge_models = new List<ChargeModel> { new ChargeModel { type = "TAX", amount = GetCurrency("2.47") }, new ChargeModel() { type = "SHIPPING", amount = GetCurrency("9.99") } } } } }; // Get PayPal Config var apiContext = PayPalConfiguration.GetAPIContext(); // Create Plan plan.Create(apiContext);
新创建的计费计划处于已创建状态。将其激活为 ACTIVE 状态,以便您的客户可以订阅该计划。要激活计划,我们需要发出 PATCH 请求:
// Activate the plan var patchRequest = new PatchRequest() { new Patch() { op = "replace", path = "/", value = new Plan() { state = "ACTIVE" } } }; plan.Update(apiContext, patchRequest);
如您所见,PayPal 库是其 REST API 的直接包装器,这很好,但与 Stripe 等其他 API 相比,该 API 也非常复杂。出于这个原因,将所有 PayPal 通信封装在对象中,为我们的应用程序提供更清晰、更简单的 API 确实是一个不错的选择。在这里,您可以看到包含在多个带参数的函数中的代码如下所示:
public static Plan CreatePlanObject(string planName, string planDescription, string returnUrl, string cancelUrl, string frequency, int frequencyInterval, decimal planPrice, decimal shippingAmount = 0, decimal taxPercentage = 0, bool trial = false, int trialLength = 0, decimal trialPrice = 0) { // Define the plan and attach the payment definitions and merchant preferences. // More Information: https://developer.paypal.com/docs/rest/api/payments.billing-plans/ return new Plan { name = planName, description = planDescription, type = PlanType.Fixed, // Define the merchant preferences. // More Information: https://developer.paypal.com/webapps/developer/docs/api/#merchantpreferences-object merchant_preferences = new MerchantPreferences() { setup_fee = GetCurrency("1"), return_url = returnUrl, cancel_url = cancelUrl, auto_bill_amount = "YES", initial_fail_amount_action = "CONTINUE", max_fail_attempts = "0" }, payment_definitions = GetPaymentDefinitions(trial, trialLength, trialPrice, frequency, frequencyInterval, planPrice, shippingAmount, taxPercentage) }; } private static List<PaymentDefinition> GetPaymentDefinitions(bool trial, int trialLength, decimal trialPrice, string frequency, int frequencyInterval, decimal planPrice, decimal shippingAmount, decimal taxPercentage) { var paymentDefinitions = new List<PaymentDefinition>(); if (trial) { // Define a trial plan that will charge 'trialPrice' for 'trialLength' // After that, the standard plan will take over. paymentDefinitions.Add( new PaymentDefinition() { name = "Trial", type = "TRIAL", frequency = frequency, frequency_interval = frequencyInterval.ToString(), amount = GetCurrency(trialPrice.ToString()), cycles = trialLength.ToString(), charge_models = GetChargeModels(trialPrice, shippingAmount, taxPercentage) }); } // Define the standard payment plan. It will represent a 'frequency' (monthly, etc) // plan for 'planPrice' that charges 'planPrice' (once a month) for #cycles. var regularPayment = new PaymentDefinition { name = "Standard Plan", type = "REGULAR", frequency = frequency, frequency_interval = frequencyInterval.ToString(), amount = GetCurrency(planPrice.ToString()), // > NOTE: For `IFNINITE` type plans, `cycles` should be 0 for a `REGULAR` `PaymentDefinition` object. cycles = "11", charge_models = GetChargeModels(trialPrice, shippingAmount, taxPercentage) }; paymentDefinitions.Add(regularPayment); return paymentDefinitions; } private static List<ChargeModel> GetChargeModels(decimal planPrice, decimal shippingAmount, decimal taxPercentage) { // Create the Billing Plan var chargeModels = new List<ChargeModel>(); if (shippingAmount > 0) { chargeModels.Add(new ChargeModel() { type = "SHIPPING", amount = GetCurrency(shippingAmount.ToString()) }); } if (taxPercentage > 0) { chargeModels.Add(new ChargeModel() { type = "TAX", amount = GetCurrency(String.Format("{0:f2}", planPrice * taxPercentage / 100)) }); } return chargeModels; }
更新计费计划
您可以通过提出“PATCH”请求来更新现有计费计划的信息。这是一个包装该调用的函数:
public static void UpdateBillingPlan(string planId, string path, object value) { // PayPal Authentication tokens var apiContext = PayPalConfiguration.GetAPIContext(); // Retrieve Plan var plan = Plan.Get(apiContext, planId); // Activate the plan var patchRequest = new PatchRequest() { new Patch() { op = "replace", path = path, value = value } }; plan.Update(apiContext, patchRequest); }
要更新计费计划描述,我们可以调用此函数并传递正确的参数:
UpdateBillingPlan( planId: "P-5FY40070P6526045UHFWuvEI", path: "/", value: new Plan { description = "new description" });
删除计费计划
理想情况下,当您不想接受新客户加入计费计划时,您需要将其更新为“未激活”状态。这不会影响该计划的现有计费协议。这可以通过调用 UpdateBillingPlan 函数来完成:
UpdateBillingPlan( planId: "P-5FY40070P6526045UHFWUVEI", path: "/", value: new Plan { state = "INACTIVE" });
计费协议
创建计费协议
创建一个或多个计费计划后,您希望开始让客户注册您的订阅计划。为此,您需要收集客户详细信息并向 PayPal 提出请求。为了能够测试此功能,我在 HomeController 中添加了几个操作:
public IActionResult Subscribe() { var plan = PayPalSubscriptionsService.CreateBillingPlan("Tuts+ Plan", "Test plan for this article", GetBaseUrl()); var subscription = PayPalSubscriptionsService.CreateBillingAgreement(plan.id, new PayPal.Api.ShippingAddress { city = "London", line1 = "line 1", postal_code = "SW1A 1AA", country_code = "GB" }, "Pedro Alonso", "Tuts+", DateTime.Now); return Redirect(subscription.GetApprovalUrl()); } public IActionResult SubscribeSuccess(string token) { // Execute approved agreement PayPalSubscriptionsService.ExecuteBillingAgreement(token); return View(); } public IActionResult SubscribeCancel(string token) { // TODO: Handle cancelled payment return RedirectToAction("Error"); }
订阅: 这是第一个被调用的动作。它正在创建一个测试计费计划,然后创建该计划的计费协议(订阅),并将用户重定向到 PayPal 以确认付款。
SubscribeSuccess:此操作是在成功订阅后用作“返回 URL”的操作。协议令牌标识符在查询字符串中传递,我们使用此令牌执行计费协议并使其处于活动状态。
订阅取消:此操作用作“取消 URL”。如果由于某种原因付款失败,或者您的客户在 PayPal 上取消付款,则用户将执行此操作,您需要进行处理。也许提供重试的选项。
正如您在前面的代码片段中看到的那样,我将大部分功能包装在几个方法中。第一个是上一节中解释过的“CreateBillingPlan”。第二个是“CreateBillingAgreement”,用于为用户订阅计划:
public static Agreement CreateBillingAgreement(string planId, ShippingAddress shippingAddress, string name, string description, DateTime startDate) { // PayPal Authentication tokens var apiContext = PayPalConfiguration.GetAPIContext(); var agreement = new Agreement() { name = name, description = description, start_date = startDate.ToString("yyyy-MM-ddTHH:mm:ss") + "Z", payer = new Payer() { payment_method = "paypal" }, plan = new Plan() { id = planId }, shipping_address = shippingAddress }; var createdAgreement = agreement.Create(apiContext); return createdAgreement; }
第三种方法是“ExecuteBillingAgreement”。订阅批准成功后,我们使用返回的令牌激活订阅:
public static void ExecuteBillingAgreement(string token) { // PayPal Authentication tokens var apiContext = PayPalConfiguration.GetAPIContext(); var agreement = new Agreement() { token = token }; var executedAgreement = agreement.Execute(apiContext); }
暂停计费协议
使用此方法暂停协议:
public static void SuspendBillingAgreement(string agreementId) { var apiContext = PayPalConfiguration.GetAPIContext(); var agreement = new Agreement() { id = agreementId }; agreement.Suspend(apiContext, new AgreementStateDescriptor() { note = "Suspending the agreement" }); }
重新激活计费协议
这个真的和上一个很像:
public static void reactivateBillingAgreement(string agreementId) { var apiContext = PayPalConfiguration.GetAPIContext(); var agreement = new Agreement() { id = agreementId }; agreement.ReActivate(apiContext, new AgreementStateDescriptor() { note = "Reactivating the agreement" }); }
取消计费协议
使用此功能取消计划:
public static void CancelBillingAgreement(string agreementId) { var apiContext = PayPalConfiguration.GetAPIContext(); var agreement = new Agreement() { id = agreementId }; agreement.Cancel(apiContext, new AgreementStateDescriptor() { note = "Cancelling the agreement" }); }
更新计费协议
这个选项非常有限,我希望从这个电话中可以更改订阅计划,以升级或降级客户。像在 Stripe 中一样,这在单个调用中不受支持。您需要通过取消当前协议并为升级或降级创建新协议来处理这种情况。这并不理想,但将来可能会改变。
结论
这是人们用来与 PayPal 集成的最常见功能的概述。他们的 API 远大于本文中介绍的集成方法——您还可以发出退款和部分退款,并且在本文介绍的示例中,它们有许多不同的边缘案例选项。
- 计费计划
- 创建计费计划
- 更新计费计划
- 删除计费计划
- 计费协议
- 创建计费协议
- 暂停计费协议
- 重新激活计费协议
- 取消计费协议
- 更新计费协议