Skip to content

Subscriptions Guide ​

Complete guide for implementing recurring billing and subscription management with Bridge Payments.

Overview ​

Bridge Payments subscriptions support:

  • ✅ Flexible Billing - Daily, weekly, monthly, quarterly, yearly
  • ✅ Free Trials - Configurable trial periods
  • ✅ Multiple Providers - Stripe, PayPal, Authorize.net (ARB)
  • ✅ Automatic Billing - Hands-off recurring payments
  • ✅ Subscription Management - Update, pause, cancel
  • ✅ Webhooks - Real-time subscription events
  • ✅ Guest Subscriptions - No account required

Creating Subscriptions ​

Basic Subscription ​

bash
curl -X POST "https://your-instance.pubflow.com/bridge-payment/subscriptions" \
  -H "Content-Type: application/json" \
  -H "X-Session-ID: session_abc123" \
  -d '{
    "customer_id": "cust_123",
    "payment_method_id": "pm_123",
    "provider_id": "stripe",
    "total_cents": 2000,
    "currency": "USD",
    "billing_interval": "monthly",
    "concept": "Premium Monthly Plan"
  }'

Response:

json
{
  "id": "sub_123",
  "customer_id": "cust_123",
  "payment_method_id": "pm_123",
  "provider_id": "stripe",
  "provider_subscription_id": "sub_stripe_xyz",
  "status": "active",
  "total_cents": 2000,
  "currency": "USD",
  "billing_interval": "monthly",
  "current_period_start": "2025-01-15T10:30:00Z",
  "current_period_end": "2025-02-15T10:30:00Z",
  "next_billing_date": "2025-02-15T10:30:00Z",
  "concept": "Premium Monthly Plan",
  "created_at": "2025-01-15T10:30:00Z"
}

Subscription with Trial ​

bash
curl -X POST "https://your-instance.pubflow.com/bridge-payment/subscriptions" \
  -H "Content-Type: application/json" \
  -H "X-Session-ID: session_abc123" \
  -d '{
    "customer_id": "cust_123",
    "payment_method_id": "pm_123",
    "provider_id": "stripe",
    "total_cents": 2000,
    "currency": "USD",
    "billing_interval": "monthly",
    "trial_days": 14,
    "concept": "Premium Plan with 14-day Trial"
  }'

Custom Billing Interval ​

bash
curl -X POST "https://your-instance.pubflow.com/bridge-payment/subscriptions" \
  -H "Content-Type: application/json" \
  -H "X-Session-ID: session_abc123" \
  -d '{
    "customer_id": "cust_123",
    "payment_method_id": "pm_123",
    "provider_id": "stripe",
    "total_cents": 5000,
    "currency": "USD",
    "billing_interval": "monthly",
    "interval_multiplier": 3,
    "concept": "Quarterly Plan (every 3 months)"
  }'

Frontend Implementation ​

React Subscription Component ​

jsx
import { useState, useEffect } from 'react';

