Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: payment callbacks #379

Merged
merged 15 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 47 additions & 6 deletions app/Http/Controllers/Api/V1/PaymentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Illuminate\Support\Facades\Validator;
use App\Models\Payment;
use App\Services\PaymentService;
use App\Models\Organisation;
use App\Models\SubscriptionPlan;
use App\Models\UserSubscription;
use Illuminate\Support\Str;
Expand All @@ -26,7 +27,7 @@ public function initiatePaymentForPayStack(Request $request)
{
// return response()->json(['h'=> 'ng']);
$validator = Validator::make($request->all(), [
// 'organisation_id' => 'required',
'organisation_id' => 'required',
'plan_id' =>'required',
'billing_option' => 'required|in:monthly,yearly',
'full_name' => 'required',
Expand All @@ -39,14 +40,31 @@ public function initiatePaymentForPayStack(Request $request)
'message' => 'Validation error: ' . $validator->errors()->first()
], 400);
}
$userIsAnAdminInOrganisation = Organisation::where('user_id', auth()->user()->id)
->where('org_id', $request->organisation_id)
->exists();
if (!$userIsAnAdminInOrganisation) {
return response()->json([
'status' => 403,
'message' => 'You do not have permission to initiate this payment'
], 403);
}

// $gateway_id = Gateway::where('code', 'paystack')->first()->id;
$subscriptionPlan = SubscriptionPlan::find($request->plan_id);
if(!$subscriptionPlan) {
return response()->json([
'status' => 404,
'message' => 'Subscription Plan not found'
], 404);
}
$data = $validator->validated();
$data['email'] = auth()->user()->email;
$data['reference'] = Str::uuid();
$data['plan_code'] = $subscriptionPlan->paystack_plan_code;
$data['plan_id'] = $subscriptionPlan->id;
$data['amount'] = $subscriptionPlan->price;
$data['organisation_id'] = $request->organisation_id;

try {

Expand All @@ -72,12 +90,13 @@ public function initiatePaymentForPayStack(Request $request)
} catch (\Exception $e) {
return response()->json([
'status' => 500,
'message' => 'Payment Initialization Failed: ' . $e->getMessage()
'message' => 'An unexpected error occurred. Please try again later.'
// 'message' => 'Payment Initialization Failed: ' . $e->getMessage()
], 500);
}
}

public function handlePaystackCallback($id, Request $request)
public function handlePaystackCallback($organisation_id, $id, Request $request)
{
$reference = $request->query('reference');

Expand All @@ -99,6 +118,7 @@ public function handlePaystackCallback($id, Request $request)
$userSubscription = new UserSubscription;
$userSubscription->user_id = auth()->user()->id;
$userSubscription->subscription_plan_id = $id;
$userSubscription->org_id = $organisation_id;
$userSubscription->save();


Expand All @@ -115,7 +135,7 @@ public function handlePaystackCallback($id, Request $request)
public function initiatePaymentForFlutterWave(Request $request)
{
$validator = Validator::make($request->all(), [
// 'organisation_id' => 'required',
'organisation_id' => 'required',
'plan_id' =>'required',
'billing_option' => 'required|in:monthly,yearly',
'full_name' => 'required',
Expand All @@ -128,8 +148,25 @@ public function initiatePaymentForFlutterWave(Request $request)
'message' => 'Validation error: ' . $validator->errors()->first()
], 400);
}

$userIsAnAdminInOrganisation = Organisation::where('user_id', auth()->user()->id)
->where('org_id', $request->organisation_id)
->exists();
// return response()->json(auth()->user()->id);
if (!$userIsAnAdminInOrganisation) {
return response()->json([
'status' => 403,
'message' => 'You do not have permission to initiate this payment'
], 403);
}
// $gateway_id = Gateway::where('code', 'flutterwave')->first()->id;
$subscriptionPlan = SubscriptionPlan::find($request->plan_id);
if(!$subscriptionPlan) {
return response()->json([
'status' => 404,
'message' => 'Subscription Plan not found'
], 404);
}

$data = $validator->validated();
$data['email'] = auth()->user()->email;
Expand All @@ -138,6 +175,8 @@ public function initiatePaymentForFlutterWave(Request $request)
$data['plan_id'] = $subscriptionPlan->id;
$data['amount'] = $subscriptionPlan->price;
$data['title'] = $subscriptionPlan->name;
$data['organisation_id'] = $request->organisation_id;
$data['title'] = $subscriptionPlan->name;

try {
// Retrieve the gateway name
Expand Down Expand Up @@ -166,12 +205,13 @@ public function initiatePaymentForFlutterWave(Request $request)
} catch (\Exception $e) {
return response()->json([
'status' => 500,
'message' => 'Payment Initialization Failed: ' . $e->getMessage()
'message' => 'An unexpected error occurred. Please try again later.'
// 'message' => 'Payment Initialization Failed: ' . $e->getMessage()
], 500);
}
}

