发布于 2026-01-06 0 阅读
0

使用 Paystack 在 Node.js 和 MongoDB 中创建简单的支付系统:分步指南 💳💸

使用 Paystack 在 Node.js 和 MongoDB 中创建简单的支付系统:分步指南 💳💸

介绍

大家好!我是 Kizito,微软的一名软件工程师(醒来啦)。在本文中,我将指导您如何在 Paystack 上开发用于支付集成的 API 端点 😎,这些端点可用于您的客户端应用程序。

如果你想使用基于 Node.js 的 Paystack 实现应用程序的盈利,那么你来对地方了🤩

先决条件

  • 具备 Javascript、Node.js 和 Express 的基础知识
  • 您应该安装 VS Code、Node.js、MongoDb 和 Postman。
  • 具备数据库使用经验(本文将使用 MongoDB)。
  • 一颗乐于学习的心❤️‍🔥

PaystackPaymentJoy

让我们深入探讨一下。

在当今数字时代,电子商务蓬勃发展,在线交易已成为常态,构建一个无缝且安全的支付系统对于企业和开发者而言都至关重要。然而,构建支付系统并非易事,而 Paystack 正是解决这一难题的利器。Paystack 是一款广受信赖的支付网关,它为开发者提供了轻松处理支付所需的各种工具。在本指南中,我们将带您深入了解如何使用 Paystack 在 Node.js 中构建前沿的支付系统。系好安全带,让我们一起踏上这段激动人心的旅程,探索 Node.js 和 Paystack 驱动的支付系统世界。

设置

如果您还没有 Paystack 账户,请先在此处创建一个:https://dashboard.paystack.com/#/signup。如果您已有账户,则可以直接登录。
完成这些步骤后,我们就可以开始开发了👩‍💻👨‍💻

本文将使用几个最常用的端点,分别是创建费用端点订阅端点🥁🥁

打开你的 VS Code,直接在终端中创建一个新文件夹,就像高级开发人员一样🫡。我把它命名为 paystack-nodejs-integration。

然后使用 cd 命令进入该目录



mkdir paystack-nodejs-integration
cd paystack-nodejs-integration


Enter fullscreen mode Exit fullscreen mode

终端

既然我们已经在同一个目录下,我们就可以……



npm init -y


Enter fullscreen mode Exit fullscreen mode

然后我们看到 package.json 文件打开了👏
接下来,让我们安装依赖项。
这些依赖项将用于开发这个强大的系统,我会逐一讲解💡。
然后我们创建 index.js、env 和 .gitignore 文件。



npm install cors dotenv express mongoose paystack-api
npm install --save-dev nodemon @ngrok/ngrok
touch index.js .env .gitignore


Enter fullscreen mode Exit fullscreen mode

如前所述,我将逐一阐述各项依赖项。

cors: CORS 是一个 Node.js 包,它提供了一个 Connect/Express 中间件,可用于启用跨域资源共享,并提供多种 API 访问选项。dotenv
Dotenv 是一个零依赖模块,它将环境变量从 .env 文件加载到 process.env 中。将配置与代码分离存储在环境中的做法基于十二要素应用方法论,我们将使用它来存储 API 密钥。express
这是我们将用于构建 API 的 Node.js 框架。mongoose
Mongoose 工具使我们能够更轻松地直接操作和交互 MongoDB。paystack
-api: Pariola paystack API 封装器,它在我们的 Node.js 应用程序中处理所有底层 paystack 连接,并为我们的集成提供函数。nodemon
nodemon 是一个工具,它通过在检测到目录中的文件更改时自动重启 Node 应用程序来帮助开发基于 Node.js 的应用程序。
ngrok:
ngrok 可为您的应用程序在 任何云端、私有网络或设备上提供即时入口访问,
并具备身份验证、负载均衡和其他关键控制功能。我们将使用它进行 webhook 事件测试,将本地主机连接到实时 URL 🔥
您可以在这里创建帐户:ngrok

Nodemon 和 Ngrok 作为开发依赖项安装,因为除非你想使用 ngrok 的一些附加功能,否则我们不需要它们在实际运行环境中使用。

所以,我们的文件夹结构应该和这个类似,现在我们在 index.js 文件中启动 express 服务器。