function SubscriptionCheckout({ planId, amount, interval }) {
  const [paymentMethods, setPaymentMethods] = useState([]);
  const [selectedMethod, setSelectedMethod] = useState('');
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    // Load saved payment methods
    fetch('/bridge-payment/payment-methods', {
      headers: { 'X-Session-ID': sessionId }
    })
      .then(res => res.json())
      .then(data => {
        setPaymentMethods(data);
        const defaultMethod = data.find(m => m.is_default);
        if (defaultMethod) {
          setSelectedMethod(defaultMethod.id);
        }
      });
  }, []);

  const handleSubscribe = async () => {
    setLoading(true);

    try {
      // Get or create customer
      const customerResponse = await fetch('/bridge-payment/customers', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-Session-ID': sessionId
        },
        body: JSON.stringify({
          provider_id: 'stripe'
        })
      });
      const customer = await customerResponse.json();

      // Create subscription
      const subscriptionResponse = await fetch('/bridge-payment/subscriptions', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-Session-ID': sessionId
        },
        body: JSON.stringify({
          customer_id: customer.id,
          payment_method_id: selectedMethod,
          provider_id: 'stripe',
          total_cents: amount,
          currency: 'USD',
          billing_interval: interval,
          trial_days: 14,
          concept: `${planId} Plan`
        })
      });

      const subscription = await subscriptionResponse.json();
      
      if (subscription.status === 'active' || subscription.status === 'trialing') {
        alert('Subscription created successfully!');
        window.location.href = '/dashboard';
      }
    } catch (error) {
      console.error('Error:', error);
      alert('Failed to create subscription');
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="max-w-md mx-auto p-6">
      <h2 className="text-2xl font-bold mb-6">Subscribe to {planId}</h2>
      
      <div className="bg-gray-50 p-4 rounded-lg mb-6">
        <div className="text-3xl font-bold mb-2">
          ${(amount / 100).toFixed(2)}
          <span className="text-lg text-gray-600">/{interval}</span>
        </div>
        <div className="text-sm text-gray-600">
          14-day free trial, then ${(amount / 100).toFixed(2)}/{interval}
        </div>
      </div>

      <div className="mb-6">
        <label className="block text-sm font-medium mb-2">
          Payment Method
        </label>
        <select
          value={selectedMethod}
          onChange={(e) => setSelectedMethod(e.target.value)}
          className="w-full p-3 border rounded"
        >
          <option value="">Select payment method</option>
          {paymentMethods.map(method => (
            <option key={method.id} value={method.id}>
              {method.card_brand} •••• {method.card_last_four}
              {method.alias && ` (${method.alias})`}
            </option>
          ))}
        </select>
      </div>

      <button
        onClick={handleSubscribe}
        disabled={!selectedMethod || loading}
        className="w-full bg-blue-600 text-white py-3 rounded-lg font-semibold disabled:opacity-50"
      >
        {loading ? 'Processing...' : 'Start Free Trial'}
      </button>

      <p className="text-xs text-gray-600 mt-4 text-center">
        You won't be charged until your trial ends. Cancel anytime.
      </p>
    </div>
  );
}

Managing Subscriptions ​

List User Subscriptions ​

bash
curl -X GET "https://your-instance.pubflow.com/bridge-payment/subscriptions" \
  -H "X-Session-ID: session_abc123"

Get Subscription Details ​

bash
curl -X GET "https://your-instance.pubflow.com/bridge-payment/subscriptions/sub_123" \
  -H "X-Session-ID: session_abc123"

Update Subscription ​

bash
# Update payment method
curl -X PUT "https://your-instance.pubflow.com/bridge-payment/subscriptions/sub_123" \
  -H "Content-Type: application/json" \
  -H "X-Session-ID: session_abc123" \
  -d '{
    "payment_method_id": "pm_new_456"
  }'

# Update amount
curl -X PUT "https://your-instance.pubflow.com/bridge-payment/subscriptions/sub_123" \
  -H "Content-Type: application/json" \
  -H "X-Session-ID: session_abc123" \
  -d '{
    "total_cents": 3000
  }'

Cancel Subscription ​

bash
# Cancel at end of billing period
curl -X POST "https://your-instance.pubflow.com/bridge-payment/subscriptions/sub_123/cancel" \
  -H "Content-Type: application/json" \
  -H "X-Session-ID: session_abc123" \
  -d '{
    "cancel_at_period_end": true
  }'

# Cancel immediately
curl -X POST "https://your-instance.pubflow.com/bridge-payment/subscriptions/sub_123/cancel" \
  -H "Content-Type: application/json" \
  -H "X-Session-ID: session_abc123" \
  -d '{
    "cancel_at_period_end": false
  }'

Reactivate Subscription ​

bash
curl -X POST "https://your-instance.pubflow.com/bridge-payment/subscriptions/sub_123/reactivate" \
  -H "X-Session-ID: session_abc123"

Subscription Management UI ​

