Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
-- AlterEnum
ALTER TYPE "PayoutStatus" ADD VALUE IF NOT EXISTS 'REQUIRES_CONFIRMATION';

-- CreateEnum
CREATE TYPE "RecurringPayoutFrequency" AS ENUM ('DAILY', 'WEEKLY', 'BI_WEEKLY', 'MONTHLY');

-- CreateEnum
CREATE TYPE "MerchantLedgerEntryType" AS ENUM ('CREDIT', 'PAYOUT_DEBIT', 'PAYOUT_RELEASE');

-- AlterTable
ALTER TABLE "Payout"
ADD COLUMN "confirmedAt" TIMESTAMP(3),
ADD COLUMN "recurringPayoutId" TEXT;

-- CreateTable
CREATE TABLE "RecurringPayout" (
"id" TEXT NOT NULL,
"merchantId" TEXT NOT NULL,
"recipientId" TEXT,
"recipientName" TEXT NOT NULL,
"destinationType" "DestType" NOT NULL,
"destination" JSONB NOT NULL,
"amount" DECIMAL(36,18) NOT NULL,
"currency" TEXT NOT NULL,
"frequency" "RecurringPayoutFrequency" NOT NULL,
"nextRunAt" TIMESTAMP(3) NOT NULL,
"lastRunAt" TIMESTAMP(3),
"active" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "RecurringPayout_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "MerchantBalance" (
"merchantId" TEXT NOT NULL,
"availableAmount" DECIMAL(36,18) NOT NULL DEFAULT 0,
"reservedAmount" DECIMAL(36,18) NOT NULL DEFAULT 0,
"currency" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "MerchantBalance_pkey" PRIMARY KEY ("merchantId", "currency")
);

