Безопасен ли мой поток оформления заказа Paypal на угловом узле?

#node.js #paypal #checkout #paypal-rest-sdk

#node.js #paypal #Оформить покупку #paypal-rest-sdk

Вопрос:

Я разрабатываю paypal checkout, используя «базовую интеграцию кнопок смарт-платежей» и интегрирую ее с серверным узлом, устанавливающим «checkout-server-sdk».

Я следил за документацией:

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

где они предлагают:

  1. «CreateOrder» запускается с клиента и вызывает сервер
  2. генерируем на сервере идентификатор заказа и возвращаем его клиенту
  3. ‘onApprove’ отправляет на сервер идентификатор заказа и утверждает его на сервере
  4. верните клиенту ответ

Я не думаю, что это хороший поток. Кто-то мог:

  1. начните платеж
  2. таким образом, приложение создает заказ на сервере, извлекая корзину покупок из базы данных, и определяет общую цену в 100 евро.
  3. создайте идентификатор заказа и отправьте его обратно клиенту
  4. вместо того, чтобы одобрить этот заказ, «плохой пользователь» может каким-то образом отправить на сервер другой идентификатор заказа, который может соответствовать более низкой цене (2 евро)
  5. чтобы он мог одобрить платеж в размере 2 евро

Поэтому я не понимаю, почему нам нужно, чтобы проверка переходила больше раз от клиента к серверу. Или, может быть, я делаю что-то не так в моем checkoutflow?

к сожалению, я чувствую, что документация 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);
}
 

Node 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
};
 

Комментарии:

1. Перефразируйте свои вопросы, они не имеют никакого смысла

2. @Preston PHX я перефразирую вопросы на примере. Я надеюсь, что они более понятны.

Ответ №1:

Причина, по которой вы «прыгаете» между клиентом и сервером, заключается в том, что одобрение плательщиком должно произойти на клиенте. Плательщик не может дать свое одобрение на вашем сервере, они не находятся на вашем сервере. Они используют клиентский браузер.


Что касается:

«плохой пользователь» может каким-то образом отправить на сервер другой идентификатор заказа, который может соответствовать более низкой цене (2 евро)

Если это произойдет, ваш сервер должен отклонить нежелательную транзакцию и не выполнять ее. В этом смысл наличия сервера. Ничего не произойдет, если ваш сервер не одобрит это.