Click here to Skip to main content
15,885,011 members
Articles / Programming Languages / C#

Client/Server Implementation of PayPal Smart Button with C# and ASP.NET Core

Rate me:
Please Sign up or sign in to vote.
4.45/5 (5 votes)
5 Aug 2020CPOL6 min read 37K   806   12   2
Implement PayPal Smart Button into your ASP.NET Core web application as recommended by PayPal
This article will take you through the steps required to implement PayPal Smart Button into your ASP.NET Core web application using the client/server approach. PayPal recommends the Client/Server approach.

Learning Outcomes

  • How to set up a PayPal Business account
  • How to get API Credentials for Sandbox – i.e., Client ID and Secret
  • How to get API Credentials for Live – i.e., Client ID and Secret
  • What fields to include in your data store
  • Server-Side code

Prerequisites

  • You have registered a URL for your e-commerce website, for example, www.delaneys.space.
  • The website is secured with an SSL certificate.
  • Your customer can sign up and sign in to your website in a secure manner using OAuth or some over secure method.
  • Your website has a backend datastore and you know how to maintain it.
  • You are familiar with Visual Studio.

Create a Spreadsheet With the Following Row Heading

1 Dashboard URL https://www.paypal.com/mep/dashboard
2 Developer Dashboard URL https://developer.paypal.com/developer/applications
3

SandBox Details

4 URL www.sandbox.paypal.com/
5 App Name  
6 Business
7 Username …@business.example.com
8 Password  
9 Client ID  
10 Secret  
11 Personal (Customer)  
12 Username ...@personal.example.com
13 Password  
14

Live Details

 
15 Username info@...
16 Password  
17 App Name  
18 Client ID  
19 Secret  

Creating a PayPal Business Account

To create a PayPal Business Account, you do not have to own a business or have a business bank account. Having said that, you should put in place practices that separate your personal financial transactions from the transactions from your e-commerce website. Therefore, I suggest using a separate personal bank account to link to your PayPal 'business' account.

  1. Navigate to www.PayPal.com.
  2. Click Sign Up, even if you already have a personal PayPal account. Remember never mix business with pleasure.

  3. Select the Business Account and click Next.
  4. Complete the short questionnaire.

    1. Select "On my website" for the question "I want to mainly accept payments:".
    2. Select an answer to the question "May annual volume is:".

  5. Provide an email address and click Continue.

    This can be any valid email address, but you may want to use 'info@your e-commerce.com'.

  6. Provide a password and click Continue.

    A confirmation email will be sent to the address provided.

    Update your spreadsheet.

    15 Username info@...
    16 Password  
  7. Complete the business contact details page and click Agree and Create Account.

    They will require your contact name, a business name, telephone number and business address.

  8. Select a business type.

    Select from a list of Individual, Sole owner, Partnership, Private company, Public company, Not-for-profit organisation, Government entity, Trust – Investment and Family.

  9. Add personal details.

    This includes your name, date of birth and address.

  10. Click Submit.

    You have now set up your PayPal Business Account. You should be presented with a dashboard display.

Get API Credentials – i.e., Client ID and Secret

  1. Go to the developer dashboard:

    2 Developer Dashboard URL https://developer.paypal.com/developer/applications

    The Sandbox option will be pre-selected. So, let's set that up first.

  2. Click Create App.
  3. Create a sandbox app name and store in your spreadsheet.

    17 App Name  
  4. Click Create App, again.

    Update the following on your spreadsheet:

    6 Business
    7 Username …@business.example.com
    9 Client ID  
    10 Secret  

    Now let's get the password.

  5. Click the SANDBOX | Accounts button or (developer.paypal.com/developer/accounts/).

    You should see two email addresses representing a business and personal account.

  6. Click the … button of the Business account and select View/Edit Account.

  7. Record the System Generated Password in your spreadsheet.

    8 Password  
  8. Click Close.
  9. Click the … button of the Personal account and select View/Edit Account.

    Record the username and password in your spreadsheet.

    11 Personal (Customer)  
    12 Username ...@personal.example.com
    13 Password  

