Guest Checkout Guide ​
Guest checkout allows you to accept payments from users without requiring them to create an account. This is perfect for donations, one-time purchases, and reducing friction in the checkout process.
Overview ​
With guest checkout, you can:
- Accept payments without user authentication
- Collect minimal information (email, name, optional phone)
- Track guest payments by email
- Convert guest data to authenticated users when they register
How It Works ​
- No Authentication Required: The
POST /payments/intentsendpoint accepts guest payments withoutX-Session-IDheader - Guest Data Tracking: Include
guest_dataobject with email, name, and optional phone - Automatic Customer Creation: System creates a guest customer record linked to the email
- Payment Method Storage: Guest payment methods can be saved for future use
- Conversion Ready: When guest registers, their data can be converted to authenticated user
Basic Guest Payment Flow ​
1. Create Payment Intent ​
Use the Create Payment Intent endpoint without authentication:
const response = await fetch(
'https://your-instance.pubflow.com/bridge-payment/payments/intents',
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
// ✅ No X-Session-ID header required for guest checkout
},
body: JSON.stringify({
// Intelligent Pricing - provide any combination:
total_cents: 5000, // Option 1: Just total
// OR
subtotal_cents: 4500, // Option 2: Breakdown
tax_cents: 500,
discount_cents: 0,
currency: 'USD',
description: 'Monthly donation to support our cause',
provider_id: 'stripe',
// Optional: Enhanced tracking
concept: 'Monthly Donation',
reference_code: 'donation_monthly',
category: 'donation',
// ✅ REQUIRED for guest checkout
guest_data: {
email: '[email protected]', // Required
name: 'John Doe', // Required
phone: '+1234567890' // Optional
}
})
}
);
const data = await response.json();
// Response: { success: true, data: { client_secret, id, ... } }2. Confirm Payment (Frontend) ​
Use Stripe.js to confirm the payment on the client side:
import { loadStripe } from '@stripe/stripe-js';
const stripe = await loadStripe('pk_test_...');
const { error } = await stripe.confirmCardPayment(data.data.client_secret, {
payment_method: {
card: cardElement,
billing_details: {
email: '[email protected]',
name: 'John Doe'
}
}
});
if (error) {
console.error('Payment failed:', error.message);
} else {
console.log('Payment successful!');
}3. Preview Guest Data (Before Registration) ​
Use the Preview Guest Data endpoint to see what data exists for an email:
// ✅ Requires authentication - user must be logged in
// ✅ Email in URL must match authenticated user's email (security)
const response = await fetch(
'https://your-instance.pubflow.com/bridge-payment/preview-guest/[email protected]',
{
headers: {
'X-Session-ID': 'user_session_token'
}
}
);
const data = await response.json();
// Response: {
// success: true,
// data: {
// customers: [...], // Guest customer records
// payment_methods: [...], // Saved payment methods
// summary: { total_customers: 1, total_payment_methods: 2 }
// }
// }Complete Example: Donation Form ​
React Component ​
import { useState } from 'react';
import { loadStripe } from '@stripe/stripe-js';
import { Elements, CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_KEY!);
function DonationForm() {
const stripe = useStripe();
const elements = useElements();
const [formData, setFormData] = useState({
email: '',
name: '',
amount: 50
});
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!stripe || !elements) return;
setLoading(true);
try {
// 1. Create payment intent via your API route
const response = await fetch('/api/create-donation', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
amount_cents: formData.amount * 100, // Convert dollars to cents
guest_data: {
email: formData.email.toLowerCase(), // Normalize email
name: formData.name
}
})
});
const result = await response.json();
if (!result.success) {
throw new Error(result.error || 'Failed to create payment intent');
}
// 2. Confirm payment with Stripe
const { error } = await stripe.confirmCardPayment(result.data.client_secret, {
payment_method: {
card: elements.getElement(CardElement)!,
billing_details: {
email: formData.email,
name: formData.name
}
}
});
if (error) {
alert(`Payment failed: ${error.message}`);
} else {
setSuccess(true);
}
} catch (error) {
console.error('Error:', error);
alert(error instanceof Error ? error.message : 'An error occurred. Please try again.');
} finally {
setLoading(false);
}
};
if (success) {
return (
<div className="success-message">
<h2>Thank you for your donation!</h2>
<p>A receipt has been sent to {formData.email}</p>
</div>
);
}
return (
<form onSubmit={handleSubmit}>
<div>
<label>Email</label>
<input
type="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
required
/>
</div>
<div>
<label>Name</label>
<input
type="text"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
required
/>
</div>
<div>
<label>Amount ($)</label>
<input
type="number"
value={formData.amount}
onChange={(e) => setFormData({ ...formData, amount: Number(e.target.value) })}
min="1"
required
/>
</div>
<div>
<label>Card Details</label>
<CardElement />
</div>
<button type="submit" disabled={!stripe || loading}>
{loading ? 'Processing...' : `Donate $${formData.amount}`}
</button>
</form>
);
}
export default function DonatePage() {
return (
<Elements stripe={stripePromise}>
<DonationForm />
</Elements>
);
}API Route (Next.js) ​
// app/api/create-donation/route.ts
export async function POST(request: Request) {
const { amount_cents, guest_data } = await request.json();
const response = await fetch(
`${process.env.BRIDGE_PAYMENTS_URL}/bridge-payment/payments/intents`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
total_cents: amount_cents,
currency: 'USD',
concept: 'Donation',
description: 'One-time donation',
provider_id: 'stripe',
guest_data
})
}
);
const data = await response.json();
return Response.json(data);
}Saving Payment Methods for Guests ​
Guests can save payment methods for future use by including setup_future_usage:
const response = await fetch(
'https://your-instance.pubflow.com/bridge-payment/payments/intents',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
total_cents: 5000,
currency: 'USD',
concept: 'First Donation',
description: 'Donation with saved payment method',
provider_id: 'stripe',
setup_future_usage: 'off_session', // ✅ Save payment method for future use
guest_data: {
email: '[email protected]',
name: 'John Doe'
}
})
}
);Converting Guests to Authenticated Users ​
When a guest creates an account with the same email, use the Convert Guest Data endpoint:
Step 1: User Registers ​
User creates account with email [email protected] and gets authenticated session.
Step 2: Convert Guest Data ​
// ✅ Must be authenticated
// ✅ guest_email must match authenticated user's email (security requirement)
const response = await fetch(
'https://your-instance.pubflow.com/bridge-payment/convert-guest',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Session-ID': 'new_user_session_token'
},
body: JSON.stringify({
guest_email: '[email protected]' // Must match authenticated user's email
})
}
);
const data = await response.json();
// Response: {
// success: true,
// data: {
// converted_customers: 1,
// converted_payment_methods: 2,
// details: { customers: [...], payment_methods: [...] }
// }
// }Step 3: Selective Conversion (Optional) ​
Convert only specific customers or payment methods:
const response = await fetch(
'https://your-instance.pubflow.com/bridge-payment/convert-guest',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Session-ID': 'user_session_token'
},
body: JSON.stringify({
guest_email: '[email protected]',
customer_ids: ['cust_123', 'cust_456'], // Optional: specific customers
payment_method_ids: ['pm_abc', 'pm_xyz'] // Optional: specific payment methods
})
}
);Best Practices ​
1. Normalize Email Addresses ​
Always convert emails to lowercase before sending to the API:
guest_data: {
email: userEmail.toLowerCase(), // ✅ Normalize to lowercase
name: userName
}2. Provide Clear Messaging ​
Explain that no account is required and their information is secure.
3. Handle Errors Gracefully ​
Provide clear error messages and allow retry without re-entering information.
4. Preview Before Converting ​
Use the preview endpoint to show users what data will be converted before they confirm.
5. Track Conversions ​
Monitor guest-to-user conversion rates to optimize your registration flow.
Security Considerations ​
- ✅ Guest emails are validated and normalized (lowercase)
- ✅ Guest conversion requires authentication
- ✅ Users can only convert data from their own email address
- ✅ Rate limiting applies to prevent abuse
- ✅ All guest payments are tracked and auditable
API Reference ​
- Create Payment Intent - Create payment with guest_data
- Preview Guest Data - Preview guest data before conversion
- Convert Guest Data - Convert guest to authenticated user
- List My Guest Data - List current user's guest data
Next Steps ​
- Subscriptions - Learn about recurring payments
- Organizations - Multi-tenant support
- Saved Payment Methods - Store payment methods
- Testing - Test guest checkout flows