# Business & Birthdays — Frontend Subscription Guide

> **For Frontend Developers**  
> **Last Updated:** 2026-07-13  
> **Base URL:** `{{APP_URL}}/api/v1`  
> **Auth:** Bearer Token (Sanctum)

---

## Overview

The subscription flow uses **Paystack** for payment processing and recurring billing. Here's the high-level flow:

```
1. Frontend fetches available plans       → GET  /subscriptions/plans
2. User selects a plan                    → (UI)
3. Frontend initializes payment           → POST /subscriptions/initialize
4. Frontend redirects user to Paystack    → (Paystack checkout page)
5. User completes payment on Paystack     → (Paystack redirects back with ?reference=xxx)
6. Frontend verifies + creates sub        → POST /subscriptions/verify-and-create
7. Frontend shows success/error           → (UI)
```

---

## Step-by-Step Implementation

### Step 1: Fetch Available Plans

Show only plans that have been configured on Paystack.

```typescript
// GET {{BASE_URL}}/subscriptions/plans
const response = await api.get('/subscriptions/plans');
const plans = response.data.data.plans;

// Each plan looks like:
interface SubscriptionPlan {
    id: number;
    name: string;           // "Basic Plan"
    slug: string;           // "basic"
    price: number;          // 2000
    daily_hours_limit: number;  // 6 (0 = unlimited)
    duration_days: number;      // 30
    features: string[] | null;
}
```

**Response:**
```json
{
    "success": true,
    "data": {
        "plans": [
            { "id": 1, "name": "Basic Plan", "price": 2000, "daily_hours_limit": 6, "duration_days": 30 }
        ]
    }
}
```

> ⚠️ Plans without `paystack_plan_code` (not synced to Paystack) will **not** appear here.

---

### Step 2: User Selects a Plan (UI only)

Let the user pick a plan and show details. No API call needed yet.

---

### Step 3: Initialize Payment

```typescript
// POST {{BASE_URL}}/subscriptions/initialize
const response = await api.post('/subscriptions/initialize', {
    plan_id: selectedPlan.id,  // e.g., 1
});

const data = response.data.data;
// data.authorization_url - URL to redirect user to Paystack
// data.reference        - Unique reference for this transaction
// data.plan             - The plan details
```

**Success Response (200):**
```json
{
    "success": true,
    "message": "Subscription payment initialized. Redirect user to authorization_url.",
    "data": {
        "authorization_url": "https://checkout.paystack.com/0peioxfhpn",
        "reference": "trx-ps-6a5490da56d01",
        "plan": {
            "id": 1,
            "name": "Basic Plan",
            "price": 2000
        }
    }
}
```

**Error Responses:**

| Status | Message | Cause |
|--------|---------|-------|
| 401 | Unauthenticated | User not logged in |
| 400 | You already have an active subscription... | User already subscribed |
| 400 | This plan is not configured for Paystack... | Plan missing `paystack_plan_code` (admin must sync) |
| 422 | The plan_id field is required. | Missing `plan_id` |

---

### Step 4: Redirect User to Paystack

You have **two options** to handle the Paystack payment:

#### Option A: Redirect (Simplest)

Open the `authorization_url` in the browser. Paystack will redirect back to your `callback_url` with the `reference` parameter.

```typescript
// Web: redirect
window.location.href = data.authorization_url;

// React Native: open in browser
import { Linking } from 'react-native';
await Linking.openURL(data.authorization_url);
```

#### Option B: Paystack Popup (Recommended for better UX)

Use Paystack's inline popup so the user never leaves your app/page.