Get API Credentials for Live

  1. Click My Apps & Credentials (developer.paypal.com/developer/applications/).
  2. Click the Live button.

  3. Click Create App.

    Record the app name in your spreadsheet.

    17 App Name  
  4. Click Create App (again).

    Record the client ID and secret in your spreadsheet.

    18 Client ID  
    19 Secret  

What Fields to Add to Your Datastore

  1. Choose a data store such as SQL Servers.
  2. You will require a Basket, Item and Invoice Model/Core classes. Add the following blue highlighted fields to your invoice table. These fields will be used by PayPal.

    Note that the FirstName, LastName and Email are stored in the User table, but are also duplicated in the Invoice table. This is because the data from the customer's PayPal account may be different from the data on the User table.

    It is up to you to decide how to include the blue fields in your data store.

Server-Side Code

  1. Create an ASP.NET Core 3.x MVC application using Visual Studio.
  2. Go to NuGet package manager and add the following packages:
    • PayPalCheckoutSdk, package version 1.0.3. I used the latest version at the time of writing.

      PayPalCheckoutSdk is merely a class library. There is no logic contained within the library. The classes are decorated with attributes to aid the serialisation into JSON.

    • PayPalHttp v1.0.0.

    • Microsoft.AspNetCore.Mvc.NewtonsoftJson. With the release of ASP.NET Core 3.0, Microsoft broke their implementation of JSON serialisation. Search for "ASP.NET Core: Blank Json {} Return After Upgrading to 3.0" to find out what to add to Startup.ConfigureServices or choose an option from the next step.

  3. Update Startup.ConfigureServices to call AddNewtonsoftJson.

    C#
    services.AddMvc()
            .AddNewtonsoftJson();

    OR:

    C#
    services.AddMvc()
            .AddNewtonsoftJson(options =>
                               options.SerializerSettings.ContractResolver =
                               new CamelCasePropertyNamesContractResolver());
  4. Create a folder called PayPal in your ASP.NET Core project.
  5. Create a class PayPalClient in the folder, with the code below.
    Remember to use the sandbox and live client Ids and secrets to populate the highlighted string content.
    C#
    using System;
    using PayPalCheckoutSdk.Core;
    
    using System.IO;
    using System.Text;
    using System.Runtime.Serialization.Json;
    
    namespace PayPal
    {
        public class PayPalClient
        {
            // Place these static properties into a settings area.
            public static string SandboxClientId { get; set; } = 
                                 "<alert>{PayPal SANDBOX Client Id}</alert>";
            public static string SandboxClientSecret { get; set; } = 
                                 "<alert>{PayPal SANDBOX Client Secret}</alert>";
    
            public static string LiveClientId { get; set; } = 
                          "<alert>{PayPal LIVE Client Id}</alert>";
            public static string LiveClientSecret { get; set; } = 
                          "<alert>{PayPal LIVE Client Secret}</alert>";
    
            ///<summary>
            /// Set up PayPal environment with sandbox credentials.
            /// In production, use LiveEnvironment.
            ///</summary>
            public static PayPalEnvironment Environment()
            {
    #if DEBUG
                // You may want to create a UAT (user exceptance tester) 
                // role and check for this:
                // "if(_unitOfWork.IsUATTester(GetUserId())" instead of fcomiler directives.
                return new SandboxEnvironment(<alert>SandboxClientId</alert>,
                                              <alert>SandboxClientSecret</alert>);
    #else
                return new LiveEnvironment(<alert>LiveClientId</alert>, 
                                           <alert>LiveClientSecret</alert>);
    #endif
            }
    
            ///<summary>
            /// Returns PayPalHttpClient instance to invoke PayPal APIs.
            ///</summary>
            public static PayPalCheckoutSdk.Core.PayPalHttpClient Client()
            {
                return new PayPalHttpClient(Environment());
            }
    
            public static PayPalCheckoutSdk.Core.PayPalHttpClient Client(string refreshToken)
            {
                return new PayPalHttpClient(Environment(), refreshToken);
            }
            
            ///<summary>
            /// Use this method to serialize Object to a JSON string.
            ///</summary>
            public static String ObjectToJSONString(Object serializableObject)
            {
                MemoryStream memoryStream = new MemoryStream();
                var writer = JsonReaderWriterFactory.CreateJsonWriter(memoryStream,
                                                                      Encoding.UTF8,
                                                                      true,
                                                                      true,
                                                                      "  ");
    
                var ser = new DataContractJsonSerializer(serializableObject.GetType(),
                                                     new DataContractJsonSerializerSettings 
                                                         {
                                                             UseSimpleDictionaryFormat = true 
                                                         });
    
                ser.WriteObject(writer,
                                serializableObject);
    
                memoryStream.Position = 0;
                StreamReader sr = new StreamReader(memoryStream);
    
                return sr.ReadToEnd();
            }
        }
    }
  6. Create a class SmartButtonHttpResponse in the folder, with code.
    C#
    using System.Net;
    using System.Net.Http.Headers;
    
    namespace PayPal
    {
        public class SmartButtonHttpResponse
        {
            readonly PayPalCheckoutSdk.Orders.Order _result;
            public SmartButtonHttpResponse(PayPalHttp.HttpResponse httpResponse)
            {
                Headers = httpResponse.Headers;
                StatusCode = httpResponse.StatusCode;
                _result = httpResponse.Result<PayPalCheckoutSdk.Orders.Order>();
            }
    
            public HttpHeaders Headers { get; }
            public HttpStatusCode StatusCode { get; }
    
            public PayPalCheckoutSdk.Orders.Order Result()
            {
                return _result;
            }
    
            public string orderID { get; set; }
        }
    }
  7. Create a class OrderBuilder in the folder, with code.

    C#
    using PayPalCheckoutSdk.Orders;
    using System.Collections.Generic;
    
    namespace PayPal
    {
        public static class OrderBuilder
        {
            /// <summary>
            /// Use classes from the PayPalCheckoutSdk to build an OrderRequest
            /// </summary>
            /// <returns></returns>
            public static OrderRequest Build()
            {
                OrderRequest orderRequest = new OrderRequest();
                
                // Add code to fill out the order request properties
                <alert>// See the attached source code for a more detailed example.</alert> 
    
                return orderRequest;
            }
        }
    }
  8. Create a controller class in the Controllers folder called CheckoutController. Add the following code:

    C#
    using Microsoft.AspNetCore.Mvc;
    using System.Threading.Tasks;
    
    using PayPalCheckoutSdk.Orders;
    
    namespace Test.Controllers
    {
        public class CheckoutController : Controller
        {
            /// <summary>
            /// Action to display the cart form for the SERVER side integration
            /// </summary>
            /// <returns></returns>
            public IActionResult Index()
            {
    #if DEBUG
                // You may want to create a UAT (user exceptance tester) role 
                // and check for this:
                // "if(_unitOfWork.IsUATTester(GetUserId())"
                // Company SANDBOX Client Id. To go live replace this with the live ID.
                ViewBag.ClientId = 
                <alert>PayPal.PayPalClient.SandboxClientId</alert>; // Get from a 
                                                           // data store or stettings
    #else
                // Company LIVE Client Id. To go live replace this with the live ID.
                ViewBag.ClientId = 
                <alert>PayPal.PayPalClient.LiveClientId</alert>; // Get from a 
                                                           // data store or stettings
    #endif
    
                ViewBag.CurrencyCode = "GBP"; // Get from a data store
                ViewBag.CurrencySign = "£";   // Get from a data store
    
                return View();
            }
    
            /// <summary>
            /// This action is called when the user clicks on the PayPal button.
            /// </summary>
            /// <returns></returns>
            [Route("api/paypal/checkout/order/create")]
            public async Task<PayPal.SmartButtonHttpResponse> Create()
            {
                var request = new PayPalCheckoutSdk.Orders.OrdersCreateRequest();
    
                request.Prefer("return=representation");
                request.RequestBody(PayPal.OrderBuilder.Build());
                
                // Call PayPal to set up a transaction
                var response = await PayPal.PayPalClient.Client().Execute(request);
                
                // Create a response, with an order id.
                var result = response.Result<PayPalCheckoutSdk.Orders.Order>();
                var payPalHttpResponse = new PayPal.SmartButtonHttpResponse(response)
                {
                    orderID = result.Id
                };
                return payPalHttpResponse;
            }
    
            /// <summary>
            /// This action is called once the PayPal transaction is approved
            /// </summary>
            /// <param name="orderId"></param>
            /// <returns></returns>
            [Route("api/paypal/checkout/order/approved/{orderId}")]
            public IActionResult Approved(string orderId)
            {
                return Ok();
            }
    
            /// <summary>
            /// This action is called once the PayPal transaction is complete
            /// </summary>
            /// <param name="orderId"></param>
            /// <returns></returns>
            [Route("api/paypal/checkout/order/complete/{orderId}")]
            public IActionResult Complete(string orderId)
            {
                // 1. Update the database.
                // 2. Complete the order process. Create and send invoices etc.
                // 3. Complete the shipping process.
                return Ok();
            }
    
            /// <summary>
            /// This action is called once the PayPal transaction is complete
            /// </summary>
            /// <param name="orderId"></param>
            /// <returns></returns>
            [Route("api/paypal/checkout/order/cancel/{orderId}")]
            public IActionResult Cancel(string orderId)
            {
                // 1. Remove the orderId from the database.
                return Ok();
            }
    
            /// <summary>
            /// This action is called once the PayPal transaction is complete
            /// </summary>
            /// <param name="orderId"></param>
            /// <returns></returns>
            [Route("api/paypal/checkout/order/error/{orderId}/{error}")]
            public IActionResult Error(string orderId,
                                       string error)
            {
                // Log the error.
                // Notify the user.
                return NoContent();
            }
        }
    }
  9. Create a Checkout folder in the Views folder and add a view called index.cshtml.

    Add the following code to create the PayPal smart button to the view.

    HTML
    <!-- Set up a container element for the PayPal smart button -->
    <div id="paypal-button-container"></div>
    
    <!-- Include the PayPal JavaScript SDK -->
    <script src="https://www.paypal.com/sdk/js?client-id=@ViewBag.ClientId&
     currency=@ViewBag.CurrencyCode"></script>
    
    <script>
    
        // This is stored just in case the user cancels the other 
        // or there is an error in the other process.
        var orderId;
        // Render the PayPal smart button into #paypal-button-container
        paypal.Buttons({
    
            // Set up the transaction
            createOrder: function (data, actions) {
                orderId = data.orderID;
                return fetch('/api/paypal/checkout/order/create/', {
                    method: 'post'
                }).then(function (res) {
                    return res.json();
                }).then(function (data) {
                    return data.orderID;
                });
            },
    
            // Finalise the transaction
            onApprove: function (data, actions) {
                return fetch('/api/paypal/checkout/order/approved/' + data.orderID, {
                    method: 'post'
                }).then(function (res) {
                    return actions.order.capture();
                }).then(function (details) {
    
                    // (Preferred) Notify the server that the transaction id complete 
                    // and have an option to display an order completed screen.
                    window.location.replace('/api/paypal/checkout/order/complete/' + 
                                             data.orderID + '/@ViewBag.CurrencyCode');
                    
                    // OR
                    // Notify the server that the transaction id complete
                    //httpGet('/api/paypal/checkout/order/complete/' + data.orderID);
    
                    // Show a success message to the buyer
                    alert('Transaction completed by ' + details.payer.name.given_name + '!');
                });
            },
    
            // Buyer cancelled the payment
            onCancel: function (data, actions) {
                httpGet('/api/paypal/checkout/order/cancel/' + data.orderID);
            },
    
            // An error occurred during the transaction
            onError: function (err) {
                httpGet('/api/paypal/checkout/order/error/' + orderId + '/' + 
                         encodeURIComponent(err));
            }
    
        }).render('#paypal-button-container');
    </script>

    Apart from the URLs, this code is the same for all solutions.

  10. Add the following JavaScript function:

    JavaScript
    function httpGet(url) {
        var xmlHttp = new XMLHttpRequest();
        xmlHttp.open("GET", url, false);
        xmlHttp.send(null);
        return xmlHttp.responseText;
    }
  11. Create a folder called Values within the PayPal folder.

  12. Add a class called CheckoutPaymentIntent.cs, within the Values folder.
    Add the following code:

    C#
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace PayPal.Values
    {
        /// <summary>
        /// The intent to either capture payment immediately or
        /// authorize a payment for an order after order creation.
        /// </summary>
        public static class CheckoutPaymentIntent
        {
            /// <summary>
            /// The merchant intends to capture payment immediately after 
            /// the customer makes a payment.
            /// </summary>
            public static string CAPTURE { get; private set; } = "CAPTURE";
    
            /// <summary>
            /// The merchant intends to authorize a payment and
            /// place funds on hold after the customer makes a payment.
            /// Authorized payments are guaranteed for up to three days but
            /// are available to capture for up to 29 days.
            /// After the three-day honor period, the original authorized payment expires
            /// and you must re-authorize the payment.
            /// You must make a separate request to capture payments on demand.
            /// This intent is not supported when you have more than one `purchase_unit` 
            /// within your order.
            /// </summary>
            public static string AUTHORIZE { get; private set; } = "AUTHORIZE";
        }
    }
  13. Add a class called CurrencyCode.cs, within the Values folder.
    Add the following code:

    C#
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace PayPal.Values
    {
        public static class CurrencyCode
        {
            /// <summary>
            /// Great British Pounds
            /// </summary>
            public static string GBP { get; private set; } = "GBP";
    
            /// <summary>
            /// US Dolars
            /// </summary>
            public static string USD { get; private set; } = "USD";
    
            /// <summary>
            /// Euros
            /// </summary>
            public static string EUR { get; private set; } = "EUR";
        }
    }

    Add additional currencies, as required.

  14. Add a class called LandingPage.cs, within the Values folder.
    Add the following code:

    C#
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace PayPal.Values
    {
        /// <summary>
        /// The type of landing page to show on the PayPal site for customer checkout.
        /// Default: NO_PREFERENCE.
        /// Source: https://developer.paypal.com/docs/api/orders/v2/
        /// </summary>
        public class LandingPage
        {
            /// <summary>
            /// When the customer clicks PayPal Checkout, 
            /// the customer is redirected to a page to log in to PayPal 
            /// and approve the payment.
            /// </summary>
            public static string LOGIN { get; private set; } = "LOGIN";
    
            /// <summary>
            /// When the customer clicks PayPal Checkout, 
            /// the customer is redirected to a page to enter credit or 
            /// debit card and other relevant billing information required to 
            /// complete the purchase.
            /// </summary>
            public static string BILLING { get; private set; } = "BILLING";
    
            /// <summary>
            /// When the customer clicks PayPal Checkout,
            /// the customer is redirected to either a page to log in to PayPal and
            /// approve the payment or to a page to enter credit or
            /// debit card and other relevant billing information
            /// required to complete the purchase, depending on their 
            /// previous interaction with PayPal.
            /// </summary>
            public static string NO_PREFERENCE { get; private set; } = "NO_PREFERENCE";
        }
    }
  15. Add a class called ShippingPreference.cs, within the Values folder.
    Add the following code:

    C#
    namespace PayPal.Values
    {
        /// <summary>
        /// The shipping preference:
        ///
        /// * Displays the shipping address to the customer.
        /// * Enables the customer to choose an address on the PayPal site.
        /// * Restricts the customer from changing the address 
        ///   during the payment-approval process.
        ///
        /// Default: GET_FROM_FILE.
        /// Source: https://developer.paypal.com/docs/api/orders/v2/
        /// </summary>
        public static class ShippingPreference
        {
            /// <summary>
            /// Use the customer-provided shipping address on the PayPal site.
            /// </summary>
            public static string GET_FROM_FILE { get; private set; } = "GET_FROM_FILE";
    
            /// <summary>
            /// Redact the shipping address from the PayPal site. 
            /// Recommended for digital goods.
            /// </summary>
            public static string NO_SHIPPING { get; private set; } = "NO_SHIPPING";
    
            /// <summary>
            /// Use the merchant-provided address. 
            /// The customer cannot change this address on the PayPal site.
            /// </summary>
            public static string SET_PROVIDED_ADDRESS { get; private set; } = 
                                                             "SET_PROVIDED_ADDRESS";
        }
    }
  16. Add a class called UserAction.cs, within the Values folder.
    Add the following code:

    C#
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace PayPal.Values
    {
        /// <summary>
        /// Configures a Continue or Pay Now checkout flow.
        /// Source: https://developer.paypal.com/docs/api/orders/v2/
        /// </summary>
        public static class UserAction
        {
            /// <summary>
            /// After you redirect the customer to the PayPal payment page,
            /// a Continue button appears. Use this option when the final amount is not known
            /// when the checkout flow is initiated and you want to redirect
            /// the customer to the merchant page without processing the payment.
            /// </summary>
            public static string CONTINUE { get; private set; } = "CONTINUE";
    
            /// <summary>
            /// After you redirect the customer to the PayPal payment page,
            /// a Pay Now button appears.
            /// Use this option when the final amount is known when the checkout is initiated
            /// and you want to process the payment immediately 
            /// when the customer clicks Pay Now.
            /// </summary>
            public static string PAY_NOW { get; private set; } = "PAY_NOW";
        }
    }
  17. Create a folder called Item within the PayPal\Values folder.

  18. Add a class called Category.cs, within the Values folder.
    Add the following code:

    C#
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace PayPal.Values.Item
    {
        /// <summary>
        /// The item category type.
        /// </summary>
        public static class Category
        {
            /// <summary>
            /// Goods that are stored, delivered, and
            /// used in their electronic format.
            /// This value is not currently supported for API callers that leverage the
            /// [PayPal for Commerce Platform]
            /// (https://www.paypal.com/us/webapps/mpp/commerce-platform) product.
            /// </summary>
            public static string DIGITAL_GOODS { get; private set; } = "DIGITAL_GOODS";
    
            /// <summary>
            /// A tangible item that can be shipped with proof of delivery.
            /// </summary>
            public static string PHYSICAL_GOODS { get; private set; } = "PHYSICAL_GOODS";
        }
    }
  19. The final step is to write code to handle what happens when the following code is called:
    • api/paypal/checkout/order/create
    • api/paypal/checkout/order/approved/{orderId}
    • api/paypal/checkout/order/complete/{orderId}
    • api/paypal/checkout/order/cancel/{orderId}
    • api/paypal/checkout/order/error/{orderId}/{error}
  20. Good luck!

History

  • 3rd August, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



Comments and Discussions

 
QuestionNick Pin
Member 1477763331-Aug-20 1:04
Member 1477763331-Aug-20 1:04 
QuestionErrors Pin
mag1327-Aug-20 5:11
mag1327-Aug-20 5:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.