Stripe 支付系统文档

Stripe 支付系统文档

Stripe 支付系统文档

目录

  1. 系统概述

  2. 配置环境

  3. 支付流程

  4. API 端点

  5. 数据库结构

  6. 核心功能详解

  7. 回调处理

  8. 成功/失败页面


系统概述

本系统是基于 Stripe 支付网关的支付解决方案,支持两种主要支付场景:

  • 网上付款 (Online Payment) - 直接充值付款

  • 商品付款 (Goods Payment) - 购买商品/订单付款

技术栈

  • 框架: Fastify (Node.js)

  • 支付方式: Stripe (卡、支付宝等)

  • 货币: 港元 (HKD)

  • 数据库: MongoDB

支持的支付方式

  • 信用卡/借记卡 (Card)

  • 支付宝 (Alipay)


配置环境

环境变量配置


# Stripe API 密钥

STRIPE_SK=sk_live_xxx        # 生产环境密钥

STRIPE_SK_DEV=sk_test_xxx    # 开发环境密钥



# Webhook 密钥

STRIPE_WEBHOOK=whsec_xxx           # 生产环境 Webhook 密钥

STRIPE_WEBHOOK_DEV=whsec_test_xxx  # 开发环境 Webhook 密钥



# 环境标识

ver=Beta  # "Beta" 为开发环境,其他为生产环境

初始化代码


const stripeSk = process.env.ver === "Beta" ? 

  process.env.STRIPE_SK_DEV : process.env.STRIPE_SK;



const stripe = new Stripe(stripeSk || "");



const webhookSecret = process.env.ver === "Beta" ? 

  process.env.STRIPE_WEBHOOK_DEV : process.env.STRIPE_WEBHOOK;

关键点:根据环境自动选择不同的 API 密钥


支付流程

完整流程图


┌─────────────────────────────────────────────────────────────┐

│                    用户发起支付                              │

└────────────────────┬────────────────────────────────────────┘

                     │

        ┌────────────┴────────────┐

        │                         │

    ┌───▼──────┐          ┌──────▼──┐

    │ 网上付款  │          │ 商品付款 │

    └───┬──────┘          └──────┬──┘

        │                        │

        │ POST /stripe/oneline   │ POST /stripe/goods

        │                        │

        │ 创建 Stripe Checkout   │ 创建 Stripe Checkout

        │ Session               │ Session

        │                        │

        └────────────┬───────────┘

                     │

        ┌────────────▼────────────┐

        │  获取支付链接           │

        │  返回 session.url       │

        └────────────┬────────────┘

                     │

        ┌────────────▼────────────┐

        │  用户跳转到 Stripe      │

        │  完成支付               │

        └────────────┬────────────┘

                     │

        ┌────────────▼────────────┐

        │  Stripe 发送 Webhook    │

        │  POST /stripe/callback  │

        └────────────┬────────────┘

                     │

        ┌────────────▼────────────┐

        │  处理回调事件           │

        │  更新订单状态           │

        │  添加用户积分等         │

        └────────────┬────────────┘

                     │

        ┌────────────▼────────────┐

        │  重定向到成功/失败页    │

        │  显示支付结果           │

        └────────────────────────┘


API 端点

1. Webhook 回调端点

POST /stripe/callback

接收 Stripe 支付事件的 Webhook 回调

请求头


Content-Type: application/json

stripe-signature: <签名>

响应


{

  "received": true

}

处理的事件

  • payment_intent.succeeded - 支付成功

元数据字段


metadata: {

  order_no: string,    // 订单号

  custom_id: string    // 支付来源: "online" | "goods"

}


2. 网上付款端点

POST /stripe/oneline

为网上充值付款创建支付会话

请求体


{

  "amount": "100",          // 金额(元),如 "100.50"

  "orderNum": "2024-001",   // 订单号

  "paymentItemValue": "充值100元"  // 支付项目名称

}

响应成功


{

  "url": "https://checkout.stripe.com/pay/cs_xxx"

}

响应失败


{

  "code": 400,

  "msg": "参数错误"

}

金额转换

  • 前端/客户端发送:(如 100 = 100元)

  • Stripe 存储:(调用 yuanToFen() 转换)


3. 商品付款端点

POST /stripe/goods

为商品购买创建支付会话

请求体