```typescript
// For Web: Include Paystack script in your HTML
// <script src="https://js.paystack.co/v1/inline.js"></script>

const handler = (window as any).PaystackPop.setup({
    key: 'pk_test_xxxxxxxxxxxxx',        // Your Paystack PUBLIC key (from .env)
    email: user.email,                    // User's email
    amount: selectedPlan.price * 100,     // Amount in kobo (e.g., 2000 * 100 = 200000)
    ref: data.reference,                  // Reference from Step 3
    currency: 'NGN',
    callback: function(response: any) {
        // 🔔 Payment successful!
        // response.reference contains the Paystack reference
        // Proceed to Step 5: verify-and-create
        verifyAndCreateSubscription(response.reference, selectedPlan.id);
    },
    onClose: function() {
        // User closed the popup without paying
        console.log('Payment cancelled by user');
    }
});
handler.openIframe();
```

---

### Step 5: Verify Payment & Create Subscription

This endpoint verifies the Paystack transaction, creates the subscription on Paystack (for recurring billing), and stores everything locally.

```typescript
// POST {{BASE_URL}}/subscriptions/verify-and-create
async function verifyAndCreateSubscription(reference: string, planId: number) {
    try {
        const response = await api.post('/subscriptions/verify-and-create', {
            reference: reference,      // From Paystack callback
            plan_id: planId,           // The plan ID the user selected
        });

        if (response.data.success) {
            // ✅ Subscription created successfully!
            const subscription = response.data.data.subscription;
            showSuccess(subscription);
        } else {
            showError(response.data.message);
        }
    } catch (error) {
        showError('Verification failed. Please contact support.');
    }
}
```

**Success Response (200):**
```json
{
    "success": true,
    "message": "Paystack subscription created successfully",
    "data": {
        "subscription": {
            "id": 10,
            "plan_name": "Basic Plan",
            "start_date": "2026-07-13",
            "end_date": "2026-08-12",
            "days_remaining": 30,
            "payment_status": "paid",
            "auto_renew": true,
            "paystack_subscription_code": "SUB_vsyqdmlzble3uii",
            "next_payment_date": "2026-08-12"
        }
    }
}
```

**Error Responses:**

| Status | Message | Cause |
|--------|---------|-------|
| 400 | Payment verification failed | Invalid/expired reference |
| 400 | No authorization found... | Payment not completed on Paystack |
| 400 | You already have an active subscription. | User already subscribed |
| 500 | Unable to create subscription... | Paystack API error |

---

### Step 6: Check Subscription Status

```typescript
// GET {{BASE_URL}}/subscriptions/my-subscription
const response = await api.get('/subscriptions/my-subscription');
const { has_active_subscription, subscription } = response.data.data;
```

**When active:**
```json
{
    "success": true,
    "data": {
        "has_active_subscription": true,
        "subscription": {
            "id": 10,
            "plan_name": "Basic Plan",
            "plan_slug": "basic",
            "price": 2000.00,
            "start_date": "2026-07-13",
            "end_date": "2026-08-12",
            "days_remaining": 30,
            "payment_status": "paid",
            "auto_renew": true,
            "paystack_subscription_code": "SUB_vsyqdmlzble3uii",
            "next_payment_date": "2026-08-12",
            "daily_hours": {
                "limit": 6,
                "limit_minutes": 360,
                "used_minutes": 0,
                "remaining_minutes": 360
            }
        }
    }
}
```

**When no active subscription:**
```json
{
    "success": true,
    "data": {
        "has_active_subscription": false,
        "subscription": null
    }
}
```

---

## Complete Frontend Implementation

