feat(payment): route FlatRender plan purchases through the broker
CI/CD / CI · Web (tsc) (push) Successful in 1m10s
CI/CD / Deploy · full stack (push) Failing after 11m4s

- identity: when FlatPay (broker) is configured, InitiateZarinPalAsync
  routes through pay.flatrender.ir instead of calling ZarinPal directly;
  new HandleBrokerCallbackAsync confirms the payment via the broker
  inquiry API (authoritative, not trusting the redirect) and activates
  the plan. New public endpoint GET /v1/payments/callback/broker
  (already public at the gateway via /callback/*). Env-gated — empty
  FlatPay__ApiKey keeps the legacy direct-ZarinPal path.
- broker: deliver webhooks inline on enqueue (best-effort) in addition
  to the retry loop, so clients credit near-instantly (db.GetWebhook +
  goroutine kick).
- compose + ENV_FILE: FlatPay__* for identity (FLATPAY_FLATRENDER_*).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-16 00:34:45 +03:30
parent ec51e87d2d
commit 376cdf6a1c
7 changed files with 187 additions and 2 deletions
@@ -65,6 +65,24 @@ public class PaymentsController(IPaymentService paymentService) : ControllerBase
return Redirect(frontendUrl);
}
// ── FlatRender Pay broker flow ────────────────────────────────────────────────
/// <summary>
/// GET /v1/payments/callback/broker?payment_id={p}&amp;id={brokerTxn}&amp;status=&amp;sign=
/// The broker redirects the user's browser here after payment. We confirm the
/// transaction authoritatively via the broker inquiry API, activate the plan,
/// then redirect to the frontend result page. Public (no JWT).
/// </summary>
[AllowAnonymous]
[HttpGet("payments/callback/broker")]
public async Task<IActionResult> BrokerCallback(
[FromQuery] Guid payment_id,
[FromQuery] string? id)
{
var frontendUrl = await paymentService.HandleBrokerCallbackAsync(payment_id, id ?? "");
return Redirect(frontendUrl);
}
// ── SnapPay flow ──────────────────────────────────────────────────────────────
/// <summary>