Skip to content

Instantly share code, notes, and snippets.

@larson-carter
Created March 3, 2025 14:23
Show Gist options
  • Save larson-carter/40009cc9004c1224c159605c3af01129 to your computer and use it in GitHub Desktop.
Save larson-carter/40009cc9004c1224c159605c3af01129 to your computer and use it in GitHub Desktop.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Models\CoverPass;
use App\Models\CoverManagement;
use App\Models\SubscriptionOption;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str; // For Str::uuid()
class PaymentController extends Controller
{
public function subscriptionStatus(Request $request)
{
$user = Auth::user();
$subscription = $user->subscription('default');
$options = SubscriptionOption::where('is_active', true)->get();
$subscriptionData = null;
if ($subscription && $subscription->stripe_status === 'active') {
$option = SubscriptionOption::where('stripe_price_id', $subscription->stripe_price)->first();
$subscriptionData = [
'id' => (string) $subscription->id,
'type' => $option ? $option->name : $subscription->stripe_price,
'user_id' => $user->id,
'duration' => $option ? $option->duration : 'unknown',
'ends_at' => $subscription->ends_at?->toIso8601String(),
'stripe_status' => $subscription->stripe_status,
'auto_renew' => !$subscription->canceled(),
];
}
return response()->json([
'has_subscription' => $user->hasActiveSubscription(),
'subscription' => $subscriptionData,
'options' => $options->map(function ($option) {
return [
'id' => $option->id,
'name' => $option->name,
'price' => $option->price,
'stripe_price' => $option->stripe_price_id,
'duration' => $option->duration,
'description' => $option->description,
'features' => $option->features,
];
}),
], 200);
}
public function processSubscriptionPayment(Request $request)
{
$request->validate([
'stripe_price' => 'required|string|exists:subscription_options,stripe_price_id',
'payment_method' => 'required|string',
]);
$user = Auth::user();
$stripePrice = $request->stripe_price;
if ($user->hasActiveSubscription()) {
return response()->json(['error' => 'User already has an active subscription'], 400);
}
$option = SubscriptionOption::where('stripe_price_id', $stripePrice)->firstOrFail();
try {
$user->addPaymentMethod($request->payment_method);
$isRecurring = $option->duration !== 'lifetime';
if ($isRecurring) {
// For recurring subscriptions:
$subscription = $user->newSubscription('default', $stripePrice)
->create($request->payment_method, [
'transfer_data' => ['destination' => config('services.stripe.connect_account_id')],
'application_fee_percent' => 5,
]);
\Stripe\Stripe::setApiKey(config('services.stripe.secret'));
$stripeSubscription = \Stripe\Subscription::retrieve($subscription->stripe_id);
$endsAt = Carbon::createFromTimestamp($stripeSubscription->current_period_end);
$subscription->ends_at = $endsAt;
$subscription->save();
return response()->json([
'has_subscription' => true,
'subscription' => [
'id' => (string) $subscription->id,
'type' => $option->name,
'user_id' => $user->id,
'duration' => $option->duration,
'ends_at' => $endsAt->toIso8601String(),
'stripe_status' => $subscription->stripe_status,
'auto_renew' => true,
],
], 201);
} else {
// One-time "lifetime" subscription:
$charge = $user->charge(
$option->price * 100,
$request->payment_method,
[
'transfer_data' => ['destination' => config('services.stripe.connect_account_id')],
'application_fee_amount' => ($option->price * 100) * 0.05,
// Only allow card; no redirect-based payment methods
'payment_method_types' => ['card'],
]
);
return response()->json([
'has_subscription' => true,
'subscription' => [
'id' => $charge->id,
'type' => $option->name,
'user_id' => $user->id,
'duration' => $option->duration,
'ends_at' => null,
'stripe_status' => 'active',
'auto_renew' => false,
],
], 201);
}
} catch (\Exception $e) {
Log::error('Subscription payment failed: ' . $e->getMessage());
return response()->json(['error' => 'Payment failed: ' . $e->getMessage()], 500);
}
}
public function processCoverPayment(Request $request)
{
$request->validate([
'payment_method' => 'required|string',
]);
$user = Auth::user();
$coverManagement = CoverManagement::firstOrFail();
if (!$coverManagement->charging_cover) {
return response()->json(['message' => 'Cover charge is not currently active.'], 400);
}
try {
$user->addPaymentMethod($request->payment_method);
// Charge for the cover pass, allowing only card payments
$charge = $user->charge(
$coverManagement->cover_cost * 100,
$request->payment_method,
[
'transfer_data' => ['destination' => config('services.stripe.connect_account_id')],
'application_fee_amount' => ($coverManagement->cover_cost * 100) * 0.05,
'payment_method_types' => ['card'], // disable redirect-based methods
]
);
$coverPass = CoverPass::create([
'id' => Str::uuid(),
'user_id' => $user->id,
'amount_paid' => $coverManagement->cover_cost,
'purchased_at' => Carbon::now(),
'expires_at' => Carbon::now()->addHours(6),
'is_used' => false,
'stripe_charge_id' => $charge->id,
]);
return response()->json([
'message' => 'Cover pass purchased successfully',
'cover_pass' => [
'id' => $coverPass->id,
'user_id' => $coverPass->user_id,
'purchased_at' => $coverPass->purchased_at->toIso8601String(),
'expires_at' => $coverPass->expires_at->toIso8601String(),
'amount_paid' => (string) $coverPass->amount_paid,
],
], 201);
} catch (\Exception $e) {
Log::error('Cover payment failed: ' . $e->getMessage());
return response()->json(['error' => 'Payment failed: ' . $e->getMessage()], 500);
}
}
public function toggleAutoRenew(Request $request)
{
$user = Auth::user();
$subscription = $user->subscription('default');
if (!$subscription) {
return response()->json(['error' => 'No active subscription found'], 400);
}
try {
if ($subscription->canceled()) {
$subscription->resume();
} else {
$subscription->cancel();
}
return response()->json([
'message' => 'Auto-renew status updated',
'auto_renew' => !$subscription->canceled(),
]);
} catch (\Exception $e) {
Log::error('Toggle auto-renew failed: ' . $e->getMessage());
return response()->json(['error' => 'Failed to update auto-renew: ' . $e->getMessage()], 500);
}
}
public function createSetupIntent(Request $request)
{
$user = Auth::user();
$setupIntent = $user->createSetupIntent();
return response()->json([
'client_secret' => $setupIntent->client_secret,
'publishable_key' => config('services.stripe.key'),
]);
}
public function getSetupIntentPaymentMethod(Request $request, $setupIntentId)
{
$user = Auth::user();
\Stripe\Stripe::setApiKey(config('services.stripe.secret'));
try {
$setupIntent = \Stripe\SetupIntent::retrieve($setupIntentId);
if ($setupIntent->customer !== $user->stripe_id) {
return response()->json(['error' => 'Unauthorized'], 403);
}
if ($setupIntent->status !== 'succeeded' || !$setupIntent->payment_method) {
return response()->json(['error' => 'SetupIntent not confirmed or no payment method attached'], 400);
}
return response()->json([
'payment_method' => $setupIntent->payment_method,
]);
} catch (\Stripe\Exception\AuthenticationException $e) {
Log::error('Stripe Authentication Error: ' . $e->getMessage());
return response()->json(['error' => 'Authentication failed with Stripe'], 500);
} catch (\Exception $e) {
Log::error('Error retrieving SetupIntent: ' . $e->getMessage());
return response()->json(['error' => 'Failed to retrieve payment method'], 500);
}
}
public function coverInfo(Request $request)
{
$user = Auth::user();
$subscription = $user->subscription('default');
$coverManagement = CoverManagement::first();
$hasMonthlySubscription = false;
$activeCoverPass = null;
$coverCost = $coverManagement ? (float) $coverManagement->cover_cost : 0.0;
$isChargingCover = $coverManagement ? $coverManagement->charging_cover : false;
if ($subscription) {
$now = Carbon::now();
$endsAt = Carbon::parse($subscription->ends_at);
if ($now->lt($endsAt)) {
$option = SubscriptionOption::where('stripe_price_id', $subscription->stripe_price)->first();
if ($option && $option->duration === 'monthly') {
$hasMonthlySubscription = true;
}
}
}
if ($hasMonthlySubscription && $isChargingCover) {
$activeCoverPass = CoverPass::where('user_id', $user->id)
->where('expires_at', '>', Carbon::now())
->where('is_used', false)
->latest('purchased_at')
->first();
}
return response()->json([
'has_monthly_subscription' => $hasMonthlySubscription,
'is_charging_cover' => $isChargingCover,
'cover_cost' => $coverCost,
'active_cover_pass' => $activeCoverPass ? [
'id' => $activeCoverPass->id,
'user_id' => $activeCoverPass->user_id,
'purchased_at' => $activeCoverPass->purchased_at->toIso8601String(),
'expires_at' => $activeCoverPass->expires_at->toIso8601String(),
'amount_paid' => (string) $activeCoverPass->amount_paid,
] : null,
], 200);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment