Процедура оплаты

Уведомление invoice.paid

Данные в уведомлении invoice.paid приходят в следующем виде...

Пример:  

{
  "object": "event",
  "id": "evt_14EccVBAhz1KYlo2C2j1B18pF",
  "created": "2015-08-09T18:51:42.201+03:00",
  "type": "invoice.paid",
  "data": {
    "object": "invoice",
    "id": "usr_inv_y1787SnkoiXuwj2ffZhaq",
    "payqrNumber": "3888888888888888",
    "created": "2015-08-09T19:31:42.201+03:00",
    "modified": "2015-08-09T19:31:42.201+03:00",
    "livemode": true,
    "payqrUserId": "000000000001",
    "scenario": "buy",
    "amount": 2000.00,
    "orderId": "101.800.54321",
    "cart": [{
      "article": "mgb1652",
      "name": "Сковорода",
      "imageUrl": "http://modastuff.ru/images/item021.gif",
      "quantity": "1",
      "amount": 1000.00
    }, {
      "article": "tfe1901",
      "name": "Кастрюля",
      "imageUrl": "http://modastuff.ru/images/item047.gif",
      "quantity": "1",
      "amount": 1000.00
    }],
    "customer": {
      "firstName": "Иван",
      "phone": "84956696756"
    },
    "delivery": {
      "country": "Россия",
      "region": "Москва",
      "city": "Москва",
      "zip": "115093",
      "street": "Партийный пер.",
      "house": "1",
      "unit": "46",
      "comment": "На углу офисного здания",
      "longitude": 55.718666,
      "latitude": 37.634119,
      "kladrId": "7700000000022260002"
    },
    "promoCard": {
      "article": "malinacard",
      "value": "9999000099990000"
    },
    "message": {
      "text": "Промо-код на следующую покупку: GGZ81USI",
      "imageUrl": "http://modastuff.ru/promo0.jpg",
      "url": "http://modastuff.ru/weekend"
    },
    "confirmWaitingInMinutes": 4320,
    "status": "paid"
  }
}

В заголовке header PQRSecretKey должен быть указан ключ для сервера из личного кабинета (SecretKeyIn).

Ответ на сервер PayQR нужно отправить в таком виде:

Пример:  

{
  "id": "evt_14EccVBAhz1KYlo2C2j1B18pF",
}

В заголовке header PQRSecretKey должен быть указан ключ магазина из личного кабинета (SecretKeyOut).

Обработчик для оплаты заказа на «1С-Битрикс» (PHP) может быть реализован следующим образом.

Пример:  

