Manual billing SQL for QR Master ================================ Use these statements for manual plan changes in the database. Replace USER_EMAIL_HERE before running. Do not use a naked UPDATE like: UPDATE "User" SET "plan" = 'PRO' WHERE email = '...'; That changes the plan but creates no billing log. Always use the matching full transaction below so UserLifecycleLog gets the alert entry. Upgrade user to PRO =================== BEGIN; WITH old_user AS ( SELECT * FROM "User" WHERE email = 'USER_EMAIL_HERE' FOR UPDATE ), updated_user AS ( UPDATE "User" SET "plan" = 'PRO', "lifecycleStage" = 'paid', "lastScoredAt" = NOW(), "lastQualifiedAt" = NOW(), "updatedAt" = NOW() WHERE "id" = (SELECT "id" FROM old_user) RETURNING * ) INSERT INTO "UserLifecycleLog" ( "id", "userId", "fromStage", "toStage", "fitScore", "intentScore", "leadScore", "reason", "createdAt" ) SELECT CONCAT('manual_', MD5(updated_user."id" || clock_timestamp()::TEXT || random()::TEXT)), updated_user."id", old_user."lifecycleStage", 'paid', updated_user."fitScore", updated_user."intentScore", updated_user."leadScore", 'subscription_created', NOW() FROM updated_user JOIN old_user ON old_user."id" = updated_user."id"; COMMIT; Upgrade user to BUSINESS ======================== BEGIN; WITH old_user AS ( SELECT * FROM "User" WHERE email = 'USER_EMAIL_HERE' FOR UPDATE ), updated_user AS ( UPDATE "User" SET "plan" = 'BUSINESS', "lifecycleStage" = 'paid', "lastScoredAt" = NOW(), "lastQualifiedAt" = NOW(), "updatedAt" = NOW() WHERE "id" = (SELECT "id" FROM old_user) RETURNING * ) INSERT INTO "UserLifecycleLog" ( "id", "userId", "fromStage", "toStage", "fitScore", "intentScore", "leadScore", "reason", "createdAt" ) SELECT CONCAT('manual_', MD5(updated_user."id" || clock_timestamp()::TEXT || random()::TEXT)), updated_user."id", old_user."lifecycleStage", 'paid', updated_user."fitScore", updated_user."intentScore", updated_user."leadScore", 'subscription_created', NOW() FROM updated_user JOIN old_user ON old_user."id" = updated_user."id"; COMMIT; Set user to FREE after deabo / subscription ended ================================================= BEGIN; WITH old_user AS ( SELECT * FROM "User" WHERE email = 'USER_EMAIL_HERE' FOR UPDATE ), updated_user AS ( UPDATE "User" SET "plan" = 'FREE', "stripeSubscriptionId" = NULL, "stripePriceId" = NULL, "stripeCurrentPeriodEnd" = NULL, "lifecycleStage" = CASE WHEN COALESCE("leadScore", 0) >= 70 THEN 'upgrade_candidate' WHEN COALESCE("leadScore", 0) >= 55 THEN 'hot' WHEN COALESCE("leadScore", 0) >= 30 THEN 'warm' WHEN "activationAt" IS NOT NULL THEN 'activated' ELSE 'cold' END, "lastScoredAt" = NOW(), "updatedAt" = NOW() WHERE "id" = (SELECT "id" FROM old_user) RETURNING * ) INSERT INTO "UserLifecycleLog" ( "id", "userId", "fromStage", "toStage", "fitScore", "intentScore", "leadScore", "reason", "createdAt" ) SELECT CONCAT('manual_', MD5(updated_user."id" || clock_timestamp()::TEXT || random()::TEXT)), updated_user."id", old_user."lifecycleStage", updated_user."lifecycleStage", updated_user."fitScore", updated_user."intentScore", updated_user."leadScore", 'subscription_deleted', NOW() FROM updated_user JOIN old_user ON old_user."id" = updated_user."id"; COMMIT;