public function handleFlutterwaveCallback($id, Request $request)
public function handleFlutterwaveCallback($organisation_id, $id, Request $request)
{
$transaction_id = $request->query('transaction_id');

Expand All @@ -192,6 +232,7 @@ public function handleFlutterwaveCallback($id, Request $request)
$userSubscription = new UserSubscription;
$userSubscription->user_id = auth()->user()->id;
$userSubscription->subscription_plan_id = $id;
$userSubscription->org_id = $organisation_id;
$userSubscription->save();

// Redirect to the specified URL with status
Expand Down
17 changes: 17 additions & 0 deletions app/Models/Organisation.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,21 @@ public function roles()
{
return $this->hasMany(Role::class, 'org_id');
}

public function subscription()
{
return $this->hasOne(UserSubscription::class, 'org_id', 'org_id');
}

public function subscriptionPlan()
{
return $this->hasOneThrough(
SubscriptionPlan::class,
UserSubscription::class,
'org_id', // Foreign key on UserSubscription table
'id', // Foreign key on SubscriptionPlan table
'org_id', // Local key on Organisation table
'subscription_plan_id' // Local key on UserSubscription table
);
}
}
5 changes: 5 additions & 0 deletions app/Models/SubscriptionPlan.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,9 @@ public function features(): BelongsToMany
->withTimestamps();
}

public function userSubscriptions()
{
return $this->hasMany(UserSubscription::class, 'subscription_plan_id', 'id');
}

}
5 changes: 5 additions & 0 deletions app/Models/UserSubscription.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ class UserSubscription extends Model
use HasFactory, HasUuids;

protected $fillable = ['subscription_plan_id', 'cancellation_reason'];

public function subscriptionPlan()
{
return $this->belongsTo(SubscriptionPlan::class, 'subscription_plan_id', 'id');
}
}
12 changes: 5 additions & 7 deletions app/Services/PaymentService.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ public function initiatePaystackPayment($data)
])->post('https://api.paystack.co/transaction/initialize', [
'email' => $data['email'],
'plan' => $data['plan_code'],
'amount' => $data['amount'],
'reference' => $data['reference'],
'callback_url' => url('/api/v1/payments/paystack/verify/'.$data['plan_id']),
'callback_url' => url('/api/v1/payments/paystack/'.$data['organisation_id'].'/verify/'.$data['plan_id']),
'metadata' => [
'cancel_action' => route('payment.cancel')
]
Expand Down Expand Up @@ -61,18 +62,15 @@ public function initiateFlutterwavePayment($data)
'tx_ref' => $data['reference'],
'amount' => $data['amount'], // Flutterwave still needs the amount
'currency' => 'USD',
'redirect_url' => url('/api/v1/payments/flutterwave/verify/'.$data['plan_id']),
'payment_plan' => $data['plan_code'],
'redirect_url' => url('/api/v1/payments/flutterwave/'.$data['organisation_id'].'/verify/'.$data['plan_id']),
'customer' => [
'email' => $data['email'],
'name' => $data['full_name']
],
'customizations' => [
'title' => 'Your Payment Title',
'title' => $data['title'],
'billing_option' => $data['billing_option'] // Include billing_option in customizations
],
'meta' => [
'source' => 'laravel-flutterwave',
'plan_code' => $data['plan_code'] // Include plan_code in meta
]
]);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up()
{
Schema::table('user_subscriptions', function (Blueprint $table) {
$table->foreignUuid('org_id')->constrained('organisations', 'org_id')->cascadeOnDelete()->cascadeOnUpdate();
});
}


/**
* Reverse the migrations.
*/
public function down(): void
{

Schema::table('user_subscriptions', function (Blueprint $table) {
$table->dropForeign(['org_id']);
$table->dropColumn('org_id');
});
}
};
4 changes: 2 additions & 2 deletions routes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,9 @@
Route::apiResource('/features', FeatureController::class);
Route::apiResource('/plans', SubscriptionController::class);
Route::post('/payments/paystack', [PaymentController::class, 'initiatePaymentForPayStack']);
Route::get('/payments/paystack/verify/{id}', [PaymentController::class, 'handlePaystackCallback']);
Route::get('/payments/paystack/{organisation_id}/verify/{id}', [PaymentController::class, 'handlePaystackCallback']);
Route::post('/payments/flutterwave', [PaymentController::class, 'initiatePaymentForFlutterWave']);
Route::get('/payments/flutterwave/verify/{id}', [PaymentController::class, 'handleFlutterwaveCallback']);
Route::get('/payments/flutterwave/{organisation_id}/verify/{id}', [PaymentController::class, 'handleFlutterwaveCallback']);
Route::get('/payments/cancel', [PaymentController::class, 'cancel'])->name('payment.cancel');
Route::post('/users/plans/{user_subscription}/cancel', [\App\Http\Controllers\Api\V1\User\SubscriptionController::class, 'destroy']);
Route::get('/users/plan', [\App\Http\Controllers\Api\V1\User\SubscriptionController::class, 'userPlan']);
Expand Down
Loading