62ea110605
Lets the broker's ZarinPal merchant / sandbox / amount-unit be set from Admin → درگاه پرداخت (persisted in payment.settings) instead of env + redeploy, and adds a per-app "test payment" button that mints a real ZarinPal StartPay link straight from the panel — no site wiring needed. - migration 33_payment_settings.sql: singleton payment.settings + a transactions.is_test column. (33, not 32 — 32 is content_render_engine.) - broker read-path precedence: per-client override > DB settings > env. - POST /v1/admin/clients/:id/test-payment + GET/PUT /v1/admin/settings. - admin UI: «تنظیمات زرینپال» tab + «پرداخت آزمایشی» button. Adversarial-review fixes (2 confirmed HIGH): - do NOT pre-seed the settings row — a seeded sandbox=TRUE default would override a production ZARINPAL_SANDBOX=false env and silently route real payments to sandbox.zarinpal.com until an admin untouched the toggle. No row → env governs until an admin saves. - test transactions are tagged is_test and the webhook dispatcher skips them, so an admin smoke-test can never notify (or credit) a real client, regardless of metadata. Broker-authoritative, not consumer-dependent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
36 lines
1.9 KiB
SQL
36 lines
1.9 KiB
SQL
-- =====================================================================
|
|
-- PAYMENT BROKER — global settings (admin-editable ZarinPal config) + is_test
|
|
-- Lets the merchant id / sandbox flag / amount unit be set from the admin
|
|
-- panel instead of env + redeploy. A client_app may still override per-site.
|
|
-- Also adds transactions.is_test so admin smoke-test payments never fire a
|
|
-- client's production webhook.
|
|
--
|
|
-- Apply manually on an existing volume (runs after 31_payment_broker.sql):
|
|
-- docker exec -i fr2-postgres psql -U flatrender -d flatrender < 33_payment_settings.sql
|
|
-- =====================================================================
|
|
|
|
CREATE SCHEMA IF NOT EXISTS payment;
|
|
SET search_path TO payment, public;
|
|
|
|
CREATE TABLE IF NOT EXISTS payment.settings (
|
|
id SMALLINT PRIMARY KEY DEFAULT 1 CHECK (id = 1), -- singleton row
|
|
zarinpal_merchant_id TEXT NOT NULL DEFAULT '',
|
|
zarinpal_sandbox BOOLEAN NOT NULL DEFAULT TRUE,
|
|
zarinpal_amount_unit TEXT NOT NULL DEFAULT 'rial', -- 'rial' | 'toman'
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- NOTE: the singleton row is intentionally NOT pre-seeded. Until an admin saves
|
|
-- settings, GetSettings returns no-row and the broker falls back to ENV
|
|
-- (ZARINPAL_MERCHANT_ID / ZARINPAL_SANDBOX / ZARINPAL_AMOUNT_UNIT). Seeding a
|
|
-- default row here would force sandbox=TRUE and silently override a production
|
|
-- env (ZARINPAL_SANDBOX=false), routing real payments to the sandbox gateway.
|
|
|
|
DROP TRIGGER IF EXISTS tg_pay_settings_updated ON payment.settings;
|
|
CREATE TRIGGER tg_pay_settings_updated BEFORE UPDATE ON payment.settings
|
|
FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at();
|
|
|
|
-- Mark admin smoke-test transactions so the webhook dispatcher never notifies a
|
|
-- real client (which could otherwise credit coins/activate a plan from a test).
|
|
ALTER TABLE payment.transactions ADD COLUMN IF NOT EXISTS is_test BOOLEAN NOT NULL DEFAULT FALSE;
|