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.
programs MigrationSchema::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 MigrationSchema::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 MigrationSchema::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 MigrationSchema::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();
});
Use the razorpay/razorpay package:
composer require razorpay/razorpay
use Razorpay\Api\Api;
$api = new Api(config('services.razorpay.key'), config('services.razorpay.secret'));
$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
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',
]);
}
Programpublic function pricingPlans() {
return $this->hasMany(ProgramPricing::class);
}
ProgramPricingpublic function program() {
return $this->belongsTo(Program::class);
}
Subscriptionpublic 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);
}
Paymentpublic function subscription() {
return $this->belongsTo(Subscription::class);
}
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>
invoices table for audit trail and PDF generation.Let me know if you want a ready-to-use Laravel starter repo for this — or a full controller + Blade page + Razorpay integration flow.