Saved Payment Methods Guide ​
Complete guide for implementing saved payment methods (cards, bank accounts) for faster checkout and recurring payments.
Overview ​
Saved payment methods allow customers to:
- ✅ Faster Checkout - One-click payments
- ✅ Recurring Payments - Subscriptions without re-entering card
- ✅ Multiple Methods - Save multiple cards/accounts
- ✅ PCI Compliant - Tokenized storage (no raw card data)
- ✅ Default Method - Set preferred payment method
Implementation Flow ​
mermaid
sequenceDiagram
participant User
participant Frontend
participant Backend
participant Provider
User->>Frontend: Enter card details
Frontend->>Provider: Tokenize card
Provider->>Frontend: Return token
Frontend->>Backend: Save payment method
Backend->>Provider: Create payment method
Provider->>Backend: Return method ID
Backend->>Backend: Store in database
Backend->>Frontend: Return saved method
Frontend->>User: Show saved methodSaving Payment Methods ​
Method 1: During Payment (Recommended) ​
Save payment method while processing a payment:
bash
# Step 1: Create payment intent with setup_future_usage
curl -X POST "https://your-instance.pubflow.com/bridge-payment/payments/intents" \
-H "Content-Type: application/json" \
-H "X-Session-ID: session_abc123" \
-d '{
"total_cents": 2000,
"currency": "USD",
"provider_id": "stripe",
"setup_future_usage": "off_session"
}'
# Step 2: Confirm payment (frontend with Stripe.js)
# Payment method is automatically saved after successful paymentFrontend (React):
jsx
import { useState } from 'react';
import { useStripe, useElements, PaymentElement } from '@stripe/react-stripe-js';
function CheckoutForm({ paymentIntentId }) {
const stripe = useStripe();
const elements = useElements();
const [saveMethod, setSaveMethod] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
// Update payment intent if user wants to save method
if (saveMethod) {
await fetch(`/bridge-payment/payments/intents/${paymentIntentId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-Session-ID': sessionId
},
body: JSON.stringify({
setup_future_usage: 'off_session'
})
});
}
// Confirm payment
const { error, paymentIntent } = await stripe.confirmPayment({
elements,
redirect: 'if_required'
});
if (paymentIntent?.status === 'succeeded') {
// Sync to save payment method
await fetch(`/bridge-payment/payments/intents/${paymentIntentId}/sync`, {
method: 'POST',
headers: { 'X-Session-ID': sessionId }
});
}
};
return (
<form onSubmit={handleSubmit}>
<PaymentElement />
<label className="flex items-center mt-4">
<input
type="checkbox"
checked={saveMethod}
onChange={(e) => setSaveMethod(e.target.checked)}
/>
<span className="ml-2">Save payment method for future use</span>
</label>
<button type="submit">Pay Now</button>
</form>
);
}Method 2: Standalone (Without Payment) ​
Save payment method without making a payment:
bash
# Using tokenized payment method (recommended)
curl -X POST "https://your-instance.pubflow.com/bridge-payment/payment-methods" \
-H "Content-Type: application/json" \
-H "X-Session-ID: session_abc123" \
-d '{
"provider_id": "stripe",
"provider_payment_method_token": "pm_1234567890",
"alias": "My Visa Card",
"is_default": true
}'Frontend (React with Stripe Elements):
jsx
import { useState } from 'react';
import { useStripe, useElements, CardElement } from '@stripe/react-stripe-js';
function AddPaymentMethod() {
const stripe = useStripe();
const elements = useElements();
const [alias, setAlias] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
// Create payment method with Stripe
const { error, paymentMethod } = await stripe.createPaymentMethod({
type: 'card',
card: elements.getElement(CardElement)
});
if (error) {
console.error('Error:', error);
return;
}
// Save to Bridge Payments
const response = await fetch('/bridge-payment/payment-methods', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Session-ID': sessionId
},
body: JSON.stringify({
provider_id: 'stripe',
provider_payment_method_token: paymentMethod.id,
alias: alias || 'My Card'
})
});
const savedMethod = await response.json();
console.log('Saved:', savedMethod);
};
return (
<form onSubmit={handleSubmit}>
<CardElement />
<input
type="text"
placeholder="Card nickname (optional)"
value={alias}
onChange={(e) => setAlias(e.target.value)}
/>
<button type="submit">Save Card</button>
</form>
);
}Listing Payment Methods ​
bash
curl -X GET "https://your-instance.pubflow.com/bridge-payment/payment-methods" \
-H "X-Session-ID: session_abc123"Response:
json
[
{
"id": "pm_123",
"provider_id": "stripe",
"type": "card",
"card_brand": "visa",
"card_last_four": "4242",
"card_exp_month": "12",
"card_exp_year": "2025",
"is_default": true,
"alias": "My Visa Card",
"created_at": "2025-01-15T10:30:00Z"
},
{
"id": "pm_456",
"provider_id": "stripe",
"type": "card",
"card_brand": "mastercard",
"card_last_four": "5555",
"is_default": false,
"alias": "Backup Card",
"created_at": "2025-01-10T08:20:00Z"
}
]Frontend (React):
jsx
import { useState, useEffect } from 'react';
function SavedPaymentMethods() {
const [methods, setMethods] = useState([]);
useEffect(() => {
fetch('/bridge-payment/payment-methods', {
headers: { 'X-Session-ID': sessionId }
})
.then(res => res.json())
.then(data => setMethods(data));
}, []);
return (
<div className="space-y-3">
{methods.map(method => (
<div key={method.id} className="border rounded-lg p-4">
<div className="flex items-center justify-between">
<div>
<div className="font-medium capitalize">
{method.card_brand} •••• {method.card_last_four}
</div>
<div className="text-sm text-gray-600">
Expires {method.card_exp_month}/{method.card_exp_year}
</div>
{method.alias && (
<div className="text-sm text-gray-500">{method.alias}</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>
);
}Charging Saved Methods ​
bash
curl -X POST "https://your-instance.pubflow.com/bridge-payment/payments/intents" \
-H "Content-Type: application/json" \
-H "X-Session-ID: session_abc123" \
-d '{
"total_cents": 2000,
"currency": "USD",
"provider_id": "stripe",
"payment_method_id": "pm_123",
"concept": "Monthly Subscription"
}'Frontend (React):
jsx
function PayWithSavedMethod({ methodId, amount }) {
const [loading, setLoading] = useState(false);
const handlePay = async () => {
setLoading(true);
// Create payment intent
const intentResponse = await fetch('/bridge-payment/payments/intents', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Session-ID': sessionId
},
body: JSON.stringify({
total_cents: amount,
currency: 'USD',
provider_id: 'stripe',
payment_method_id: methodId
})
});
const intent = await intentResponse.json();
// Confirm payment
const confirmResponse = await fetch(
`/bridge-payment/payments/intents/${intent.id}/confirm`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Session-ID': sessionId
},
body: JSON.stringify({
payment_method_id: methodId
})
}
);
const result = await confirmResponse.json();
if (result.status === 'succeeded') {
alert('Payment successful!');
}
setLoading(false);
};
return (
<button onClick={handlePay} disabled={loading}>
{loading ? 'Processing...' : `Pay $${(amount / 100).toFixed(2)}`}
</button>
);
}Managing Payment Methods ​
Set Default Method ​
bash
curl -X POST "https://your-instance.pubflow.com/bridge-payment/payment-methods/pm_123/set-default" \
-H "X-Session-ID: session_abc123"Update Method ​
bash
curl -X PUT "https://your-instance.pubflow.com/bridge-payment/payment-methods/pm_123" \
-H "Content-Type: application/json" \
-H "X-Session-ID: session_abc123" \
-d '{
"alias": "Updated Card Name",
"is_default": true
}'Delete Method ​
bash
curl -X DELETE "https://your-instance.pubflow.com/bridge-payment/payment-methods/pm_123" \
-H "X-Session-ID: session_abc123"Frontend (React):
jsx
function PaymentMethodActions({ methodId, onUpdate }) {
const handleSetDefault = async () => {
await fetch(`/bridge-payment/payment-methods/${methodId}/set-default`, {
method: 'POST',
headers: { 'X-Session-ID': sessionId }
});
onUpdate();
};
const handleDelete = async () => {
if (confirm('Delete this payment method?')) {
await fetch(`/bridge-payment/payment-methods/${methodId}`, {
method: 'DELETE',
headers: { 'X-Session-ID': sessionId }
});
onUpdate();
}
};
return (
<div className="flex gap-2">
<button onClick={handleSetDefault}>Set as Default</button>
<button onClick={handleDelete} className="text-red-600">Delete</button>
</div>
);
}Security Best Practices ​
- Use Tokenization - Never store raw card data
- PCI Compliance - Use provider SDKs for card input
- Verify Ownership - Ensure user owns payment method
- Secure Storage - Bridge Payments handles secure storage
- Audit Trail - Log payment method changes
Next Steps ​
- Payment Methods API - Complete API reference
- Subscriptions Guide - Use saved methods for subscriptions
- Stripe Integration - Stripe-specific features