Skip to content

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 method

Saving Payment Methods ​

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 payment

Frontend (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 ​

  1. Use Tokenization - Never store raw card data
  2. PCI Compliance - Use provider SDKs for card input
  3. Verify Ownership - Ensure user owns payment method
  4. Secure Storage - Bridge Payments handles secure storage
  5. Audit Trail - Log payment method changes

Next Steps ​