npx skills add ...
npx skills add skills-il/tax-and-finance --skill israeli-payment-orchestrator
Orchestrate Israeli payment gateways (Cardcom, Tranzila, PayMe, Meshulam, iCredit, Pelecard) with unified routing, fallback, and installments (tashlumim). Use when user asks about multi-gateway payment integration, "slikat kartisim", "tashlumim", payment routing, Shva network, BOI payment-services regulation, gateway comparison, or building a payment abstraction layer for Israeli merchants. Provides unified API patterns, installment handling, Shva clearing rules, and regulatory compliance. Do NOT use for single gateway setup (use cardcom-payment-gateway or tranzila-payment-gateway instead).
npx skills add skills-il/tax-and-finance --skill israeli-payment-orchestrator
Ask the user about their payment needs:
| Requirement | Hebrew | Description | Impact on Gateway Choice |
|---|---|---|---|
| Installments (tashlumim) | תשלומים | Split payment into monthly installments | Not all gateways support all installment types |
| Bit / Apple Pay | ביט / אפל פיי | Alternative payment methods | Gateway-specific integrations |
Use scripts/compare_gateways.py to generate a comparison matrix, or reference the table below:
| Gateway | API Style | Installments | Recurring | Hosted Page | Bit | Amount field unit |
|---|---|---|---|---|---|---|
| Cardcom | REST JSON | Regular, credit | Yes | Redirect or iframe (Low Profile) | Yes (UrlToBit) | Major units (decimal) |
| Tranzila | Form-encoded CGI (legacy) + REST JSON (API V2) | Regular, credit | Yes | Iframe, redirect or Hosted Fields | Yes (dedicated Bit API; bit_pay=1 on the iframe) | Major units (decimal) |
| PayMe | REST JSON | Unverified | Unverified | Unverified | Unverified | Unverified, confirm first |
| Meshulam (Grow) | multipart/form-data | Regular, credit | Yes | Iframe + redirect | Yes (cancelBitTransaction exists) | Major units (decimal) |
| iCredit | WCF .svc service | Unverified | Unverified | Unverified | Unverified | Unverified, confirm first |
| Pelecard | REST JSON | Regular, credit | Yes | Redirect or iframe | Yes, per pelecard-payment-gateway | Minor units (agorot) |
On pricing: this skill does not publish fee bands. None of the six gateways publishes a rate card. The merchant discount rate is contractual and quoted per merchant on volume, industry and chargeback profile, and earlier versions of this skill carried per-gateway bands that no vendor stands behind. A comparison table that names a cheapest gateway on invented numbers ranks on nothing, and the ordering flips on a difference smaller than any real negotiation. Send the user to each gateway for a written quote.
"Unverified" is not "no". Where no public source establishes a capability either way, this skill says so rather than guessing a negative. PayMe and iCredit are unverified across most of the row, not because they lack the features but because neither publishes a reachable API reference. Treat every PayMe or iCredit design decision as needing vendor confirmation first.
The per-gateway skills are the authority on their own gateway. For field names, error codes, auth handshakes and endpoint paths, defer to cardcom-payment-gateway, tranzila-payment-gateway, grow-payment-gateway and pelecard-payment-gateway. PayMe and iCredit have no dedicated skill, which is why several of their cells read "unverified".
Build a unified payment abstraction:
Normalize the money unit in the adapter, never in the caller. The gateways disagree on what the amount field carries: Pelecard takes agorot (minor units), while Grow takes decimal shekels. The per-gateway skills (pelecard-payment-gateway, grow-payment-gateway) are the authority on each; PayMe and iCredit have no skill and no reachable public docs, so establish their unit with a one-shekel test charge before going live. Keep one canonical integer unit inside the orchestrator and convert in each gateway adapter. A shared float amount across adapters that disagree by a factor of one hundred is an error generator in both directions, and the mistake is silent until a customer complains.
Define routing rules for selecting the optimal gateway:
| Rule | Priority | Logic | Example |
|---|---|---|---|
| Reversal and settlement cost | Medium | Route on costs the merchant can actually know, from its own contracts | A charge that is likely to be cancelled goes to a gateway whose batch stays open longest, because a void clears nothing while a credit clears twice |
| Feature match | High | Route based on required features | Bit-eligible sales to a gateway whose terminal has Bit enabled |
| Availability | Critical | Route away from failed/degraded gateways | If Tranzila is down, failover to Cardcom |
| Volume balancing | Low | Distribute load across gateways | 60/40 split between primary and secondary |
| Card type | High | Some gateways handle specific cards better | Diners Club routing |
Routing logic:
The installment mode is carried in the CreditType field (cred_type on Tranzila's legacy surface), which is distinct from the transaction type (סוג עסקה).
Only three CreditType values are published by any vendor. Tranzila's current parameter table publishes exactly these three (1 one payment, 6 credit installments, 8 installments). Cardcom's v11 API schema defaults CreditType to 1 and names only 6 explicitly ("6 - israel credit for multiple payments").
| Type | Hebrew | CreditType | How It Works | Who Pays Interest |
|---|---|---|---|---|
| Single payment | תשלום אחד | 1 | One immediate charge | n/a |
| Credit installments | קרדיט | 6 | The card company finances; customer pays it in installments with interest | Customer pays interest to the issuer |
| Regular installments | תשלומים | 8 | Merchant is paid the full amount, issuer collects from the customer monthly | Customer, no interest by default |
| "Payments without interest" | תשלומים ללא ריבית | 8 | Same wire value as regular installments; the merchant absorbs the financing cost commercially | Merchant absorbs cost |
Do not send 2, 3, 4, 5 or 9. Earlier versions of this skill listed them as a "canonical enum" on the strength of a third-party integration library rather than a vendor document. They appear in no current vendor parameter table and Cardcom's schema contains none of them; Tranzila's own parameter table publishes only 1, 6 and 8, and its response-code table answers anything else with 017 Unauthorized credit type for this transaction. In particular there is no routable "club installments = 9": club and issuer-loyalty programmes are a terminal and issuer configuration, not a CreditType your code selects. If a merchant needs one, that is a conversation with the acquirer, not a field value.
Implementation notes:
cred_type at all. Tranzila's, for instance, uses payment_plan plus installments_number. Decide which surface you are on before copying a parameter name across.Design resilient payment processing:
Three rules, and the third is the one that costs money:
302 means "debit was successful but merchant is not responding", so money DID move; 301 and 303 leave the outcome genuinely unknown. Only 302 asserts a charge. An orchestrator that maps all three to "failed, try the next gateway" will double-charge every customer whose first attempt was slow. Park the transaction, resolve it against the gateway's own lookup, and only then decide.Parking is only half the job; design the reconciliation before you ship the retry. park_for_reconciliation is not a stub you fill in later, it is what turns an unknown outcome back into money or goods. Three things have to exist before the first charge, not after the first incident:
ParamX works this way, as <order-id>-<attempt-n>. An idempotency key is what the gateway dedupes on, so it must be STABLE per logical charge and re-sent unchanged on every retry: Grow's transactionUniqueIdentifier works this way, and minting a fresh one per attempt defeats Grow's own duplicate detection. Check which of the two a given field is in the per-gateway skill before you generate it.references/gateway-matrix.md names the lookup call for each gateway. An unknown still unknown at the end of the window is an operations task, and someone has to own it. Those three messages are Pelecard's own wording, retrievable without credentials from /services/GetErrorMessageEn with parameter errorCode; an unassigned code comes back as Pelecard's misspelled UnkownError, which is how you tell a real code from an invented one. Build the equivalent three-state map for every gateway you route to, from that gateway's own code table, before you write any retry logic.An authorization is not a sale, and releasing one is not a refund. Most of these gateways support a two-step flow: hold the amount now, collect it later (Pelecard ActionType J5, Tranzila verify_mode=5 / tranmode=V, Cardcom Operation: SuspendedDeal). Two failures follow from ignoring it. An orchestrator that records an authorization as "approved" and never issues the capture never collects the money, and the hold expires quietly. And an orchestrator that releases a hold with its refund call is using the wrong operation: Tranzila releases a limit with txn_type=reversal, not cancel, and Pelecard releases one with /services/DeleteIshur, not DeleteTran. Carry action on the request (Step 3) and give the adapter a capture() and a release() alongside void() and refund(). references/gateway-matrix.md holds the per-gateway calls.
Reversing money that HAS been taken is the operation an orchestrator uniquely owns, and it is the one that differs most between gateways. There are two mechanically different actions, and picking the wrong one costs the merchant twice.
The dividing line is settlement, and it arrives fast: on most terminals the batch closes the same business day. A router that optimizes on cost (Step 4) while ignoring this is optimizing half the ledger.
| Gateway | Void, before settlement | Refund / credit, after | Partial refund |
|---|---|---|---|
| Cardcom | Transactions/RefundByTransactionId with CancelOnly: true ("cancellation only, before deposit of the transaction") | same endpoint without CancelOnly | Yes, PartialSum. AllowMultipleRefunds defaults to false |
| Tranzila | txn_type=cancel on API V2 | legacy tranmode=C{index} (needs the separate CreditPass credential); txn_type=credit on API V2 | Yes |
| PayMe | Unverified | api/refund-sale exists | Unverified |
| Meshulam (Grow) | No card void. Bit only: cancelBitTransaction, which requires pageCode; confirm its full parameter set in grow-payment-gateway | refundTransaction, amount field is refundSum and sum is rejected | Yes, but blocked once the transaction is settled or transmitted, and once funds are at the bank it needs manual approval. Grow signals each case with its own error code; see grow-payment-gateway |
| iCredit | Unverified | Unverified | Unverified |
| Pelecard | /services/DeleteTran, while the batch is still open. /services/GetTransDataBeforeBc shows what is still voidable | No refund endpoint exists on /PaymentGW. A credit is a new opposite transaction; do not guess its path | No. DeleteTran is whole-transaction only |
Three things to design for:
AllowMultipleRefunds to false, while Grow accepts successive partials until the total exceeds the original. Relying on the gateway to catch it means one of the six will not.CreditPass that has to be enabled on the terminal, and Cardcom's RefundByTransactionId requires ApiPassword, which its charge endpoints do not take at all. A reversal flow that was never tested end to end usually fails on exactly this.pelecard-payment-gateway carries a sourced summary of the Consumer Protection Law distance-selling rules; treat it as a summary rather than the statute.Run python scripts/compare_gateways.py --refunds to print this matrix.
Comply with Bank of Israel and Shva regulations:
| Regulation | Hebrew | Requirement | Impact |
|---|---|---|---|
| Transaction data retention | שמירת נתוני עסקאות | Retain transaction records for the statutory period | The retention period is set by tax and bookkeeping law, not by the gateway or by PCI. Confirm the current period with the Tax Authority or the merchant's accountant before designing a purge policy; do not copy a number from an integration guide |
| PCI DSS | תקן PCI | Card data security | Use tokenization, never store full card numbers. Israeli acquirers may add local requirements on top of the international standard |
| Consumer Protection | הגנת הצרכן | Statutory cancellation rights on distance sales | The cancellation window, the refund deadline, any cap on a cancellation fee, and whether particular consumer groups get a longer window are all set by the Consumer Protection Law and its regulations, not by the gateway. This skill deliberately encodes none of them. Establish every one of them from the Consumer Protection Authority or from counsel before building a refund flow around them |
| Anti-fraud | מניעת הונאה | 3D Secure, velocity checks | Implement 3DS2 for CNP transactions |
User says: "I need to accept payments with installments, with fallback if one gateway goes down" Actions:
python scripts/compare_gateways.py --features installments,recurring. Pick two gateways whose cells are documented rather than unverified.User says: "We use Tranzila but want to add PayMe so we can accept Bit" Actions:
bit_pay=1 flag on the iframe. So does Cardcom (UrlToBit), Grow and Pelecard. Adding a whole second gateway to obtain a feature the current one has is a large integration for nothing.User says: "Which gateway is cheapest for our 500 daily transactions averaging 200 NIS?" Actions:
python scripts/compare_gateways.py --example.
Result: A negotiating position and a capability comparison, not a false rankingscripts/compare_gateways.py -- Prints a capability matrix of the six gateways and a void/refund/partial-refund matrix. Filters on documented features and refuses an unrecognised feature name rather than silently matching everything. It deliberately does no cost estimation. Run: python scripts/compare_gateways.py --help, --example, --refunds, --features bitreferences/gateway-matrix.md -- Cross-gateway comparison: API formats and base URLs, auth schemes, installment support, Bit and wallet support, amount units, the authorize / capture / release calls, the void / refund / partial-refund matrix, the per-gateway transaction-lookup used for reconciliation, and the Pelecard status codes that decide whether money moved. It deliberately carries no fee table. Consult when evaluating or switching gateways.| Source | URL | What to Check |
|---|---|---|
| Cardcom v11 API schema | https://secure.cardcom.solutions/swagger/v11/swagger.json | The authoritative field list: CreditType values, UrlToBit, and the RefundByTransactionId request with PartialSum and CancelOnly |
| Tranzila Documentation | https://docs.tranzila.com/ | Which of the four surfaces you are on, the published cred_type values, and the API V2 four-header HMAC auth |
| PayMe | https://payme.io/ | PayMe publishes no openly reachable developer-docs URL (payme.io/developers 301s to the homepage and docs.paymeservice.com answers a Cloudflare 1014). Request the API reference from PayMe directly, and confirm the money unit and the refund parameter names with them before the first charge |
| Meshulam (Grow) Reference | https://developers.grow.business/reference/overview | Production base = secure.meshulam.co.il, refundSum on refunds, and the error codes that gate partial refunds |
| Rivhit (iCredit) | https://www.rivhit.co.il/ | iCredit is a Rivhit product. Its API is a WCF .svc service; the refund surface is not publicly documented |
| Pelecard services index | https://gateway21.pelecard.biz/services | The live endpoint list, including DeleteTran and GetTransDataBeforeBc. gateway20 and gateway21 are generations, not environments |
| Bank of Israel: Payment Systems Oversight | https://www.boi.org.il/en/economic-roles/supervision-and-regulation/payment-systems-oversight/ | Which systems are "controlled payment systems" and therefore inside the BOI's oversight perimeter, plus the oversight directives and the access requirements for connecting to a payment system. Note that this page does not settle who supervises a non-bank payment-service provider; establish that with the regulator directly rather than assuming it from here |
status: "0" and an err object. Pelecard's init returns 200 with Error.ErrCode. Tranzila's legacy CGI returns 200 with an HTML alert page rather than JSON at all. Normalizing this is the orchestrator's job and it is the single most common way an agent-written integration reports a failed charge as paid. Parse the body, never the status..svc service. Agents may apply one gateway's format to another.gateway20 and gateway21 are gateway generations and return byte-identical responses; what decides whether a call is a test or a real charge is the terminal number and credentials. An agent that "switches to the sandbox host" and then runs test data will put real charges through.transactionUniqueIdentifier, which must stay stable across retries of the same logical charge; on Pelecard the passthrough ParamX is a per-attempt correlator rather than a dedupe key, and dedupe happens on the returned transaction id instead. The two are not interchangeable. For the others, look the field and the duplicate code up in the per-gateway skill before relying on retry safety.TranzilaTK, and a PayMe buyer_key are proprietary and mutually incompatible, each usable only on the gateway that issued it. This means the fallback/failover pattern in this skill applies to first-charge and one-time transactions only. A tokenized or recurring charge cannot fail over to a second gateway, recurring billing is pinned to its originating gateway. True cross-gateway resilience for saved cards requires network tokenization or a multi-vault strategy, not this token field. Agents may wrongly assume a stored token works on any gateway in the priority list.getPaymentProcessInfo/approve; for others, the gateway's transaction-lookup or webhook-signature/notify verification) before fulfilling the order. Webhook/IPN payloads must be authenticated per each gateway's scheme, they are not interchangeable.017Cause: Most often the CreditType value itself, not the gateway. Only 1, 6 and 8 are published by any vendor; 2, 3, 4, 5 and 9 circulate in third-party libraries but appear in no current vendor parameter table, and Tranzila answers them with 017 Unauthorized credit type for this transaction.
Solution: Send 8 for regular installments or 6 for issuer-financed credit. If the merchant genuinely needs a club or loyalty programme, that is a terminal and acquirer configuration rather than a field value, so route the request to the acquirer rather than retrying with another number. Verified negative: switching gateways does not fix a rejected CreditType, because the value is rejected upstream of the gateway.
Cause: Either every configured gateway is degraded, or, far more often, one gateway timed out and the orchestrator treated that as a failure and moved on. Solution: Separate the two. A timeout leaves the outcome unknown and must be parked for reconciliation, not failed over (Step 6). Only a provable connect failure justifies moving to the next gateway. Once that distinction is in place, add a circuit breaker with health checks and alert on degradation.
Cause: Retry logic sent the same payment twice, usually after treating a timeout as a failure. Solution: Map your idempotency key onto the gateway's own duplicate-detection field, and treat a timeout as an unknown outcome rather than a failure (Step 6). Look the transaction up before retrying anything. Never retry a bank decline.
Cause: Likeliest cause, not confirmed for every gateway: the credit was queued rather than completed, or it was a void on an already-settled transaction. Verified negative: on Pelecard this is NOT a /PaymentGW refund endpoint failing, because no such endpoint exists.
Solution: Establish which side of settlement the original transaction is on before choosing the call (Step 7), and reconcile against the gateway's own transaction lookup rather than the reversal call's own response.
def select_gateway(request: PaymentRequest, gateways: list) -> str:
# 1. Filter by feature support (tashlumim type, Bit, etc.)
eligible = [g for g in gateways if g.supports(request)]
# 2. Remove unhealthy gateways
healthy = [g for g in eligible if g.is_healthy()]
# 3. Sort by cost for this transaction type
ranked = sorted(healthy, key=lambda g: g.priority_for(request))
# Rank on merchant-configured priority, NOT on a fee table.
# No Israeli gateway publishes rates, so a "cheapest gateway" sort
# can only be built on invented numbers. See Step 2.
# 4. Return best match (or raise if none available)
if not ranked:
raise NoGatewayAvailable()
return ranked[0]# Fallback strategy
GATEWAY_PRIORITY = ["cardcom", "tranzila"] # documented gateways only, see below
async def process_with_fallback(request: PaymentRequest) -> PaymentResult:
last_error = None
for gateway_name in GATEWAY_PRIORITY:
gateway = get_gateway(gateway_name)
if not gateway.is_healthy():
continue # דלג על שער לא זמין
try:
result = await gateway.charge(request)
if result.outcome in ("approved", "declined"):
return result # both are settled answers, stop here
# outcome == "unknown": the charge MAY have gone through.
# Do not fail over. Park it for reconciliation.
await park_for_reconciliation(request, gateway_name)
raise OutcomeUnknown(gateway_name)
except GatewayTimeoutError:
# A timeout is NOT a failure. It is an unknown outcome.
await park_for_reconciliation(request, gateway_name)
raise OutcomeUnknown(gateway_name)
except GatewayConnectFailed as e:
# The request never reached the gateway, so nothing was charged.
last_error = str(e)
continue # נסה שער הבא
raise AllGatewaysFailedError(last_error)