pardakht 0.8.1 copy "pardakht: ^0.8.1" to clipboard
pardakht: ^0.8.1 copied to clipboard

One type-safe API across Iranian payment gateways. Pure Dart, explicit currency units, a unified error taxonomy and server-safe verification.

Changelog #

All notable changes to this project are documented here. The format follows Keep a Changelog, and this project adheres to Semantic Versioning.

Unreleased #

Planned #

  • Phase 5 hardening. All seven gateways from the original project brief (Zarinpal, Zibal, IDPay, PayPing, Pay.ir, Vandar, IranDargah) are now shipped.

0.8.1 #

Fixes a real bug in VandarGateway, found by checking its documentation against a second, more current source before publishing.

Fixed #

  • VandarGateway was pointed at Vandar's v3 IPG endpoints, which are stale. The adapter was originally written against a community-maintained documentation mirror dated 2023; checking it against Vandar's own current documentation and a Postman collection dated 2026-02-25 found the real, current API is v4 (/api/v4/send, /v4/{token}, /api/v4/transaction, /api/v4/verify) — the request and response shapes are unchanged in substance, only the version segment moved. All four endpoints, the contract test, the adapter tests and the fixtures were updated to match.
  • valid_card_number is now always sent as an array, matching the current documentation; the previous version occasionally sent a bare string.
  • VandarGateway.inquire now reads trackingCode (falling back to refnumber) as InquiryResult.referenceId and paymentDate as InquiryResult.paidAt — both newly confirmed fields on the transaction endpoint's response. VandarGateway.verify now reads paymentDate as VerificationResult.paidAt too.

