Stripe Checkout:从 Node 迁移到 Firebase Functions

Stripe Checkout: moving from Node to Firebase Functions

我的 Angular 11 应用使用 Stripe Checkout 和 Express 服务器来处理付款。 Angular & Node.js 一切正常。我想使用 Firebase Functions 而不是 Node,但是当我调用我的 Firebase Functions 时,出现错误:

IntegrationError: stripe.redirectToCheckout: You must provide one of lineItems, items, or sessionId.

Angular代码:

checkout(): void {
    var stripe = Stripe(environment.stripe.key);

    var productName = 'T-shirt!!';
    var price = '2000';

    const options = {
        method: 'POST',
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json;charset=UTF-8'
        },
        body: JSON.stringify({
            productName: productName,
            price: price
        })
    };
    var url = 'http://localhost:4242/create-checkout-session';                      // Node server
    //var url = 'http://localhost:5000/MY_FIREBASE_PROJECT/us-central1/stripeTest'; // Firebase emulation
    fetch(url, options)
        .then(function (response) {
            return response.json();
        })
        .then(function (session) {
            return stripe.redirectToCheckout({ sessionId: session.id });
        })
        .then(function (result) {
            if (result.error) {                                                    // redirect fails due to browser or network error
                alert(result.error.message);
            }
        })
        .catch(function (error) {
            console.error('Error:', error);
        });
}

节点服务器:

const express = require('express');
const app = express();
const stripe = require('stripe')('MY_SECRET_KEY')
const port = 4242;

app.use(express.json())                                                          // parse request body as JSON

app.use(function (req, res, next) {
    res.setHeader('Access-Control-Allow-Origin', 'http://localhost:4200');       // website you wish to allow to connect
    // res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');                                                                            // request methods you wish to allow
    res.setHeader('Access-Control-Allow-Methods', 'POST');
    res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');             
    next();                                                                      // pass to next layer of middleware
});

app.post('/create-checkout-session', async (req, res) => {
    var productName = req.body.productName;
    var price = req.body.price;

    console.log('body = ', req.body);
    console.log('price = ', req.body.price);

    const session = await stripe.checkout.sessions.create({
        payment_method_types: ['card'],
        line_items: [
            {
                price_data: {
                    currency: 'usd',
                    product_data: {
                        name: productName,
                    },
                    unit_amount: price,
                },
                quantity: 1,
            },
        ],
        mode: 'payment',
        success_url: 'http://localhost:4200/home?action=success',
        cancel_url: 'http://localhost:4200/home?action=cancel',
    });

    res.json({ id: session.id });
});
app.listen(port, () => console.log('Listening on port ' + port + '!'));

Firebase 函数:

const stripe = require('stripe')('MY_SECRET_KEY')
const functions = require("firebase-functions");
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);

exports.stripeTest = functions.https.onCall((data, context) => {
    var productName = data['productName'];
    var price = data['price'];
    console.log('data: ', data);
    console.log('product name = ', productName);
    console.log('price = ', price);

    const session = stripe.checkout.sessions.create({
        payment_method_types: ['card'],
        line_items: [
            {
                price_data: {
                    currency: 'usd',
                    product_data: {
                        name: productName,
                    },
                    unit_amount: price,
                },
                quantity: 1,
            },
        ],
        mode: 'payment',
        success_url: 'http://localhost:4200/home?action=success',
        cancel_url: 'http://localhost:4200/home?action=cancel',
    }, async (err, data) => {});

    return { id: session.id };
})

我不明白这个问题,因为我传入 line_items。谢谢!

session.idtypeof 'undefined',如错误消息提示的那样。 这意味着,它必须与 data 一起传递到函数中(没有可用的服务器端会话)。

还要验证 属性 名称,因为至少 line_items 对 Stripe API 是未知的。

感谢大家帮助我来到这里。

使用 Firebase Functions 时,您需要使用 httpsCallable 而不是 fetch...

从 Angular 进行调用

Angular代码:

import { AngularFireFunctions } from '@angular/fire/functions';

constructor(
    // ...
    private afFun: AngularFireFunctions) {

    afFun.useEmulator("localhost", 5000);
}

checkoutFirebase(): void {
    var stripe = Stripe(environment.stripe.key);
    this.afFun.httpsCallable("stripeTest")({ productName: 'T-shirt', price: '400' })
        .subscribe(result => {           // the result is your Stripe sessionId
            console.log({result});       

            stripe.redirectToCheckout({
                sessionId: result,
            }).then(function (result) {
                console.log(result.error.message);
            });
        });
}

然后我们需要在 Firebase 中将 async 添加到 onCall

Firebase 函数:

exports.stripeTest = functions.https.onCall(async (data, context) => {
    var productName = data['productName'];
    var price = data['price'];

    const session = await stripe.checkout.sessions.create({
        payment_method_types: ['card'],
        line_items: [
            {
                price_data: {
                    currency: 'usd',
                    product_data: {
                        name: productName,
                    },
                    unit_amount: price,
                },
                quantity: 1,
            },
        ],
        mode: 'payment',
        success_url: 'http://localhost:4200/home?action=success',
        cancel_url: 'http://localhost:4200/home?action=cancel',
    });

    return session.id;
})

反弹 Ryan Loggerythm 的更新答案。

在大约半小时左右摆弄 Stripe 的文档解决方案(这似乎更适合让开发人员试用演示而不是实际实施该技术)和 Medium 上的其他指南后,我加载了您更新的答案你瞧,它绝对完美无瑕,所有这些都在 2 段内,1 段用于 Angular,1 段用于 Firebase 函数,因此使用 Stripe 的 Checkout 产品而不是制作我们自己的结帐(或转向 Shopify 的):交钥匙简单。

与此同时,对于这个简单的解决方案,Stripe 的 API 文档感觉就像是一个迷宫。也许 Ryan 应该是制作 Stripe 文档的人