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
Create your own Lightning.cshtml in Pages/Checkout/:
@page "/checkout/lightning/{invoiceId}"
@model LightningCheckoutModel
<div class="lightning-checkout">
<h1>Complete Your Payment</h1>
<div class="payment-amount">
<span class="currency">@Model.Currency</span>
<span class="amount">@Model.Amount.ToString("N2")</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-checkout.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
Express Checkout Button
Add a quick checkout button anywhere:
@using LightningEnable.Kentico.Pages.Shared
<partial name="_LightningExpressButton" model="new LightningExpressButtonModel
{
OrderId = Model.OrderNumber,
Amount = Model.TotalAmount,
Currency = Model.Currency,
ReturnUrl = Url.Page("/Order/Confirmation")
}" />
Accordion Checkout
Apple-style collapsible checkout:
@using LightningEnable.Kentico.Pages.Checkout.Partials
<partial name="_AccordionCheckout" model="new AccordionCheckoutModel
{
CartId = Model.CartId,
Sections = new List<CheckoutSection>
{
CheckoutSection.Contact,
CheckoutSection.Shipping,
CheckoutSection.Payment
}
}" />
Order Summary Sidebar
Display real-time order summary:
<partial name="_OrderSummary" model="new OrderSummaryModel
{
LineItems = Model.CartItems,
Subtotal = Model.Subtotal,
Tax = Model.Tax,
Shipping = Model.Shipping,
Total = Model.Total,
Currency = Model.Currency
}" />
Payment Status Polling
The included JavaScript handles real-time status updates:
// lightning-checkout.js (included with package)
class LightningCheckout {
constructor(invoiceId, options = {}) {
this.invoiceId = invoiceId;
this.pollInterval = options.pollInterval || 2000;
this.statusEndpoint = options.statusEndpoint ||
`/api/lightning/status/public/${invoiceId}`;
this.startPolling();
}
async startPolling() {
this.polling = setInterval(async () => {
const response = await fetch(this.statusEndpoint);
const data = await response.json();
this.updateStatus(data.status);
if (data.status === 'paid') {
this.stopPolling();
this.onPaid();
} else if (data.status === 'expired') {
this.stopPolling();
this.onExpired();
}
}, this.pollInterval);
}
stopPolling() {
clearInterval(this.polling);
}
updateStatus(status) {
const statusEl = document.getElementById('status');
statusEl.className = `payment-status ${status}`;
switch (status) {
case 'unpaid':
statusEl.textContent = 'Waiting for payment...';
break;
case 'processing':
statusEl.textContent = 'Payment detected, confirming...';
break;
case 'paid':
statusEl.textContent = 'Payment confirmed!';
break;
case 'expired':
statusEl.textContent = 'Invoice expired. Please try again.';
break;
}
}
onPaid() {
// Redirect to success page
window.location.href = `/checkout/lightning/success?order=${this.invoiceId}`;
}
onExpired() {
// Show retry option
document.getElementById('retry-button').style.display = 'block';
}
}
// Initialize on page load
document.addEventListener('DOMContentLoaded', () => {
const invoiceId = document.body.dataset.invoiceId;
new LightningCheckout(invoiceId);
});
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
[HttpPost("place-order")]
public async Task<IActionResult> PlaceOrder(CheckoutModel model)
{
if (model.PaymentMethod == "lightning")
{
// Create Lightning payment
var payment = await _lightningGateway.CreatePaymentAsync(
model.OrderNumber,
model.Total,
model.Currency
);
// Redirect to Lightning checkout
return Redirect($"/checkout/lightning/{payment.InvoiceId}");
}
// 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