PAYATHON 2026

如何为慈善网站设置用户授权的自动扣款服务?

支付小周

结论

不要自行保存银行卡号,也不要直接对接各家银行。更合适的做法是接入支持“定期付款”或“直接扣款”的持牌支付服务商,由服务商完成用户身份验证、付款授权、扣款凭证保存和每月自动扣款。

常见方案分为两类:

  • 银行卡定期捐赠:用户首次填写卡信息并授权,支付服务商保存支付凭证,此后按月自动扣款。
  • 银行账户自动扣款:通过 ACH Direct Debit、SEPA Direct Debit、Bacs Direct Debit 等机制建立 mandate(扣款授权),银行按照约定处理后续付款。

具体能使用哪种方案,取决于慈善组织的注册地、捐赠者所在国家、收款币种,以及支付服务商是否接入当地的自动扣款网络。

为什么不能让网站直接操作银行扣款

自动扣款并不是简单的定时转账,通常需要处理以下事项:

  • 明确展示扣款金额、频率和生效时间;
  • 获得并保存用户授权;
  • 验证银行卡或银行账户;
  • 按规定发送扣款前通知;
  • 处理强客户认证、争议、退款和授权撤销;
  • 安全保存支付凭证;
  • 遵守 PCI DSS、隐私保护、反洗钱和当地慈善募款规定。

如果网站直接收集并保存完整卡号,PCI DSS 的合规范围会大幅扩大。使用支付服务商提供的托管结账页或安全支付组件,可以让敏感支付信息不经过网站自己的服务器。

推荐的实现流程

1. 选择支持定期捐赠的支付服务商

选择时应确认服务商是否:

  • 允许慈善组织开户;
  • 支持目标国家和币种;
  • 支持 recurring payments、subscriptions 或 direct debit;
  • 提供托管结账页或前端安全支付组件;
  • 支持 webhook;
  • 允许用户取消授权,并能处理退款和付款失败重试;
  • 可以导出对账、捐赠记录和费用明细。

Stripe、PayPal、GoCardless 等服务商通常具备这些能力,但产品覆盖范围和慈善费率会发生变化,应以组织所在地对应的官方产品说明为准。

2. 明确定期捐赠条款

用户确认前,页面至少应说明:

  • 每次扣款金额;
  • 扣款币种;
  • 扣款周期,例如每月一次;
  • 首次扣款日期;
  • 后续扣款日期或计算规则;
  • 取消定期捐赠的方法;
  • 退款和争议的处理方式;
  • 收款组织的名称和联系方式;
  • 隐私政策和支付数据的处理方式。

如果扣款金额会变化,就不能按固定金额订阅处理。可变金额扣款通常需要额外通知或不同类型的授权。

3. 在服务端创建定期付款会话

下面以类似 Stripe Checkout 的服务端流程说明整体架构。实际字段、支付方式和可用地区应以当前 SDK 和账户配置为准。

import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

app.post("/api/donations/monthly", async (req, res) => {
  const { priceId } = req.body;

  // priceId 必须在服务端校验,不能允许浏览器任意指定金额。
  const allowedPriceIds = new Set([
    process.env.MONTHLY_DONATION_PRICE_ID
  ]);

  if (!allowedPriceIds.has(priceId)) {
    return res.status(400).json({ error: "Invalid donation option" });
  }

  const session = await stripe.checkout.sessions.create({
    mode: "subscription",
    line_items: [
      {
        price: priceId,
        quantity: 1
      }
    ],
    success_url:
      "https://charity.example/donation/success?session_id={CHECKOUT_SESSION_ID}",
    cancel_url:
      "https://charity.example/donation/cancelled"
  });

  res.json({ url: session.url });
});

浏览器只负责请求创建会话,然后跳转到支付服务商的页面:

async function startMonthlyDonation(priceId) {
  const response = await fetch("/api/donations/monthly", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({ priceId })
  });

  if (!response.ok) {
    throw new Error("Unable to create donation session");
  }

  const { url } = await response.json();
  window.location.assign(url);
}