jsx
function SubscriptionManager({ subscriptionId }) {
  const [subscription, setSubscription] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(`/bridge-payment/subscriptions/${subscriptionId}`, {
      headers: { 'X-Session-ID': sessionId }
    })
      .then(res => res.json())
      .then(data => {
        setSubscription(data);
        setLoading(false);
      });
  }, [subscriptionId]);

  const handleCancel = async () => {
    if (!confirm('Cancel subscription?')) return;

    await fetch(`/bridge-payment/subscriptions/${subscriptionId}/cancel`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Session-ID': sessionId
      },
      body: JSON.stringify({
        cancel_at_period_end: true
      })
    });

    alert('Subscription will cancel at end of billing period');
    window.location.reload();
  };

  const handleReactivate = async () => {
    await fetch(`/bridge-payment/subscriptions/${subscriptionId}/reactivate`, {
      method: 'POST',
      headers: { 'X-Session-ID': sessionId }
    });

    alert('Subscription reactivated');
    window.location.reload();
  };

  if (loading) return <div>Loading...</div>;

  return (
    <div className="max-w-2xl mx-auto p-6">
      <h2 className="text-2xl font-bold mb-6">Subscription Details</h2>

      <div className="bg-white border rounded-lg p-6 mb-6">
        <div className="flex justify-between items-start mb-4">
          <div>
            <h3 className="text-xl font-semibold">{subscription.concept}</h3>
            <p className="text-gray-600">
              ${(subscription.total_cents / 100).toFixed(2)}/{subscription.billing_interval}
            </p>
          </div>
          <span className={`px-3 py-1 rounded text-sm ${
            subscription.status === 'active' ? 'bg-green-100 text-green-800' :
            subscription.status === 'trialing' ? 'bg-blue-100 text-blue-800' :
            subscription.status === 'canceled' ? 'bg-red-100 text-red-800' :
            'bg-gray-100 text-gray-800'
          }`}>
            {subscription.status}
          </span>
        </div>

        <div className="space-y-2 text-sm">
          <div className="flex justify-between">
            <span className="text-gray-600">Next billing date:</span>
            <span className="font-medium">
              {new Date(subscription.next_billing_date).toLocaleDateString()}
            </span>
          </div>
          
          {subscription.trial_end && (
            <div className="flex justify-between">
              <span className="text-gray-600">Trial ends:</span>
              <span className="font-medium">
                {new Date(subscription.trial_end).toLocaleDateString()}
              </span>
            </div>
          )}

          {subscription.cancel_at_period_end && (
            <div className="bg-yellow-50 p-3 rounded mt-4">
              <p className="text-sm text-yellow-800">
                Subscription will cancel on {new Date(subscription.current_period_end).toLocaleDateString()}
              </p>
            </div>
          )}
        </div>
      </div>

      <div className="flex gap-3">
        {subscription.status === 'active' && !subscription.cancel_at_period_end && (
          <button
            onClick={handleCancel}
            className="px-4 py-2 border border-red-600 text-red-600 rounded hover:bg-red-50"
          >
            Cancel Subscription
          </button>
        )}

        {subscription.cancel_at_period_end && (
          <button
            onClick={handleReactivate}
            className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
          >
            Reactivate Subscription
          </button>
        )}
      </div>
    </div>
  );
}

Webhook Events ​

Handle subscription events:

javascript
// Subscription created
{
  "event": "subscription.created",
  "data": {
    "subscription_id": "sub_123",
    "status": "trialing",
    "trial_end": "2025-01-29T10:30:00Z"
  }
}

// Subscription payment succeeded
{
  "event": "invoice.payment_succeeded",
  "data": {
    "subscription_id": "sub_123",
    "amount": 2000,
    "currency": "USD"
  }
}

// Subscription payment failed
{
  "event": "invoice.payment_failed",
  "data": {
    "subscription_id": "sub_123",
    "amount": 2000,
    "attempt_count": 1
  }
}

// Subscription canceled
{
  "event": "subscription.deleted",
  "data": {
    "subscription_id": "sub_123",
    "canceled_at": "2025-02-15T10:30:00Z"
  }
}

Best Practices ​

  1. Offer Free Trials - Increase conversion rates
  2. Save Payment Methods - Required for subscriptions
  3. Handle Failed Payments - Retry logic and notifications
  4. Provide Cancellation - Easy cancellation builds trust
  5. Send Notifications - Email receipts and reminders
  6. Monitor Churn - Track cancellations and reasons

Next Steps ​