加拿大网站的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、商品信息和部分文案可以配置,但布局的自定义空间有限。
基本流程如下:
- 顾客在网站选择商品。
- 浏览器请求服务器创建 Checkout Session。
- 服务器向 Stripe 提交商品和金额。
- 浏览器跳转到 Stripe 返回的结账地址。
- 支付完成后,Stripe 通过 webhook 通知服务器。
- 服务器验证事件并更新订单状态。
下面是 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 提供的组件采集,不应经过自己的服务器。
典型流程如下:
- 服务器根据订单创建
PaymentIntent。 - 前端取得
client_secret。 - Stripe Elements 收集付款信息。
- 前端调用 Stripe.js 确认付款。
- 服务器通过 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 接受;
- 需要复杂的多商户、订阅或资金分账结构;
- 已有线下收单合同,希望统一结算。
确定平台前,应向候选服务商提交真实的业务类型、预计月交易额、平均客单价、退款率和主要顾客所在国家,请对方确认是否接受这类业务。对于受限制商品、订阅服务、预售、数字产品和跨境业务,不能只根据注册页面判断平台是否可用。
备注:内容仅供参考。