支付栈文件夹结构

index.js



// imports
const express = require("express");
const dotenv = require("dotenv");
const cors = require("cors");

// specify the port from our enviroment variable
const PORT = process.env.PORT || 8080;
app.use(cors());
app.use(express.json());
// connect database

// routes

app.get('/', async (req, res) => {
    res.send("Hello World!");
});

app.listen(PORT, () => {
    console.log(
        `Server running in ${process.env.NODE_ENV} mode on port ${PORT}`
    );
});


Enter fullscreen mode Exit fullscreen mode

.env



PORT=8000
NODE_ENV=development


Enter fullscreen mode Exit fullscreen mode

.gitignore



node_modules
.env


Enter fullscreen mode Exit fullscreen mode

package.json



{
  "name": "paystack-nodejs-integration",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "cors": "^2.8.5",
    "dotenv": "^16.3.1",
    "express": "^4.18.2",
    "mongoose": "^7.5.0",
    "paystack-api": "^2.0.6"
  },
  "devDependencies": {
    "@ngrok/ngrok": "^0.6.0",
    "nodemon": "^3.0.1"
  }
}



Enter fullscreen mode Exit fullscreen mode

请注意json文件中的脚本。我们将在终端中使用它来进行开发。

npm run dev

连接节点
然后我们在 Postman 中尝试该 URL。

PostmanImage

谁来查!(高兴地喊道)

现在让我们连接到 MongoDB Compass。
MongoDB指南针

然后我们在.env文件中设置 MONGODB_URI。



MONGODB_URI=mongodb://0.0.0.0:27017/paystack-node-integration


Enter fullscreen mode Exit fullscreen mode

之后,我们在config/db.js文件中添加 MongoDB 配置,然后保存。



const mongoose = require("mongoose");
mongoose.set("strictQuery", false);

const connectDB = async () => {
  try {
    const conn = await mongoose.connect(process.env.MONGODB_URI, {
      useUnifiedTopology: true,
      useNewUrlParser: true,
    });

    console.log(`MongoDB Connected: ${conn.connection.host}`);
  } catch (err) {
    console.error(`Error: ${err.message}`);
    process.exit(1);
  }
};

module.exports = connectDB;


Enter fullscreen mode Exit fullscreen mode

然后我们在index.js文件中导入并调用该函数。



// imports
const connectDB = require("./config/db.js");

// connect database
connectDB();


Enter fullscreen mode Exit fullscreen mode

成功连接!我们的数据库已连接。

已连接的 MongoDB

现在让我们创建一个系统,让用户可以使用他们的银行卡为活动捐款,还可以订阅您创建的计划。

我们首先来创建数据库的用户模型。
创建一个名为 models 的文件夹,并在该文件夹中创建一个名为 userModel.js 的文件。

models/userModel.js



const mongoose = require("mongoose");

const userSchema = new mongoose.Schema({
    fullname: {
        type: String,
    },
    email: {
        type: mongoose.Schema.Types.Mixed,
    },
    paystack_ref: {
        type: String,
    },
    amountDonated: {
        type: Number,
    },
    isSubscribed: {
        type: Boolean,
    },
    planName: {
        type: String,
    },
    timeSubscribed: {
        type: Date,
    },
});

const User = mongoose.model("user", userSchema);

module.exports = User;


Enter fullscreen mode Exit fullscreen mode

我们将使用常见的 MVC(模型、视图、控制器)架构模式,但我们的接口是路由端点而不是视图。
因此,让我们创建两个额外的文件夹:控制器路由,以便我们的应用程序更易于调试、更具可扩展性,并且更容易被其他开发人员理解,除非你解释得太多了(你会解释得过于详细)。

我们目前想创建两个端点。第一个端点用于创建用户,第二个端点用于获取用户。
控制器中,我们创建一个名为userController.js的新文件。

controllers/userController.js



const User = require("../models/userModel");
// Require paystack library

const createUser = async (req, res) => {
    let { email, fullname } = req.body;

    const user = new User({
        fullname,
        email,
    });
    await user.save();

    res.status(201).send({
        data: user,
        message: "User created successfully",
        status: 0,
    });
}

