Skip to main content

Checkout Flow

This guide explains the Lightning payment checkout flow in Kentico Commerce and how to customize it.

Payment Flow Overview

Customer Journey:

1. Add to Cart

v
2. Proceed to Checkout

v
3. Enter Shipping/Billing Info

v
4. Select "Bitcoin Lightning" Payment

v
5. Redirect to Lightning Checkout Page ──────────────┐
│ │
v │
6. Display Payment Options: │
• Lightning Invoice QR Code │
• On-chain Bitcoin Address │
• Hosted Checkout Link │
│ │
v │
7. Customer Pays via Wallet ───────────────────────────┤
│ │
v │
8. Webhook Confirms Payment ───────────────────────────┤
│ │
v │
9. Redirect to Success Page │
│ │
v
10. Order Fulfilled

Built-In Pages

The package includes pre-built Razor pages:

PageRouteDescription
Lightning.cshtml/checkout/lightning/{invoiceId}Main checkout with QR code
LightningSuccess.cshtml/checkout/lightning/successPayment confirmation
LightningCancel.cshtml/checkout/lightning/cancelCancelled/expired payment

Customizing the Checkout Page

Override the Default Template

The built-in pages ship inside the package (a Razor Class Library). To customize, create your own Lightning.cshtml in your project's Pages/Checkout/ folder — pages in your application take precedence over pages from the package. The page model exposes InvoiceId, LightningInvoice, OnchainAddress, AmountSats, AmountBtc, AmountUsd, and ExpiresAt:

@page "/checkout/lightning/{invoiceId}"
@model LightningEnable.Kentico.Pages.Checkout.LightningModel

<div class="lightning-checkout">
<h1>Complete Your Payment</h1>

<div class="payment-amount">
<span class="currency">USD</span>
<span class="amount">@Model.AmountUsd.ToString("N2")</span>
<span class="sats">(@Model.AmountSats sats)</span>
</div>

<div class="payment-options">
<!-- Lightning QR Code -->
<div class="lightning-option">
<h3>Pay with Lightning</h3>
<canvas id="qr-lightning"></canvas>
<input type="text"
value="@Model.LightningInvoice"
readonly
id="lightning-invoice" />
<button onclick="copyInvoice()">Copy Invoice</button>
</div>

<!-- On-chain Bitcoin -->
<div class="onchain-option">
<h3>Pay with Bitcoin</h3>
<canvas id="qr-onchain"></canvas>
<code>@Model.OnchainAddress</code>
</div>
</div>

<div class="payment-status" id="status">
Waiting for payment...
</div>
</div>

@section Scripts {
<script src="~/js/lightning-payment-poller.js"></script>
}

Style the Checkout

Add custom CSS:

/* wwwroot/css/lightning-checkout.css */

.lightning-checkout {
max-width: 600px;
margin: 0 auto;
padding: 2rem;
}

.payment-amount {
text-align: center;
margin: 2rem 0;
}

.payment-amount .currency {
font-size: 1.5rem;
color: #666;
}

.payment-amount .amount {
font-size: 3rem;
font-weight: bold;
}

.payment-options {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
}

.payment-status {
text-align: center;
padding: 1rem;
border-radius: 8px;
margin-top: 2rem;
}

.payment-status.waiting {
background: #fef3c7;
color: #92400e;
}

.payment-status.paid {
background: #dcfce7;
color: #166534;
}

.payment-status.expired {
background: #fee2e2;
color: #991b1b;
}

UI Components

The package's partial views take a dynamic model, so you pass an anonymous object.

Express Checkout Button

Add a quick checkout button anywhere (pair it with the bundled lightning-checkout.js, which handles the button click and redirect):

<partial name="_LightningExpressButton" model="@(new
{
OrderId = Model.OrderNumber,
Amount = Model.TotalAmount,
Currency = Model.Currency
})" />

<script src="~/js/lightning-checkout.js"></script>

Accordion Checkout

Apple-style collapsible checkout (Contact → Shipping → Payment → Review), rendered with the bundled accordion-checkout.js and accordion-checkout.css:

<partial name="_AccordionCheckout" model="@(new
{
CartId = Model.CartId
})" />

Order Summary Sidebar

Display a collapsible order summary:

<partial name="_OrderSummary" />

Payment Status Polling

The bundled lightning-payment-poller.js handles real-time status updates. It polls /api/lightning/status (default: every 3 seconds, with exponential backoff on errors) and fires callbacks on status changes:

// lightning-payment-poller.js is included with the package

const poller = new LightningPaymentPoller({
invoiceId: document.body.dataset.invoiceId,

// Optional overrides
apiBaseUrl: '/api/lightning/status', // default
pollInterval: 3000, // default: 3 seconds
maxRetries: 5, // consecutive error retries

onStatusChange: (status) => {
const statusEl = document.getElementById('status');
statusEl.className = `payment-status ${status.status}`;
},
onProcessing: () => {
document.getElementById('status').textContent =
'Payment detected, confirming...';
},
onPaid: (status) => {
window.location.href = status.redirectUrl
|| '/checkout/lightning/success';
},
onExpired: () => {
document.getElementById('status').textContent =
'Invoice expired. Please try again.';
document.getElementById('retry-button').style.display = 'block';
},
onFailed: () => {
document.getElementById('status').textContent = 'Payment failed.';
}
});

poller.start();

Integrating with Existing Checkout

If you have an existing checkout flow:

1. Add Lightning as Payment Option

<div class="payment-methods">
<label>
<input type="radio" name="paymentMethod" value="credit-card" />
Credit Card
</label>

<label>
<input type="radio" name="paymentMethod" value="lightning" />
Bitcoin Lightning Network
<img src="~/img/lightning-logo.svg" alt="Lightning" />
</label>
</div>

2. Handle Selection

Use IPaymentGateway.CreateOrReuseSessionAsync with an OrderSnapshot. Note that AmountMinor is in minor currency units (cents for USD), and SuccessUrl/CancelUrl are required:

[HttpPost("place-order")]
public async Task<IActionResult> PlaceOrder(CheckoutModel model)
{
if (model.PaymentMethod == "lightning")
{
// Create (or reuse) a Lightning payment session
var result = await _lightningGateway.CreateOrReuseSessionAsync(
new OrderSnapshot
{
OrderNumber = model.OrderNumber,
AmountMinor = (long)(model.Total * 100), // cents
Currency = model.Currency,
CustomerEmail = model.Email,
SuccessUrl = new Uri($"{baseUrl}/checkout/lightning/success"),
CancelUrl = new Uri($"{baseUrl}/checkout/lightning/cancel")
},
HttpContext.RequestAborted);

// Redirect to the Lightning checkout page
return Redirect(result.RedirectUrl.ToString());
}

// Handle other payment methods
return await ProcessCreditCard(model);
}

Mobile Optimization

The checkout is mobile-optimized by default:

@media (max-width: 768px) {
.payment-options {
grid-template-columns: 1fr;
}

.qr-code canvas {
width: 100% !important;
max-width: 250px;
}

.payment-amount .amount {
font-size: 2rem;
}
}

Next Steps