// Package billing is the Billing & Subscription bounded context: plans, // Stripe checkout/portal, webhook ingestion, and the per-plan daily // message quota. System of record for `subscriptions`, `stripe_events` // and `usage_daily` (see services/billing/migrations/0001_init.sql). // // Mirrors internal/handlers/billing.go + internal/billing/*.go + // internal/db/subscriptions.go. syntax = "proto3"; package enkly.billing.v1; option go_package = "enkly/gen/billing/v1;billingv1"; import "google/protobuf/timestamp.proto"; service BillingService { // GetOrCreateSubscription replaces db.GetOrCreateSubscription: lazily // inserts the default free-plan row the first time a user's // subscription is looked up. rpc GetOrCreateSubscription(GetOrCreateSubscriptionRequest) returns (Subscription); // GetPlanCatalog replaces billing.Catalog + billing.PlanPriceIDs for // rendering /subscription — returns every plan and whether its Stripe // price is currently configured (so an unconfigured plan renders as // "Unavailable", same as today). rpc GetPlanCatalog(GetPlanCatalogRequest) returns (GetPlanCatalogResponse); // CreateCheckoutSession replaces Handlers.CreateCheckout. rpc CreateCheckoutSession(CreateCheckoutSessionRequest) returns (CreateCheckoutSessionResponse); // SyncCheckoutSession replaces Handlers.syncCheckoutSession, the // fallback path SubscriptionSuccess uses when the webhook hasn't // landed yet. rpc SyncCheckoutSession(SyncCheckoutSessionRequest) returns (Subscription); // CreateBillingPortalSession replaces Handlers.BillingPortal. rpc CreateBillingPortalSession(CreateBillingPortalSessionRequest) returns (CreateBillingPortalSessionResponse); // IngestStripeWebhook replaces Handlers.StripeWebhook / // applyStripeEvent. The gateway still terminates the public // POST /webhooks/stripe route (so the URL Stripe is configured with // never changes) but forwards the raw body and signature header here // untouched — signature verification needs STRIPE_WEBHOOK_SECRET, // which only billing holds. rpc IngestStripeWebhook(IngestStripeWebhookRequest) returns (IngestStripeWebhookResponse); // CheckAndIncrementUsage replaces the daily-cap check + db. // IncrementDailyUsage call in Handlers.Chat. Called by the agent // service once per chat turn, after the message is accepted and // before the agent run starts (same ordering as the monolith). rpc CheckAndIncrementUsage(CheckAndIncrementUsageRequest) returns (CheckAndIncrementUsageResponse); // GetDailyUsage replaces db.DailyUsage — a read-only lookup (no // increment) used by the gateway's /subscription page to show // today's count without miscounting it as a sent message. rpc GetDailyUsage(GetDailyUsageRequest) returns (GetDailyUsageResponse); } enum Plan { PLAN_UNSPECIFIED = 0; PLAN_FREE = 1; PLAN_BASIC = 2; PLAN_PRO = 3; PLAN_PREMIUM = 4; } message PlanInfo { Plan plan = 1; string name = 2; int32 price_eur = 3; int32 daily_message_limit = 4; // 0 = unlimited bool available = 5; // Stripe price configured (always true for free) } message Subscription { int64 user_id = 1; Plan plan = 2; string status = 3; // Stripe's subscription status, or "active" for free google.protobuf.Timestamp current_period_end = 4; bool cancel_at_period_end = 5; // has_billing_account reports whether a Stripe customer exists for // this user (so the gateway knows whether to show "Manage billing"), // without exposing the actual customer ID. The raw // stripe_customer_id / stripe_subscription_id / stripe_price_id are // deliberately NOT exposed here, mirroring models.Subscription's // json:"-" — they never need to leave billing. bool has_billing_account = 6; } message GetOrCreateSubscriptionRequest { int64 user_id = 1; } message GetPlanCatalogRequest {} message GetPlanCatalogResponse { repeated PlanInfo plans = 1; string publishable_key = 2; // Stripe publishable key, for Checkout.js } message CreateCheckoutSessionRequest { int64 user_id = 1; string email = 2; Plan plan = 3; string success_url = 4; string cancel_url = 5; } message CreateCheckoutSessionResponse { string checkout_url = 1; } message SyncCheckoutSessionRequest { int64 user_id = 1; string stripe_session_id = 2; } message CreateBillingPortalSessionRequest { int64 user_id = 1; string return_url = 2; } message CreateBillingPortalSessionResponse { string portal_url = 1; } message IngestStripeWebhookRequest { bytes payload = 1; string signature_header = 2; } message IngestStripeWebhookResponse { bool accepted = 1; } message CheckAndIncrementUsageRequest { int64 user_id = 1; } message CheckAndIncrementUsageResponse { bool allowed = 1; int32 used = 2; int32 limit = 3; // 0 = unlimited string plan_name = 4; } message GetDailyUsageRequest { int64 user_id = 1; } message GetDailyUsageResponse { int32 used = 1; }