How I Built a Custom Payment Checkout Flow with PayPal
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#
- Go to [PayPal Developer Dashboard](https://developer.paypal.com/) and log in.
- Navigate to Dashboard → My Apps & Credentials and create a new app.
- 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:
- Navigate to the [PayPal Developer Dashboard](https://developer.paypal.com/).
- Go to Webhooks and create a new webhook URL.
- Subscribe to
PAYMENT.CAPTURE.COMPLETEDandCHECKOUT.ORDER.APPROVEDevents.
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…