mcpField
I
israeli-payment-orchestrator

israeli-payment-orchestrator

github.com/skills-il开发38156 次安装1 天前更新
代码仓库

关于 israeli-payment-orchestrator

用于设计以以色列支付网关为核心的支付编排技能,涵盖能力比较、路由、分期、金额单位规范化,以及安全重试与对账。

主要功能

  1. 01比较网关能力
  2. 02统一不同网关 API 的金额单位
  3. 03按功能与健康状态路由支付
  4. 04建模分期类型
  5. 05设计故障转移与对账逻辑

适用场景

  1. 01按所需支付方式选择网关
  2. 02网关故障时路由交易
  3. 03避免超时后重复扣款
  4. 04为多个网关构建统一适配层

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/skills-il/tax-and-finance --skill israeli-payment-orchestrator

安装后在 Claude Code 中输入/israeli-payment-orchestrator确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evidence.json
metadata.json
optimization-log.json
SKILL_HE.md
references/
domain-checklist.md
gateway-matrix.md
scripts/
compare_gateways.py

详细介绍

以色列支付编排器

说明

第 1 步:评估支付需求

询问用户的支付需求:

需求希伯来语说明对网关选择的影响
分期付款(tashlumim)תשלומים将付款拆分为每月分期付款并非所有网关都支持所有分期付款类型
Bit / Apple Payביט / אפל פיי替代支付方式取决于网关的集成方式
第 2 步:比较网关

使用 scripts/compare_gateways.py 生成比较矩阵,或参考下表:

网关API 风格分期付款定期付款托管页面Bit金额字段单位
CardcomREST JSON普通、信用卡是重定向或 iframe(Low Profile)是(UrlToBit)主要单位(小数)
Tranzila表单编码 CGI(旧版)+ REST JSON(API V2)普通、信用卡是iframe、重定向或托管字段是(专用 Bit API;iframe 上的 bit_pay=1)主要单位(小数)
PayMeREST JSON未验证未验证未验证未验证未验证,请先确认
Meshulam (Grow)multipart/form-data普通、信用卡是iframe + 重定向是(存在 cancelBitTransaction)主要单位(小数)
iCreditWCF .svc 服务未验证未验证未验证未验证未验证,请先确认
PelecardREST JSON普通、信用卡是重定向或 iframe是,参见 pelecard-payment-gateway次要单位(agorot)

关于定价:此技能不公布费率区间。 这六家网关都没有公布费率表。商户折扣率由合同约定,并根据商户的交易量、行业和拒付情况单独报价;此技能的早期版本曾列出各网关的费率区间,但没有供应商愿意为这些数据背书。基于编造数字指定最便宜网关的比较表毫无依据,而且只要差异小于任何实际谈判可能产生的差异,排名就会颠倒。请让用户向各网关索取书面报价。

“未验证”不等于“不支持”。 如果没有公开来源能够证实某项功能是否可用,此技能会如实说明,而不会猜测其不支持。PayMe 和 iCredit 的大多数项目均未验证,这并非因为它们不具备这些功能,而是因为两者都没有发布可访问的 API 参考文档。涉及 PayMe 或 iCredit 的每项设计决策,都应先向供应商确认。

各网关专属技能是对应网关的权威资料。 关于字段名称、错误代码、身份验证流程和端点路径,请以 cardcom-payment-gateway、tranzila-payment-gateway、grow-payment-gateway 和 pelecard-payment-gateway 为准。PayMe 和 iCredit 没有专属技能,因此它们的多个单元格标为“未验证”。

第 3 步:设计编排层

构建统一的支付抽象:

# Unified payment interface pattern
class PaymentRequest:
    action: str              # פעולה - "sale" (charge now) or "authorize" (hold now,
                             # capture later). Two different gateway calls, see Step 7.
    amount: int              # סכום - store ONE canonical unit, agorot, as an integer
                             # and convert per gateway on the way out (see below)
    currency: str            # מטבע - "ILS" default
    installments: int        # תשלומים - 1 = regular, 2-36 = installments
    installment_type: str    # סוג תשלומים - "regular", "credit", "club"
    card_token: str          # טוקן כרטיס - for recurring
    description: str         # תיאור עסקה
    customer_id: str         # מזהה לקוח
    idempotency_key: str     # מפתח אידמפוטנטי - prevent duplicates