<?php
  if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED !== true)
      die();

  $raw_data = file_get_contents("php://input");
  //кастомная функция логирования, для отладки
  payQrLogToFile($raw_data);
  $event = json_decode($raw_data);

  $secretKeyIn  = $arParams['SECRET_KEY_IN']; // Входящий ключ SecretKeyIn этого «Магазина», указанный в личном кабинете
  $secretKeyOut = $arParams['SECRET_KEY_OUT']; // Исходящий ключ SecretKeyOut этого «Магазина», указанный в личном кабинете

  header("PQRSecretKey:" . $secretKeyOut); // Подписать ответ исходящим ключом для подтверждения, что ответ действительно от «Магазина»

  if ($event->type == "invoice.order.creating" && CModule::IncludeModule("iblock") && CModule::IncludeModule("sale") && CModule::IncludeModule("catalog")) /* Проверяем тип события на соответствие типу события для создания заказа и задействуем нужные компоненты*/ {
      $invoice         = $event->data; // Объект «Счет на оплату» (invoice)
      $orderGroupParts = explode(".", $invoice->orderGroup);
      /* Получаем пришедшие идентификаторы учетной системы ИД сайта ИД пользователя на сайте и ИД пользователя в учетной системе (это редкая особенность «1С-Битрикс», часто в учетных системах понятия ИД сайта и ИД пользователя отсутствуют, а в формировании участвуют только артикулы позиций в заказе) */
      $siteId          = $orderGroupParts[0]; // ИД сайта (был сохранен на уровне кнопки PayQR в data-orderGroup первым параметром)
      $fuserId         = $orderGroupParts[1]; // ИД пользователя на сайте (был сохранен на уровне кнопки PayQR в data-orderGroup вторым параметром)
      $userId          = $orderGroupParts[2]; // ИД пользователя в учетной системе (был сохранен на уровне кнопки PayQR в data-orderGroup третьим параметром)
      if (empty($userId))
          $userId = $arParams['DEFAULT_USER_ID'];

      $arFieldsOrder = array();

      $arFieldsOrder[$arParams['FIELD_FIRSTNAME_ID']] .= ', ' . $invoice->customer->firstName;
      $arFieldsOrder[$arParams['FIELD_LASTNAME_ID']] .= ', ' . $invoice->customer->lastName;
      $arFieldsOrder[$arParams['FIELD_PHONE_ID']] .= ', ' . $invoice->customer->phone;
      $arFieldsOrder[$arParams['FIELD_EMAIL_ID']] .= ', ' . $invoice->customer->email;

      $arFieldsOrder[$arParams['FIELD_COUNTRY_ID']] .= ', ' . $invoice->delivery->country;
      $arFieldsOrder[$arParams['FIELD_CITY_ID']] .= ', ' . $invoice->delivery->city;
      $arFieldsOrder[$arParams['FIELD_ZIP_ID']] .= ', ' . $invoice->delivery->zip;

      if ($arParams['FIELD_ADDRESS_ID'] > 0):
          $arFieldsOrder[$arParams['FIELD_ADDRESS_ID']] .= ', ';
          if (!empty($invoice->delivery->street))
              $arAddress[] = 'ул. ' . $invoice->delivery->street;
          if (!empty($invoice->delivery->house))
              $arAddress[] = 'д. ' . $invoice->delivery->house;
          if (!empty($invoice->delivery->unit))
              $arAddress[] = 'корп.. ' . $invoice->delivery->unit;
          if (!empty($invoice->delivery->building))
              $arAddress[] = 'стр. ' . $invoice->delivery->building;
          if (!empty($invoice->delivery->flat))
              $arAddress[] = 'кв. ' . $invoice->delivery->flat;
          if (!empty($invoice->delivery->hallway))
              $arAddress[] = 'подъезд ' . $invoice->delivery->hallway;
          if (!empty($invoice->delivery->floor))
              $arAddress[] = 'этаж ' . $invoice->delivery->floor;
          if (!empty($invoice->delivery->intercom))
              $arAddress[] = 'домофон ' . $invoice->delivery->intercom;
          $arFieldsOrder[$arParams['FIELD_ADDRESS_ID']] .= implode(', ', $arAddress);
      endif;

      //$arFieldsOrder[$arParams['FIELD_PAYQR_ID']] = $invoice->payqrNumber;
      $commentOrder = $invoice->delivery->comment;

      AddMessage2Log('FUSERID: ' . $fuserId . ' | USERID: ' . $userId . ' | ');

      $dbBasketItems = CSaleBasket::GetList( // Получаем корзину этого пользователя из интернет-сайта
          array(
          "NAME" => "ASC",
          "ID" => "ASC"
      ), array(
          "FUSER_ID" => $fuserId,
          "LID" => SITE_ID,
          "ORDER_ID" => "NULL"
      ), false, false, array(
          "ID",
          "PRODUCT_ID",
          "PRODUCT_PRICE_ID",
          "NAME",
          "QUANTITY",
          "CAN_BUY",
          "PRICE"
      ));
      $arBasketItems = array();
      while ($arBasketItem = $dbBasketItems->Fetch()) /* На всякий случай, приводим состояние корзины на интернет-сайте в соответствие с содержанием заказа в PayQR (заказ, который начал оформляться в PayQR, приоритетнее) */
          $arBasketItems[] = $arBasketItem;
      $removingPositionKeys = array();
      /* Массив удаляемых из корзины PayQR элементов, которые не удалось поместить в корзину магазина (нет такого товара или не удалось добавить к корзину) */
      $invoice->amount      = 0; // Очищаем итоговую сумму счета для пересчета по текущим ценам интернет-сайта, так как они могли измениться

      payQrLogToFile("\\narBasketItems: " . var_export($arBasketItems, true));
      payQrLogToFile("\\ninvoice->cart: " . var_export($invoice->cart, true));

      foreach ($invoice->cart as $positionKey => $position) // Проверяем корзину PayQR и обновляем корзину магазина
          {
          $arPrice                   = CPrice::GetByID(intval($position->article));
          $arDiscounts               = CCatalogDiscount::GetDiscountByPrice($arPrice["ID"], $USER->GetUserGroupArray(), "N", SITE_ID);
          $discountPrice             = CCatalogProduct::CountPriceWithDiscount($arPrice["PRICE"], $arPrice["CURRENCY"], $arDiscounts);
          $arPrice["DISCOUNT_PRICE"] = $discountPrice;
          payQrLogToFile("\\narPrice: " . var_export($arPrice, true));
          if ($arProduct = CIBlockElement::GetByID(intval($arPrice["PRODUCT_ID"]))->Fetch()) {
              $position->amount = $arPrice["DISCOUNT_PRICE"] * $position->quantity; // Актуализируем сумму по позиции
              $position->name   = $arProduct["NAME"]; // Актуализируем название позиции
              $removingIndex    = -1;
              foreach ($arBasketItems as $index => $arBasketItem) /* Проверяем текущую корзину на интернет-сайте на соответствие заказу в приложении */
                  if ($arBasketItem["PRODUCT_PRICE_ID"] == $position->article) {
                      if ($arBasketItem["QUANTITY"] != $position->quantity) // Проверяем совпадение количества
                          CSaleBasket::Update($arBasketItem["ID"], array(
                              "QUANTITY" => $position->quantity
                          ));
                      /* Приводим в соответствие с тем, что в приложении, если есть расхождения */
                      $removingIndex = $index;
                      break;
                  }
              if ($removingIndex >= 0)
                  unset($arBasketItems[$removingIndex]);
                  /* Удаляем элементы из списка, которые успешно обработали, за ненадобностью */
              else if (!CSaleBasket::Add(array(
                      /* Заполняем корзину в учетной системе для создания заказа (это редкая особенность «1С-Битрикс», часто в учетных системах можно сразу создать заказ, а потом заполнить его содержимое, без необходимости эмуляции предварительного создания корзины) */
                      "PRODUCT_ID" => $arPrice["PRODUCT_ID"],
                      "PRODUCT_PRICE_ID" => $arPrice["ID"],
                      "PRICE" => $arPrice["PRICE"],
                      "CURRENCY" => $arParams['CURRENCY'],
                      "QUANTITY" => $position->quantity,
                      "LID" => $siteId,
                      "FUSER_ID" => $fuserId,
                      "DELAY" => "N",
                      "CAN_BUY" => "Y",
                      "NAME" => $arProduct["NAME"],
                      "CALLBACK_FUNC" => "",
                      "MODULE" => "sale",
                      "NOTES" => "",
                      "ORDER_CALLBACK_FUNC" => "",
                      "DETAIL_PAGE_URL" => "",
                      "CANCEL_CALLBACK_FUNC" => "",
                      "PAY_CALLBACK_FUNC" => ""
                  )))
                  $removingPositionKeys[] = $positionKey;
                  /* Удаляем позицию из заказа в приложении, если учетная система отказала в добавлении этой позиции в корзину по любым причинам */
              $invoice->amount += $position->amount; // Пересчитываем итоговую сумму заказа на основе данных учетной системы
          } else
              $removingPositionKeys[] = $positionKey;
              /* Удаляем позицию из заказа в приложении, если указанный товар не был найден в учетной системе (например, закончился на складе) */
      }
      foreach ($arBasketItems as $arBasketItem) /* Удаляем из корзины на интернет-сайте те товары, которые отсутствуют в заказе в приложении PayQR */
          CSaleBasket::Delete($arBasketItem["ID"]);
      foreach ($removingPositionKeys as $positionKey) /* Удаляем из корзины на интернет-сайте те товары, которые отсутствуют в учетной системе интернет-сайта */
          unset($invoice->cart[$positionKey]);

      payQrLogToFile("\\narBasketItems: " . var_export($arBasketItems, true));
      payQrLogToFile("\\ninvoice->amount: " . var_export($invoice->amount, true));

      $dbBasketItems = CSaleBasket::GetList( // Получаем корзину этого пользователя из интернет-сайта
          array(
          "NAME" => "ASC",
          "ID" => "ASC"
      ), array(
          "FUSER_ID" => $fuserId,
          "LID" => SITE_ID,
          "ORDER_ID" => "NULL"
      ), false, false, array(
          "ID",
          "PRODUCT_ID",
          "PRODUCT_PRICE_ID",
          "NAME",
          "QUANTITY",
          "CAN_BUY",
          "PRICE"
      ));
      $arBasketItems = array();
      while ($arBasketItem = $dbBasketItems->Fetch()) /* На всякий случай, приводим состояние корзины на интернет-сайте в соответствие с содержанием заказа в PayQR (заказ, который начал оформляться в PayQR, приоритетнее) */
          $arBasketItems[] = $arBasketItem;
      payQrLogToFile("\\narBasketItems: " . var_export($arBasketItems, true));
      //exit;
      if (count($invoice->cart) > 0) /* Создаем заказ в учетной системе интернет-сайта, если оформляемая корзина после всех проверок осталась непустой, иначе не создаем заказ */ {
          $invoice->orderId = CSaleOrder::Add(array( // Добавляем заказ в учетную систему
              "LID" => $siteId,
              "PERSON_TYPE_ID" => 1,
              "PAYED" => "N",
              "CANCELED" => "N",
              "STATUS_ID" => "N",
              "PRICE" => $invoice->amount,
              "CURRENCY" => $arParams['CURRENCY'],
              "USER_ID" => intval($userId),
              "USER_DESCRIPTION" => $commentOrder
          ));

          if ($invoice->orderId == false)
              AddMessage2Log('Ошибка добавления заказа');


          if (count($arFieldsOrder) > 0) {
              foreach ($arFieldsOrder as $id => $value) {

                  $value = substr($value, 2);

                  if ($id > 0 && !empty($value) && $arOrderProps = CSaleOrderProps::GetByID($id)) {
                      CSaleOrderPropsValue::Add(array(
                          "ORDER_ID" => $invoice->orderId,
                          "ORDER_PROPS_ID" => $id,
                          "NAME" => $arOrderProps['NAME'],
                          "CODE" => $arOrderProps['CODE'],
                          "VALUE" => $value
                      ));
                  }

              }
          }


          CSaleBasket::OrderBasket($invoice->orderId, $fuserId, $siteId);
          /* Связываем только что созданный заказ с текущей корзиной на интернет-сайте, чтобы корзина на интернет-сайте не осталась «брошенной» */
      }
      //------- Формирование заказа завершено в 1C-Битрикс -------
      $raw_data = json_encode($event);
      payQrLogToFile($raw_data);
      echo $raw_data;
  } else if ($event->type == "invoice.paid" && CModule::IncludeModule("sale")) /* Проверяем тип события на соответствие типу события для оплаты заказа и задействуем нужные компоненты этой учетной системы */ {
      /* логика оплаты заказа */
  } else
      echo json_encode(array(
          "id" => $event->id
      ));
?>