React Integration ​
Production-Ready Integration Pattern
This guide shows battle-tested integration patterns for Bridge Payments in React applications. Supports Create React App, Vite, and other React setups.
Bridge Payments is a production-ready REST API that you integrate directly using fetch. Official React SDK coming soon!
Installation ​
bash
# Install Stripe.js for payment UI
npm install @stripe/stripe-js @stripe/react-stripe-js
# or
yarn add @stripe/stripe-js @stripe/react-stripe-jsDirect API Integration
Bridge Payments is a REST API - no Bridge Payments-specific packages needed! Just use fetch to make HTTP requests to your Bridge Payments instance. Works with any React setup.
Configuration ​
Environment Variables ​
Create .env:
bash
# Bridge Payments
REACT_APP_BRIDGE_PAYMENTS_URL=https://your-instance.pubflow.com
REACT_APP_STRIPE_PUBLISHABLE_KEY=pk_test_...
# Flowless (for authentication)
REACT_APP_FLOWLESS_URL=https://your-flowless.pubflow.comFor Vite, use VITE_ prefix:
bash
VITE_BRIDGE_PAYMENTS_URL=https://your-instance.pubflow.com
VITE_STRIPE_PUBLISHABLE_KEY=pk_test_...API Client ​
Create src/lib/bridge-payments.js:
javascript
const BRIDGE_URL = process.env.REACT_APP_BRIDGE_PAYMENTS_URL ||
import.meta.env.VITE_BRIDGE_PAYMENTS_URL;
export async function createPaymentIntent(data) {
const response = await fetch(`${BRIDGE_URL}/bridge-payment/payments/intents`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Session-ID': data.sessionId
},
body: JSON.stringify({
total_cents: data.total_cents,
currency: data.currency,
provider_id: data.provider_id || 'stripe'
})
});
if (!response.ok) {
throw new Error('Failed to create payment intent');
}
return response.json();
}
export async function syncPaymentIntent(paymentIntentId, sessionId) {
const response = await fetch(
`${BRIDGE_URL}/bridge-payment/payments/intents/${paymentIntentId}/sync`,
{
method: 'POST',
headers: {
'X-Session-ID': sessionId
}
}
);
if (!response.ok) {
throw new Error('Failed to sync payment intent');
}
return response.json();
}
export async function getPaymentMethods(sessionId) {
const response = await fetch(
`${BRIDGE_URL}/bridge-payment/payment-methods`,
{
headers: {
'X-Session-ID': sessionId
}
}
);
if (!response.ok) {
throw new Error('Failed to fetch payment methods');
}
return response.json();
}
export async function createPaymentMethod(data, sessionId) {
const response = await fetch(
`${BRIDGE_URL}/bridge-payment/payment-methods`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Session-ID': sessionId
},
body: JSON.stringify(data)
}
);
if (!response.ok) {
throw new Error('Failed to create payment method');
}
return response.json();
}Payment Component ​
Create src/components/CheckoutForm.jsx:
jsx
import { useState } from 'react';
import {
useStripe,
useElements,
PaymentElement
} from '@stripe/react-stripe-js';
import { syncPaymentIntent } from '../lib/bridge-payments';
export default function CheckoutForm({ paymentIntentId, amount, onSuccess }) {
const stripe = useStripe();
const elements = useElements();
const [error, setError] = useState(null);
const [processing, setProcessing] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
if (!stripe || !elements) return;
setProcessing(true);
setError(null);
const { error: submitError, paymentIntent } = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: `${window.location.origin}/success`
},
redirect: 'if_required'
});
if (submitError) {
setError(submitError.message || 'Payment failed');
setProcessing(false);
} else if (paymentIntent && paymentIntent.status === 'succeeded') {
// Sync with backend
const sessionId = localStorage.getItem('session_id');
if (sessionId) {
await syncPaymentIntent(paymentIntentId, sessionId);
}
// Call success callback
if (onSuccess) {
onSuccess(paymentIntent);
}
}
};
return (
<form onSubmit={handleSubmit} className="space-y-6">
<PaymentElement />
{error && (
<div className="text-red-600 text-sm bg-red-50 p-3 rounded">
{error}
</div>
)}
<button
type="submit"
disabled={!stripe || processing}
className="w-full bg-blue-600 text-white py-3 rounded-lg font-semibold disabled:opacity-50 disabled:cursor-not-allowed hover:bg-blue-700 transition"
>
{processing ? 'Processing...' : `Pay $${(amount / 100).toFixed(2)}`}
</button>
</form>
);
}Payment Page ​
Create src/pages/CheckoutPage.jsx:
jsx
import { useState, useEffect } from 'react';
import { loadStripe } from '@stripe/stripe-js';
import { Elements } from '@stripe/react-stripe-js';
import CheckoutForm from '../components/CheckoutForm';
import { createPaymentIntent } from '../lib/bridge-payments';
const stripePromise = loadStripe(
process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY ||
import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY
);
export default function CheckoutPage() {
const [clientSecret, setClientSecret] = useState('');
const [paymentIntentId, setPaymentIntentId] = useState('');
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const amount = 2000; // $20.00
useEffect(() => {
const sessionId = localStorage.getItem('session_id');
if (!sessionId) {
window.location.href = '/login';
return;
}
createPaymentIntent({
total_cents: amount,
currency: 'USD',
provider_id: 'stripe',
sessionId
})
.then((data) => {
setClientSecret(data.client_secret);
setPaymentIntentId(data.id);
setLoading(false);
})
.catch((err) => {
console.error('Error:', err);
setError(err.message);
setLoading(false);
});
}, []);
const handleSuccess = (paymentIntent) => {
console.log('Payment successful:', paymentIntent);
window.location.href = '/success';
};
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-lg">Loading...</div>
</div>
);
}
if (error) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-red-600">Error: {error}</div>
</div>
);
}
return (
<div className="max-w-md mx-auto p-6 mt-10">
<h1 className="text-3xl font-bold mb-6">Checkout</h1>
<div className="bg-gray-50 p-4 rounded-lg mb-6">
<div className="flex justify-between items-center">
<span className="text-gray-600">Total</span>
<span className="text-2xl font-bold">
${(amount / 100).toFixed(2)}
</span>
</div>
</div>
{clientSecret && (
<Elements stripe={stripePromise} options={{ clientSecret }}>
<CheckoutForm
paymentIntentId={paymentIntentId}
amount={amount}
onSuccess={handleSuccess}
/>
</Elements>
)}
</div>
);
}Saved Payment Methods ​
Create src/components/SavedPaymentMethods.jsx:
jsx
import { useState, useEffect } from 'react';
import { getPaymentMethods } from '../lib/bridge-payments';
export default function SavedPaymentMethods({ onSelect }) {
const [methods, setMethods] = useState([]);
const [loading, setLoading] = useState(true);
const [selected, setSelected] = useState(null);
useEffect(() => {
const sessionId = localStorage.getItem('session_id');
getPaymentMethods(sessionId)
.then((data) => {
setMethods(data);
setLoading(false);
})
.catch((err) => {
console.error('Error:', err);
setLoading(false);
});
}, []);
const handleSelect = (method) => {
setSelected(method.id);
if (onSelect) {
onSelect(method);
}
};
if (loading) {
return <div>Loading payment methods...</div>;
}
if (methods.length === 0) {
return <div>No saved payment methods</div>;
}
return (
<div className="space-y-3">
<h3 className="font-semibold text-lg mb-3">Saved Payment Methods</h3>
{methods.map((method) => (
<div
key={method.id}
onClick={() => handleSelect(method)}
className={`
border rounded-lg p-4 cursor-pointer transition
${selected === method.id ? 'border-blue-600 bg-blue-50' : 'border-gray-300 hover:border-gray-400'}
`}
>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className="text-2xl">
{method.card_brand === 'visa' && '💳'}
{method.card_brand === 'mastercard' && '💳'}
{method.card_brand === 'amex' && '💳'}
</div>
<div>
<div className="font-medium capitalize">
{method.card_brand} •••• {method.card_last_four}
</div>
{method.alias && (
<div className="text-sm text-gray-600">{method.alias}</div>
)}
</div>
</div>
{method.is_default && (
<span className="text-xs bg-green-100 text-green-800 px-2 py-1 rounded">
Default
</span>
)}
</div>
</div>
))}
</div>
);
}Guest Checkout ​
jsx
import { useState } from 'react';
import { loadStripe } from '@stripe/stripe-js';
import { Elements } from '@stripe/react-stripe-js';
import CheckoutForm from '../components/CheckoutForm';
const stripePromise = loadStripe(process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY);
export default function GuestCheckout() {
const [clientSecret, setClientSecret] = useState('');
const [paymentIntentId, setPaymentIntentId] = useState('');
const [guestData, setGuestData] = useState({ email: '', name: '' });
const handleCreatePayment = async (e) => {
e.preventDefault();
const response = await fetch(
`${process.env.REACT_APP_BRIDGE_PAYMENTS_URL}/bridge-payment/payments/intents`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
total_cents: 2000,
currency: 'USD',
provider_id: 'stripe',
guest_data: guestData
})
}
);
const data = await response.json();
setClientSecret(data.client_secret);
setPaymentIntentId(data.id);
};
if (!clientSecret) {
return (
<form onSubmit={handleCreatePayment} className="max-w-md mx-auto p-6">
<h2 className="text-2xl font-bold mb-6">Guest Checkout</h2>
<input
type="email"
placeholder="Email"
value={guestData.email}
onChange={(e) => setGuestData({ ...guestData, email: e.target.value })}
required
className="w-full p-3 border rounded mb-3"
/>
<input
type="text"
placeholder="Full Name"
value={guestData.name}
onChange={(e) => setGuestData({ ...guestData, name: e.target.value })}
required
className="w-full p-3 border rounded mb-6"
/>
<button
type="submit"
className="w-full bg-blue-600 text-white py-3 rounded-lg font-semibold"
>
Continue to Payment
</button>
</form>
);
}
return (
<div className="max-w-md mx-auto p-6">
<Elements stripe={stripePromise} options={{ clientSecret }}>
<CheckoutForm paymentIntentId={paymentIntentId} amount={2000} />
</Elements>
</div>
);
}Best Practices ​
- Error Handling - Show user-friendly error messages
- Loading States - Provide feedback during async operations
- Validation - Validate form inputs before submission
- Security - Never expose secret keys in frontend code
- Testing - Use test mode during development
Next Steps ​
- Payments API - Complete API reference
- Payment Methods API - Manage saved cards
- Guest Checkout Guide - Implement guest payments