PAYATHON 2026

加拿大网站的Online merchant account选择

支付阿杰

结论

对于商品不足 10 件的加拿大网站,通常没有必要单独申请传统的 Online merchant account。使用 Stripe 或 PayPal 这类支付服务商(PSP)更合适:

  • 想快速上线,减少结账流程的开发工作,可以选择 Stripe Checkout。
  • 需要自定义支付页面,可以选择 Stripe Elements。
  • 想覆盖习惯使用 PayPal 的顾客,可以同时提供 PayPal Checkout。
  • 如果线下已经在使用 Square,可以考虑继续使用 Square Online Payments。

Google Checkout 已经停止服务,不能再用于加拿大网站。各平台的可用功能、行业限制和费率会因商户所在地、交易币种及账户类型而异,具体应以支付平台面向加拿大商户的最新协议为准。

为什么小型网站更适合 PSP

传统 merchant account 通常涉及收单机构、支付网关、月费、合同期限和单独的技术集成。对于销量不高、商品数量很少的网站,这些固定成本和管理工作往往得不偿失。

Stripe、PayPal 等服务把网关、收单和风控集中在一个平台内,通常可以:

  • 避免自行保存信用卡号;
  • 接受 Visa、Mastercard 等信用卡;
  • 减少前期部署工作;
  • 使用托管结账页或嵌入式前端组件;
  • 处理退款、争议和交易查询;
  • 通过 API 接入现有网站。

选择平台时,不能只看每笔交易的百分比费率。固定手续费、退款费、拒付费、跨境交易费、货币转换费、提现周期和账户冻结规则也需要逐项核对。对于交易量较低的网站,月费尤其值得关注,因为固定费用对总成本的影响可能超过交易费。

推荐的实现方式

方案一:Stripe Checkout

Stripe Checkout 是由 Stripe 托管的结账页面。网站负责展示商品,顾客付款时会跳转到 Stripe 提供的页面,付款完成后再返回网站。

这种方式适合希望尽快上线,又不想直接处理银行卡数据的项目。页面颜色、Logo、商品信息和部分文案可以配置,但布局的自定义空间有限。

基本流程如下:

  1. 顾客在网站选择商品。
  2. 浏览器请求服务器创建 Checkout Session。
  3. 服务器向 Stripe 提交商品和金额。
  4. 浏览器跳转到 Stripe 返回的结账地址。
  5. 支付完成后,Stripe 通过 webhook 通知服务器。
  6. 服务器验证事件并更新订单状态。

下面是 Node.js/Express 示例。实际项目必须从数据库读取商品价格,不能信任浏览器传来的金额。

import express from "express";
import Stripe from "stripe";

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

app.use(express.json());

const products = {
  mug: {
    name: "Coffee Mug",
    unitAmount: 2500
  },
  shirt: {
    name: "T-Shirt",
    unitAmount: 4000
  }
};

