| 1 | # frozen_string_literal: true |
| 2 | |
| 3 | module Api |
| 4 | module V1 |
| 5 | # siGit Code billing for the authenticated user. The desktop app / CLI call |
| 6 | # these with the smbCloud bearer token; checkout/portal return a URL the |
| 7 | # client opens in a browser (purchases happen on the web, not in-app, to |
| 8 | # avoid the App Store cut). |
| 9 | class BillingController < Api::BaseController |
| 10 | before_action :authenticate_token! |
| 11 | |
| 12 | # GET /api/v1/billing — current plan + cloud entitlement. |
| 13 | def show |
| 14 | subscription = current_user.subscription |
| 15 | render json: { |
| 16 | plan: subscription&.plan || "free", |
| 17 | status: subscription&.status || "active", |
| 18 | entitled_to_cloud: current_user.entitled_to_cloud?, |
| 19 | cloud_requests_used: current_user.cloud_requests_used, |
| 20 | cloud_allowance: current_user.cloud_allowance, |
| 21 | current_period_end: subscription&.current_period_end |
| 22 | }, status: :ok |
| 23 | end |
| 24 | |
| 25 | # POST /api/v1/billing/checkout { plan: "pro" | "team" } → { url } |
| 26 | def checkout |
| 27 | return render_billing_unavailable unless StripeService.configured? |
| 28 | |
| 29 | plan = params[:plan].presence || "pro" |
| 30 | unless StripeService::PRICE_LOOKUP_KEYS.key?(plan) |
| 31 | return render_error(ERR_INVALID, "Unknown plan.", status: :unprocessable_entity) |
| 32 | end |
| 33 | |
| 34 | url = StripeService.checkout_url( |
| 35 | user: current_user, |
| 36 | plan: plan, |
| 37 | success_url: "#{request.base_url}/billing?billing=success", |
| 38 | cancel_url: "#{request.base_url}/billing?billing=cancel" |
| 39 | ) |
| 40 | render json: { url: url }, status: :ok |
| 41 | end |
| 42 | |
| 43 | # POST /api/v1/billing/portal → { url } |
| 44 | def portal |
| 45 | return render_billing_unavailable unless StripeService.configured? |
| 46 | |
| 47 | url = StripeService.portal_url( |
| 48 | user: current_user, |
| 49 | return_url: "#{request.base_url}/billing" |
| 50 | ) |
| 51 | return render_error(ERR_INVALID, "No subscription to manage.", status: :not_found) unless url |
| 52 | |
| 53 | render json: { url: url }, status: :ok |
| 54 | end |
| 55 | |
| 56 | private |
| 57 | |
| 58 | def render_billing_unavailable |
| 59 | render_error(ERR_UNKNOWN, "Billing is not configured.", status: :service_unavailable) |
| 60 | end |
| 61 | end |
| 62 | end |
| 63 | end |