This commit is contained in:
Timo Knuth
2026-07-06 00:38:26 +02:00
parent 91bd940edc
commit 192c186027

View File

@@ -1,37 +1,59 @@
Manual billing SQL for QR Master QR Master automatic billing alarm SQL
================================ =====================================
Use these statements for manual plan changes in the database. Run this SQL once in the database.
Replace USER_EMAIL_HERE before running.
Do not use a naked UPDATE like: After it is installed, every database update of "User"."plan" automatically
UPDATE "User" SET "plan" = 'PRO' WHERE email = '...'; creates a "UserLifecycleLog" entry.
That changes the plan but creates no billing log. Always use the matching full That means:
transaction below so UserLifecycleLog gets the alert entry. - FREE -> PRO or BUSINESS creates reason = subscription_created
- PRO/BUSINESS -> FREE creates reason = subscription_deleted
- PRO -> BUSINESS or BUSINESS -> PRO creates reason = subscription_updated
Upgrade user to PRO No customer email needs to be typed into this file.
=================== No manual log insert is needed after this is installed.
BEGIN;
WITH old_user AS ( CREATE OR REPLACE FUNCTION qrmaster_log_plan_change()
SELECT * RETURNS trigger AS $$
FROM "User" DECLARE
WHERE email = 'USER_EMAIL_HERE' next_stage TEXT;
FOR UPDATE log_reason TEXT;
), BEGIN
updated_user AS ( IF NEW."plan" IS NOT DISTINCT FROM OLD."plan" THEN
UPDATE "User" RETURN NEW;
SET END IF;
"plan" = 'PRO',
"lifecycleStage" = 'paid', IF NEW."plan"::TEXT IN ('PRO', 'BUSINESS') THEN
"lastScoredAt" = NOW(), next_stage := 'paid';
"lastQualifiedAt" = NOW(), ELSIF COALESCE(NEW."leadScore", 0) >= 70 THEN
"updatedAt" = NOW() next_stage := 'upgrade_candidate';
WHERE "id" = (SELECT "id" FROM old_user) ELSIF COALESCE(NEW."leadScore", 0) >= 55 THEN
RETURNING * next_stage := 'hot';
) ELSIF COALESCE(NEW."leadScore", 0) >= 30 THEN
next_stage := 'warm';
ELSIF NEW."activationAt" IS NOT NULL THEN
next_stage := 'activated';
ELSE
next_stage := 'cold';
END IF;
IF OLD."plan"::TEXT = 'FREE' AND NEW."plan"::TEXT IN ('PRO', 'BUSINESS') THEN
log_reason := 'subscription_created';
ELSIF OLD."plan"::TEXT IN ('PRO', 'BUSINESS') AND NEW."plan"::TEXT = 'FREE' THEN
log_reason := 'subscription_deleted';
ELSE
log_reason := 'subscription_updated';
END IF;
NEW."lifecycleStage" := next_stage;
NEW."lastScoredAt" := NOW();
IF next_stage IN ('paid', 'hot', 'upgrade_candidate') THEN
NEW."lastQualifiedAt" := NOW();
END IF;
INSERT INTO "UserLifecycleLog" ( INSERT INTO "UserLifecycleLog" (
"id", "id",
"userId", "userId",
@@ -43,121 +65,26 @@ INSERT INTO "UserLifecycleLog" (
"reason", "reason",
"createdAt" "createdAt"
) )
SELECT VALUES (
CONCAT('manual_', MD5(updated_user."id" || clock_timestamp()::TEXT || random()::TEXT)), CONCAT('planlog_', MD5(NEW."id" || clock_timestamp()::TEXT || random()::TEXT)),
updated_user."id", NEW."id",
old_user."lifecycleStage", OLD."lifecycleStage",
'paid', next_stage,
updated_user."fitScore", COALESCE(NEW."fitScore", 0),
updated_user."intentScore", COALESCE(NEW."intentScore", 0),
updated_user."leadScore", COALESCE(NEW."leadScore", 0),
'subscription_created', log_reason,
NOW() NOW()
FROM updated_user );
JOIN old_user ON old_user."id" = updated_user."id";
COMMIT; RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Upgrade user to BUSINESS
========================
BEGIN; DROP TRIGGER IF EXISTS qrmaster_user_plan_change_alarm ON "User";
WITH old_user AS ( CREATE TRIGGER qrmaster_user_plan_change_alarm
SELECT * BEFORE UPDATE OF "plan" ON "User"
FROM "User" FOR EACH ROW
WHERE email = 'USER_EMAIL_HERE' EXECUTE FUNCTION qrmaster_log_plan_change();
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;