app.post("/api/create-checkout-session", async (req, res) => {
  const product = products[req.body.productId];

  if (!product) {
    return res.status(400).json({ error: "Invalid product" });
  }

  const session = await stripe.checkout.sessions.create({
    mode: "payment",
    line_items: [
      {
        price_data: {
          currency: "cad",
          product_data: {
            name: product.name
          },
          unit_amount: product.unitAmount
        },
        quantity: 1
      }
    ],
    success_url: "https://example.ca/order/success?session_id={CHECKOUT_SESSION_ID}",
    cancel_url: "https://example.ca/cart"
  });

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

app.listen(3000);

前端只需调用服务器接口,然后跳转到返回的地址:

<button id="buy-button">立即购买</button>

<script>
  document
    .getElementById("buy-button")
    .addEventListener("click", async () => {
      const response = await fetch("/api/create-checkout-session", {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify({
          productId: "mug"
        })
      });

      if (!response.ok) {
        alert("暂时无法创建订单,请稍后重试。");
        return;
      }

      const { url } = await response.json();
      window.location.href = url;
    });
</script>

示例中的 unit_amount 以货币的最小单位计价。2500 表示 CAD 25.00,不是 CAD 2,500。

方案二:Stripe Elements

如果支付表单需要保留网站自己的视觉风格,可以使用 Stripe Elements。银行卡数据应由 Stripe 提供的组件采集,不应经过自己的服务器。

典型流程如下:

  1. 服务器根据订单创建 PaymentIntent。
  2. 前端取得 client_secret。
  3. Stripe Elements 收集付款信息。
  4. 前端调用 Stripe.js 确认付款。
  5. 服务器通过 webhook 确认最终结果。

服务器端示例:

app.post("/api/create-payment-intent", async (req, res) => {
  const product = products[req.body.productId];

  if (!product) {
    return res.status(400).json({ error: "Invalid product" });
  }

  const paymentIntent = await stripe.paymentIntents.create({
    amount: product.unitAmount,
    currency: "cad",
    automatic_payment_methods: {
      enabled: true
    },
    metadata: {
      productId: req.body.productId
    }
  });

  res.json({
    clientSecret: paymentIntent.client_secret
  });
});

Elements 比托管结账页更灵活,但开发和测试工作也会增加。订单状态、重复提交、错误提示和付款后的页面逻辑都需要自行处理。

方案三:PayPal Checkout

PayPal Checkout 可以作为信用卡支付的补充,方便拥有 PayPal 账户的顾客付款。它也提供托管组件和服务器端 API。

顾客能否在不登录 PayPal 账户的情况下直接使用信用卡,可能受商户账户、地区、风控和顾客所在地影响,因此不能假定所有顾客都会看到相同的付款选项。比较不同方案时,还要单独核对货币转换和跨境交易成本。

上线时不能省略的环节

使用 webhook 确认支付

不能因为顾客进入 success_url,就直接把订单标记为已付款。返回页面可能被伪造,顾客也可能已经完成付款,却没有成功返回网站。

服务器应先验证支付平台发送的 webhook 签名,再根据事件更新订单:

app.post(
  "/webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    let event;

    try {
      event = stripe.webhooks.constructEvent(
        req.body,
        req.headers["stripe-signature"],
        process.env.STRIPE_WEBHOOK_SECRET
      );
    } catch (error) {
      return res.status(400).send(`Webhook Error: ${error.message}`);
    }

    if (event.type === "checkout.session.completed") {
      const session = event.data.object;

      // 使用 session.id 做幂等检查,再更新订单状态。
      // 不要在这里重复发货或重复扣减库存。
    }

    res.sendStatus(200);
  }
);

在 Express 中,webhook 路由必须保留原始请求体。路由的放置顺序需要谨慎,不能先让全局 express.json() 改写该路由的请求体。

金额必须由服务器决定

浏览器提交的商品 ID、数量和优惠码都可能被篡改。服务器应检查:

  • 商品是否存在并且可以销售;
  • 单价是否来自可信数据库;
  • 数量是否在允许范围内;
  • 折扣是否有效;
  • 税费和运费是否正确;
  • 最终币种是否符合预期。

不要让前端直接提交 amount,再把这个值原样传给支付 API。

降低 PCI DSS 范围

即使使用托管结账页,商户通常仍需承担相应的 PCI DSS 责任。完整卡号、有效期和 CVC 都不应写入自己的数据库、日志、分析系统或错误报告。

托管页面通常最容易控制合规范围。完全自定义的银行卡表单则会大幅增加安全和合规方面的负担。

处理加拿大税费

支付处理器负责收款,并不代表它会自动替商户完成税务义务。是否需要收取 GST/HST、PST 或 QST,要根据商户所在地、顾客所在地、商品类型和注册情况判断。

如果使用支付平台的自动税务功能,还要确认它是否覆盖实际销售地区和商品类别。税务判断不明确时,应咨询熟悉加拿大电商业务的会计或税务专业人士。

准备退款与争议处理

上线前应弄清楚:

  • 退款时是否退还原交易手续费;
  • 发生拒付时会收取哪些费用;
  • 需要保留哪些物流或交付凭证;
  • 数字商品如何证明已经交付;
  • 账户余额不足时如何处理退款;
  • 平台是否会设置准备金或延迟结算。

这些条款对实际成本的影响,往往比页面上显示的单笔费率更大。

实际选择建议

对于商品较少的网站,可以先使用 Stripe Checkout,再根据需要增加 PayPal Checkout。这样既能减少开发工作,也可以覆盖信用卡和 PayPal 用户。

只有在以下情况下,才有必要优先考虑传统 merchant account:

  • 交易额已经较高,可以谈到明显更低的费率;
  • 需要指定的收单银行或支付网关;
  • 所属行业无法被普通 PSP 接受;
  • 需要复杂的多商户、订阅或资金分账结构;
  • 已有线下收单合同,希望统一结算。

确定平台前,应向候选服务商提交真实的业务类型、预计月交易额、平均客单价、退款率和主要顾客所在国家,请对方确认是否接受这类业务。对于受限制商品、订阅服务、预售、数字产品和跨境业务,不能只根据注册页面判断平台是否可用。

备注:内容仅供参考。