feat(billing): implement billing module with Stripe and Mercado Pago integration
- Add Stripe and Mercado Pago providers for handling subscriptions. - Create billing repository and access services for managing billing records. - Implement webhook handlers for processing events from Stripe and Mercado Pago. - Add routes for checkout, canceling subscriptions, and retrieving billing status. - Introduce billing status management with appropriate state transitions. - Create tests for billing functionalities including webhook idempotency and provider resolution. - Document the billing module architecture, environment variables, and API endpoints.
This commit is contained in:
52
apps/backend/prisma/billing.prisma
Normal file
52
apps/backend/prisma/billing.prisma
Normal file
@@ -0,0 +1,52 @@
|
||||
enum BillingProvider {
|
||||
STRIPE
|
||||
MERCADOPAGO
|
||||
PAYPAL
|
||||
}
|
||||
|
||||
enum BillingStatus {
|
||||
TRIAL
|
||||
ACTIVE
|
||||
PAST_DUE
|
||||
CANCELED
|
||||
SUSPENDED
|
||||
}
|
||||
|
||||
model ComplexBilling {
|
||||
complexId String @id @map("complex_id") @db.Uuid
|
||||
status BillingStatus @default(TRIAL)
|
||||
planCode String @map("plan_code") @db.VarChar(10)
|
||||
currency String @db.VarChar(3)
|
||||
provider BillingProvider?
|
||||
providerCustomerId String? @map("provider_customer_id")
|
||||
providerSubscriptionId String? @map("provider_subscription_id")
|
||||
providerPreapprovalId String? @map("provider_preapproval_id")
|
||||
currentPeriodStart DateTime? @map("current_period_start")
|
||||
currentPeriodEnd DateTime? @map("current_period_end")
|
||||
trialEndsAt DateTime? @map("trial_ends_at")
|
||||
canceledAt DateTime? @map("canceled_at")
|
||||
suspendedAt DateTime? @map("suspended_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
complex Complex @relation(fields: [complexId], references: [id], onDelete: Cascade)
|
||||
events BillingEvent[]
|
||||
|
||||
@@map("complex_billings")
|
||||
}
|
||||
|
||||
model BillingEvent {
|
||||
id String @id @db.Uuid
|
||||
complexId String @map("complex_id") @db.Uuid
|
||||
eventType String @map("event_type") @db.VarChar(100)
|
||||
provider BillingProvider
|
||||
providerEventId String? @unique @map("provider_event_id")
|
||||
providerData Json? @map("provider_data")
|
||||
previousStatus BillingStatus? @map("previous_status")
|
||||
newStatus BillingStatus? @map("new_status")
|
||||
processedAt DateTime @default(now()) @map("processed_at")
|
||||
billing ComplexBilling @relation(fields: [complexId], references: [complexId])
|
||||
|
||||
@@index([provider, providerEventId])
|
||||
@@index([complexId])
|
||||
@@map("billing_events")
|
||||
}
|
||||
@@ -15,6 +15,7 @@ model Complex {
|
||||
invitations ComplexInvitation[]
|
||||
courts Court[]
|
||||
recurringGroups RecurringBookingGroup[]
|
||||
billing ComplexBilling?
|
||||
|
||||
@@index([planCode])
|
||||
@@index([complexSlug])
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "BillingProvider" AS ENUM ('STRIPE', 'MERCADOPAGO', 'PAYPAL');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "BillingStatus" AS ENUM ('TRIAL', 'ACTIVE', 'PAST_DUE', 'CANCELED', 'SUSPENDED');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "complex_billings" (
|
||||
"complex_id" UUID NOT NULL,
|
||||
"status" "BillingStatus" NOT NULL DEFAULT 'TRIAL',
|
||||
"plan_code" VARCHAR(10) NOT NULL,
|
||||
"currency" VARCHAR(3) NOT NULL,
|
||||
"provider" "BillingProvider",
|
||||
"provider_customer_id" TEXT,
|
||||
"provider_subscription_id" TEXT,
|
||||
"provider_preapproval_id" TEXT,
|
||||
"current_period_start" TIMESTAMP(3),
|
||||
"current_period_end" TIMESTAMP(3),
|
||||
"trial_ends_at" TIMESTAMP(3),
|
||||
"canceled_at" TIMESTAMP(3),
|
||||
"suspended_at" TIMESTAMP(3),
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "complex_billings_pkey" PRIMARY KEY ("complex_id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "billing_events" (
|
||||
"id" UUID NOT NULL,
|
||||
"complex_id" UUID NOT NULL,
|
||||
"event_type" VARCHAR(100) NOT NULL,
|
||||
"provider" "BillingProvider" NOT NULL,
|
||||
"provider_event_id" TEXT,
|
||||
"provider_data" JSONB,
|
||||
"previous_status" "BillingStatus",
|
||||
"new_status" "BillingStatus",
|
||||
"processed_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_events_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "billing_events_provider_event_id_key" ON "billing_events"("provider_event_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "billing_events_provider_provider_event_id_idx" ON "billing_events"("provider", "provider_event_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "billing_events_complex_id_idx" ON "billing_events"("complex_id");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "complex_billings" ADD CONSTRAINT "complex_billings_complex_id_fkey" FOREIGN KEY ("complex_id") REFERENCES "complexes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "billing_events" ADD CONSTRAINT "billing_events_complex_id_fkey" FOREIGN KEY ("complex_id") REFERENCES "complex_billings"("complex_id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
Reference in New Issue
Block a user