Обновление стоимости и корзины

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

  1. Пользователь меняет состав корзины (или конечную сумму заказа).
  2. Продавец обновляет данные у себя на сервере.
  3. Продавец обновляет данные в платежной сессии Yandex Pay.

Проблема в том, что пользователь может начать процесс оплаты где-то между 1 и 2 пунктом, не дождавшись обновленных данных. Таким образом, он нажмет на кнопку и откроет форму Yandex Pay со старой корзиной.

Чтобы избежать подобной ситуации, нужно обновлять корзину с блокировкой кнопки Yandex Pay на время обновления.

Метод paymentSession.update принимает в качестве аргумента функцию (которая тут же выполняется), результатом работы которой ожидается Promise. Кнопка блокируется до резолва Promise.

Пример

function onYaPayLoad() {
    const YaPay = window.YaPay;
    let activeSession = null;

    // Данные платежа
    const paymentData = {
        env: YaPay.PaymentEnv.Sandbox,
        version: 2,
        countryCode: YaPay.CountryCode.Ru,
        currencyCode: YaPay.CurrencyCode.Rub,
        merchant: {
            id: '<YOUR_MERCHANT_ID>',
            name: 'test-merchant-name',
            url: 'https://test-merchant-url.ru',
        },
        order: getNewOrder(),
        paymentMethods: [
            {
                type: YaPay.PaymentMethodType.Card,
                gateway: 'test-gateway',
                gatewayMerchantId: 'test-gateway-merchant-id',
                allowedAuthMethods: [YaPay.AllowedAuthMethod.PanOnly],
                allowedCardNetworks: [
                    YaPay.AllowedCardNetwork.Visa,
                    YaPay.AllowedCardNetwork.Mastercard,
                    YaPay.AllowedCardNetwork.Mir,
                    YaPay.AllowedCardNetwork.Maestro,
                    YaPay.AllowedCardNetwork.VisaElectron,
                ],
            },
        ],
    };

    // Обработчик на получение платежного токена
    function onPaymentProcess(event) {
        console.log(`Payment token — ${event.token}`);
    }

    // Обработчик на ошибки при оплате
    function onPaymentError(event) {
        console.log(`Payment error — ${event.reason}`);
    }

    // Обработчик на отмену оплаты
    function onPaymentAbort(event) {}

    // Создать платежную сессию.
    YaPay.createSession(paymentData, {
        onProcess: onPaymentProcess,
        onAbort: onPaymentAbort,
        onError: onPaymentError,
    })
        .then(function (paymentSession) {
            activeSession = paymentSession;
            paymentSession.mountButton(document.querySelector('#button_container'), {
                type: YaPay.ButtonType.Pay,
                theme: YaPay.ButtonTheme.Black,
                width: YaPay.ButtonWidth.Auto,
            });
        })
        .catch(function (err) {
            // Не получилось создать платежную сессию.
        });

    // Эмуляция обновления корзины
    //   apiRequestPromise — Promise api-запроса на обновление корзины на сервисе
    onOrderUpdate(function (apiRequestPromise) {
        if (!activeSession) {
            return console.log('Payment session is not created');
        }

        const newOrder = getNewOrder();

        // Обновляем корзину с блокировкой кнопки
        activeSession.update(async function () {
            await apiRequestPromise;

            return {
                order: newOrder,
            };
        });
    });
}

/**
 * Сопроводительные функции для иллюстрации работы
 */

function getNewOrder() {
    const randomInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;

    const node = document.querySelector('#order_amount');
    const amount = randomInt(1000, 20000).toFixed(2);

    node.innerHTML = Intl.NumberFormat('ru-RU', {
        style: 'currency',
        currency: 'RUB',
    }).format(amount);

    return {
        id: `order-id-${Date.now()}`,
        total: { amount },
    };
}

function onOrderUpdate(cb) {
    const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

    const updateButton = document.querySelector('#update_order_button');

    updateButton.addEventListener('click', function () {
        // Эмуляция Promise'a запроса на обновление корзины на сервере
        const apiRequestPromise = wait(2000);

        cb(apiRequestPromise);
    });
}