{

  "amount": "99.99",        // 金额(元)

  "orderNum": "ORD-2024-001",  // 订单号

  "sku_id": "SKU-123"       // SKU ID

}

响应成功


{

  "url": "https://checkout.stripe.com/pay/cs_xxx"

}

响应失败


{

  "code": 400,

  "msg": "参数错误"

}


4. 支付成功页面

GET /stripe-success

显示支付成功页面

查询参数


type=online        // 支付类型: "online" | "goods" | "order"

orderNum=xxx       // 订单号

页面显示信息

  • 订单号

  • 商品名称(多语言支持:英文、繁体、中文)

  • 支付金额

  • 支付时间

  • 订单创建时间

  • 返回链接


5. 支付失败页面

GET /stripe-cancel

显示支付取消/失败页面

查询参数


type=online        // 支付类型: "online" | "goods" | "order"

orderNum=xxx       // 订单号


数据库结构

1. web_online_pay (网上付款表)

| 字段 | 类型 | 说明 |

|------|------|------|

| _id | ObjectId | 主键 |

| userId | ObjectId | 用户ID |

| customerId | string | 客户编号 |

| invoiceId | string | 发票编号 |

| amount | number | 缴费金额(分) |

| paymentItem | 1-7 | 缴费项目类型 |

| email | string | 电子邮箱 |

| pay_type | 0-7 | 支付方式(7=Stripe) |

| pay_ip | string | 支付IP |

| pay_time | number | 支付时间戳 |

| status | 1 | 2 | 1=未付款,2=已付款 |

| add_time | number | 创建时间戳 |

| orderNum | string | 订单编号 |

| stripe_payment_intent_id | string | Stripe 支付意图 ID |

2. web_order (订单表)

| 字段 | 类型 | 说明 |

|------|------|------|

| _id | ObjectId | 主键 |

| orderNum | string | 订单编号 |

| userId | ObjectId | 用户ID |

| goodsId | ObjectId | 商品ID |

| sku_name | string | 规格名称(中文) |

| sku_name_en | string | 规格名称(英文) |

| sku_name_tw | string | 规格名称(繁体) |

| price_total | number | 总价(分) |

| status | 1-8 | 订单状态 |

| pay_type | 0-6 | 支付方式 |

| pay_time | number | 支付时间戳 |

| add_time | number | 创建时间戳 |

| stripe_payment_intent_id | string | Stripe 支付意图 ID |

订单状态

  • 1: 待付款

  • 2: 待确认

  • 3: 待注册

  • 4: 已完成

  • 5: 已取消

  • 6: 已下载

  • 7: 申请退款

  • 8: 已退款

3. web_points (用户积分表)

| 字段 | 类型 | 说明 |

|------|------|------|

| _id | ObjectId | 主键 |

| userId | ObjectId | 用户ID |

| points | number | 积分数量 |

| source | 1-5 | 积分来源(4=商品消费) |

| status | 1 | 2 | 1=隐藏,2=正常 |

| out_trade_no | string | 关联订单号 |

| description | string | 描述(中文) |

| description_en | string | 描述(英文) |

| description_tw | string | 描述(繁体) |

| add_time | number | 创建时间戳 |


核心功能详解

1. 创建支付会话

网上付款示例


const session = await stripe.checkout.sessions.create({

  mode: 'payment',

  payment_method_types: ['card', 'alipay'],  // 支付方式

  line_items: [

    {

      price_data: {

        currency: 'hkd',                     // 货币

        product_data: { name: paymentItemValue },

        unit_amount: yuanToFen(amount),      // 转换为分

      },

      quantity: 1,

    },

  ],

  payment_intent_data: {

    metadata: {

      order_no: orderNum,     // 订单号

      custom_id: 'online'     // 支付来源标识

    }

  },

  success_url: 'https://www.dadate.com/stripe-success?type=online&orderNum=' + orderNum,

  cancel_url: 'https://www.dadate.com/stripe-cancel?type=online&orderNum=' + orderNum,

});



return { url: session.url };

关键参数说明

| 参数 | 说明 |

|------|------|

| mode | 'payment' - 一次性支付模式 |

| payment_method_types | 支持的支付方式数组 |

| currency | 货币代码('hkd' = 港元) |

| unit_amount | 金额(分),需要从元转换 |