class PaymentResult:
    outcome: str             # תוצאה - "approved" | "declined" | "unknown"
                             # NOT a bool: "unknown" is a real third state, see Step 6
    gateway_used: str        # שער תשלום שנבחר
    transaction_id: str      # מזהה עסקה
    approval_number: str     # מספר אישור
    shva_reference: str      # מספר שב"א
    gateway_status_code: str # קוד סטטוס גולמי - keep the vendor's own code, not just
    raw_response: dict       # the normalised outcome, so a parked or mis-mapped
                             # transaction stays auditable afterwards
    installment_details: dict

在适配器中统一金额单位,绝不要在调用方处理。 各网关的金额字段单位并不一致:Pelecard 使用 agorot(次要单位),而 Grow 使用谢克尔小数金额。各网关专属技能(pelecard-payment-gateway、grow-payment-gateway)分别是对应网关的权威资料;PayMe 和 iCredit 没有专属技能,也没有可访问的公开文档,因此在正式上线前,请先用 1 谢克尔进行测试扣款,以确认它们的金额单位。在编排器内部使用单一的标准整数单位,并在各网关适配器中进行转换。如果各适配器使用的单位相差 100 倍,却共用 float 金额,就会在两个方向上产生错误,而且问题会一直悄无声息,直到客户投诉。

步骤 4:实现网关路由

定义路由规则,以选择最优网关:

规则优先级逻辑示例
撤销与结算成本中根据商户实际能够获知的成本进行路由,这些成本来自商户自己的合同对于可能被取消的扣款,将其路由到批次开放时间最长的网关,因为撤销不会清算任何款项,而退款会清算两次
功能匹配高根据所需功能进行路由将符合 Bit 条件的销售路由到已启用 Bit 的终端所在网关
可用性关键将流量从故障或性能下降的网关移开如果 Tranzila 宕机,则故障转移到 Cardcom
流量均衡低在多个网关之间分配负载主网关与次网关按 60/40 分配
卡类型高某些网关对特定卡的处理更好Diners Club 路由

路由逻辑:

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]
步骤 5:处理分期付款(Tashlumim)

分期付款模式通过 CreditType 字段传递(在 Tranzila 的旧版接口中为 cred_type),它与交易类型(סוג עסקה)不同。

所有供应商公开的 CreditType 值只有三种。 Tranzila 当前的参数表恰好列出了这三种(1 一次付款、6 信用分期、8 分期)。Cardcom 的 v11 API 架构将 CreditType 默认设为 1,并且只明确列出 6(“6 - israel credit for multiple payments”)。

类型希伯来语CreditType工作方式谁支付利息
单次付款תשלום אחד1立即扣款一次不适用
信用分期קרדיט6发卡机构提供融资;客户分期还款并支付利息客户向发卡机构支付利息
常规分期תשלומים8商户收到全额,发卡机构每月向客户收款默认情况下客户不支付利息
“免息分期”תשלומים ללא ריבית8与常规分期使用相同的线缆值;商户以商业方式承担融资成本商户承担成本

不要发送 2、3、4、5 或 9。 此技能的早期版本根据第三方集成库,而非供应商文档,将这些值列为“规范枚举”。当前供应商参数表中没有这些值,Cardcom 的架构也没有包含其中任何值;Tranzila 自己的参数表只公开了 1、6 和 8,其响应代码表对其他任何值均返回 017 Unauthorized credit type for this transaction。尤其是,不存在可路由的“俱乐部分期 = 9”:俱乐部和发卡机构忠诚度计划属于终端和发卡机构配置,而不是代码可选择的 CreditType。如果商户需要此功能,应与收单机构沟通,而不是设置字段值。

