How I Built a Custom Payment Checkout Flow with PayPal

/4 min read

A step-by-step guide on how I built a custom e-commerce checkout flow using PayPal, including order creation, capture, and webhook handling.

How I Built a Custom Payment Checkout Flow with PayPal

Integrating a payment gateway is one of the most crucial steps in building an e-commerce platform. When I was working on my project, I chose PayPal for its global reach, security, and developer-friendly APIs.

This guide will walk you through how I built a custom PayPal checkout flow using Next.js, .NET, and PayPal APIs, covering:
✔️ Order creation
✔️ Payment capture
✔️ Webhook handling


Why PayPal?#

Before diving into the implementation, here’s why I chose PayPal:
✅ Ease of use – Well-documented API with SDKs for multiple platforms.
✅ Security – Built-in fraud protection and compliance with PCI-DSS standards.
✅ Global reach – Supports multiple currencies and regions.
✅ Flexible integration – Can be used via PayPal’s hosted checkout or a fully customized API-driven experience.


Step 1: Setting Up a PayPal Developer Account#

  1. Go to [PayPal Developer Dashboard](https://developer.paypal.com/) and log in.
  2. Navigate to Dashboard → My Apps & Credentials and create a new app.
  3. Copy the Client ID and Secret Key for both sandbox and live environments.

Step 2: Installing PayPal SDK in Next.js#

To integrate PayPal into my Next.js frontend, I installed the PayPal SDK:

npm install @paypal/react-paypal-js

Then, I wrapped my application with the PayPalScriptProvider:

import { PayPalScriptProvider } from "@paypal/react-paypal-js";

const App = ({ children }) => {
  return (
    <PayPalScriptProvider options={{ "client-id": process.env.NEXT_PUBLIC_PAYPAL_CLIENT_ID }}>
      {children}
    </PayPalScriptProvider>
  );
};

export default App;

Step 3: Creating the PayPal Checkout Button#

I used the PayPalButtons component to create an interactive payment button:

import { PayPalButtons } from "@paypal/react-paypal-js";

const CheckoutButton = ({ totalAmount }) => {
  return (
    <PayPalButtons
      createOrder={(data, actions) => {
        return actions.order.create({
          purchase_units: [
            {
              amount: {
                value: totalAmount.toFixed(2),
              },
            },
          ],
        });
      }}
      onApprove={(data, actions) => {
        return actions.order.capture().then((details) => {
          console.log("Transaction completed by", details.payer.name.given_name);
        });
      }}
      onError={(err) => {
        console.error("PayPal Checkout Error:", err);
      }}
    />
  );
};

export default CheckoutButton;

Step 4: Creating a PayPal Order in .NET (Backend)#

On the backend (.NET Core API), I created an endpoint to generate PayPal orders.

[HttpPost("create-order")]
public async Task<IActionResult> CreateOrder([FromBody] OrderRequest request)
{
    var client = new PayPalHttpClient(new SandboxEnvironment(PAYPAL_CLIENT_ID, PAYPAL_SECRET));

    var orderRequest = new OrderRequest()
    {
        Intent = "CAPTURE",
        PurchaseUnits = new List<PurchaseUnitRequest>()
        {
            new PurchaseUnitRequest()
            {
                Amount = new AmountWithBreakdown()
                {
                    CurrencyCode = "USD",
                    Value = request.TotalAmount.ToString("F2")
                }
            }
        }
    };

    var createOrderRequest = new OrdersCreateRequest();
    createOrderRequest.Prefer("return=representation");
    createOrderRequest.RequestBody(orderRequest);

    var response = await client.Execute(createOrderRequest);
    var order = response.Result<Order>();

    return Ok(new { orderId = order.Id, approvalUrl = order.Links.First(l => l.Rel == "approve").Href });
}

Step 5: Capturing the Payment#

Once the user approves the order, PayPal generates an orderId, which must be captured to finalize the transaction.

[HttpPost("capture-payment/{orderId}")]
public async Task<IActionResult> CapturePayment(string orderId)
{
    var client = new PayPalHttpClient(new SandboxEnvironment(PAYPAL_CLIENT_ID, PAYPAL_SECRET));

    var captureRequest = new OrdersCaptureRequest(orderId);
    captureRequest.RequestBody(new OrderActionRequest());

    var response = await client.Execute(captureRequest);
    var capturedOrder = response.Result<Order>();

    return Ok(new { status = capturedOrder.Status, transactionId = capturedOrder.PurchaseUnits[0].Payments.Captures[0].Id });
}

Step 6: Handling Webhooks for Payment Confirmation#

To ensure order status is updated correctly, I set up PayPal Webhooks:

  1. Navigate to the [PayPal Developer Dashboard](https://developer.paypal.com/).
  2. Go to Webhooks and create a new webhook URL.
  3. Subscribe to PAYMENT.CAPTURE.COMPLETED and CHECKOUT.ORDER.APPROVED events.

Webhook Handling in .NET#

[HttpPost("paypal-webhook")]
public async Task<IActionResult> PayPalWebhook([FromBody] WebhookEvent webhookEvent)
{
    if (webhookEvent.EventType == "PAYMENT.CAPTURE.COMPLETED")
    {
        var paymentId = webhookEvent.Resource["id"].ToString();
        var order = _orderService.FindOrderByPaymentId(paymentId);
        if (order != null)
        {
            order.Status = "Paid";
            await _orderService.UpdateOrder(order);
        }
    }

    return Ok();
}

Final Thoughts#

🚀 After integrating PayPal into my e-commerce checkout flow, I achieved:
✅ A seamless payment experience for users.
✅ Secure transactions with PayPal’s built-in fraud protection.
✅ Automated payment verification using PayPal Webhooks.

By leveraging PayPal’s APIs, I was able to create a fully customized checkout experience that fits my e-commerce platform's needs.

Loading comments…