首页 > 解决方案 > 我的 Paypal 结帐流程 Angular-Node 安全吗?

问题描述

我正在使用“基本智能支付按钮集成”开发贝宝结帐,并将其与安装“结帐-服务器-sdk”的服务器节点集成。

我遵循了文档:

https://developer.paypal.com/docs/checkout/reference/server-integration/set-up-transaction/ https://github.com/paypal/Checkout-NodeJS-SDK

他们建议:

  1. 'createOrder' 从客户端开始并调用服务器
  2. 在服务器上生成一个 orderID 并将其返回给客户端
  3. 'onApprove' 将 orderID 发送到服务器并在服务器上批准它
  4. 将响应返回给客户端

我不认为这是一个很好的流程。有人可以:

  1. 开始付款
  2. 所以应用程序在服务器上创建从 db 获取购物车的订单并详细说明 100 欧元的总价格。
  3. 生成 orderID 并将其发送回客户端
  4. 而不是批准这个订单,一个“坏用户”可以以某种方式向服务器发送另一个可能对应于较低价格(2欧元)的订单ID
  5. 所以他可以批准支付 2 欧元

所以我不明白为什么我们需要让结帐从客户端跳到服务器更多次。或者我在结账流程上做错了什么?

不幸的是,我觉得 Paypal 的文档很不清楚。

checkout.component.html

<!-- * here there is a form where i get shipment info, invoice info and so on ->

<!-- * PAYPAL SMART BUTTONS -->
<div>
    <div #paypal></div>
</div>

checkout.component.ts

onFormSubmit() {
  this.isFormSubmitted = true;
  // set paypal settings and show the paypal buttons
  this.paypalSetting(this.shippmentInfo, this.invoiceRequired, this.invoice, this.addressInvoice);
}

async paypalSetting(shipment, invoiceRequired, invoice, addressInvoice) {

  await paypal
    .Buttons({
      style: {
        size: 'responsive',
        label: 'pay',
      },
      experience: {
        input_fields: {
          no_shipping: 1,
        },
      },
      createOrder: async (data, actions) => {
        console.log('CREATE ORDER -->');
        var paypalOrderId;

        //generate new order
        await this.apiService.newOrder().toPromise().then(
          (res) => {
            console.log('ON CREATE: SUCCESSFULLY CREATED')
            paypalOrderId = res.order.paypalOrderId;

            // ????? someone here could change 'paypalOrderId' with another value !!!! 

            //I also would like to return the 'paypalOrderId' only here !!
          },
          (err) => {
            console.log('ON CREATE: ERROR: ' + err);
            // how should i manage this error ? i should skip the flow to onError but HOW ?
          }
        );
        return paypalOrderId;
      },
      onApprove: async (data, actions) => {
        console.log('APPROVE ORDER -->');
        var paypalOrderId = data.orderID;

        console.log('ON APPROVE: save the order on server/DB')

        await this.apiService.saveOrder(shipment, invoiceRequired, invoice, addressInvoice, paypalOrderId).toPromise().then(
          (res) => {
            console.log('ON APPROVE: ORDER APPROVED')
            this.isPaid = true;
            //if isPaid i can show a 'success page'
          },
          (err) => {
            console.log('ON APPROVE: ERROR: ' + err);
            this.isPaid = false;
          }
        );
      },
      onError: (err) => {
        console.log('ON ERROR: ' + err);
      },
    })
    .render(this.paypalElement.nativeElement);
}

节点 api.js

//* paypal
const paypal = require('@paypal/checkout-server-sdk');
const payPalClient = require('../paypalManager');

router.post('/newOrder', tokenManager.verifyAccessToken, async function (req, res, next) {
  
  const idUser = req.userId;

  // I get the shoppingcart of the user 'idUser'

  // i calculate the total price
  var totalPrice;

  //* Call PayPal to set up a transaction
  let order;
  const request = new paypal.orders.OrdersCreateRequest();
  request.prefer("return=representation");
  request.requestBody({
    intent: 'CAPTURE',
    purchase_units: [{
      description: 'payment ecc..', /
      amount: {
        currency_code: 'EUR',
        value: totalPrice
      }
    }],
    application_context: {
      brand_name: "brand",
      shipping_preference: 'NO_SHIPPING',
    },
  });
  let response = await payPalClient.client().execute(request);
  order = response;
  const paypalOrderId = order.result.id;

  // return a successful response to the client with the order ID
  return res.json({
    status: 200,
    order: {
      paypalOrderId: paypalOrderId,
    },
    message: "Paypal order sucessfully created",
  });

});


router.post('/saveOrder', tokenManager.verifyAccessToken, async function (req, res, next) {
  const idUser = req.userId;
  var paypalOrderId = req.body.paypalOrderId;

  try {
    connection.beginTransaction(async () => {
      try {
        // here i insert all the checkout infos in DB

        // confirm the queries executions
        connection.commit(async function (err) {
          if (err) {
            //return connection.rollback(function () {
            connection.rollback(function () {
              return next(createError.Unauthorized("Sql query error: " + err)); //! or error.message
            });
          }

          //* here i send the Emails to confirm the checkout
          
          //* capture/approve the order
          console.log('CAPTURING THE ORDER')
          var request = new paypal.orders.OrdersCaptureRequest(paypalOrderId);
          request.requestBody({});
          // Call API with your client and get a response for your call
          let response = await payPalClient.client().execute(request);

          //*response
          return res.json({
            status: 200,
            message: "Paypal sucessfully approved",
          });

        });// end commit

      } catch (error) {
        connection.rollback(function () {
          return next(createError.Unauthorized("Sql query error " + error)); //! or error.message
        });
      }

    });// end transaction

  } catch (error) {
    return next(error);
  }

});

节点 paypalManager.js

'use strict';

/**
 * PayPal Node JS SDK dependency
 */
const checkoutNodeJssdk = require('@paypal/checkout-server-sdk');

/**
 * Returns PayPal HTTP client instance with environment that has access
 * credentials context. Use this instance to invoke PayPal APIs, provided the
 * credentials have access.
 */
function client() {
  return new checkoutNodeJssdk.core.PayPalHttpClient(environment());
}

/**
 * Set up and return PayPal JavaScript SDK environment with PayPal access credentials.
 * This sample uses SandboxEnvironment. In production, use LiveEnvironment.
 */
function environment() {
  let clientId = process.env.PAYPAL_CLIENT_ID;
  let clientSecret = process.env.PAYPAL_CLIENT_SECRET;

  return new checkoutNodeJssdk.core.SandboxEnvironment(
    clientId, clientSecret
  );
}


module.exports = {
  client: client,
  prettyPrint: prettyPrint
};

标签: node.jspaypalcheckoutpaypal-rest-sdk

解决方案


您在客户端和服务器之间“跳跃”的原因是付款人的批准必须发生在客户端上。付款人无法在您的服务器上给予批准,他们不在您的服务器上。他们正在使用客户端浏览器。


关于:

“坏用户”可能会以某种方式向服务器发送另一个可能对应于较低价格(2 欧元)的 orderID

如果发生这种情况,您的服务器应该拒绝不需要的交易,而不是继续它。这就是拥有服务器的意义所在。除非您的服务器正常,否则什么都不会发生。


推荐阅读