实现说明:

  • 分期上限属于终端和发卡机构配置,并非网关常量。应向收单机构询问具体终端的上限,而不要将其硬编码。
  • 发卡机构信用分期(CreditType 6)有自己的最低分期数限制。Tranzila 文档规定信用分期至少为 3 期,并指出持卡人必须拥有发卡机构提供的信用授权,因此信用分期交易可能因与网关无关的原因失败。每期最低金额由发卡机构和终端配置;各网关技能会提供这些信息,不要在此处硬编码。
  • “免息分期”是与收单机构之间的商业安排,而非线缆值:客户无需支付利息,因为融资成本由商户承担。不要报出补贴比例,该比例因商户而异,且未公开。
  • API V2 接口可能完全不使用 cred_type。例如,Tranzila 使用 payment_plan 和 installments_number。在照搬参数名称之前,先确定所使用的接口。
步骤 6:实现回退和重试逻辑

设计具有韧性的支付处理流程:

# 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)

有三条规则,第三条会造成经济损失:

  1. 银行拒绝(余额不足、卡片被盗)时,绝不要改用其他网关重试。结果不会改变,而且速度检查会开始标记这张卡。
  2. 仅当有确凿证据表明请求从未到达网关(DNS 故障、连接被拒绝、TLS 故障)时才重试。
  3. 绝不要将未经验证的网关加入故障转移链。 只有确认以下三件事后,网关才能列入优先级列表:金额字段使用什么单位、哪个字段用于传递你的幂等键,以及如何撤销该网关上的扣款。若转移到一个金额单位尚未确认的网关,回退路径本身就会变成金额扩大 100 倍的 bug。
  4. 超时不等于失败。 它是第三种结果;将其故障转移是经典的重复扣款 bug。Pelecard 通过自己的状态码明确区分了这些情况:302 表示“扣款成功,但商户无响应”,也就是说钱确实已经划转;301 和 303 则表示结果确实未知。只有 302 确认发生了扣款。若编排器将这三种状态都映射为“失败,尝试下一个网关”,那么每个首次尝试响应较慢的客户都会被重复扣款。先暂存交易,使用网关自己的查询接口核实,然后再做决定。

暂存只是工作的一半;上线重试前,先设计好对账流程。 park_for_reconciliation 不是留待以后填写的存根,它负责将未知结果转化为资金或商品。以下三件事必须在首次扣款前就绪,而不是等到第一次事故发生后:

  • 在调用前持久化一个密钥,以便之后通过它查询交易。 请求超时时,网关自己的交易 ID 还不存在,因此不能用它作为该密钥。使用由你控制的透传字段。这里有两个规则相反的不同字段,混淆它们本身就会导致重复扣款。 关联标识符用于标识一次尝试,因此每次尝试都必须唯一;对账时按订单 ID 前缀匹配:Pelecard 的 ParamX 就是这样使用的,例如 <order-id>-<attempt-n>。幂等键用于让网关去重,因此同一笔逻辑扣款必须保持稳定,并在每次重试时原样重新发送:Grow 的 transactionUniqueIdentifier 就是这样使用的;每次尝试都生成新的值,会破坏 Grow 自己的重复检测机制。生成密钥前,请先在各网关对应的技能说明中确认该字段属于哪一种。
  • 重试时使用相同的密钥。 超时或 5xx 错误后,使用新的幂等键重新发送请求,正是导致客户被扣两次款的原因。请原样重发同一个密钥,或先完成对账。
  • 针对每个网关的查询方式、轮询时限和终态决策(扣款并发货、退款,或升级给人工处理)。references/gateway-matrix.md 列出了各网关的查询调用。在时限结束后仍然未知的结果属于运维任务,必须有人负责处理。Pelecard 对这三种状态的原文说明可通过 /services/GetErrorMessageEn 查询,无需凭据,参数为 errorCode;未分配的代码会返回 Pelecard 拼写错误的 UnkownError,这可以用来区分真实代码和臆造的代码。在编写任何重试逻辑之前,请根据每个目标网关自己的代码表,为其建立等效的三状态映射。
步骤 7:处理授权、撤销、退款和部分退款

授权不等于销售,撤销授权也不等于退款。 这些支付网关大多支持两步流程:现在冻结金额,稍后再扣款(Pelecard ActionType J5、Tranzila verify_mode=5 / tranmode=V、Cardcom Operation: SuspendedDeal)。忽略这一点会导致两个问题。编排器将授权记录为“已批准”后,如果从未发起扣款,就收不到款项,而冻结也会悄然过期。编排器如果用退款调用来解除冻结,就是调用了错误的操作:Tranzila 使用 txn_type=reversal 而不是 cancel 来释放额度;Pelecard 使用 /services/DeleteIshur 而不是 DeleteTran 来释放额度。在请求中携带 action(步骤 3),并在适配器中提供 capture() 和 release(),与 void() 和 refund() 并列。各支付网关对应的调用见 references/gateway-matrix.md。

