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.
Openfront provides a flexible payment adapter system that supports multiple payment providers including Stripe, PayPal, and manual payment processing. The payment system is built with a provider-agnostic architecture that makes it easy to integrate additional payment gateways.
Payment Provider Architecture
The payment system in Openfront uses an adapter pattern that abstracts payment provider implementation details. Each payment provider implements a standard set of functions:
createPaymentFunction - Initialize a payment intent
capturePaymentFunction - Capture an authorized payment
refundPaymentFunction - Process refunds
getPaymentStatusFunction - Check payment status
generatePaymentLinkFunction - Generate provider-specific payment links
handleWebhookFunction - Process webhook events
Payment Providers
Stripe Integration
Openfront includes full Stripe integration with support for automatic payment methods, payment intents, and webhook processing.
Configuration
Add your Stripe credentials to your environment variables:
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
Implementation
The Stripe adapter (features/integrations/payment/stripe.ts) implements all required payment functions:
import Stripe from "stripe";
const getStripeClient = () => {
const stripeKey = process.env.STRIPE_SECRET_KEY;
if (!stripeKey) {
throw new Error("Stripe secret key not configured");
}
return new Stripe(stripeKey, {
apiVersion: "2023-10-16",
});
};
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,
};
}
Capturing Payments
export async function capturePaymentFunction({ paymentId, amount }) {
const stripe = getStripeClient();
const paymentIntent = await stripe.paymentIntents.capture(paymentId, {
amount_to_capture: amount,
});
return {
status: paymentIntent.status,
amount: paymentIntent.amount_captured,
data: paymentIntent,
};
}
Processing Refunds
export async function refundPaymentFunction({ paymentId, amount }) {
const stripe = getStripeClient();
const refund = await stripe.refunds.create({
payment_intent: paymentId,
amount,
});
return {
status: refund.status,
amount: refund.amount,
data: refund,
};
}
Webhook Handling
Stripe webhooks are verified using signature validation:
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 Integration
PayPal integration supports both sandbox and production environments with OAuth2 authentication.
Configuration
NEXT_PUBLIC_PAYPAL_CLIENT_ID=...
PAYPAL_CLIENT_SECRET=...
PAYPAL_WEBHOOK_ID=...
NEXT_PUBLIC_PAYPAL_SANDBOX=true # Set to false for production
Currency Handling
PayPal has special handling for no-division currencies (JPY, KRW, etc.):
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);
};
Creating PayPal Orders
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,
};
}
Manual Payment
The manual payment provider allows processing payments outside the automated flow, useful for bank transfers, checks, or other offline payment methods.
Payment Flow
Create a shopping cart for the customer’s region:
mutation {
createCart(data: {
region: { connect: { id: "region_id" } }
}) {
id
total
}
}
Initialize payment sessions for all available providers in the region:
mutation {
createPaymentSessions(cartId: "cart_id") {
id
paymentSessions {
id
providerId
status
}
}
}
Customer selects their preferred payment method:
mutation {
setPaymentSession(cartId: "cart_id", providerId: "pp_stripe_***") {
id
selectedPaymentSession {
providerId
data
}
}
}
Finalize the cart and capture payment:
mutation {
completeCart(cartId: "cart_id") {
order {
id
displayId
paymentStatus
}
}
}
Multi-Currency Support
Openfront handles multiple currencies automatically:
- Amounts are stored in the smallest currency unit (cents)
- Currency conversion happens at the region level
- No-division currencies (JPY, KRW) are handled appropriately
- Each payment provider receives properly formatted amounts
Payment Sessions
Payment sessions track payment state for each provider:
PaymentSession {
providerId: string
amount: integer
data: json // Provider-specific data
status: "pending" | "authorized" | "captured" | "canceled"
cart: → Cart
paymentProvider: → PaymentProvider
}
Creating Custom Payment Adapters
To add a new payment provider:
- Create a new file in
features/integrations/payment/
- Implement all required functions:
// features/integrations/payment/custom-provider.ts
export async function createPaymentFunction({ cart, amount, currency }) {
// Initialize payment with your provider
return {
paymentId: "...",
clientData: { ... }
};
}
export async function capturePaymentFunction({ paymentId, amount }) {
// Capture the payment
return {
status: "captured",
amount: capturedAmount,
};
}
export async function refundPaymentFunction({ paymentId, amount }) {
// Process refund
return {
status: "refunded",
amount: refundedAmount,
};
}
export async function getPaymentStatusFunction({ paymentId }) {
// Check payment status
return {
status: "authorized",
amount: paymentAmount,
};
}
export async function generatePaymentLinkFunction({ paymentId }) {
return `https://provider.com/payment/${paymentId}`;
}
export async function handleWebhookFunction({ event, headers }) {
// Validate and process webhook
return {
isValid: true,
event: processedEvent,
type: event.type,
resource: event.data,
};
}
- Register the adapter in
features/integrations/payment/index.ts:
export const paymentProviderAdapters = {
stripe: () => import("./stripe"),
paypal: () => import("./paypal"),
manual: () => import("./manual"),
custom: () => import("./custom-provider"),
};
- Create the provider in the admin dashboard under Payment Providers
Security Best Practices
- Never expose secret keys in client-side code
- Always validate webhook signatures
- Use environment variables for credentials
- Implement proper error handling
- Log payment events for audit trails
- Use HTTPS for all payment communications
Testing Payments
Use provider test credentials for development:
Stripe Test Cards:
- Success:
4242 4242 4242 4242
- Decline:
4000 0000 0000 0002
- Authentication Required:
4000 0025 0000 3155
PayPal Sandbox:
- Use sandbox credentials from PayPal Developer Dashboard
- Test with sandbox buyer accounts