PAYATHON 2026

如何通过 credit card 和 payment gateway 验证用户年龄?

支付老李

结论

通常无法通过 credit card 或普通 payment gateway 验证持卡人是否年满 18 岁。

支付网关主要确认卡片是否有效、交易是否获批,以及付款人是否完成 3-D Secure 等认证。它一般不会向商户提供持卡人的出生日期或年龄。即使付款成功,也不能据此认定用户已经成年。

如果业务需要限制未成年人使用,应把年龄验证和支付处理设为两个独立环节。先通过专门的 identity/age verification 服务完成验证,再允许用户进入支付和游戏流程。

为什么信用卡不能作为年龄证明

信用卡付款无法可靠证明年龄,原因包括:

  • 未成年人可能持有附属卡,或被列为授权用户。
  • 用户可能使用父母、监护人或其他人的卡片。
  • debit card、prepaid card 和 virtual card 的申请条件不等同于成年要求。
  • 卡片可能被借用或盗用,也可能由数字钱包代为提交。
  • payment gateway 通常只能看到经过令牌化或脱敏的卡片信息。
  • CVV、AVS 和 3-D Secure 验证的是支付凭据、账单信息或持卡人身份,不是年龄。

因此,交易通过最多只能说明发卡行批准了付款,不能证明当前用户就是持卡人,也不能证明其已满 18 岁。

推荐的验证流程

较稳妥的流程如下:

  1. 用户注册并填写出生日期。
  2. 服务端根据出生日期进行初步年龄判断。
  3. 对年龄限制严格的用户,调用独立的 age verification 服务。
  4. 服务端保存验证结果和必要的审计信息,不保存多余的身份证件数据。
  5. 年龄验证通过后,再创建 payment session。
  6. payment gateway 完成支付后,通过已验证签名的 webhook 更新订单状态。
  7. 用户同时满足年龄验证通过和支付成功两个条件后,才能开始游戏。

可以把用户状态拆分为两个字段:

age_verification_status:
  pending | approved | rejected | expired

payment_status:
  pending | paid | failed | refunded

授权条件应由服务端判断:

can_start_game =
  age_verification_status == "approved"
  AND payment_status == "paid"

不能仅凭前端传入的 isAdult: true 放行用户。

出生日期的服务端初步判断

下面的示例按自然日期判断用户是否已满 18 岁,避免直接用毫秒数除以 365 天,从而引发闰年或生日临界点错误。

function isAtLeastAge(dateOfBirth, minimumAge, now = new Date()) {
  const birth = new Date(`${dateOfBirth}T00:00:00Z`);

  if (Number.isNaN(birth.getTime())) {
    throw new Error("Invalid date of birth");
  }

  const today = new Date(
    Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())
  );

  const threshold = new Date(
    Date.UTC(
      today.getUTCFullYear() - minimumAge,
      today.getUTCMonth(),
      today.getUTCDate()
    )
  );

  return birth <= threshold;
}

console.log(isAtLeastAge("2008-09-01", 18));

这个判断只能用于 self-declaration 初筛,无法证明用户填写的出生日期真实有效。

接入独立年龄验证服务

各服务商的 API、字段和验证方式差异很大。下面仅展示通用结构,并非任何真实服务商的接口。

import express from "express";

const app = express();
app.use(express.json());

app.post("/api/age-verification/session", async (req, res) => {
  const user = await loadAuthenticatedUser(req);

  if (!user) {
    return res.status(401).json({ error: "Authentication required" });
  }

  if (!isAtLeastAge(user.dateOfBirth, 18)) {
    return res.status(403).json({ error: "Minimum age requirement not met" });
  }

  const verificationSession =
    await ageVerificationProvider.createSession({
      referenceId: user.id,
      minimumAge: 18,
      callbackUrl: "https://example.com/age-verification/callback"
    });

  await saveAgeVerification({
    userId: user.id,
    providerSessionId: verificationSession.id,
    status: "pending"
  });

  res.json({
    redirectUrl: verificationSession.redirectUrl
  });
});