```typescript
import axios from 'axios';

const api = axios.create({
    baseURL: '{{APP_URL}}/api/v1',
    headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
});

// Attach auth token
api.interceptors.request.use(config => {
    const token = localStorage.getItem('auth_token');
    if (token) config.headers.Authorization = `Bearer ${token}`;
    return config;
});

interface SubscriptionPlan {
    id: number;
    name: string;
    slug: string;
    price: number;
    daily_hours_limit: number;
    duration_days: number;
    features: string[] | null;
}

interface ActiveSubscription {
    id: number;
    plan_name: string;
    plan_slug: string;
    price: number;
    start_date: string;
    end_date: string;
    days_remaining: number;
    payment_status: 'pending' | 'paid' | 'failed' | 'refunded';
    auto_renew: boolean;
    paystack_subscription_code: string | null;
    next_payment_date: string | null;
    daily_hours: {
        limit: number;
        limit_minutes: number;
        used_minutes: number;
        remaining_minutes: number;
    };
}

class SubscriptionService {
    /**
     * Step 1: Get available plans
     */
    async getPlans(): Promise<SubscriptionPlan[]> {
        const { data } = await api.get('/subscriptions/plans');
        return data.data.plans;
    }

    /**
     * Step 3: Initialize payment
     */
    async initializePayment(planId: number) {
        const { data } = await api.post('/subscriptions/initialize', { plan_id: planId });
        return data.data; // { authorization_url, reference, plan }
    }

    /**
     * Step 5: Verify payment and create subscription
     */
    async verifyAndCreate(reference: string, planId: number): Promise<ActiveSubscription> {
        const { data } = await api.post('/subscriptions/verify-and-create', {
            reference,
            plan_id: planId,
        });
        return data.data.subscription;
    }

    /**
     * Step 6: Get current subscription
     */
    async getMySubscription(): Promise<{ has_active_subscription: boolean; subscription: ActiveSubscription | null }> {
        const { data } = await api.get('/subscriptions/my-subscription');
        return data.data;
    }

    /**
     * Full purchase flow (using Paystack popup)
     */
    async purchaseWithPaystackPopup(
        plan: SubscriptionPlan,
        paystackPublicKey: string,
        userEmail: string
    ): Promise<ActiveSubscription> {
        // Step 3: Initialize
        const initResult = await this.initializePayment(plan.id);
        const { authorization_url, reference } = initResult;

        // Step 4: Open Paystack popup
        const paid = await new Promise<boolean>((resolve) => {
            const handler = (window as any).PaystackPop.setup({
                key: paystackPublicKey,
                email: userEmail,
                amount: plan.price * 100,
                ref: reference,
                currency: 'NGN',
                callback: () => resolve(true),
                onClose: () => resolve(false),
            });
            handler.openIframe();
        });

        if (!paid) {
            throw new Error('Payment was cancelled by user');
        }

        // Step 5: Verify and create subscription
        const subscription = await this.verifyAndCreate(reference, plan.id);
        return subscription;
    }
}

export default new SubscriptionService();
```

---

## Testing Checklist

- [ ] `GET /subscriptions/plans` returns plans (not empty)
- [ ] `POST /subscriptions/initialize` returns `authorization_url`
- [ ] Paystack checkout page loads and accepts card
- [ ] After payment, user is redirected back with `?reference=xxx`
- [ ] `POST /subscriptions/verify-and-create` returns subscription details
- [ ] `GET /subscriptions/my-subscription` shows active subscription
- [ ] `GET /subscriptions/remaining-hours` shows daily limits

---

## Useful Endpoints Summary

| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| GET | `/subscriptions/plans` | No | List available plans |
| POST | `/subscriptions/initialize` | Yes | Initialize Paystack payment |
| POST | `/subscriptions/verify-and-create` | Yes | Verify payment & create subscription |
| GET | `/subscriptions/my-subscription` | Yes | Get active subscription |
| GET | `/subscriptions/history` | Yes | Past subscriptions (paginated) |
| POST | `/subscriptions/cancel-auto-renew` | Yes | Disable auto-renewal |
| GET | `/subscriptions/remaining-hours` | Yes | Daily hours remaining |
| POST | `/subscriptions/paystack/enable` | Yes | Re-enable disabled subscription |
| POST | `/subscriptions/paystack/disable` | Yes | Disable/cancel subscription |
| GET | `/subscriptions/paystack/{code}/manage/link` | Yes | Get card update link |
| POST | `/subscriptions/paystack/{code}/manage/email` | Yes | Send card update email |
