ز Zarinpal for EmDash
v0.1.1 · tested against the live Zarinpal sandbox

Take Rial & Toman payments on your EmDash site.

A Zarinpal gateway plugin for EmDash CMS on Cloudflare Workers and Node.js. Payment links, a server-side checkout API, verified callbacks, automatic reconciliation and an admin ledger, all in one sandboxed plugin.

The Zarinpal payments ledger in the EmDash admin, showing a paid and a pending payment

Built for real money

Every rule below came from testing against Zarinpal's sandbox and a five-round code review.

🔗

Payment links

Set a price in the admin and paste a button on any page. No server code needed.

🧾

Server checkout API

Your code sets the price and order ID. Idempotency keys mean a retry never creates a second payment.

✅

Verified, never assumed

A payment is paid only when Zarinpal's verify call says so. Status=OK in the URL is never trusted.

🔁

Automatic reconciliation

Buyers who pay and close the tab are found in the background. Outages never make the plugin give up.

📊

Admin ledger

Filters, reference IDs, card masks, fees, a details view, Verify now and a dashboard widget.

🛡️

Least privilege

In EmDash's sandbox its only permission is calling payment.zarinpal.com and sandbox.zarinpal.com. No access to content, media or users.

How a payment works

The plugin stores the payment before it talks to Zarinpal, and verifies with the amount and currency it stored.

Buyer clicks Pay

The plugin records the payment and asks Zarinpal for an authority.

Buyer pays at the bank

On Zarinpal's page, in Rial or Toman.

Callback verifies

Only a 100 or 101 from verify marks it paid.

Your site fulfils

Server code asks the plugin until the payment is settled, checks order, amount, currency and environment, and fulfils exactly once.

Install

For a site made from an EmDash *-cloudflare template. The README also covers Node.js and every setting.

1. Add the package

pnpm add github:taleghanidev/emdash-plugin-zarinpal#v0.1.1

2. Register it in astro.config.mjs

import { d1, r2, sandbox } from "@emdash-cms/cloudflare";
import zarinpal from "emdash-plugin-zarinpal";

emdash({
  database: d1({ binding: "DB" }),
  storage: r2({ binding: "MEDIA" }),
  // Workers Paid: isolated sandbox (recommended).
  // Also needs the LOADER binding and PluginBridge export.
  sandboxRunner: sandbox(),
  sandboxed: [zarinpal],
  // Workers Free: replace the two lines above with
  // plugins: [zarinpal],
})

The sandbox needs a worker_loaders binding named LOADER and PluginBridge exported from src/worker.ts. See sandbox setup.

3. Set the secrets and copy the return pages

npx emdash secrets generate
npx wrangler secret put EMDASH_ENCRYPTION_KEY
npx wrangler secret put ZARINPAL_API_KEY   # only for server checkout
cp -R node_modules/emdash-plugin-zarinpal/examples/astro/src/* src/

4. Deploy and configure

Run pnpm run deploy, open Plugins → zarinpal → settings, enter your merchant ID (any UUID in sandbox mode) and your site URL. The domain must match the one registered for your Zarinpal terminal.

Which mode should I use on Cloudflare?

SandboxedIn-process
Cloudflare planWorkers PaidFree or Paid
Configsandboxed: [zarinpal]plugins: [zarinpal]
IsolationOwn Dynamic Worker, can reach only ZarinpalNone: runs inside your site's Worker with its full authority, including secrets. Install only if you trust the code.
Limits10 subrequests, 50 ms CPU per plugin callYour Worker's limits, shared with EmDash (Free: 10 ms CPU per request)
TestedUnit tests through the Cloudflare sandbox runner; browser runs on Node's workerd sandboxLoads, admin renders and routes answer on the Cloudflare template; no deployed payment yet
WhyEmDash runs sandboxed plugins as Cloudflare Dynamic Workers, which are only on the Workers Paid plan.

Screens

Captured by the Playwright end-to-end run against sandbox.zarinpal.com. Click to enlarge.

Tested, not just written

Run on 19 October 2026 with EmDash 1.3.1.

36
tests through EmDash's Cloudflare sandbox runner
3
real browser flows against the Zarinpal sandbox
6
rounds of design and code review with Codex
1
permission: call Zarinpal's two hosts

راهنمای فارسی

افزونهٔ درگاه زرین‌پال برای سایت‌های EmDash روی Cloudflare Workers یا Node.js.

لینک پرداخت

مبلغ را در پنل مدیریت تعیین کنید و دکمهٔ پرداخت را در هر صفحه بگذارید.

پرداخت از سمت سرور

کد سایت شما مبلغ و شمارهٔ سفارش را تعیین می‌کند؛ تکرار درخواست، پرداخت دوم نمی‌سازد.

تأیید واقعی

پرداخت فقط وقتی «پرداخت‌شده» است که متد verify زرین‌پال آن را تأیید کند.

بررسی خودکار

اگر خریدار پرداخت کند و به سایت برنگردد، افزونه پرداخت را پیدا می‌کند.

نصب

pnpm add github:taleghanidev/emdash-plugin-zarinpal#v0.1.1
  • در astro.config.mjs افزونه را با sandboxed: [zarinpal] (پلن پولی Workers، ایزوله) یا plugins: [zarinpal] (پلن رایگان، بدون ایزوله‌سازی؛ فقط اگر به کد اعتماد دارید) ثبت کنید.
  • کلید رمزنگاری را با npx emdash secrets generate بسازید و با wrangler secret put ذخیره کنید.
  • صفحه‌های نمونهٔ پوشهٔ examples/astro را در سایت کپی کنید.
  • در تنظیمات افزونه مرچنت‌کد و نشانی سایت را وارد کنید؛ دامنه باید با دامنهٔ ثبت‌شده در زرین‌پال یکی باشد.

راهنمای کامل در گیت‌هاب