这里使用预先在支付平台创建的 priceId,不直接采用前端提交的金额,以免用户篡改价格。如果允许用户自定义金额,必须在服务端校验币种、最小金额、最大金额和整数精度,再创建相应的付款计划。

4. 使用 webhook 确认付款状态

不能根据用户是否打开“支付成功”页面来判断捐赠是否成功。用户可能会关闭浏览器,银行账户扣款也可能仍在异步处理中。付款结果应以通过服务商签名验证的 webhook 事件为准。

app.post(
  "/webhooks/payment-provider",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    let event;

    try {
      event = stripe.webhooks.constructEvent(
        req.body,
        req.headers["stripe-signature"],
        process.env.STRIPE_WEBHOOK_SECRET
      );
    } catch {
      return res.status(400).send("Invalid webhook signature");
    }

    switch (event.type) {
      case "invoice.paid":
        await recordSuccessfulDonation(event.data.object);
        break;

      case "invoice.payment_failed":
        await recordFailedDonation(event.data.object);
        break;

      case "customer.subscription.deleted":
        await markRecurringDonationCancelled(event.data.object);
        break;
    }

    res.sendStatus(200);
  }
);

不同支付方式和服务商使用的事件名称可能不同。例如,银行直接扣款可能先进入 processing 状态,几天后才会成功或失败。因此,数据库不能只有“已支付”和“未支付”两种状态,而应支持类似以下状态:

pending
processing
paid
failed
refunded
disputed
cancelled

Webhook 还需要做幂等处理。支付服务商可能重复发送同一个事件,因此应保存事件 ID,确保同一笔捐赠不会重复入账,也不会重复发送收据。

5. 提供取消和付款方式管理入口

用户应能方便地完成以下操作:

  • 取消后续自动扣款;
  • 更新银行卡或银行账户;
  • 查看历史捐赠;
  • 下载收据;
  • 修改定期捐赠金额;
  • 联系组织处理退款或错误扣款。

可以使用支付服务商提供的 customer portal,也可以自行制作管理页面,但敏感支付信息仍应交由支付服务商托管。

银行扣款与银行卡扣款的区别

方式用户体验到账速度常见特点
银行卡定期付款首次输入卡信息并验证通常较快覆盖广,但卡可能过期或被拒付
ACH Direct Debit授权美国银行账户扣款通常较慢费用可能较低,付款结果具有延迟
SEPA Direct Debit授权欧元区账户扣款通常较慢需要保存 mandate,并遵守通知和退款规则
Bacs Direct Debit授权英国银行账户扣款通常较慢有特定的授权、通知和担保规则

“每个月月底扣款”需要明确定义,因为每个月的天数不同。可以选择:

  • 每月最后一个自然日;
  • 每月固定日期,例如 28 日;
  • 用户首次捐赠日期所对应的每月日期。

如果支付服务商无法直接设置“每月最后一天”,可以由服务端定时创建付款,但这样会增加失败重试、幂等控制和时区处理的复杂度。对于慈善网站,固定在每月 28 日通常更容易实现。

安全与合规注意事项

  • 不要在日志、数据库或错误报告中记录完整卡号、CVV 或银行登录凭据。
  • API Secret Key 和 webhook secret 只能保存在服务端环境变量中。
  • 全站使用 HTTPS,并为管理后台启用多因素认证。
  • 金额、币种和捐赠计划不能只在前端校验。
  • 验证 webhook 签名,并防止重复处理。
  • 建立针对扣款失败、退款、拒付和授权撤销的处理流程。
  • 明确区分一次性捐赠和定期捐赠,不能默认勾选定期扣款。
  • 保存用户接受的条款版本、授权时间,以及支付服务商返回的 mandate 或订阅标识。
  • 发送“可抵税收据”前,应先确认当地法律要求。普通付款确认不一定是合规的慈善捐赠凭证。

最终架构应由网站展示捐赠方案并管理捐赠记录,支付服务商采集付款信息、保存授权和执行定期扣款,网站通过 webhook 同步真实的付款状态。这样既能让用户在线授权,也能避免网站自行承担银行接入和敏感支付数据处理的全部风险。

备注:内容仅供参考。