Administrator
Administrator
发布于 2025-08-01 / 24 阅读
0
0

Node.js接入paypal支付(webhook)

Node.js+paypal ServersSDK OrderV2

文档:https://developer.paypal.com/docs/api/orders/sdk/v2/

引入依赖

npm i @paypal/paypal-server-sdk

创建客户端

  client = new Client({
    clientCredentialsAuthCredentials: {
      oAuthClientId: '<填写自己的clientId>',
      oAuthClientSecret: '<填写自己的Secret>'
    },
    timeout: 0,
    environment: Environment.Sandbox, //Sandbox沙盒,调试用;Production,生产环境,上线时设置
    logging: {
      logLevel: LogLevel.Warn,
      logRequest: {
        logBody: false
      },
      logResponse: {
        logHeaders: false
      }
    },
  });
  ordersController = new OrdersController(client);

收款流程

后端创建订单->用户跳转到payer-action->用户在页面完成授权->webhook中收到APPROVED事件,后端SDK执行capture冻结资金->完成后WEBHOOK收到COMPLETED事件后更新数据库并完成发货

(注意需要到创建的应用中添加webhook为自己的域名和路由)

创建订单

    const collect = {
      body: {
        intent: CheckoutPaymentIntent.Capture,
        purchaseUnits: [
          {
            amount: {
              currencyCode: 'TWD', //新台币,可以在文档中找到其它货币代码
              value: '108.00', //金额
            },
            customId: '自定义字段,在后续capture和completed时会回传',
            referenceId: customField,
          }
        ],
        paymentSource: {
          paypal: {
            experienceContext: {
              shippingPreference: ShippingPreference.GetFromFile,
              returnUrl: 'https://example.com/returnUrl', //可自定义,用户完成授权后会跳转到这里
              cancelUrl: 'https://example.com/cancelUrl',
              landingPage: PaypalExperienceLandingPage.Login,
              userAction: PaypalExperienceUserAction.PayNow,
              paymentMethodPreference: PayeePaymentMethodPreference.ImmediatePaymentRequired,
            },
          },
        },
      }
    }

    try {
      
      const { result, ...httpResponse } = await ordersController.createOrder(collect);
      // Get more response info...
      // const { statusCode, headers } = httpResponse;
      return result
    } catch (error) {
      console.log(error);
      
      return false
      if (error instanceof ApiError) {
        const errors = error.result;
        // const { statusCode, headers } = error;
      }
    }
{
"id": "5O190127TN364715T",
"status": "PAYER_ACTION_REQUIRED",
"payment_source": {
"paypal": { }
},
"links": [
{
"href": "https://api-m.paypal.com/v2/checkout/orders/5O190127TN364715T",
"rel": "self",
"method": "GET"
},
{
"href": "https://www.paypal.com/checkoutnow?token=5O190127TN364715T",
"rel": "payer-action",
"method": "GET"
}
]
}

如果成功,result中是这样的JSON,将payer-action对应的HREF在前端重定向让用户授权

webhook处理路由

app.post('/paypal-event', async function(req, res, next){
    if(req.body.event_type == 'CHECKOUT.ORDER.APPROVED') {
        await paypal.capture(req.body.resource.id)
        console.log('paypal-event CHECKOUT.ORDER.APPROVED', req.body.resource.id);
    }else if(req.body.event_type == 'PAYMENT.CAPTURE.COMPLETED') { //付款完成
        console.log('paypal-event PAYMENT.CAPTURE.COMPLETED', req.body.resource.supplementary_data.related_ids.order_id);
        let ret = await SQL.paypalCallback(req.body); //自行实现更新数据库的逻辑
        if(ret == false) {
            res.sendStatus(500)
        }
    }else { //未处理的类型
        console.log('paypal-event no handler',req.body.event_type, JSON.stringify(req.body));
    }
    res.send('ok')
})
//paypal.captrue实现
  async function capture (id){
    if(ordersController == null) {
      return false
    }
    const collect = {
      id
    }
    try {
      const { result, ...httpResponse } = await ordersController.captureOrder(collect);
      // Get more response info...
      // const { statusCode, headers } = httpResponse;
      return result
    } catch (error) {
      return false
      if (error instanceof ApiError) {
        const errors = error.result;
        // const { statusCode, headers } = error;
      }
    }
  }

Sandbox调试环境

后端中创建paypal客户端时将environment设为Sandbox,创建的订单的二级域名则是sandbox,打开创建的订单需要登录,用沙盒的用户端账号,步骤:登录商家端控制台https://developer.paypal.com/dashboard/,点sandbox accounts,用Person类型的账号登录,控制台中可以设置余额。

注意控制台右上角有开关,沙盒和商家身份创建的应用不一样,设环境为沙盒后,要用沙盒身份创建的应用的clientId和secret


评论