验证服务可能使用身份证件、数据库匹配、数字身份或其他方式。应优先选择只返回用户是否达到年龄门槛的方案,例如返回 over_18: true,以免获取完整的出生日期和证件内容。

处理验证结果

不能因为用户的浏览器跳转到了成功页面,就认定验证已经通过。验证结果应通过服务端回调或 webhook 获取,并校验签名。

app.post(
  "/api/webhooks/age-verification",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    const signature = req.headers["x-provider-signature"];

    let event;

    try {
      event = verifyAgeWebhookSignature(
        req.body,
        signature,
        process.env.AGE_WEBHOOK_SECRET
      );
    } catch {
      return res.status(400).send("Invalid signature");
    }

    if (event.type === "verification.completed") {
      await updateAgeVerification(event.data.sessionId, {
        status: event.data.over18 ? "approved" : "rejected",
        verifiedAt: new Date()
      });
    }

    res.sendStatus(204);
  }
);

x-provider-signature、事件名称和返回字段都是示意名称。实际接入时,必须替换为服务商文档规定的内容。

年龄通过后再创建支付

支付接口必须重新读取服务端保存的验证状态:

app.post("/api/payments/session", async (req, res) => {
  const user = await loadAuthenticatedUser(req);

  if (!user) {
    return res.status(401).json({ error: "Authentication required" });
  }

  const verification = await getAgeVerification(user.id);

  if (
    !verification ||
    verification.status !== "approved" ||
    verification.expiresAt <= new Date()
  ) {
    return res.status(403).json({
      error: "Valid age verification required"
    });
  }

  const paymentSession = await paymentGateway.createSession({
    amount: req.body.amount,
    currency: req.body.currency,
    referenceId: user.id,
    successUrl: "https://example.com/payment/success",
    cancelUrl: "https://example.com/payment/cancel"
  });

  res.json({
    redirectUrl: paymentSession.redirectUrl
  });
});

价格、币种和商品信息也应由服务端根据商品或订单计算,不能直接信任客户端提交的 amount。

需要特别注意的事项

合规要求

如果这里的 gaming 涉及真钱赌博、投注、奖品兑换或其他受监管活动,只验证用户是否年满 18 岁可能还不够。实际要求可能包括:

  • 身份验证和 KYC;
  • 所在国家或地区验证;
  • 更高的最低年龄;
  • 制裁名单或反洗钱检查;
  • 自我排除名单检查;
  • 支付方式和资金来源限制;
  • 特定牌照及记录保存要求。

最低年龄并非所有地区都是 18 岁,应以用户所在地、运营所在地和牌照条款为准。具体法律义务需要由熟悉目标司法辖区的律师或合规人员确认。

支付服务商限制

部分 payment gateway 或 acquiring bank 会限制赌博、博彩、抽奖及类似的高风险业务。技术上能够收款,不代表服务商合同允许经营这类业务。上线前应如实申报业务类型,并取得相应的商户类别和审批。

隐私与数据安全

身份证件和出生日期属于敏感个人信息。处理这些数据时应注意:

  • 只收集完成验证所需的数据。
  • 尽量让专业服务商直接处理证件。
  • 本地只保存验证状态、验证时间和服务商引用 ID。
  • 设置验证结果的有效期和重新验证规则。
  • 对数据实施访问控制、加密和审计。
  • 制定数据删除、申诉和人工复核流程。
  • 不要把完整卡号、CVV 或证件图片写入应用日志。

支付卡数据应交给符合相应 PCI DSS 要求的支付服务处理。优先使用 hosted checkout 或 tokenization,减少应用直接接触卡片数据的范围。

最终建议

普通银行卡支付流程不能提供可靠的年龄证明,因此不应寻找一个能够从信用卡读取年龄的 payment gateway。合适的流程是:

账户认证
→ 出生日期初筛
→ 独立年龄或身份验证
→ 服务端记录验证结果
→ payment gateway 收款
→ 验证支付 webhook
→ 服务端授权开始游戏

信用卡可以用于支付,但不能作为年龄凭证。

备注:内容仅供参考。