| metadata | 元数据,支付成功时返回 |

| custom_id | 用于区分支付来源 |

| success_url | 支付成功后重定向 URL |

| cancel_url | 支付取消后重定向 URL |


2. 金额转换函数


// 元 -> 分

yuanToFen(100) => 10000



// 分 -> 元

fenToYuan(10000) => 100



// 格式化显示

formatNumberWithCommas(100.5) => "100.50"


3. 支付事件处理

事件类型:payment_intent.succeeded


const event = stripe.webhooks.constructEvent(

  request.body,

  sig,

  webhookSecret

);



if (event.type === 'payment_intent.succeeded') {

  const paymentIntent = event.data.object;

  

  // 提取关键信息

  const orderNo = paymentIntent.metadata?.order_no;

  const custom_id = paymentIntent.metadata?.custom_id;

  const amount = paymentIntent.amount;

  const payment_intent_id = paymentIntent.id;

}


回调处理

Webhook 验证


// 需要 Raw Body Buffer

app.addContentTypeParser('application/json', 

  { parseAs: 'buffer' }, 

  (req, body, done) => {

    done(null, body);

  }

);



// 验证签名

const event = stripe.webhooks.constructEvent(

  request.body,     // Buffer

  sig,               // Stripe 签名

  webhookSecret      // Webhook 密钥

);

重要

  • 必须使用 raw body 进行验证

  • 不能预先解析 JSON

  • 签名验证失败会抛出异常


处理流程

A. 网上付款(custom_id === "online")


if (custom_id === "online") {

  await collection("web_online_pay").updateMany({

    orderNum: orderNo,

    status: 1  // 未付款状态

  }, {

    $set: {

      status: 2,                          // 已付款

      pay_type: 1,                        // 支付方式 (1=微信,这里应该是Stripe)

      pay_time: Date.now(),

      stripe_payment_intent_id: payment_intent_id

    }

  });

}

更新内容

  • 状态从 1(未付款)→ 2(已付款)

  • 记录支付时间

  • 保存 Stripe 支付 ID


B. 商品付款(custom_id === "goods")


if (custom_id === "goods") {

  goodsCallBack({ 

    out_trade_no: orderNo, 

    amount, 

    stripe_payment_intent_id: payment_intent_id 

  });

}

调用 goodsCallBack() 函数处理商品支付逻辑


商品支付回调函数


async function goodsCallBack({ 

  out_trade_no,              // 订单号

  amount,                    // 金额(分)

  stripe_payment_intent_id   // Stripe 支付ID

}) {

  // 1. 更新订单状态

  const orderInfo = await collection("web_order").findOneAndUpdate({

    orderNum: out_trade_no,

    status: 1  // 待付款

  }, {

    $set: {

      status: 2,                              // 待确认

      pay_type: 1,                            // Stripe

      pay_time: Date.now(),

      stripe_payment_intent_id

    }

  });



  if (orderInfo) {

    const userId = orderInfo.userId;



    // 2. 增加用户积分

    await collection("web_user").updateOne({

      _id: userId

    }, {

      $inc: {

        points: amount  // 增加积分

      }

    });



    // 3. 添加积分记录

    await collection("web_points").insertOne({

      userId: userId,

      points: amount,

      source: 4,                      // 商品消费

      status: 2,                      // 正常

      out_trade_no,                   // 关联订单

      description: `购买【${orderInfo.title}】赠送【${amount}】积分`,

      description_en: `purchase 【${orderInfo.title_en}】gift 【${amount}】points`,

      description_tw: `購買【${orderInfo.title_tw}】贈送【${amount}】積分`,

      add_time: Date.now()

    });

  }

}

执行步骤

  1. ✅ 查询并更新订单(状态 1→2)

  2. ✅ 增加用户积分

  3. ✅ 创建积分记录(含多语言描述)


成功/失败页面

成功页面处理