对已扣款项进行冲正,是编排器独有的职责,也是各支付网关之间差异最大的操作。这两种操作在机制上截然不同,选错会让商家承担双重成本。

  • 撤销(ביטול)会在交易结算前取消交易。交易不会进入清算网络,因此不产生清算费用,客户通常也不会看到扣款。
  • 退款 / 贷记(זיכוי)是针对已结算扣款发起的一笔新的反向交易。该交易会单独进行清算,因此商家需要为两个方向的交易都支付清算费用。

分界点是结算,而结算来得很快:大多数终端会在同一营业日关闭批次。在忽略这一点的情况下,按成本优化的路由器(步骤 4)优化的只是账本的一半。

支付网关撤销,结算前退款 / 贷记,结算后部分退款
Cardcom使用 CancelOnly: true 调用 Transactions/RefundByTransactionId(“仅取消,且须在交易入账前”)调用同一端点,但不带 CancelOnly支持,使用 PartialSum。AllowMultipleRefunds 默认为 false
Tranzila在 API V2 中使用 txn_type=cancel旧版 tranmode=C{index}(需要单独的 CreditPass 凭据);在 API V2 中使用 txn_type=credit支持
PayMe未验证存在 api/refund-sale未验证
Meshulam (Grow)不支持银行卡撤销。仅 Bit 支持:cancelBitTransaction,需要 pageCode;请在 grow-payment-gateway 中确认完整参数集refundTransaction,金额字段为 refundSum,不接受 sum支持,但交易结算或传输后会被阻止;资金到达银行后则需要人工批准。Grow 会为每种情况返回各自的错误代码;见 grow-payment-gateway
iCredit未验证未验证未验证
Pelecard在批次仍开放时调用 /services/DeleteTran。/services/GetTransDataBeforeBc 会显示哪些交易仍可撤销/PaymentGW 上没有退款端点。贷记是一笔新的反向交易;不要猜测其路径不支持。DeleteTran 只能针对整笔交易操作

需要考虑以下三点:

  • **切勿假设操作对称。**退款调用并不是在扣款调用上加一个负号。尤其是 Pelecard,发送负数总额更可能导致再次扣款,而不是撤销交易。
  • 所有支持 Bit 的支付网关都应使用单独的 Bit 退款流程,因为 Bit 是账户间主动转账,而非银行卡授权。
  • **表格中的“未验证”单元格代表真实的信息缺口,并不表示默认不支持。**对于 PayMe 和 iCredit,请先向供应商确认,再基于假设构建撤销流程。
  • **为每笔交易维护自己的累计退款账本,并在调用前拒绝超额退款。**各支付网关在是否阻止超额退款方面并不一致:Cardcom 默认将 AllowMultipleRefunds 设为 false,而 Grow 接受连续部分退款,直到累计金额超过原金额。依赖支付网关拦截意味着六家中总有一家做不到。
  • **退款往往需要扣款时不需要的第二项凭证。**Tranzila 的旧版信用退款路径需要单独的 CreditPass,且必须在终端上启用;Cardcom 的 RefundByTransactionId 则需要 ApiPassword,而其扣款端点完全不需要此凭证。未经端到端测试的撤销流程通常恰恰会在这里失败。
  • **分期销售的退款因收单机构而异,不能简单按期数均分金额。**发卡机构是否会停止收取剩余款项,以及商家已收取的全额款项如何追回,都会因收单机构而异。对于分期付款交易,上线退款流程前务必确认这些情况。
  • 退款期限是法律规定的,而非支付网关规定的,且义务在于发起退款,而非确保客户收到退款。由于入账退款按发卡机构自己的周期结算,排队等待处理的退款必须受到监控,不能发起后就置之不理。pelecard-payment-gateway 提供了有来源依据的《消费者保护法》远程销售规则摘要;请将其视为摘要,而非法律原文。