-- CreateTable
CREATE TABLE "MerchantLedgerEntry" (
"id" TEXT NOT NULL,
"merchantId" TEXT NOT NULL,
"payoutId" TEXT,
"paymentId" TEXT,
"type" "MerchantLedgerEntryType" NOT NULL,
"amount" DECIMAL(36,18) NOT NULL,
"currency" TEXT NOT NULL,
"description" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "MerchantLedgerEntry_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE INDEX "Payout_scheduledAt_idx" ON "Payout"("scheduledAt");

-- CreateIndex
CREATE INDEX "Payout_recurringPayoutId_idx" ON "Payout"("recurringPayoutId");

-- CreateIndex
CREATE INDEX "RecurringPayout_merchantId_idx" ON "RecurringPayout"("merchantId");

-- CreateIndex
CREATE INDEX "RecurringPayout_active_nextRunAt_idx" ON "RecurringPayout"("active", "nextRunAt");

-- CreateIndex
CREATE INDEX "RecurringPayout_recipientId_idx" ON "RecurringPayout"("recipientId");

-- CreateIndex
CREATE UNIQUE INDEX "MerchantLedgerEntry_paymentId_type_key" ON "MerchantLedgerEntry"("paymentId", "type");

-- CreateIndex
CREATE INDEX "MerchantLedgerEntry_merchantId_createdAt_idx" ON "MerchantLedgerEntry"("merchantId", "createdAt");

-- CreateIndex
CREATE INDEX "MerchantLedgerEntry_payoutId_idx" ON "MerchantLedgerEntry"("payoutId");

-- CreateIndex
CREATE INDEX "MerchantLedgerEntry_paymentId_idx" ON "MerchantLedgerEntry"("paymentId");

-- AddForeignKey
ALTER TABLE "Payout" ADD CONSTRAINT "Payout_recurringPayoutId_fkey" FOREIGN KEY ("recurringPayoutId") REFERENCES "RecurringPayout"("id") ON DELETE SET NULL ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "RecurringPayout" ADD CONSTRAINT "RecurringPayout_merchantId_fkey" FOREIGN KEY ("merchantId") REFERENCES "Merchant"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "RecurringPayout" ADD CONSTRAINT "RecurringPayout_recipientId_fkey" FOREIGN KEY ("recipientId") REFERENCES "Recipient"("id") ON DELETE SET NULL ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "MerchantBalance" ADD CONSTRAINT "MerchantBalance_merchantId_fkey" FOREIGN KEY ("merchantId") REFERENCES "Merchant"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "MerchantLedgerEntry" ADD CONSTRAINT "MerchantLedgerEntry_merchantId_fkey" FOREIGN KEY ("merchantId") REFERENCES "Merchant"("id") ON DELETE CASCADE ON UPDATE CASCADE;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "Payout"
ADD COLUMN "confirmationCodeHash" TEXT,
ADD COLUMN "confirmationCodeExpiresAt" TIMESTAMP(3),
ADD COLUMN "confirmationAttempts" INTEGER NOT NULL DEFAULT 0;
83 changes: 83 additions & 0 deletions apps/api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ model Merchant {
notifications Notification[]
settlementKey MerchantSettlementKey?
recipients Recipient[]
recurringPayouts RecurringPayout[]
balances MerchantBalance[]
ledgerEntries MerchantLedgerEntry[]
}

model ApiKey {
Expand Down Expand Up @@ -302,20 +305,85 @@ model Payout {
status PayoutStatus @default(PENDING)
stellarTxHash String?
scheduledAt DateTime?
confirmedAt DateTime?
confirmationCodeHash String?
confirmationCodeExpiresAt DateTime?
confirmationAttempts Int @default(0)
completedAt DateTime?
failureReason String?
batchId String?
idempotencyKey String? @unique
recipientId String?
recurringPayoutId String?
createdAt DateTime @default(now())
merchant Merchant @relation(fields: [merchantId], references: [id])
recipient Recipient? @relation(fields: [recipientId], references: [id])
recurringPayout RecurringPayout? @relation(fields: [recurringPayoutId], references: [id], onDelete: SetNull)

@@index([merchantId])
@@index([status])
@@index([scheduledAt])
@@index([batchId])
@@index([createdAt])
@@index([recipientId])
@@index([recurringPayoutId])
}

model RecurringPayout {
id String @id @default(cuid())
merchantId String
recipientId String?
recipientName String
destinationType DestType
destination Json
amount Decimal @db.Decimal(36, 18)
currency String
frequency RecurringPayoutFrequency
nextRunAt DateTime
lastRunAt DateTime?
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
merchant Merchant @relation(fields: [merchantId], references: [id], onDelete: Cascade)
recipient Recipient? @relation(fields: [recipientId], references: [id], onDelete: SetNull)
payouts Payout[]

@@index([merchantId])
@@index([active, nextRunAt])
@@index([recipientId])
}

// One row per merchant per currency — balances in different currencies must
// never be summed into a single amount.
model MerchantBalance {
merchantId String
availableAmount Decimal @default(0) @db.Decimal(36, 18)
reservedAmount Decimal @default(0) @db.Decimal(36, 18)
currency String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
merchant Merchant @relation(fields: [merchantId], references: [id], onDelete: Cascade)

@@id([merchantId, currency])
}

model MerchantLedgerEntry {
id String @id @default(cuid())
merchantId String
payoutId String?
paymentId String?
type MerchantLedgerEntryType
amount Decimal @db.Decimal(36, 18)
currency String
description String?
createdAt DateTime @default(now())
merchant Merchant @relation(fields: [merchantId], references: [id], onDelete: Cascade)

// A payment may be credited at most once, even under concurrent webhooks.
@@unique([paymentId, type])
@@index([merchantId, createdAt])
@@index([payoutId])
@@index([paymentId])
}

// Saved payout destination. Reconstructed to match migration
Expand All @@ -331,6 +399,7 @@ model Recipient {
updatedAt DateTime @updatedAt
merchant Merchant @relation(fields: [merchantId], references: [id], onDelete: Cascade)
payouts Payout[]
recurringPayouts RecurringPayout[]

@@unique([merchantId, name])
@@index([merchantId])
Expand All @@ -346,12 +415,26 @@ enum DestType {

enum PayoutStatus {
PENDING
REQUIRES_CONFIRMATION
PROCESSING
COMPLETED
FAILED
CANCELLED
}

enum RecurringPayoutFrequency {
DAILY
WEEKLY
BI_WEEKLY
MONTHLY
}

enum MerchantLedgerEntryType {
CREDIT
PAYOUT_DEBIT
PAYOUT_RELEASE
}

// ── Webhooks ──────────────────────────────────────────────────

model WebhookEvent {
Expand Down
Loading
Loading