Here's how you can implement this subscription schema in Laravel using Eloquent models, migrations, and Razorpay integration support for both monthly and full payment modes.


✅ Step 1: Migrations

programs Migration

Schema::create('programs', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->text('description')->nullable();
    $table->integer('duration_months'); // e.g. 3
    $table->timestamps();
});

program_pricing Migration

Schema::create('program_pricing', function (Blueprint $table) {
    $table->id();
    $table->foreignId('program_id')->constrained()->onDelete('cascade');
    $table->enum('name', ['monthly', 'full']);
    $table->decimal('price', 10, 2);
    $table->boolean('per_month')->default(false);
    $table->unique(['program_id', 'name']);
    $table->timestamps();
});

subscriptions Migration

Schema::create('subscriptions', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained()->onDelete('cascade');
    $table->foreignId('program_id')->constrained()->onDelete('cascade');
    $table->foreignId('pricing_plan_id')->constrained('program_pricing')->onDelete('cascade');
    $table->date('start_date');
    $table->date('end_date');
    $table->enum('status', ['active', 'cancelled', 'expired'])->default('active');
    $table->timestamps();
});

payments Migration

Schema::create('payments', function (Blueprint $table) {
    $table->id();
    $table->foreignId('subscription_id')->constrained()->onDelete('cascade');
    $table->decimal('amount', 10, 2);
    $table->date('payment_date');
    $table->date('month_for')->nullable(); // e.g. 2025-07-01 for monthly plans
    $table->string('razorpay_payment_id')->nullable();
    $table->string('payment_mode')->nullable();
    $table->enum('payment_status', ['success', 'failed', 'pending'])->default('pending');
    $table->timestamps();
});

✅ Step 2: Razorpay Integration (Controller Sample)

Use the razorpay/razorpay package:

composer require razorpay/razorpay

Razorpay Setup

use Razorpay\Api\Api;

$api = new Api(config('services.razorpay.key'), config('services.razorpay.secret'));

Creating Razorpay Order (For Payment Page)

$order = $api->order->create([
    'receipt'         => 'order_rcptid_11',
    'amount'          => $amount * 100, // in paise
    'currency'        => 'INR',
    'payment_capture' => 1
]);

// Return `$order['id']` to the frontend Razorpay Checkout

Handling Razorpay Callback (webhook or success callback)

public function handleRazorpaySuccess(Request $request)
{
    $subscription = Subscription::find($request->subscription_id);

    Payment::create([
        'subscription_id' => $subscription->id,
        'amount' => $request->amount,
        'payment_date' => now(),
        'month_for' => $subscription->pricingPlan->per_month ? now()->startOfMonth() : null,
        'razorpay_payment_id' => $request->razorpay_payment_id,
        'payment_status' => 'success',
        'payment_mode' => 'razorpay',
    ]);
}

✅ Step 3: Model Relationships

Program

public function pricingPlans() {
    return $this->hasMany(ProgramPricing::class);
}

ProgramPricing

public function program() {
    return $this->belongsTo(Program::class);
}

Subscription

public function user() {
    return $this->belongsTo(User::class);
}

public function program() {
    return $this->belongsTo(Program::class);
}

public function pricingPlan() {
    return $this->belongsTo(ProgramPricing::class, 'pricing_plan_id');
}

public function payments() {
    return $this->hasMany(Payment::class);
}

Payment

public function subscription() {
    return $this->belongsTo(Subscription::class);
}

✅ Razorpay Checkout Frontend

On frontend, use Razorpay Checkout with the generated order_id:

<script src="https://checkout.razorpay.com/v1/checkout.js"></script>
<script>
var options = {
    "key": "{{ config('services.razorpay.key') }}",
    "amount": "{{ $amount }}", 
    "currency": "INR",
    "name": "Program Subscription",
    "order_id": "{{ $razorpayOrderId }}",
    "handler": function (response){
        // call /handle-razorpay-success with response.razorpay_payment_id
    }
};
var rzp = new Razorpay(options);
rzp.open();
</script>

🧠 Optional Improvements


Let me know if you want a ready-to-use Laravel starter repo for this — or a full controller + Blade page + Razorpay integration flow.