active() ->whereNotNull('stripe_price_id') ->orderBy('sort_order') ->orderBy('amount') ->get(); return CreditProductResource::collection($products); } public function checkout( Request $request, CreditProduct $creditProduct, PaymentTransactionRecorder $transactions, ): JsonResponse { abort_unless($creditProduct->is_active, 404); abort_unless(filled($creditProduct->stripe_price_id), 404); $user = $request->user(); $this->ensureStripeCustomer($user); $metadata = [ 'credit_product_id' => $creditProduct->getKey(), 'credit_product_type' => $creditProduct->type->value, 'credits' => (string) $creditProduct->credits, ]; $sessionOptions = [ 'success_url' => $this->redirectUrl('success'), 'cancel_url' => $this->redirectUrl('cancel'), 'metadata' => $metadata, 'client_reference_id' => $creditProduct->getKey(), ]; if ($creditProduct->type === CreditProductType::MONTHLY) { if ($user->subscribed('default')) { return response()->json([ 'message' => __('api.billing.active_subscription_exists'), ], 409); } $checkout = $user ->newSubscription('default', $creditProduct->stripe_price_id) ->withMetadata($metadata) ->checkout($sessionOptions); } else { $sessionOptions['invoice_creation'] = [ 'enabled' => true, 'invoice_data' => [ 'metadata' => $metadata, ], ]; $checkout = $user->checkout([ $creditProduct->stripe_price_id => 1, ], $sessionOptions); } $session = $checkout->asStripeCheckoutSession(); $transactions->recordCheckoutSession( user: $user, product: $creditProduct, 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]); } }