feat(payments): route coin purchases through FlatRender Pay broker
ZarinPal only accepts callbacks on pay.flatrender.ir, so bargevasat pays through the shared broker and is credited via a signed webhook. - FlatPayService: broker client (HMAC-signed /v1/pay/request) + webhook signature verification + in-memory idempotency guard. - Program.cs: /api/coins/pay/request prefers the broker when configured (FlatPay__ApiKey/Secret set), else the legacy direct ZarinPal path; new public POST /api/coins/pay/webhook verifies the HMAC and credits coins from the echoed metadata (idempotent). - appsettings + docker-compose: FlatPay config (empty ⇒ legacy path). - web: recognise the broker's ?status=Paid return + re-refresh profile (coins are credited server-side via webhook). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+11
-5
@@ -82,22 +82,28 @@ export default function Page() {
|
||||
useEffect(() => {
|
||||
init();
|
||||
|
||||
// ZarinPal payment return (?pay=success&coins= / ?pay=failed)
|
||||
// Payment return — legacy direct ZarinPal (?pay=success&coins= / ?pay=failed)
|
||||
// OR the FlatRender Pay broker (?pay=done&status=Paid|Failed&id=…&sign=…).
|
||||
// With the broker, coins are credited server-side via webhook; we just refresh.
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const pay = params.get("pay");
|
||||
if (pay) {
|
||||
if (pay === "success") {
|
||||
const brokerStatus = params.get("status"); // Paid | Failed | Cancelled | Expired
|
||||
if (pay || brokerStatus) {
|
||||
const ok = pay === "success" || brokerStatus === "Paid";
|
||||
if (ok) {
|
||||
const coins = params.get("coins");
|
||||
pushNotification({
|
||||
kind: "system",
|
||||
titleFa: "پرداخت موفق",
|
||||
titleEn: "Payment successful",
|
||||
bodyFa: coins ? `${coins} سکه به حساب شما اضافه شد` : undefined,
|
||||
bodyEn: coins ? `${coins} coins added` : undefined,
|
||||
bodyFa: coins ? `${coins} سکه به حساب شما اضافه شد` : "سکهها بهزودی به حساب شما اضافه میشوند",
|
||||
bodyEn: coins ? `${coins} coins added` : "Your coins will be credited shortly",
|
||||
icon: "💰",
|
||||
route: "shop",
|
||||
});
|
||||
useSessionStore.getState().refreshProfile();
|
||||
// Re-refresh shortly after, in case the webhook lands a moment later.
|
||||
setTimeout(() => useSessionStore.getState().refreshProfile(), 4000);
|
||||
} else {
|
||||
pushNotification({ kind: "system", titleFa: "پرداخت ناموفق بود", titleEn: "Payment failed", icon: "⚠️" });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user