Documentation Index Fetch the complete documentation index at: https://mintlify.com/openshiporg/openfront/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Openfront supports multiple payment providers through a flexible adapter system. You can integrate Stripe, PayPal, manual payment methods (like Cash on Delivery), or build custom payment adapters.
Payment Adapter Interface
All payment providers in Openfront implement a standard adapter interface with these core functions:
createPaymentFunction - Initialize a payment intent or order
capturePaymentFunction - Capture an authorized payment
refundPaymentFunction - Process refunds
getPaymentStatusFunction - Check payment status
generatePaymentLinkFunction - Generate payment dashboard links
handleWebhookFunction - Verify and process webhook events
The adapter pattern is defined in features/integrations/payment/index.ts:
features/integrations/payment/index.ts
export const paymentProviderAdapters = {
stripe : () => import ( "./stripe" ),
paypal : () => import ( "./paypal" ),
manual : () => import ( "./manual" ),
};
Stripe Configuration
Environment Variables
Add these variables to your .env file:
STRIPE_SECRET_KEY = sk_test_...
STRIPE_WEBHOOK_SECRET = whsec_...
Implementation Details
The Stripe adapter (features/integrations/payment/stripe.ts) uses the official Stripe SDK:
Create Payment
Capture Payment
Refund Payment
export async function createPaymentFunction ({ cart , amount , currency }) {
const stripe = getStripeClient ();
const paymentIntent = await stripe . paymentIntents . create ({
amount ,
currency: currency . toLowerCase (),
automatic_payment_methods: {
enabled: true ,
},
});
return {
clientSecret: paymentIntent . client_secret ,
paymentIntentId: paymentIntent . id ,
};
}
Webhook Verification
Stripe webhooks are verified using signature validation:
features/integrations/payment/stripe.ts
export async function handleWebhookFunction ({ event , headers }) {
const webhookSecret = process . env . STRIPE_WEBHOOK_SECRET ;
const stripe = getStripeClient ();
try {
const stripeEvent = stripe . webhooks . constructEvent (
JSON . stringify ( event ),
headers [ 'stripe-signature' ],
webhookSecret
);
return {
isValid: true ,
event: stripeEvent ,
type: stripeEvent . type ,
resource: stripeEvent . data . object ,
};
} catch ( err ) {
throw new Error ( `Webhook signature verification failed: ${ err . message } ` );
}
}
PayPal Configuration
Environment Variables
NEXT_PUBLIC_PAYPAL_CLIENT_ID = your_client_id
PAYPAL_CLIENT_SECRET = your_client_secret
NEXT_PUBLIC_PAYPAL_SANDBOX = true # Set to false for production
PAYPAL_WEBHOOK_ID = your_webhook_id
Implementation Details
The PayPal adapter (features/integrations/payment/paypal.ts) handles currency formatting, including zero-decimal currencies:
Create Order
Capture Payment
export async function createPaymentFunction ({ cart , amount , currency }) {
const accessToken = await getPayPalAccessToken ();
const baseUrl = getPayPalBaseUrl ();
const response = await fetch (
` ${ baseUrl } /v2/checkout/orders` ,
{
method: "POST" ,
headers: {
"Content-Type" : "application/json" ,
Authorization: `Bearer ${ accessToken } ` ,
},
body: JSON . stringify ({
intent: "AUTHORIZE" ,
purchase_units: [
{
amount: {
currency_code: currency . toUpperCase (),
value: formatPayPalAmount ( amount , currency ),
},
},
],
}),
}
);
const order = await response . json ();
return {
orderId: order . id ,
status: order . status ,
};
}
Currency Handling
PayPal supports zero-decimal currencies (JPY, KRW, etc.) which require special formatting:
features/integrations/payment/paypal.ts
const NO_DIVISION_CURRENCIES = [
"JPY" , "KRW" , "VND" , "CLP" , "PYG" , "XAF" , "XOF" ,
"BIF" , "DJF" , "GNF" , "KMF" , "MGA" , "RWF" , "XPF" ,
"HTG" , "VUV" , "XAG" , "XDR" , "XAU"
];
const formatPayPalAmount = ( amount : number , currency : string ) : string => {
const upperCurrency = currency . toUpperCase ();
const isNoDivision = NO_DIVISION_CURRENCIES . includes ( upperCurrency );
if ( isNoDivision ) {
return amount . toString ();
}
return ( amount / 100 ). toFixed ( 2 );
};
Manual Payment Provider
The manual payment provider is used for Cash on Delivery, bank transfers, and other offline payment methods:
features/integrations/payment/manual.ts
export async function createPaymentFunction ({ cart , amount , currency }) {
return {
status: 'pending' ,
data: {
status: 'pending' ,
amount ,
currency: currency . toLowerCase (),
}
};
}
export async function capturePaymentFunction ({ paymentId , amount }) {
return {
status: 'captured' ,
amount ,
data: {
status: 'captured' ,
amount ,
captured_at: new Date (). toISOString (),
}
};
}
Building Custom Payment Adapters
To create a custom payment provider:
Create a new file in features/integrations/payment/
Implement all required adapter functions
Register your adapter in the paymentProviderAdapters object
Example Custom Adapter
features/integrations/payment/custom.ts
export async function createPaymentFunction ({ cart , amount , currency }) {
// Call your payment gateway API
const response = await fetch ( 'https://api.yourgateway.com/payments' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ process . env . CUSTOM_GATEWAY_KEY } ` ,
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({
amount ,
currency ,
metadata: {
cartId: cart . id ,
},
}),
});
const payment = await response . json ();
return {
paymentId: payment . id ,
status: payment . status ,
clientSecret: payment . client_secret ,
};
}
export async function capturePaymentFunction ({ paymentId , amount }) {
// Implement capture logic
}
export async function refundPaymentFunction ({ paymentId , amount }) {
// Implement refund logic
}
export async function getPaymentStatusFunction ({ paymentId }) {
// Implement status check
}
export async function generatePaymentLinkFunction ({ paymentId }) {
return `https://dashboard.yourgateway.com/payments/ ${ paymentId } ` ;
}
export async function handleWebhookFunction ({ event , headers }) {
// Implement webhook verification
const signature = headers [ 'x-gateway-signature' ];
const isValid = verifySignature ( event , signature );
if ( ! isValid ) {
throw new Error ( 'Invalid webhook signature' );
}
return {
isValid: true ,
event ,
type: event . type ,
resource: event . data ,
};
}
Register Custom Adapter
features/integrations/payment/index.ts
export const paymentProviderAdapters = {
stripe : () => import ( "./stripe" ),
paypal : () => import ( "./paypal" ),
manual : () => import ( "./manual" ),
custom : () => import ( "./custom" ), // Add your custom adapter
};
Using Payment Providers in Your Application
Payment providers are managed through the KeystoneJS admin panel. The PaymentProvider model stores configuration:
type PaymentProvider {
id : ID !
name : String !
provider : String ! # stripe, paypal, manual, custom
isActive : Boolean !
regions : [ Region ! ] !
apiKey : String
webhookSecret : String
}
Load and use a payment adapter:
import { paymentProviderAdapters } from '@/features/integrations/payment' ;
const adapter = await paymentProviderAdapters [ provider . provider ]();
const result = await adapter . createPaymentFunction ({
cart ,
amount: cart . total ,
currency: cart . currency . code ,
});
Best Practices
Store API keys in environment variables, never in code
Always verify webhook signatures
Use HTTPS for all payment communications
Implement rate limiting on webhook endpoints
Implement proper error handling for network failures
Log payment errors for debugging
Provide clear error messages to users
Implement retry logic for transient failures
Use sandbox/test modes during development
Test with various currencies and amounts
Verify webhook delivery and retry logic
Test payment capture and refund flows
Handle zero-decimal currencies correctly
Convert amounts to smallest currency unit (cents)
Store currency codes with transactions
Test currency conversion edge cases