运行 python scripts/compare_gateways.py --refunds 可打印此矩阵。

第 8 步:确保符合法规要求

遵守以色列银行和 Shva 的法规:

法规希伯来语要求影响
交易数据保留שמירת נתוני עסקאות在法定期限内保留交易记录保留期限由税务和簿记法律规定,而非支付网关或 PCI 规定。设计清理策略前,请向税务局或商家的会计师确认当前期限;不要直接照搬集成指南中的数字
PCI DSSתקן PCI卡数据安全使用令牌化,切勿存储完整卡号。以色列收单机构可能会在国际标准基础上增加本地要求
消费者保护הגנת הצרכן远程销售中的法定取消权取消期限、退款期限、取消费用的任何上限,以及特定消费者群体是否享有更长的期限,均由《消费者保护法》及其条例规定,而非支付网关。本技能特意不编码其中任何规定。围绕这些规定构建退款流程前,请向消费者保护局或法律顾问逐项确认
反欺诈מניעת הונאה3D Secure、频率检查为 CNP 交易实施 3DS2

示例

示例 1:多支付网关配置

用户说:“我需要支持分期付款,并在某个支付网关宕机时进行故障转移” 操作:

  1. 评估:分期付款(常规和信用)以及高可用性。
  2. 比较:运行 python scripts/compare_gateways.py --features installments,recurring。选择两个单元格均有文档记录、而非未验证的支付网关。
  3. 设计:统一的金额单位、映射到各支付网关自身字段的幂等键,以及三态结果。
  4. 实施:将分期销售路由至主支付网关,仅在能够确认连接失败时才进行故障转移。 结果:具备主备路由和分期处理功能的编排层
示例 2:添加 Bit

用户说:“我们使用 Tranzila,但想添加 PayMe,以便接受 Bit” 操作:

  1. 首先质疑这个前提。Tranzila 已通过专用的 Bit API 和 iframe 上的 bit_pay=1 标志支持 Bit。Cardcom(UrlToBit)、Grow 和 Pelecard 也支持。为了获得当前网关已有的功能而添加整个第二个网关,会带来大量集成工作,却毫无必要。
  2. 检查终端,而不是网关。收单机构必须在商户的终端上启用 Bit。“API 支持”与“你的终端已开通此功能”是两个不同的问题,真正会造成阻碍的是后者。
  3. 在将交易路由到 Bit 之前,先从收单机构和对应网关的技能文档中确认 Bit 自身的限制,而不是依赖此表。Bit 是账户到账户的推送支付,因此适用的限制与银行卡不同:预计会有分期付款、币种、单笔交易金额和月交易金额方面的限制,其撤销路径也可能与银行卡不同。请获取该商户自身终端的实际数值。把 Bit 当作布尔值处理的路由器,会发送 Bit 无法处理的交易,并在最糟糕的时刻失败。
  4. 只有在终端确实无法开通 Bit 时,才需要第二个网关;这时做出的就是商业决策,而非技术决策。 结果:大多数情况下,只需打一次电话开通,而不必进行第二次集成
示例 3:成本比较

用户说:“对于我们每天 500 笔、平均金额为 200 NIS 的交易,哪个网关最便宜?” 操作:

  1. 明确说明,无法根据公开信息回答这个问题。六家网关都没有公布费率表,费率要根据交易量、行业和拒付情况逐个商户协商。任何直接列出胜出者的表格都是在捏造输入数据。
  2. 提供真正决定结果的信息:用于协商的月交易额,此处为 500 * 30 * 200 = 3,000,000 NIS,并建议在该交易量下向每家网关索取书面报价。
  3. 列出折扣费率掩盖的成本,这些成本在这种规模下通常影响更大:由商户承担的免息分期利息、结算周期的营运资金成本、每笔退款和拒付的费用,以及先扣款再退款的组合会结算两次,而撤销则一次也不结算。
  4. 对于可明确了解的能力,进行比较:python scripts/compare_gateways.py --example。 结果:获得谈判筹码和能力比较,而不是错误的排名

附带资源

