如何在 Node.js 中付款后将 JWT 用户 ID 与 PayPal 自定义 ID 关联?

问题描述 投票:0回答:1

我正在 Node.js 应用程序中开发一项功能,需要将从 JWT(JSON Web 令牌)获取的用户 ID 与成功付款后来自 PayPal 的

custom_id
相关联。用户 ID 和
custom_id
在我的系统中具有不同的用途:用户 ID 标识登录用户,而
custom_id
是在付款成功后由 PayPal 生成的。我的目标是将每笔交易的这两个标识符存储在我的数据库中,以便将订阅付款直接链接到用户帐户。

我设想的流程如下:

  1. 用户登录,生成包含其用户 ID 的 JWT。
  2. 用户发起PayPal订阅付款。
  3. 成功付款后,PayPal 会为交易提供
    custom_id
    (例如“I-YXA1XYR62EKE”)。
  4. 我需要从 JWT 捕获此
    custom_id
    以及用户 ID,并将两者存储在我的数据库中,从而将付款与用户关联起来。

以下是我目前处理身份验证和付款启动的方式。然而,在付款成功后,我一直坚持有效地捕获来自 PayPal 的

custom_id
并将其与数据库中的用户 ID 链接起来。

代码片段

中间件/ auth.js

const jwt = require("jsonwebtoken");

const auth = async (req, res, next) => {
  try {
    const token = req.header("x-auth-token");

    if (!token)
      return res.status(401).json({ msg: "No auth token, access denied." });

    const verified = jwt.verify(token,  process.env.ACCESS_TOKEN_SECRET);

    if (!verified)
      return res
        .status(401)
        .json({ msg: "Token verification failed, authorization denied." });

    req.user = verified.id;
    req.token = token;
    next();
  } catch (e) {
    res.status(500).json({ error: e.message });
  }
};

module.exports = auth;

routes/paypal.js

paypalRouter.post("/create-subscription", auth, async (req,res) => {
  try {
    const userId = req.user;
    const subscriptionDetails = {
      plan_id: process.env.PAYPAL_SANDBOX_BUSSINESS_SUBSCRIPTION_PLAN_ID,
      custom_id: userId, 
      application_context: {
        brand_name: "brand.com",
        return_url: "http://localhost:3001/paypal/subscriptions/webhook",
        cancel_url: "http://localhost:3001/paypal/cancel-subscription",
      }
    };

    const params = new URLSearchParams();
    params.append("grant_type", "client_credentials");
    const authResponse = await axios.post(
      "https://api-m.sandbox.paypal.com/v1/oauth2/token",
      params,
      {
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
        },
        auth: {
          username: process.env.PAYPAL_CLIENT_ID,
          password: process.env.PAYPAL_SECRET,
        },
      }
    );

    const access_token = authResponse.data.access_token;

    const response = await axios.post(
      "https://api-m.sandbox.paypal.com/v1/billing/subscriptions",
      subscriptionDetails,
      {
        headers: {
          Authorization: `Bearer ${access_token}`,
        },
      }
    );
    

   

    console.log("subscriptionId from PAYPAL (e.g "I-YXA1XYR62EKE")" + response.data.id);
    console.error();
    return res.json({ subscriptionId: response.data.id, ...response.data });

  } catch (error) {
    console.log(error);
    return res.status(500).json("Something goes wrong");
  }
});


paypalRouter.post("/subscriptions/webhook", async (req, res) => {
  try {
    const webhookEvent = req.body;

    const verification = {
      auth_algo: req.headers['paypal-auth-algo'],
      cert_url: req.headers['paypal-cert-url'],
      transmission_id: req.headers['paypal-transmission-id'],
      transmission_sig: req.headers['paypal-transmission-sig'],
      transmission_time: req.headers['paypal-transmission-time'],
      webhook_id: process.env.Webhook_ID,
      webhook_event: webhookEvent
    };

    const params = new URLSearchParams(); 
    params.append("grant_type", "client_credentials");

    const authResponse = await axios.post(
      "https://api-m.sandbox.paypal.com/v1/oauth2/token",
      params,
      {
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
        },
        auth: {
          username: process.env.PAYPAL_CLIENT_ID,
          password: process.env.PAYPAL_SECRET,
        },
      }
    );

    const accessToken = authResponse.data.access_token;

    const verifyResponse = await axios.post(
      "https://api-m.sandbox.paypal.com/v1/notifications/verify-webhook-signature",
      verification,
      {
        headers: {
          Authorization: `Bearer ${accessToken}`,
          'Content-Type': 'application/json'
        },
      }
    );

    if (verifyResponse.data.verification_status === "SUCCESS") {
    
      if (webhookEvent.event_type === "PAYMENT.SALE.COMPLETED") {
        console.log("Payment sale completed event received", webhookEvent);
        
        const userId = webhookEvent.resource.custom_id; // 
        const subscriptionId = webhookEvent.resource.id; //
  
        try {
          await User.findByIdAndUpdate(userId, { subscriptionId: subscriptionId });
          console.log("User subscription ID updated successfully");
        } catch (error) {
          console.error("Failed to update user subscription ID:", error);
          return res.status(500).send("Failed to update user subscription ID.");
        }      


        return res.status(200).send('Event processed');
      }
    } else {
      console.log("Failed to verify webhook signature:", verifyResponse.data);
      return res.status(401).send('Webhook signature verification failed');
    }
  } catch (error) {
    console.error("Error handling webhook event:", error.response ? error.response.data : error.message);
    return res.status(500).send("An error occurred while handling the webhook event.");
  }
});

我不确定付款成功后捕获

custom_id
并将其与我的数据库中的用户 ID 相关联的最佳实践。具体来说,我正在寻找以下方面的指导:

  • 如何从 PayPal 的 webhook 通知中检索和处理
    custom_id
    以确保付款成功。
  • 更新数据库以将 PayPal 的
    custom_id
    与从 JWT 中提取的用户 ID 链接起来的最佳实践。

任何在 Node.js 应用程序中处理类似场景的见解或示例将不胜感激。

node.js mongodb jwt paypal-rest-sdk paypal-subscriptions
1个回答
0
投票

PayPal 提供 custom_id(例如“I-YXA1XYR62EKE”)

这样的值通常在 PayPal 中称为订阅的

id
,而不是
custom_id

查看您的代码,您似乎在创建订阅时实际上将 userId 值存储在

custom_id
字段中。

paypalRouter.post("/create-subscription", auth, async (req,res) => {
  try {
    const userId = req.user;

这样就可以了。

当您收到网络钩子时,您已经完美地获取了这两个值:

      if (webhookEvent.event_type === "PAYMENT.SALE.COMPLETED") {
        console.log("Payment sale completed event received", webhookEvent);
        
        const userId = webhookEvent.resource.custom_id; // 
        const subscriptionId = webhookEvent.resource.id; //

因此,您的问题询问了与某些 JWT 相关的下一步的“最佳实践”。据我所知,答案是……无论做什么都是有意义的。您拥有所有相关数据,可以用它做您想做的事。如果您仍然不确定,我想查看一个关于不清楚的地方的更具体的问题。

© www.soinside.com 2019 - 2024. All rights reserved.