PAYATHON 2026

如何在 Netsuite 中处理 Debit Card 分期付款?

支付阿杰

结论

NetSuite 可以记录 Debit Card 支付的订金,也可以按计划生成后续应收款。不过,分期生成账单和自动从 Debit Card 扣款是两个不同的环节。

Debit Card 通常仍通过信用卡收单通道处理。能否保存卡片并在之后定期扣款,取决于支付服务商、NetSuite 的支付处理配置、发卡地区和持卡人授权规则。仅靠 NetSuite 的标准销售订单功能,无法保证完成自动扣款。

比较稳妥的流程是:

  1. 使用 Sales Order 管理订单。
  2. 将首付款记录为 Customer Deposit。
  3. 通过 Billing Schedule 分期生成 Invoice。
  4. 每期使用支付服务商保存的 token 扣款,或向客户发送付款链接。
  5. 扣款成功后,创建 Customer Payment 并应用到对应账单。

如果支付服务商不支持 Debit Card tokenization 或 merchant-initiated transaction,NetSuite 仍可管理分期应收,但客户需要在每一期主动付款。

Debit Card 和 Credit Card 的处理差异

从 NetSuite 业务单据的角度看,Debit Card 和 Credit Card 的订单流程并非完全不同,主要差异在支付处理环节:

  • Credit Card 通常支持预授权、捕获和保存支付凭证。
  • Debit Card 可能要求即时扣款,并通过 PIN、OTP 或 3-D Secure 完成验证。
  • 部分 Debit Card 不支持持卡人不在场时的后续自动扣款。
  • 卡片凭证应由支付服务商以 token 形式保存。
  • 如果实际使用的是银行账户直接扣款,而非带卡组织标识的 Debit Card,则应采用 ACH、SEPA Direct Debit 等银行扣款方案。

因此,不能只看卡面上的 Debit 或 Credit 来决定流程,还要确认支付服务商实际返回的支付能力。

推荐的业务流程

1. 创建 Sales Order

在 Sales Order 中保存商品、总金额、客户和付款条款。客户支付订金后,不要立即将整笔订单视为已付款。

如果项目需要按交付阶段开票,可以在订单或订单行上配置 Billing Schedule。实际可用字段取决于账户已启用的功能、表单和业务配置。

2. 收取并记录订金

客户首次付款时,可以通过支付页面或已配置的 NetSuite 支付处理服务发起扣款。扣款成功后,将这笔金额记录为与 Sales Order 关联的 Customer Deposit。

Customer Deposit 是客户预收款,并非针对 Invoice 的直接付款。该款项通常先计入客户订金负债账户,之后再应用到相应的 Invoice。

3. 按计划生成 Invoice

使用 Billing Schedule 按日期、比例或固定金额生成后续 Invoice。例如:

  • 签约时支付 20%;
  • 30 天后开具金额为 40% 的 Invoice;
  • 60 天后开具剩余 40% 的 Invoice。

Billing Schedule 只负责确定开票时间和金额,不会自动执行扣款。

4. 处理每期付款

每期 Invoice 到期时,可以选择以下方式:

  • 使用支付服务商保存的 payment token 自动扣款;
  • 向客户发送支付链接,让客户重新完成验证;
  • 财务人员收到外部支付结果后录入 Customer Payment;
  • 使用 ACH 或 Direct Debit 等更适合周期扣款的支付方式。

自动扣款成功后,应创建 Customer Payment,并准确应用到对应的 Invoice。如果扣款失败,不要生成已付款记录。Invoice 应保持未结状态,随后进入催收或重试流程。

SuiteScript 示例

下面的 SuiteScript 2.1 示例用于在支付服务商确认扣款成功后,创建 Customer Payment 并将其应用到指定 Invoice。

这段代码只演示应收账款入账,不会向 Debit Card 发起扣款。实际扣款必须通过已经配置的支付处理服务完成。

/**
 * @NApiVersion 2.1
 * @NScriptType ScheduledScript
 */
