ec51e87d2d
A generic multi-client payment gateway so FlatRender, meezi.ir and bargevasat.ir can all pay through ZarinPal's single verified callback domain (pay.flatrender.ir). New Go service services/payment (clones the notification skeleton + vendored deps): - migration 31_payment_broker.sql — `payment` schema: client_apps, transactions, webhook_deliveries. - ZarinPal v4 client ported from the proven identity PaymentService (request.json -> StartPay -> verify.json; codes 100/101). - client API: POST /v1/pay/request + /v1/pay/inquiry, authed by X-Api-Key + HMAC body signature; GET /callback/zarinpal (the single verified endpoint) verifies, then 302s the user back to the site's return_url (signed) and fires a signed, retried webhook. - per-client ZarinPal merchant override (default = shared merchant); amount stored canonically in Rial, unit to ZarinPal env-configurable. - admin API /v1/admin/* (FlatRender admin JWT): client-app CRUD + key issue/rotate + transactions list. Deploy wiring: payment-svc in docker-compose.v2.yml (host port 1607), pay.flatrender.ir server block in mirror-nginx conf, ENV_FILE + README updates (cert SAN + manual migration note). Admin UI: src/components/admin/PaymentsAdmin.tsx (client apps with one-time key reveal + rotate, transactions table) + /admin/payments page + nav link + fa/en strings; pay-admin proxy route to payment-svc. Docs/SDK: deploy/PAYMENTS.md (integration contract) + deploy/sdk/flatpay.js (zero-dep Node client + webhook verifier) for meezi/any site. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
64 lines
2.6 KiB
Go
64 lines
2.6 KiB
Go
// Package web serves the broker's minimal hosted pages (RTL Persian). Clients
|
|
// normally redirect users back to their OWN return_url; these pages are a
|
|
// branded landing + a fallback result screen for direct visits.
|
|
package web
|
|
|
|
import (
|
|
"fmt"
|
|
"html"
|
|
"net/http"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
const shell = `<!doctype html><html lang="fa" dir="rtl"><head><meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>%s</title>
|
|
<style>
|
|
:root{color-scheme:dark}
|
|
body{margin:0;font-family:Tahoma,Vazirmatn,system-ui,sans-serif;background:#0b0d17;color:#e7e9f3;
|
|
display:flex;min-height:100vh;align-items:center;justify-content:center;text-align:center}
|
|
.card{background:#141726;border:1px solid #232742;border-radius:20px;padding:40px 32px;max-width:420px;width:90%%}
|
|
.logo{font-weight:800;font-size:22px;letter-spacing:.5px;color:#7c8cff}
|
|
h1{font-size:20px;margin:18px 0 8px}
|
|
p{color:#9aa0bd;line-height:1.9;font-size:14px;margin:6px 0}
|
|
.badge{display:inline-block;padding:6px 16px;border-radius:999px;font-size:13px;margin-top:14px}
|
|
.ok{background:rgba(34,197,94,.15);color:#4ade80}
|
|
.fail{background:rgba(239,68,68,.15);color:#f87171}
|
|
.muted{font-size:12px;color:#6a708f;margin-top:18px}
|
|
</style></head><body><div class="card">%s</div></body></html>`
|
|
|
|
func page(title, inner string) string {
|
|
return "<!--FlatPay-->" + fmt.Sprintf(shell, html.EscapeString(title), inner)
|
|
}
|
|
|
|
func Landing(c *gin.Context) {
|
|
inner := `<div class="logo">FlatRender Pay</div>
|
|
<h1>درگاه پرداخت امن</h1>
|
|
<p>این سرویس پرداختهای آنلاین را از طریق زرینپال پردازش میکند.</p>
|
|
<p class="muted">برای پرداخت، از طریق وبسایت مربوطه اقدام کنید.</p>`
|
|
c.Header("Content-Type", "text/html; charset=utf-8")
|
|
c.String(http.StatusOK, page("FlatRender Pay", inner))
|
|
}
|
|
|
|
// Result is an optional fallback screen a client may point its return_url at.
|
|
func Result(c *gin.Context) {
|
|
status := c.Query("status")
|
|
ref := c.Query("ref_id")
|
|
var inner string
|
|
if status == "Paid" {
|
|
inner = `<div class="logo">FlatRender Pay</div>
|
|
<h1>پرداخت موفق</h1>
|
|
<span class="badge ok">پرداخت با موفقیت انجام شد</span>`
|
|
if ref != "" {
|
|
inner += `<p class="muted">کد پیگیری: ` + html.EscapeString(ref) + `</p>`
|
|
}
|
|
} else {
|
|
inner = `<div class="logo">FlatRender Pay</div>
|
|
<h1>پرداخت ناموفق</h1>
|
|
<span class="badge fail">پرداخت انجام نشد یا لغو شد</span>`
|
|
}
|
|
c.Header("Content-Type", "text/html; charset=utf-8")
|
|
c.String(http.StatusOK, page("نتیجه پرداخت", inner))
|
|
}
|