脚本
  • scripts/compare_gateways.py -- 打印六家网关的能力矩阵,以及撤销/退款/部分退款矩阵。根据有文档记录的功能进行筛选;遇到无法识别的功能名称时会拒绝执行,而不是静默匹配所有内容。此脚本有意不估算成本。运行:python scripts/compare_gateways.py --help、--example、--refunds、--features bit
参考资料
  • references/gateway-matrix.md -- 跨网关比较:API 格式和基础 URL、身份验证方案、分期付款支持、Bit 和钱包支持、金额单位、授权/捕获/释放调用、撤销/退款/部分退款矩阵、用于对账的各网关交易查询方式,以及决定资金是否转移的 Pelecard 状态码。此文档有意不包含费用表。在评估或切换网关时请查阅。

参考链接

来源URL检查内容
Cardcom v11 API schemahttps://secure.cardcom.solutions/swagger/v11/swagger.json权威字段列表:CreditType 的取值、UrlToBit,以及包含 PartialSum 和 CancelOnly 的 RefundByTransactionId 请求
Tranzila 文档https://docs.tranzila.com/确认你使用的是四种接口中的哪一种、已发布的 cred_type 取值,以及 API V2 的四个请求头 HMAC 身份验证
PayMehttps://payme.io/PayMe 未公开可直接访问的开发者文档 URL(payme.io/developers 会通过 301 重定向到主页,docs.paymeservice.com 则返回 Cloudflare 1014)。请直接向 PayMe 索取 API 参考资料,并在首次扣款前与其确认金额单位和退款参数名称
Meshulam (Grow) 参考资料https://developers.grow.business/reference/overview生产环境基础 URL 为 secure.meshulam.co.il、退款时使用 refundSum,以及限制部分退款的错误代码
Rivhit (iCredit)https://www.rivhit.co.il/iCredit 是 Rivhit 的产品。其 API 是 WCF .svc 服务;退款接口未公开文档
Pelecard 服务索引https://gateway21.pelecard.biz/services实际使用的端点列表,包括 DeleteTran 和 GetTransDataBeforeBc。gateway20 和 gateway21 代表不同代际,并非不同环境
以色列银行:支付系统监督https://www.boi.org.il/en/economic-roles/supervision-and-regulation/payment-systems-oversight/哪些系统属于“受监管的支付系统”,因而处于以色列银行的监管范围内;以及监管指令和接入支付系统的要求。请注意,此页面并未明确由谁监管非银行支付服务提供商;应直接向监管机构确认,不要据此自行推断