define(['N/record'], (record) => {
    const createCustomerPayment = ({
        customerId,
        invoiceId,
        amount,
        externalReference
    }) => {
        const payment = record.create({
            type: record.Type.CUSTOMER_PAYMENT,
            isDynamic: true
        });

        payment.setValue({
            fieldId: 'customer',
            value: customerId
        });

        payment.setValue({
            fieldId: 'payment',
            value: amount
        });

        payment.setValue({
            fieldId: 'memo',
            value: `Gateway transaction: ${externalReference}`
        });

        const lineCount = payment.getLineCount({
            sublistId: 'apply'
        });

        let invoiceFound = false;

        for (let i = 0; i < lineCount; i += 1) {
            payment.selectLine({
                sublistId: 'apply',
                line: i
            });

            const transactionId = payment.getCurrentSublistValue({
                sublistId: 'apply',
                fieldId: 'internalid'
            });

            if (Number(transactionId) === Number(invoiceId)) {
                payment.setCurrentSublistValue({
                    sublistId: 'apply',
                    fieldId: 'apply',
                    value: true
                });

                payment.setCurrentSublistValue({
                    sublistId: 'apply',
                    fieldId: 'amount',
                    value: amount
                });

                payment.commitLine({
                    sublistId: 'apply'
                });

                invoiceFound = true;
                break;
            }
        }

        if (!invoiceFound) {
            throw new Error(
                `Invoice ${invoiceId} is not available for customer ${customerId}`
            );
        }

        return payment.save({
            enableSourcing: true,
            ignoreMandatoryFields: false
        });
    };

    const execute = () => {
        // 参数应来自经过验证的支付成功事件或内部任务队列。
        const result = {
            customerId: 123,
            invoiceId: 456,
            amount: 1000.00,
            externalReference: 'gateway-transaction-id'
        };

        createCustomerPayment(result);
    };

    return { execute };
});

不同账户可能有不同的必填字段、子公司、币种、A/R Account、部门、地点和自定义审批字段,因此上线前需要在 Sandbox 中验证。

如果接入支付服务商的 webhook,还需要验证签名,并通过交易编号实现幂等处理,避免同一通知创建两笔 Customer Payment。

不建议直接用脚本保存或处理卡号

不要在自定义字段、脚本参数、日志或自建数据库中保存以下信息:

  • 完整卡号;
  • CVV;
  • PIN;
  • OTP 或 3-D Secure 验证内容。

卡片凭证应使用支付服务商提供的 token,或由 NetSuite 支持的 Payment Instrument 管理。即使对卡号进行了简单加密,也不代表已经符合 PCI DSS 等合规要求。

实施前需要确认的事项

先向支付服务商确认以下能力:

  • 是否接受目标地区和卡组织的 Debit Card;
  • 是否支持 tokenization;
  • 是否允许后续 merchant-initiated transaction;
  • 首次付款是否必须进行 3-D Secure 或其他持卡人验证;
  • 如何处理 token 失效、换卡和拒付;
  • 是否支持部分退款、重复扣款检测和 webhook;
  • 自动扣款是否需要客户单独授权或签署 mandate。

然后在 NetSuite 中确认:

  • 是否已启用 Customer Deposit、Billing Schedule 和相关支付功能;
  • 支付处理插件或 SuiteApp 是否支持当前业务;
  • 订金应用到 Invoice 时的会计处理是否正确;
  • 如何处理多币种、税费、退款和订单取消;
  • 扣款失败后的重试次数、通知方式和催收规则;
  • 是否需要通过 Map/Reduce 或 Scheduled Script 定期处理到期账单。

更合适的实现选择

如果客户只需要分期开票,不要求自动扣款,可以使用 Sales Order、Customer Deposit、Billing Schedule 和 Invoice。这种方案的实施风险最低。

如果必须自动扣取 Debit Card 款项,应优先选择明确支持 tokenized recurring payments 的 NetSuite 支付服务商,并取得客户授权。如果当地 Debit Card 对周期扣款限制较多,可以改用 ACH、SEPA Direct Debit,或在每期发送付款链接。

NetSuite 负责管理订单、账单和应收状态,卡片凭证与实际扣款则交给合规的支付服务商处理。

备注:内容仅供参考。