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
  • Convert guest data to authenticated users when they register

How It Works ​

  1. No Authentication Required: The POST /payments/intents endpoint accepts guest payments without X-Session-ID header
  2. Guest Data Tracking: Include guest_data object with email, name, and optional phone
  3. Automatic Customer Creation: System creates a guest customer record linked to the email
  4. Payment Method Storage: Guest payment methods can be saved for future use
  5. 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:

typescript
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:

typescript
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:

typescript
// ✅ 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 ​

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 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) ​

typescript
// 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:

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',
      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 ​

typescript
// ✅ 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:

typescript
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:

typescript
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 ​

Next Steps ​