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:
| Page | Route | Description |
|---|---|---|
Lightning.cshtml | /checkout/lightning/{invoiceId} | Main checkout with QR code |
LightningSuccess.cshtml | /checkout/lightning/success | Payment confirmation |
LightningCancel.cshtml | /checkout/lightning/cancel | Cancelled/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
- Configuration - Advanced options
- Webhooks Setup - Payment notifications
- Troubleshooting - Common issues