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

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