app.get('/stripe-success', async (request, reply) => {

  const language = request.language || 'en';

  const { type, orderNum } = request.query;



  let obj = {

    title: "",

    amount: "",

    pay_time: "",

    add_time: "",

    rejectUrl: "",

    orderNum: ""

  };



  // 商品/订单类型

  if (['goods', 'order'].includes(type)) {

    const info = await collection("web_order")

      .findOne({ orderNum, status: 2 });  // 已付款

    

    obj.orderNum = info?.orderNum || "";

    obj.title = language === 'en' 

      ? info?.sku_name_en 

      : language === 'tw' 

        ? info?.sku_name_tw 

        : info?.sku_name;

    obj.amount = formatNumberWithCommas(fenToYuan(info?.price_total || 0));

    obj.pay_time = dayjs(info?.pay_time).format("YYYY-MM-DD HH:mm:ss");

    obj.add_time = dayjs(info?.add_time).format("YYYY-MM-DD HH:mm:ss");

    obj.rejectUrl = "https://user.dadate.com/user/order?orderNum=" + orderNum;

  }



  // 网上付款类型

  if (type === "online") {

    const info = await collection("web_online_pay")

      .findOne({ orderNum, status: 2 });  // 已付款

    

    obj.orderNum = info?.orderNum || "";

    obj.amount = formatNumberWithCommas(fenToYuan(info?.amount || 0));

    obj.pay_time = dayjs(info?.pay_time).format("YYYY-MM-DD HH:mm:ss");

    obj.add_time = dayjs(info?.add_time).format("YYYY-MM-DD HH:mm:ss");

    obj.rejectUrl = "https://user.dadate.com/user/onlinePay?orderNum=" + orderNum;

  }



  return reply.view('stripe_type.hbs', {

    type,

    payType: "success",

    ...obj

  });

});

失败页面处理


app.get('/stripe-cancel', async (request, reply) => {

  // 同成功页面逻辑,但查询条件改为 status: 1(未付款)

  const info = await collection("web_order")

    .findOne({ orderNum, status: 1 });  // 未付款

  

  return reply.view('stripe_type.hbs', {

    type,

    payType: "cancel",  // 标识为失败

    ...obj

  });

});

多语言支持

| 语言 | 代码 |

|------|------|

| 英文 | en |

| 繁体中文 | tw |

| 中文 | 其他 |


const title = language === 'en' 

  ? info?.sku_name_en 

  : language === 'tw' 

    ? info?.sku_name_tw 

    : info?.sku_name;


集成检查清单

  • 配置 Stripe API 密钥(开发/生产)

  • 配置 Webhook 密钥

  • 配置 success_url 和 cancel_url

  • 配置 Stripe Webhook 端点为 /stripe/callback

  • 验证数据库表结构

  • 配置多语言 i18n

  • 测试支付流程

  • 配置支付成功/失败页面模板


常见问题

Q1: 为什么要使用 Raw Body?

A: Stripe 需要原始请求体来验证 Webhook 签名。预先解析 JSON 会改变 body 内容,导致签名验证失败。

Q2: 金额为什么是分?

A: 国际支付标准使用最小货币单位(分、美分等)以避免浮点精度问题。

Q3: 支付失败后如何重试?

A: 用户可以在失败页面重新发起支付,或从用户中心重新支付。系统不会自动重试。

Q4: 如何处理重复 Webhook?

A: Stripe 会重试失败的 Webhook。数据库查询条件已过滤,幂等操作是安全的。


故障排查

| 问题 | 排查步骤 |

|------|---------|

| Webhook 验证失败 | 1. 检查 webhookSecret 是否正确 2. 确认使用 Raw Body 3. 查看错误日志 |

| 订单状态未更新 | 1. 检查 Webhook 是否收到 2. 验证数据库查询条件 3. 检查元数据是否正确 |

| 金额显示错误 | 检查是否正确调用 fenToYuan() 转换函数 |

| 支付页面出现错误 | 1. 验证参数是否正确 2. 检查模板文件 3. 查看服务器日志 |


相关文件

  • 源代码:src/routes/pay/stripe.ts

  • 数据库模型:

    • src/DB/web_online_pay.ts

    • src/DB/web_order.ts

    • src/DB/web_points.ts

  • 工具函数:src/utils/utils.ts

  • 模板文件:views/stripe_type.hbs


更新日志

| 版本 | 日期 | 说明 |

|------|------|------|

| 1.0 | 2024-10 | 初始版本 |


最后更新: 2024-10-20


本文由萧兮的博客原创发布,欢迎转载,转载务必保留原文链接。

萧兮的博客https://www.20010515.xyz · 原文:https://www.20010515.xyz/posts/308deee9-d76b-4ddd-bfda-028b7abdec8e