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 <noreply@anthropic.com>
This commit is contained in:
Ashwin Kumar Sivakumar 2026-08-14 01:38:59 +05:30
parent eefc0457be
commit 8e4d64d33b
3 changed files with 60 additions and 48 deletions

View file

@ -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;

View file

@ -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;

View file

@ -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;