const getUser = async (req, res) => {
    try {
        let { id } = req.params;
        const user = await User.findById(id);

        res.status(200).send({
            user,
            message: "Found user Details",
            status: 0,
        });
    } catch (err) {
        res.status(500).send({ data: {}, error: err.message, status: 1 });
    }
};

// initialize transaction

module.exports = {
    createUser,
    getUser,
};


Enter fullscreen mode Exit fullscreen mode

之后,我们创建一个名为routes的文件夹,并在其中创建一个名为userRoutes.js的文件,该文件将用于调用我们的控制器函数。routes
/userRoutes.js



const express = require("express");
const userRoute = express.Router();

const {
    getUser,
    createUser,
} = require("../controllers/userController");

userRoute.get("/getUser/:id", getUser);
userRoute.post("/createUser", createUser);

module.exports = {
    userRoute,
};


Enter fullscreen mode Exit fullscreen mode

为了能够使用这些路由,我们需要在index.js文件中导入它们。



// routes
const { userRoute } = require("./routes/userRoutes.js");
app.use("/users", userRoute);


Enter fullscreen mode Exit fullscreen mode

目前一切顺利!我们的文件夹结构应该如下所示。 现在我们在 Postman 上测试用户创建端点,发现它运行正常,并且已反映在我们的数据库中🎈
结构

用户创建测试

数据库
恭喜你们走到这一步👏👏 我想我们俩都值得喝一杯🍻
这就是我们一直期待的时刻。
接下来进入下一阶段,我们要创建一个系统,让用户可以为活动捐款,也可以订阅你们创建的计划。

这是两个关键的实现方式。

  1. 竞选捐款
  2. 计划订阅

活动捐款
想象一下,如果我们正在组织一场活动,需要人们通过捐款来筹集资金,那么我们可以为此创建一个 API 端点。

在此之前,我们先前往 Paystack 控制面板设置,复制我们的私钥,以便能够与账户进行交互。https ://dashboard.paystack.com/#/settings/developers
本文将以测试模式进行演示,不会涉及任何真实资金交易。⚠️ 切记,任何情况下都不要将您的私钥分享给任何人,因此它将存储在一个环境变量中,该变量在任何部署中都会被移除。如果您不小心公开了您的私钥,请立即生成一个新的密钥。⚠️
支付栈密钥

在.env文件中添加新行,并将我们的密钥复制粘贴到该行中



TEST_SECRET=YOUR_SECRET_KEY


Enter fullscreen mode Exit fullscreen mode

在我们的controllers/userController.js 文件中,我们添加了以下代码来初始化交易,并保存交易引用,以便获取特定交易的详细信息,这在您日常的银行交易中应该很常见。https
://paystack.com/docs/api/charge/#create

controllers/userController.js



// Require paystack library
const paystack = require("paystack-api")(process.env.TEST_SECRET);

// initialize transaction
const initializeTrans = async (req, res) => {
    try {
        let { id } = req.params;
        const { email, amount, plan, } = req.body;

        const response = await paystack.transaction.initialize({
            email,
            amount,
            plan, // optional but we'll use for subscription
        });

        const data = {
            paystack_ref: response.data.reference,
        };

        await User.findByIdAndUpdate(id, data);

        res.status(200).send({
            data: response.data,
            message: response.message,
            status: response.status,
        });

    } catch (error) {
        res.status(400).send({ data: {}, error: `${error.message}`, status: 1 });
    }
};

// verify transaction

module.exports = {
    ...,
    initializeTrans,
};


Enter fullscreen mode Exit fullscreen mode

如果你看一下上面的代码,你会注意到我们有 3 个输入参数被传递到正文参数中:
电子邮件、金额和计划(计划是可选的,但由于我们以后还会有订阅功能,所以我们会稍后使用它)。

让我们把它添加到我们的用户路由路由
routes/userRoutes.js中。



const {
    ...,
    initializeTrans,
} = require("../controllers/userController");

userRoute.post("/initiatetransaction/:id", initializeTrans);


Enter fullscreen mode Exit fullscreen mode

好的!是时候在 Postman 上测试一下了。
我把金额设为 300000,之所以设为 3000 是因为货币单位保留两位小数,付款人的邮箱地址也设为我自己的邮箱。