注意事项

  • 在上述任何一家网关中,HTTP 200 都不代表付款成功。 Tranzila 在其响应代码表顶部明确说明:这些是应用级错误代码,而响应代码会是成功(200)。Grow 会返回 200,同时包含 status: "0" 和一个 err 对象。Pelecard 的 init 会返回 200,同时包含 Error.ErrCode。Tranzila 的旧版 CGI 则会返回一个 HTML 提示页面,而非 JSON。规范化这些响应是编排器的职责;Agent 编写的集成最常见的错误,就是将失败的扣款报告为已付款。应解析响应正文,绝不要只看状态码。
  • 此技能中的每家以色列支付网关(Cardcom、Tranzila、PayMe、Meshulam、iCredit、Pelecard)都有完全不同的 API 格式:Cardcom 使用 JSON;Tranzila 在旧版接口上使用表单编码的键值对,在 API V2 上使用 JSON;Meshulam 使用 multipart/form-data,并通过单独的页面代码参数;iCredit 则是 WCF .svc 服务。Agent 可能会把一家网关的格式套用到另一家。
  • 主机名不是环境切换开关。 对于 Pelecard,gateway20 和 gateway21 是不同代的网关,返回的响应逐字节相同;决定调用是测试还是实际扣款的是终端编号和凭据。如果 Agent“切换到沙盒主机”后使用测试数据,就会产生真实扣款。
  • 幂等键背后必须对应网关字段。 每家网关都有自己的重复检测机制和重复错误代码。如果编排器生成的键从未映射到网关自身的字段,那它就只是摆设:网关会欣然接受同一笔扣款两次。在 Grow 上,重复检测字段是 transactionUniqueIdentifier;对同一笔逻辑扣款的重试,该字段必须保持不变。在 Pelecard 上,透传参数 ParamX 是每次尝试的关联标识,而不是去重键;去重依赖返回的交易 ID。两者不可互换。对于其他网关,在依赖重试安全性之前,应查阅对应网关的技能,确认相关字段和重复错误代码。
  • 以色列的支付处理要求企业在以色列注册(osek murshe/patur)。Agent 可能会在确认企业已向税务局完成适当注册之前,就建议开通支付处理。
  • 在大多数网关中,Bit(以色列主流移动支付)的退款接口与信用卡退款接口不同。Agent 可能会对 Bit 交易使用信用卡退款接口。
  • 已保存的卡令牌无法跨网关通用。 Cardcom 令牌、Tranzila 的 TranzilaTK 和 PayMe 的 buyer_key 都是专有令牌,彼此不兼容,且只能用于签发该令牌的网关。这意味着此技能中的备用/故障切换模式仅适用于首次扣款和一次性交易。令牌化扣款或定期扣款无法切换到第二家网关;定期扣款只能在发起扣款的网关上进行。若要真正实现已保存卡片的跨网关容错,需要网络令牌化或多保险库策略,而不是使用此令牌字段。Agent 可能会错误地认为,已存储的令牌可用于优先级列表中的任何网关。
  • 网关客户端侧的“成功”状态始终可能被伪造,务必在服务端重新验证。 不要相信浏览器重定向或客户端提交的状态能证明付款成功。履行订单前,应通过服务器到服务器查询向发卡网关确认每笔交易(对于 Meshulam/Grow,使用 getPaymentProcessInfo/approve;对于其他网关,使用网关的交易查询,或验证 webhook 签名/通知)。必须按照各网关各自的机制验证 webhook/IPN 载荷,不能互换使用。
  • 3DS2 是异步挑战流程,不是一次调用。 无卡交易可能返回“无感验证”(内联批准),也可能触发挑战,将客户重定向到发卡机构,然后通过回调 URL 返回。编排器必须持久化交易信息,在回调时恢复流程,并确保挑战返回仍由发起挑战的同一家网关处理(3DS 会话限定于特定网关)。Agent 可能会将 3DS 建模为同步布尔值,导致挑战中的交易丢失。

故障排查

错误:"不支持此分期类型" / Tranzila 017

原因:通常是 CreditType 值本身有问题,而非网关问题。所有供应商公布的取值只有 1、6 和 8;2、3、4、5 和 9 虽然在第三方库中流传,但目前没有任何供应商的参数表列出这些值,Tranzila 会对它们返回 017 Unauthorized credit type for this transaction。 解决方案:常规分期请发送 8,由发卡机构提供融资的信用则发送 6。如果商户确实需要俱乐部或忠诚度计划,这属于终端和收单机构配置,而非字段值,因此应将请求转交给收单机构,而不是改用其他数字重试。已验证的否定结论:更换网关无法解决被拒绝的 CreditType,因为该值在到达网关之前就已被拒绝。

错误:"故障转移时网关超时"

原因:要么所有已配置的网关都处于性能下降状态,要么(更常见得多)某个网关超时,而编排器将其视为失败并转向下一个网关。 解决方案:区分这两种情况。超时意味着结果未知,应暂停处理以便对账,而不是故障转移(步骤 6)。只有能够确认的连接失败才足以转向下一个网关。明确这一区别后,再添加带健康检查的断路器,并在性能下降时发出警报。

错误:"检测到重复交易"

原因:重试逻辑重复发送了同一笔付款,通常是因为将超时当成了失败。 解决方案:将你的幂等键映射到网关自身的重复检测字段,并将超时视为结果未知,而非失败(步骤 6)。重试任何操作前,先查询该笔交易。切勿重试银行拒绝的交易。

错误:退款调用返回成功,但客户表示款项并未退回

原因:最可能的原因(并非已确认适用于所有网关)是退款金额已进入队列但尚未完成,或对已结算的交易执行了撤销。已验证的否定结论:在 Pelecard 上,这并非 /PaymentGW 退款端点故障,因为该端点并不存在。 解决方案:选择调用方式前,先确认原交易处于结算的哪一阶段(步骤 7),并通过网关自己的交易查询进行对账,而不要依赖撤销调用本身的响应。

评论

支持 Markdown