From 8e4d64d33bdd1428e9cd57083efad59a899995e9 Mon Sep 17 00:00:00 2001 From: Ashwin Kumar Sivakumar Date: Fri, 14 Aug 2026 01:38:59 +0530 Subject: [PATCH] fix(db): correct ai_credit_packages pricing (paise not rupees), add missing ai_credit_orders table Discovered while wiring up role-scoped AI credit packages: the active seed migration (20260615060600) stored price_inr as 99/349/999/2499 - values that read like whole rupees, but every consumer (PayU's paise_to_rupee_string, matching pricing_packages' existing paise convention) treats price_inr as paise, meaning a real purchase would have charged ~1% of the intended price. The actual fix already existed as 20260705110000_ai_credit_packages .up.sql.skip (correct paise values + the ai_credit_orders table create_order inserts into) but got disabled in the same bulk skip pass as everything else this session has been un-tangling - its CREATE TABLE ai_credit_packages (no IF NOT EXISTS) hit 'relation already exists' since 20260615060600 had already created it, aborting before ai_credit_orders was ever created. Net effect before this fix: buying AI credits was completely broken (create_order would 500 - no ai_credit_orders table to insert into). Confirmed zero rows anywhere in ai_credit_orders (the table simply didn't exist) - nothing to reconcile, this is a pre-launch fix. New migration (not resurrecting the old .skip file, to keep a clean linear history matching this session's other fixes): UPDATE the 4 known-wrong rows by name to their correct paise values, CREATE TABLE IF NOT EXISTS ai_credit_orders. Applied to both nxtgauge_test and prod; verified. Co-Authored-By: Claude Sonnet 5 --- ...60705110000_ai_credit_packages.up.sql.skip | 48 ------------------- ...redit_packages_pricing_and_orders.down.sql | 12 +++++ ..._credit_packages_pricing_and_orders.up.sql | 48 +++++++++++++++++++ 3 files changed, 60 insertions(+), 48 deletions(-) delete mode 100644 crates/db/migrations/20260705110000_ai_credit_packages.up.sql.skip create mode 100644 crates/db/migrations/20260814000000_fix_ai_credit_packages_pricing_and_orders.down.sql create mode 100644 crates/db/migrations/20260814000000_fix_ai_credit_packages_pricing_and_orders.up.sql diff --git a/crates/db/migrations/20260705110000_ai_credit_packages.up.sql.skip b/crates/db/migrations/20260705110000_ai_credit_packages.up.sql.skip deleted file mode 100644 index 9190d95..0000000 --- a/crates/db/migrations/20260705110000_ai_credit_packages.up.sql.skip +++ /dev/null @@ -1,48 +0,0 @@ --- AI credit purchase packages + PayU order tracking, matching the --- already-shipped frontend contract in --- nxtgauge-frontend-solid/src/components/dashboard/CreditsPage.tsx --- (GET /api/ai-credits, POST /api/ai-credits/order, POST /api/ai-credits/verify). --- price_inr is stored in paise (matches pricing_packages.price_inr's --- existing convention in this codebase), not rupees. - -BEGIN; - -CREATE TABLE ai_credit_packages ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - name VARCHAR(150) NOT NULL, - description TEXT, - credits INT NOT NULL, - price_inr INT NOT NULL, -- paise - is_active BOOLEAN NOT NULL DEFAULT TRUE, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE INDEX idx_ai_credit_packages_active ON ai_credit_packages(is_active, price_inr); - -INSERT INTO ai_credit_packages (name, description, credits, price_inr) VALUES -('Starter AI Credits', '50 AI credits for casual usage', 50, 9900), -('Pro AI Credits', '200 AI credits for power users', 200, 34900), -('Business AI Credits', '750 AI credits for teams', 750, 99900), -('Enterprise AI Credits', '2500 AI credits for heavy usage', 2500, 249900); - --- PayU order tracking for AI credit purchases, separate from the --- TraceCoins `payments` table (crates/db/migrations/20260317190300_portfolio_payments.up.sql) --- per the earlier decision to keep AI credits fully separate from TraceCoins. -CREATE TABLE ai_credit_orders ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL REFERENCES users(id), - package_id UUID NOT NULL REFERENCES ai_credit_packages(id), - txnid VARCHAR(100) UNIQUE NOT NULL, - payu_payment_id VARCHAR(100), - amount_inr INT NOT NULL, -- paise - credits INT NOT NULL, - status VARCHAR(20) NOT NULL DEFAULT 'PENDING', -- PENDING, SUCCESS, FAILED - verified_at TIMESTAMPTZ, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE INDEX idx_ai_credit_orders_user_id ON ai_credit_orders(user_id); -CREATE INDEX idx_ai_credit_orders_txnid ON ai_credit_orders(txnid); - -COMMIT; diff --git a/crates/db/migrations/20260814000000_fix_ai_credit_packages_pricing_and_orders.down.sql b/crates/db/migrations/20260814000000_fix_ai_credit_packages_pricing_and_orders.down.sql new file mode 100644 index 0000000..1eb4959 --- /dev/null +++ b/crates/db/migrations/20260814000000_fix_ai_credit_packages_pricing_and_orders.down.sql @@ -0,0 +1,12 @@ +BEGIN; + +DROP INDEX IF EXISTS idx_ai_credit_orders_txnid; +DROP INDEX IF EXISTS idx_ai_credit_orders_user_id; +DROP TABLE IF EXISTS ai_credit_orders; + +UPDATE ai_credit_packages SET price_inr = 99 WHERE name = 'Starter AI Credits' AND price_inr = 9900; +UPDATE ai_credit_packages SET price_inr = 349 WHERE name = 'Pro AI Credits' AND price_inr = 34900; +UPDATE ai_credit_packages SET price_inr = 999 WHERE name = 'Business AI Credits' AND price_inr = 99900; +UPDATE ai_credit_packages SET price_inr = 2499 WHERE name = 'Enterprise AI Credits' AND price_inr = 249900; + +COMMIT; diff --git a/crates/db/migrations/20260814000000_fix_ai_credit_packages_pricing_and_orders.up.sql b/crates/db/migrations/20260814000000_fix_ai_credit_packages_pricing_and_orders.up.sql new file mode 100644 index 0000000..e8cbc1c --- /dev/null +++ b/crates/db/migrations/20260814000000_fix_ai_credit_packages_pricing_and_orders.up.sql @@ -0,0 +1,48 @@ +-- Fixes a tangled pair of migrations discovered while wiring up role-scoped +-- AI credit packages: +-- +-- 20260615060600_ai_credit_packages.up.sql (active) created +-- ai_credit_packages and seeded it with price_inr = 99/349/999/2499 - +-- values that look like whole rupees, but every consumer of this column +-- (apps/payments/src/payu.rs's paise_to_rupee_string, and this repo's own +-- pricing_packages.price_inr convention) treats price_inr as PAISE. So a +-- "Starter AI Credits" purchase would have charged ₹0.99, not ₹99. +-- +-- 20260705110000_ai_credit_packages.up.sql.skip (disabled) was the +-- correction - right paise values (9900/34900/99900/249900) AND the +-- ai_credit_orders table apps/payments/src/ai_credits.rs's create_order +-- actually inserts into - but it re-issued CREATE TABLE ai_credit_packages +-- without IF NOT EXISTS, so it errored on "relation already exists" and +-- got skipped in the same bulk cleanup as everything else this session +-- has been un-tangling, taking ai_credit_orders down with it. +-- +-- Net effect fixed here: the purchase flow was 100% broken (create_order +-- would 500 immediately - no ai_credit_orders table to insert into) and, +-- had it worked, would have wildly underscharged. Confirmed zero rows in +-- ai_credit_orders anywhere (the table didn't exist), so there is no real +-- purchase history to reconcile - this is a pre-launch fix, not a refund. + +BEGIN; + +UPDATE ai_credit_packages SET price_inr = 9900 WHERE name = 'Starter AI Credits' AND price_inr = 99; +UPDATE ai_credit_packages SET price_inr = 34900 WHERE name = 'Pro AI Credits' AND price_inr = 349; +UPDATE ai_credit_packages SET price_inr = 99900 WHERE name = 'Business AI Credits' AND price_inr = 999; +UPDATE ai_credit_packages SET price_inr = 249900 WHERE name = 'Enterprise AI Credits' AND price_inr = 2499; + +CREATE TABLE IF NOT EXISTS ai_credit_orders ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id), + package_id UUID NOT NULL REFERENCES ai_credit_packages(id), + txnid VARCHAR(100) UNIQUE NOT NULL, + payu_payment_id VARCHAR(100), + amount_inr INT NOT NULL, -- paise + credits INT NOT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'PENDING', -- PENDING, SUCCESS, FAILED + verified_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_ai_credit_orders_user_id ON ai_credit_orders(user_id); +CREATE INDEX IF NOT EXISTS idx_ai_credit_orders_txnid ON ai_credit_orders(txnid); + +COMMIT;