邮差
成功了👏
我们要点击 authorization_url,看看会发生什么。

Paystack
太棒了朋友们💪!我们可以看到可用的付款选项并进行付款。
付款后,我们需要确认付款是否成功,这可以通过两种方式实现。

  1. 使用 paystack 验证端点https://paystack.com/docs/payments/verify-payments/
  2. 使用 Webhook 监听事件https://paystack.com/docs/payments/webhooks/

本文将介绍这两种方法🥁

我们首先使用验证端点来验证刚刚完成的交易。验证通过后,我们会添加用户捐赠的金额,并将数据库中的交易编号更改为“成功”。

controllers/userController.js



// verify transaction
const verifyTrans = async (req, res) => {
    try {
        let { id } = req.params;

        const user = await User.findById(id);

        if (user.paystack_ref == "success")
            return res.status(401).send({
                data: {},
                message: "Transaction has been verified",
                status: 1,
            });

        const response = await paystack.transaction.verify({
            reference: user.paystack_ref
        });

        if (response.data.status == "success") {
            const data = {
                paystack_ref: response.data.status,
                amountDonated: response.data.amount,
            };
            await User.findByIdAndUpdate(id, data);

            return res
                .status(200)
                .send({
                    data: response.data,
                    message: response.message,
                    status: response.status,
                });
        } else {
            return res
                .status(200)
                .send({
                    data: response.data,
                    message: response.message,
                    status: response.status,
                });
        }

    } catch (error) {
        res.status(400).send({ data: {}, error: `${error.message}`, status: 1 });
    }
};

module.exports = {
    ...,
    verifyTrans,
};


Enter fullscreen mode Exit fullscreen mode

routes/userRoutes.js



const {
    ...,
    verifyTrans,
} = require("../controllers/userController");

userRoute.post("/verifytransaction/:id", verifyTrans);


Enter fullscreen mode Exit fullscreen mode

在 Postman 上测试完接口后,我们可以从响应和数据库中验证它工作正常🤩🤩

邮差

MongoDB
太棒了!现在我们确定一切正常,可以继续处理计划订阅了。接下来,我们将学习如何使用 Webhook 进行事件监听 👂🪝

订阅计划
假设您是一家厨房应用的 CEO,您希望用户能够订阅以访问应用内不同厨师的服务,那么您需要为他们创建多个订阅计划,例如按周按月等等。让我们创建这些计划并在应用中启用订阅功能。
我们也可以直接从 Paystack 控制面板创建计划,但我将通过编程方式来实现。

我们在控制器中创建一个名为planController.js 的新文件,我们将在其中创建 createPlan、getPlan 和 webhook 函数。

controllers/planController.js



// Require the library
const paystack = require("paystack-api")(process.env.TEST_SECRET);

const createPlan = async (req, res) => {
    try {
        const { interval, name, amount } = req.body;

        const response = await paystack.plan.create({
            name,
            amount,
            interval,
        });

        res.status(200).send({
            data: response.data,
            message: response.message,
            status: response.status,
        });

    } catch (error) {
        res.status(400).send({ data: {}, error: `${error.message}`, status: 1 });
    }
};

const getPlans = async (req, res) => {
    try {
        const response = await paystack.plan.list();

        res.status(200).send({
            data: response.data,
            message: response.message,
            status: response.status,
        });

    } catch (error) {
        res.status(400).send({ data: {}, error: `${error.message}`, status: 1 });
    }
};

// our webhook function for event listening

module.exports = {
    createPlan,
    getPlans,
};


Enter fullscreen mode Exit fullscreen mode

我们在路由表中创建一个名为planRoutes.js 的新文件。

routes/planRoutes.js



const express = require("express");
const planRoute = express.Router();

const {
    createPlan,
    getPlans,
} = require("../controllers/planController");

planRoute.get("/getPlans", getPlans);
planRoute.post("/createPlan", createPlan);

module.exports = {
    planRoute,
};


Enter fullscreen mode Exit fullscreen mode

我们也不要忘记在index.js 文件中导入它。

index.js



// routes
const { planRoute } = require("./routes/planRoutes.js");

