Description
In src/lib/achievements.ts, when a user unlocks an achievement that grants an item (
eward_type === "unlock_item"), the system inserts the reward into the purchases table using an upsert:
typescript await sb .from("purchases") .upsert(purchaseRows, { onConflict: "developer_id,item_id" });
The purchaseRows specify provider: "achievement" and �mount_cents: 0. If a user previously purchased this exact item legitimately through Stripe, they will already have a record in purchases with provider: "stripe", �mount_cents: >0, and their Stripe transaction ID.
Because of the onConflict: "developer_id,item_id" instruction, this upsert will destructively overwrite the original paid purchase record, permanently erasing the financial transaction audit trail for that item.
Recommendation
Change the upsert to an insert and catch the 23505 unique constraint violation, or check if the user already owns the item before attempting to insert the achievement reward.
Description
In src/lib/achievements.ts, when a user unlocks an achievement that grants an item (
eward_type === "unlock_item"), the system inserts the reward into the purchases table using an upsert:
typescript await sb .from("purchases") .upsert(purchaseRows, { onConflict: "developer_id,item_id" });The purchaseRows specify provider: "achievement" and �mount_cents: 0. If a user previously purchased this exact item legitimately through Stripe, they will already have a record in purchases with provider: "stripe", �mount_cents: >0, and their Stripe transaction ID.
Because of the onConflict: "developer_id,item_id" instruction, this upsert will destructively overwrite the original paid purchase record, permanently erasing the financial transaction audit trail for that item.
Recommendation
Change the upsert to an insert and catch the 23505 unique constraint violation, or check if the user already owns the item before attempting to insert the achievement reward.