121 lines
3.5 KiB
PL/PgSQL
121 lines
3.5 KiB
PL/PgSQL
-- ============================================================================
|
|
-- Mobile monetization hardening
|
|
-- Atomically revoke Google Play linked purchase tokens before applying a new
|
|
-- verified purchase. This prevents upgrade/downgrade and resubscribe chains
|
|
-- from retaining two entitlements.
|
|
-- ============================================================================
|
|
|
|
BEGIN;
|
|
|
|
CREATE OR REPLACE FUNCTION public.apply_verified_google_play_purchase(
|
|
p_user_id uuid,
|
|
p_platform text,
|
|
p_product_id text,
|
|
p_store_transaction_id text,
|
|
p_token_hash text,
|
|
p_linked_token_hash text,
|
|
p_purchase_token text,
|
|
p_purchase_state text,
|
|
p_purchase_at timestamptz,
|
|
p_expires_at timestamptz,
|
|
p_auto_renewing boolean,
|
|
p_acknowledged boolean,
|
|
p_tier text,
|
|
p_entitled boolean,
|
|
p_verification jsonb
|
|
) RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = public, pg_temp
|
|
AS $$
|
|
DECLARE
|
|
v_linked_purchase_id uuid;
|
|
v_linked_user_id uuid;
|
|
v_linked_revoked boolean := false;
|
|
v_result jsonb;
|
|
BEGIN
|
|
IF p_platform <> 'google_play' THEN
|
|
RAISE EXCEPTION 'invalid_platform';
|
|
END IF;
|
|
|
|
IF p_linked_token_hash IS NOT NULL THEN
|
|
IF p_linked_token_hash !~ '^[0-9a-f]{64}$' OR p_linked_token_hash = p_token_hash THEN
|
|
RAISE EXCEPTION 'invalid_linked_purchase_token';
|
|
END IF;
|
|
|
|
SELECT id, user_id
|
|
INTO v_linked_purchase_id, v_linked_user_id
|
|
FROM public.iap_purchases
|
|
WHERE platform = 'google_play'
|
|
AND token_hash = p_linked_token_hash
|
|
FOR UPDATE;
|
|
|
|
IF v_linked_user_id IS NOT NULL AND v_linked_user_id <> p_user_id THEN
|
|
RAISE EXCEPTION 'linked_purchase_owned_by_other_user';
|
|
END IF;
|
|
|
|
IF v_linked_purchase_id IS NOT NULL THEN
|
|
UPDATE public.iap_purchases
|
|
SET purchase_state = 'expired',
|
|
expires_at = least(coalesce(expires_at, now()), now()),
|
|
auto_renewing = false,
|
|
verified_at = now(),
|
|
updated_at = now()
|
|
WHERE id = v_linked_purchase_id;
|
|
|
|
UPDATE public.subscriptions
|
|
SET tier = 'free',
|
|
status = 'expired',
|
|
current_period_end = now(),
|
|
cancel_at = now(),
|
|
provider = 'none',
|
|
payment_provider = 'none',
|
|
store_product_id = NULL,
|
|
store_purchase_id = NULL,
|
|
auto_renewing = false,
|
|
updated_at = now()
|
|
WHERE user_id = p_user_id
|
|
AND store_purchase_id = v_linked_purchase_id;
|
|
|
|
IF FOUND THEN
|
|
UPDATE public.profiles
|
|
SET tier = 'free',
|
|
updated_at = now()
|
|
WHERE id = p_user_id;
|
|
END IF;
|
|
v_linked_revoked := true;
|
|
END IF;
|
|
END IF;
|
|
|
|
v_result := public.apply_verified_store_purchase(
|
|
p_user_id,
|
|
p_platform,
|
|
p_product_id,
|
|
p_store_transaction_id,
|
|
p_token_hash,
|
|
p_purchase_token,
|
|
p_purchase_state,
|
|
p_purchase_at,
|
|
p_expires_at,
|
|
p_auto_renewing,
|
|
p_acknowledged,
|
|
p_tier,
|
|
p_entitled,
|
|
p_verification
|
|
);
|
|
|
|
RETURN v_result || jsonb_build_object('linked_purchase_revoked', v_linked_revoked);
|
|
END;
|
|
$$;
|
|
|
|
REVOKE ALL ON FUNCTION public.apply_verified_google_play_purchase(
|
|
uuid, text, text, text, text, text, text, text, timestamptz, timestamptz,
|
|
boolean, boolean, text, boolean, jsonb
|
|
) FROM PUBLIC, anon, authenticated;
|
|
|
|
GRANT EXECUTE ON FUNCTION public.apply_verified_google_play_purchase(
|
|
uuid, text, text, text, text, text, text, text, timestamptz, timestamptz,
|
|
boolean, boolean, text, boolean, jsonb
|
|
) TO service_role;
|
|
|
|
COMMIT;
|