app.use("/plans", planRoute);


Enter fullscreen mode Exit fullscreen mode

保存文件并测试createPlan端点后,我们发现它运行正常 👍

创建计划
我们已成功创建了一个供用户订阅的套餐计划🍻
接下来呢?没错,我们需要订阅该套餐,但在订阅之前,我们将添加 webhook 功能来监听 paystack 发送的交易事件。

因此,我们创建一个名为helpers 的新文件夹,并在其中创建一个名为webhookHelpers.js的文件。该文件包含三个函数,分别在chargeSuccessplanChargeSuccesscancelSubscription
事件更新时触发。您可以根据自己的使用需求创建更多函数。https ://paystack.com/docs/payments/subscriptions/

helpers/webhookHelpers.js



const User = require("../models/userModel");

// Require the library
const paystack = require("paystack-api")(process.env.TEST_SECRET);

// Paystack webhook helpers: Functions that should be called on paystack event updates
// invoicePaymentFailed, invoiceCreation, invoiceUpdate, subscriptionNotRenewed, subscriptionDisabled, chargeSuccess

const chargeSuccess = async (data) => {
    try {
        const output = data.data;
        const reference = output.reference;
        // console.log(output);

        const user = await User.findOne({ paystack_ref: reference });
        const userId = user._id;
        console.log("Updating charge status");

        if (user.paystack_ref == "success")
            return ({
                data: {},
                message: "Transaction has been verified",
                status: 1,
            });

        const response = await paystack.transaction.verify({
            reference: user.paystack_ref
        })

        if (response.data.status == "success") {
            const data = {
                paystack_ref: response.data.status,
                amountDonated: output.amount,
            }
            await User.findByIdAndUpdate(userId, data);

            console.log("Charge Successful");
        } else {
            console.log("Charge Unsuccessful");
        }

    } catch (error) {
        console.log({ data: {}, error: `${error.message}`, status: 1 });
    }
};

// succesful subscription
const planChargeSuccess = async (data) => {
    try {
        const output = data.data;
        const reference = output.reference;
        // console.log(output);

        const user = await User.findOne({ paystack_ref: reference });
        const userId = user._id;
        // console.log(user, reference);

        console.log("Updating charge status");

        // subscribe for user
        if (user.paystack_ref == "success")
            return ({
                data: {},
                message: "Transaction has been verified",
                status: 1,
            });

        const response = await paystack.transaction.verify({
            reference: user.paystack_ref
        })

        if (response.data.status == "success") {
            await User.findByIdAndUpdate(userId, {
                isSubscribed: true,
                paystack_ref: response.data.status,
                planName: output.plan.name,
                timeSubscribed: response.data.paid_at,
            });
            console.log("Charge Successful");
        } else {
            console.log("Charge Unsuccessful");
        }

    } catch (error) {
        console.log({ data: {}, error: `${error.message}`, status: 1 });
    }
};

// invoicePaymentFailed
const cancelSubscription = async (data) => {
    try {
        const output = data.data;
        const reference = output.reference;
        // console.log(output);

        const user = await User.findOne({ paystack_ref: reference });
        const userId = user._id;

        console.log("Cancelling subscription...");

        await User.findByIdAndUpdate(userId, {
            isSubscribed: true,
            paystack_ref: response.data.status,
            planName: "cancelled",
        });
        console.log("User Subscription Cancelled");

    } catch (error) {
        console.log({ data: {}, error: `${error.message}`, status: 1 });
    }
};

module.exports = {
    planChargeSuccess,
    chargeSuccess,
    cancelSubscription,
};


Enter fullscreen mode Exit fullscreen mode

在我们的planController中,我们导入了辅助函数。
一旦创建了像成功收费这样的操作,它就会触发一个事件,我们将选择一个端点 URL,供 paystack 向其发送数据。

controllers/planController.js



// Require the library
const { planChargeSuccess, chargeSuccess, cancelSubscription, } = require("../helpers/webhookHelpers");

