Skip to content

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
  • Optionally convert guest customers to authenticated users later

Configuration ​

Guest checkout is enabled by default. Configure it in your environment:

bash
# Enable guest checkout (default: true)
GUEST_CHECKOUT_ENABLED=true

# Require email for guest checkout (default: true)
GUEST_REQUIRE_EMAIL=true

# Guest token expiration in seconds (default: 3600)
GUEST_TOKEN_EXPIRATION=3600

Basic Guest Payment Flow ​

1. Create Payment Intent ​

typescript
const response = await fetch(
  'https://your-instance.pubflow.com/bridge-payment/payments/intents',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
      // No authentication required!
    },
    body: JSON.stringify({
      subtotal_cents: 4500,
      tax_cents: 500,
      total_cents: 5000,
      currency: 'USD',
      concept: 'Donation',
      description: 'Monthly donation to support our cause',
      provider_id: 'stripe',
      guest_data: {
        email: 'donor@example.com',
        name: 'John Doe',
        phone: '+1234567890' // Optional
      }
    })
  }
);

const { client_secret, id } = await response.json();

2. Confirm Payment (Frontend) ​

typescript
import { loadStripe } from '@stripe/stripe-js';

const stripe = await loadStripe('pk_test_...');

const { error } = await stripe.confirmCardPayment(client_secret, {
  payment_method: {
    card: cardElement,
    billing_details: {
      email: 'donor@example.com',
      name: 'John Doe'
    }
  }
});

if (error) {
  console.error('Payment failed:', error.message);
} else {
  console.log('Payment successful!');
}

3. Retrieve Guest Payments ​

typescript
// List all payments for a guest email
const response = await fetch(
  'https://your-instance.pubflow.com/bridge-payment/payments/guest/donor@example.com'
);

const payments = await response.json();

Complete Example: Donation Form ​

React Component ​

tsx
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
      const response = await fetch('/api/create-donation', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          amount: formData.amount * 100, // Convert to cents
          guest_data: {
            email: formData.email,
            name: formData.name
          }
        })
      });
      
      const { client_secret } = await response.json();
      
      // 2. Confirm payment
      const { error } = await stripe.confirmCardPayment(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('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) ​

typescript
// app/api/create-donation/route.ts
export async function POST(request: Request) {
  const { amount, 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,
        currency: 'USD',
        concept: 'Donation',
        description: 'One-time donation',
        provider_id: 'stripe',
        guest_data
      })
    }
  );
  
  return response.json();
}

Saving Payment Methods for Guests ​

Guests can save payment methods for future use:

typescript
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',
      setup_future_usage: 'off_session', // Save payment method
      guest_data: {
        email: 'donor@example.com',
        name: 'John Doe'
      }
    })
  }
);

Best Practices ​

1. Collect Minimal Information ​

Only ask for email and name. Phone is optional.

2. Provide Clear Messaging ​

Explain that no account is required and their information is secure.

3. Send Confirmation Emails ​

Use external webhooks to send receipt emails to guest customers.

4. Track Guest Conversions ​

Monitor which guests convert to registered users.

5. Handle Errors Gracefully ​

Provide clear error messages and allow retry without re-entering information.

Security Considerations ​

  • Guest emails are validated and normalized (lowercase)
  • Guest tokens expire after configured time (default: 1 hour)
  • Rate limiting applies to guest checkout to prevent abuse
  • All guest payments are tracked and auditable

Converting Guests to Users ​

When a guest creates an account, you can link their previous payments:

typescript
// After user registration, update guest payments
const response = await fetch(
  `${baseUrl}/bridge-payment/admin/link-guest-payments`,
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Session-ID': newUserSessionId
    },
    body: JSON.stringify({
      guest_email: 'donor@example.com'
    })
  }
);

Next Steps ​