active() ->whereNotNull('purpose') ->whereNotNull('stripe_price_id') ->orderBy('sort_order') ->orderBy('amount') ->get(); return BillingProductResource::collection($products); } public function checkout( Request $request, BillingProduct $billingProduct, PaymentTransactionRecorder $transactions, ): JsonResponse { abort_unless($billingProduct->is_active, 404); abort_unless($billingProduct->purpose !== null, 404); abort_unless(filled($billingProduct->stripe_price_id), 404); $user = $request->user(); $this->ensureStripeCustomer($user); $metadata = [ 'billing_product_id' => $billingProduct->getKey(), 'billing_product_purpose' => $billingProduct->purpose->value, ]; $sessionOptions = [ 'success_url' => $this->redirectUrl('success'), 'cancel_url' => $this->redirectUrl('cancel'), 'metadata' => $metadata, 'client_reference_id' => $billingProduct->getKey(), ]; if ($billingProduct->purpose === BillingProductPurpose::SUBSCRIPTION) { if ($user->subscribed('default')) { return response()->json([ 'message' => __('api.billing.active_subscription_exists'), ], 409); } $checkout = $user ->newSubscription('default', $billingProduct->stripe_price_id) ->withMetadata($metadata) ->checkout($sessionOptions); } else { if ($user->hasPurchasedCertification() || $user->account_verified_at !== null) { return response()->json([ 'message' => __('api.billing.certification_already_purchased'), ], 409); } $sessionOptions['invoice_creation'] = [ 'enabled' => true, 'invoice_data' => [ 'metadata' => $metadata, ], ]; $checkout = $user->checkout([ $billingProduct->stripe_price_id => 1, ], $sessionOptions); } $session = $checkout->asStripeCheckoutSession(); $transactions->recordCheckoutSession( user: $user, product: $billingProduct, stripeCheckoutSessionId: $session->id, stripeCustomerId: $session->customer, amount: $session->amount_total, currency: $session->currency, ); return response()->json([ 'id' => $session->id, 'url' => $session->url, ]); } public function portal(Request $request): JsonResponse { $user = $request->user(); $this->ensureStripeCustomer($user); if (! $user->subscribed('default')) { return response()->json([ 'message' => __('api.billing.no_active_subscription'), ], 409); } $portalUrl = $user->billingPortalUrl($this->redirectUrl('portal')); return response()->json([ 'url' => $portalUrl, ]); } public function redirectToMobileApp(string $status): RedirectResponse { abort_unless(in_array($status, ['success', 'cancel', 'portal'], true), 404); return redirect()->away(MobileDeepLink::to("billing/{$status}")); } private function ensureStripeCustomer(mixed $user): void { if (! is_object($user) || ! method_exists($user, 'createOrGetStripeCustomer')) { return; } $user->createOrGetStripeCustomer(); } private function redirectUrl(string $status): string { return route('billing.web-return', ['status' => $status]); } }