// our webhook function for event listening
// you can edit this to your style
const addWebhook = async (req, res) => {
    try {
        let data = req.body;
        console.log('Webhook data: ', data);

        switch (data) {
            case data.event = "invoice.payment_failed":
                await cancelSubscription(data);
                console.log("Invoice Failed");
                break;
            case data.event = "invoice.create":
                console.log("invoice created");
                break;
            case data.event = "invoice.update":
                data.data.status == "success" ?
                    await planChargeSuccess(data) :
                    console.log("Update Failed");
                break;
            case data.event = "subscription.not_renew":
                console.log("unrenewed");
                break;
            case data.event = "subscription.disable":
                console.log("disabled");
                break;
            case data.event = "transfer.success":
                console.log("transfer successful");
                break;
            case data.event = "transfer.failed":
                console.log("transfer failed");
                break;
            case data.event = "transfer.reversed":
                console.log("transfer reversed");
                break;
            case data.event = "subscription.disable":
                console.log("disabled");
                break;

            default:
                // successful charge
                const obj = data.data.plan;
                console.log("Implementing charges logic...");
                // object comparison verifying if its a normal payment or a plan
                // charges for subscription and card
                Object.keys(obj).length === 0 && obj.constructor === Object ?
                    await chargeSuccess(data) :
                    // charge sub
                    await planChargeSuccess(data);
                console.log("Successful");
                break;
        }

    } catch (error) {
        res.status(400).send({ data: {}, error: `${error.message}`, status: 1 });
    }
};
module.exports = {
    ...,
    addWebhook,
};


Enter fullscreen mode Exit fullscreen mode

我们将在计划路线中将其称为



const {
    ...,
    addWebhook,
} = require("../controllers/planController");

planRoute.post("/paystackWebhook", addWebhook);


Enter fullscreen mode Exit fullscreen mode

走到这一步,我们都应该奖励自己一顿美味的捣碎山药,对吧?😁
最后冲刺!
在开始测试我们的端点之前,我们需要使用ngrok工具将本地主机部署到线上,以便将 URL 添加到 Paystack。https
://dashboard.paystack.com/#/settings/developers 然后,我们登录https://dashboard.ngrok.com/get-started/your-authtoken获取我们的令牌,复制它并将其添加到我们的.env文件中。
webhookurl

.env



NGROK_AUTHTOKEN=YOUR_AUTH_TOKEN


Enter fullscreen mode Exit fullscreen mode

我们在index.js 文件中添加以下代码行。

index.js



// imports
const ngrok = require("@ngrok/ngrok");

// at the bottom
if (process.env.NODE_ENV == "development") {
    (async function () {
        const url = await ngrok.connect({ addr: PORT, authtoken_from_env: true, authtoken: process.env.NGROK_AUTHTOKEN });
        console.log(`Ingress established at: ${url}`);
    })();
} 


Enter fullscreen mode Exit fullscreen mode

然后我们保存,终端界面应该和这个类似,点击链接后应该会看到“Hello World!”。 接下来,我们将链接添加到 Paystack 的测试 Webhook URL 中,以便触发事件。你的链接应该有所不同。https ://ce41-102-88-63-21.ngrok-free.app/plans/paystackWebhook
ngrokterminal

页
注意:每次 nodemon 重启我们的服务器时,webhook URL 都会改变。

太好了!我们在 Postman 上使用initiatetransaction端点进行测试,但请注意,这次我们在请求体中添加了一个计划,该金额将被我们创建的计划金额覆盖。
订阅
paystackpay

webhook 日志

mongodbsub

耶🥳🎉 运行流畅🤩
现在我们可以站起来伸展一下身体了……感觉真好😋

总结:
我们通过使用 paystack 交易、REST API、数据库建模、构建稳固的架构、使用 nodemon、dotenv、mongoose、ngrok 等工具和库,学习并提升了很多技能。我们还看到了 webhook 和事件在开发实际应用程序以及初始化捐款或订阅计划的支付流程中的重要性。

Paystack 还有更多功能,您可以在其文档中了解。

如果您遇到任何问题,欢迎在此留言或通过LinkedInTwitter与我联系。

再次恭喜🎉
感谢你远道而来,祝你一切顺利。

Paystack 文档的GitHub 代码库链接

文章来源:https://dev.to/kizito007/creating-a-simple- payment-system-in-nodejs-and-mongodb-using-paystack-a-step-by-step-guide-2mc4