Notes for integrators #

  • If you built against pardakht 0.8.0's VandarGateway, no code changes are needed on your side — the public API is unchanged. Requests will now reach the correct, current Vandar endpoint instead of a stale one.
  • doc/gateway_specs/vandar.md now documents this correction in full, including a field-by-field comparison of what changed between v3 and v4, and doubles as a worked example of why this project checks a vendor's current site rather than stopping at the first source that answers.
  • doc/gateway_specs/irandargah.md gained a second pass too, confirming (via IranDargah's own sandbox documentation, including its raw page text) that the v2 API it targets is genuinely live — real test card numbers, a real sandbox transaction cap and rate limit — without resolving the adapter's core open question, what a repeat verify call returns. It remains @experimental. The same pass added a regression test locking in that IranDargahGateway.createSession decides success from the response's boolean success field alone, never from status_code — two accounts of the same sandbox example disagreed on whether a successful /v2/payments call reports status_code: 100 or 200, which is exactly the ambiguity this design choice was already built to be indifferent to.

0.8.0 #

Adds the seventh and final provider adapter from the original project brief.

Added #

  • IranDargahGateway and IranDargahCredentials, covering payment session creation and verification, authenticated with a Bearer token — the first gateway in this package to authenticate that way rather than with a body field — and the first to document (and honour) an Idempotency-Key header.
  • IranDargah's documented error-code table, mapped onto PaymentErrorCode, with its -2 validation error classified further by inspecting which field name appears in the response's nested error.details.validation object.
  • doc/gateway_specs/irandargah.md, which documents a genuine, unresolved conflict between IranDargah's current documentation and two independent, well-established community SDKs describing an older API — and the single most important open question in the package: what a repeat verify call actually returns.

Notes for integrators #

  • IranDargahGateway is annotated @experimental, and should be treated with more caution than any other adapter in this package. Two independent community SDKs describe a completely different, older IranDargah API — a different domain, a different authentication scheme, and different field names throughout — from the one this adapter targets. The vendor's own current documentation was used, per this project's source ranking, but this project had no IranDargah account to confirm any of it against a live response. See doc/gateway_specs/irandargah.md.
  • There is no confirmed signal for a repeat verification. Every other gateway in this package maps an explicit "already verified" code to VerificationStatus.alreadyVerified; IranDargah's documentation gives no worked example of what a second verify call actually returns. IranDargahGateway.verify therefore maps only a clean success to verified and treats everything else as a rejection — the safe direction to be wrong in, but genuinely unconfirmed. This is the first thing to resolve before this adapter is trusted with any payment a merchant might verify twice, which in practice is every payment.
  • IranDargah is sent Rial, with a documented minimum of 100,000 Rial and a maximum of 4,000,000,000 Rial.
  • direct_verify is deliberately never sent. IranDargah documents a flag that lets a callback carry a bank reference without a server-side verify call — the exact trust model this package's entire design refuses to build, since a callback is an unauthenticated request anyone can forge.
  • Inquiry and refund are not implemented. IranDargah documents GET endpoints for reading transaction state, but no response shape for either — only the POST endpoints came with worked examples — so this adapter does not guess at one. No refund-creation endpoint is documented at all, only cancellation of a still-pending payment.

0.7.0 #

Adds the sixth provider adapter.

Added #

  • VandarGateway and VandarCredentials, covering payment session creation, verification and status inquiry (transaction).
  • Vandar's shared HTTP-status error table, used to classify createSession rejections since send carries no per-field error code of its own, and its verify-specific status table, mapped onto PaymentErrorCode and VerificationStatus.
  • doc/gateway_specs/vandar.md, sourced directly from Vandar's own documentation site, which — unlike Pay.ir's — was fully reachable while this adapter was written.

Notes for integrators #

  • Vandar is sent Rial. Its documented minimum is 1,000 Rial, with no currency override anywhere in the IPG documentation.
  • A repeat verification arrives as status: 2 ("already verified"), the same field verify uses for its own success (status: 1) — not a separate error channel the way Pay.ir's -6 arrives. VandarGateway.verify checks for 2 before treating any other status as a rejection.
  • callback_url and the redirect's Referer header must both match a domain registered in the Vandar panel, itself matching the domain registered with Shaparak for the merchant's terminal. This adapter cannot check either locally — the registered domain list is not available through any API it calls — so a mismatch surfaces only as an ordinary rejection after the call is made. See doc/gateway_specs/vandar.md.
  • createSession's rejection carries no per-field error code, only a flat array of Persian messages. Every other gateway in this package maps a numeric code; Vandar's send does not have one, so this adapter classifies the failure by HTTP status instead — the one structured signal send actually offers — and preserves every message, joined, as gatewayMessage.
  • Refund is not implemented. Vandar's refund service lives on a different host, under a different path shape, authenticated with an OAuth bearer token from a separate flow rather than the single api_key this adapter's credentials model. VandarGateway.refund always throws UnsupportedGatewayOperationError.
  • No sandbox credential or header exists for the IPG service. Vandar's own docs direct a merchant to ask its support team to enrol a business named "sandbox" — there is no self-service equivalent of Zibal's zibal merchant or Pay.ir's api: "test", so capabilities.supportsSandbox is false and there is no VandarCredentials.test().
  • Settlement splitting is not documented for the IPG service. It appears only under Vandar's separate Settlement and Batch Settlement services.

0.6.0 #

Adds the fifth provider adapter.

Added #

  • PayIrGateway and PayIrCredentials, covering payment session creation and verification. PayIrCredentials.test() sends the documented api: "test" sandbox value.
  • Pay.ir's documented errorCode table, mapped onto PaymentErrorCode, and its -6 duplicate/already-completed signal mapped onto VerificationStatus.alreadyVerified.
  • doc/gateway_specs/payir.md, reconstructed from a search engine's index of Pay.ir's own documentation page and cross-checked against a well-starred community PHP client, since the page itself could not be reached directly while writing this adapter.

Notes for integrators #

  • PayIrGateway is annotated @experimental. Neither pay.ir nor docs.pay.ir could be reached from the environment this adapter was written in — not even the vendor's own site loaded, which is a step further than IdPayGateway's situation, where only the API host was down. Every fact comes from a search engine's cached index of the vendor's documentation page, cross-checked against a community client. Test it against Pay.ir's api: "test" sandbox, from an environment that can actually reach pay.ir, before trusting it with a real payment, then remove the @experimental annotation and the // TODO(spec): above it.
  • Pay.ir is sent Rial. Its documented amount range, 10,000 to 500,000,000 Rial, is expressed as a plain integer with no currency override.
  • A repeat verification arrives as errorCode: -6 ("تراکنش تکراریست یا قبلا انجام شده" — duplicate, or already completed), through the same error channel a genuine failure uses. PayIrGateway.verify checks for this specific code before treating a status: 0 response as a rejection, the same shape as Zarinpal's 101 and Zibal's 201.
  • Pay.ir documents no inquiry endpoint distinct from verify, and no refund or reversal endpoint at all. Both PayIrGateway.inquire and PayIrGateway.refund always throw UnsupportedGatewayOperationError.
  • Settlement splitting is not implemented for Pay.ir. Its split uses a percentage tied to a mobile number, a different shape from the fixed-Money, IBAN-addressed Wage model this package uses for Zarinpal and Zibal. Forcing one shape into the other would either lose precision or misrepresent a mobile number as an IBAN, so it is left unimplemented rather than modelled incorrectly; see doc/gateway_specs/payir.md.

0.5.0 #

Adds the fourth provider adapter.

Added #

  • PayPingGateway and PayPingCredentials, covering payment session creation, verification and reversal (full-amount only), with the isReversible flag passed through PaymentRequest.metadata.
  • PayPing's documented error codes and its metaData.code already-verified signal, mapped onto PaymentErrorCode and VerificationStatus and pinned by tests.
  • doc/gateway_specs/payping.md, sourced from PayPing's own machine-readable OpenAPI document, with one live probe confirming its authentication behaviour.

Notes for integrators #

  • PayPing is sent Toman, not Rial. Confirmed by the provider's own OpenAPI document, which states outright that every service uses Toman — this had been the highest-risk open question in the project before the adapter shipped, since it contradicts most other Iranian providers.
  • PayPing's reference is two identifiers, not one, and the second does not exist until after the payer has paid. createSession returns a PaymentSession.reference holding only paymentCode; the paymentRefId needed to verify or reverse arrives solely in the callback. Call verify with CallbackPayload.reference — a composite paymentCode:paymentRefId string — never with PaymentSession.reference.
  • A repeat verification arrives as HTTP 409 with metaData.code: 110, not as a distinguished field on a 200 response the way other providers signal it. PayPingGateway.verify maps this combination to VerificationStatus.alreadyVerified.
  • verify can answer HTTP 202 while genuinely still processing. PayPingGateway retries this specific status in a loop scoped to verify alone, deliberately separate from GatewayHttpClient's own retry-on-5xx — wrapping the whole call in a second retry layer using the same policy was tried during development and multiplied the attempt count instead of bounding it, caught immediately by the shared contract suite. See doc/adding_a_gateway.md, section 5a, if writing another adapter with a similar "still deciding" response.
  • PayPing documents no partial refund. PayPingGateway.refund throws UnsupportedGatewayOperationError if RefundRequest.amount is set; otherwise it reverses the full payment.
  • No PayPing merchant account was available during development. Every response shape beyond the authentication probe comes from documentation alone; see doc/gateway_specs/UNVERIFIED.md for exactly what remains unconfirmed. Unlike IdPayGateway, the adapter is not marked @experimental, since the provider was reachable and its documentation is authoritative — but test it against a real account before trusting it with a real payment.

0.4.0 #

Adds the third provider adapter, completing Phase 3.

Added #

  • IdPayGateway and IdPayCredentials, covering payment creation, verification and status inquiry, with the payer name/phone/mail hint.
  • All of IDPay's documented error_code values and its full transaction status table, mapped onto PaymentErrorCode and VerificationStatus and pinned by tests.
  • doc/gateway_specs/idpay.md, transcribed from IDPay's official API guide.

Notes for integrators #

  • IdPayGateway is annotated @experimental. IDPay's API host (api.idpay.ir) returned 502 Bad Gateway on every attempt made during development, so unlike ZarinpalGateway and ZibalGateway — both confirmed against live traffic — nothing about this adapter has been verified against a real response. It is built entirely from IDPay's own documentation, which is thorough, but test it against IDPay's sandbox yourself before trusting it with a real payment. See doc/gateway_specs/idpay.md.
  • IDPay's sandbox is a header, not a host or a credential. GatewayOptions.sandbox sends X-SANDBOX: 1 alongside the same API key used in production, unlike Zarinpal's separate host or Zibal's dedicated test merchant.
  • A paid transaction must be verified within ten minutes, or IDPay refunds the payer automatically — a stricter and more consequential deadline than either Zarinpal or Zibal impose.
  • IDPay's callback can arrive as either a GET query string or a POST body, configured per web service in the IDPay dashboard. parseCallback accepts a plain map either way, so both work without any extra handling.
  • Double-spend detection is explicitly the merchant's own responsibility, per IDPay's documentation — store id and track_id and enforce their uniqueness yourself.
  • IDPay documents no refund or reversal endpoint. IdPayGateway.refund always throws UnsupportedGatewayOperationError.

0.3.0 #

Adds the second provider adapter.

Added #

  • ZibalGateway and ZibalCredentials, covering payment request, verification and status inquiry, with settlement splitting, card restriction and the payer mobile hint. ZibalCredentials.test() exposes Zibal's permanent test account.
  • All of Zibal's documented result codes across the request, verify and inquiry endpoints, and its full transaction status table, mapped onto PaymentErrorCode and VerificationStatus and pinned by tests.
  • example/zibal_sandbox.dart, a real end-to-end payment against Zibal's test account.
  • Response fixtures under test/fixtures/zibal/, including bodies captured from the live test account.
  • doc/gateway_specs/zibal.md, sourced from Zibal's own machine-readable OpenAPI document plus a live capture, each fact dated and cited.

Notes for integrators #

  • Zibal splits "did the call succeed" from "what state is the payment in" across two fields, result and status, and they are not interchangeable. An inquire call answers result: 100 — the report was produced — even for a transaction that was never paid; only status says what actually happened. ZibalGateway.verify decides its outcome entirely from result; ZibalGateway.inquire decides its outcome entirely from status. Treating either field as authoritative for the other operation is the most common Zibal integration bug, and this package's contract test suite pins the distinction so it cannot regress.
  • Zibal has no separate sandbox host. The literal merchant value zibal is what selects the test environment, on the same gateway.zibal.ir used in production.
  • Zibal documents no refund or reversal endpoint. ZibalGateway.refund always throws UnsupportedGatewayOperationError.

0.2.0 #

Adds the first provider adapter.

Added #

  • ZarinpalGateway and ZarinpalCredentials, covering payment request, verification, status inquiry and reversal, with settlement splitting (تسهیم), card-restricted payment, the payer mobile hint and the sandbox environment.
  • All 33 documented Zarinpal result codes, mapped onto PaymentErrorCode and pinned by tests. An unmapped code falls back to unknown and is logged rather than throwing.
  • GatewayCapabilities.requiresDescription, because Zarinpal rejects a request without one and the adapter will not invent text shown to a payer.
  • example/zarinpal_sandbox.dart, a real end-to-end payment against Zarinpal's sandbox that needs no merchant account.
  • Response fixtures under test/fixtures/zarinpal/, including bodies captured from the live sandbox with the HTTP status codes Zarinpal actually used.
  • doc/gateway_specs/zarinpal.md, recording every endpoint, the complete result-code table and the currency unit, each with a source URL and a retrieval date.

Notes for integrators #

  • Zarinpal is always sent Rial. Its request endpoint accepts a currency field of IRR or IRT, but its verify endpoint does not, and the amount there is documented as Rial unconditionally. Requesting in Toman and verifying in Rial produces error -50 after the payer has been charged, so the adapter never sends currency. Callers keep passing whatever Money unit they think in.
  • Zarinpal answers 4xx with a result code in the body, including 401 for an unpaid session and 422 for a rejected amount. These reach the adapter rather than being thrown as transport failures.
  • Zarinpal's reverse endpoint takes no amount, so refunds are all-or-nothing and only within 30 minutes of payment.

0.1.0 #

First release. The core is complete; no provider adapter has shipped yet.

Added #

  • Money, an immutable amount that carries its currency unit. Zero and negative amounts are rejected at construction, conversions between Rial and Toman are exact or throw, and every operation is checked for overflow.
  • PaymentGateway, the provider-independent interface, together with PaymentRequest, PaymentSession, VerificationRequest, VerificationResult, InquiryRequest, InquiryResult, RefundRequest, RefundResult, PayerInfo, Wage and CallbackPayload.
  • VerificationStatus.alreadyVerified as a distinct success state, so a repeat verification cannot be mistaken for a failure.
  • A sealed PaymentException hierarchy — GatewayRejectedException, NetworkPaymentException, TimeoutPaymentException, MalformedResponseException and ConfigurationException — with the normalised PaymentErrorCode enum. Provider codes and messages are preserved verbatim.
  • UnsupportedGatewayOperationError and the UnsupportedOperations mixin, so optional operations report their absence rather than returning null.
  • GatewayCapabilities, for branching on what a provider supports instead of type-checking concrete gateway classes.
  • HttpClientAdapter with a package:http default, so every network call is injectable and no test touches the network.
  • RetryPolicy and RetryRunner, with exponential backoff and jitter applied only to idempotent operations. createSession and refund are never retried automatically.
  • PaymentLogger with a silent default, and Redactor, which strips registered credentials by exact value and masks card numbers, national identifiers, phone numbers, IBANs and authorization headers by pattern.
  • GatewayCredentials, whose toString cannot be overridden to disclose a key.
  • GatewayHttpClient, the shared plumbing that gives every adapter the same timeout, retry, redaction and error-translation behaviour.
  • GatewayRegistry, for choosing a provider at runtime.
  • PaymentMessageResolver with English and Persian message maps.
  • RemotePaymentGateway and the PaymentGateway.remote factory, implementing proxy mode for clients that must not hold merchant credentials, with a documented backend contract and a working reference backend in example/backend/.
  • A shared contract test suite under test/contract/, which every adapter must pass.
3
likes
160
points
42
downloads

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

One type-safe API across Iranian payment gateways. Pure Dart, explicit currency units, a unified error taxonomy and server-safe verification.

Repository (GitHub)
View/report issues

Topics

#payment #iran #gateway #fintech #zarinpal

License

MIT (license)

Dependencies

http, meta

More

Packages that depend on pardakht