Интеграция с Яндекс.Деньги. Обработка ответа.
18.02.2016

Разрабатывая систему приема платежей, возникла необходимость получения уведомления от платежной системы о результатах обработки платежа. В качестве платежной системы была выбрана платформа Яндекс.Деньги. Данная платформа осуществляет уведомление посредством HTTP POST запроса по тому адресу который вы ей укажете в настройках своего кошелька.

С задачей обработки POST запроса хорошо справляется обычная PLSQL процедура на которую даны соответствующие привилегии.

Итак по порядку:

Вот URL по которму будет обращаться система Яндекс.Деньги:

http://1apex.ru/apex/payment

Вот процедура, которая обрабатывает этот запрос (scott имя схемы):

CREATE OR REPLACE PROCEDURE scott.payment(
  notification_type IN VARCHAR2 := NULL,
  operation_id      IN VARCHAR2 := NULL,
  amount            IN VARCHAR2 := NULL,
  withdraw_amount   IN VARCHAR2 := NULL,
  currency          IN VARCHAR2 := NULL,
  datetime          IN VARCHAR2 := NULL,
  sender            IN VARCHAR2 := NULL,
  codepro           IN VARCHAR2 := NULL,
  operation_label   IN VARCHAR2 := NULL,
  sha1_hash         IN VARCHAR2 := NULL,
  test_notification IN VARCHAR2 := NULL,
  unaccepted        IN VARCHAR2 := NULL,
  label             IN VARCHAR2 := NULL) IS
BEGIN
  /* некий код обрабатывающий параметры */
END;


Выбор параметров процедуры не случайный, их состав продиктован платежной системой и для данного POST запроса должен быть именно таким. Можно больше, но ни как не меньше, иначе POST запрос будете возвращать ошибку 404 - страница не найдена.

На процедуру даны привилиегии:

GRANT EXECUTE ON scott.payment TO PUBLIC;
CREATE PUBLIC SYNONYM payment FOR scott.payment;

В функции apex_040200.wwv_flow_epg_include_mod_local добавлено разрешение на данную процедуру:

CREATE OR REPLACE FUNCTION apex_040200.wwv_flow_epg_include_mod_local(procedure_name IN VARCHAR2) RETURN BOOLEAN IS
BEGIN
  IF UPPER(procedure_name) IN ('PAYMENT') THEN
    RETURN TRUE;
  ELSE
    RETURN FALSE;
  END IF;
END wwv_flow_epg_include_mod_local;

Все. Можно вызывать процедуру методом POST. Я тестировал утилитой: DHC

Вроде все сделано, работает, можно идти спать. Ан нет.

Если в теле процедуры произойдет исключение, то в браузере вы получите ошибку 404 Not Found

Что бы понять природу ошибки придеться включить логирование вызовов через шлюз Embedded PLSQL Gateway:

BEGIN
  dbms_epg.set_global_attribute('log-level', 3);
END;

Подобнее об этом здесь

Логи смотреть на сервере в каталоге, где пишется alert-лог.

В моем случае проблема была вызвана тем, что в процедуре payment происходило обновление статуса записи о платеже, при обновлении срабатывал триггер, который должен был отправлять почтовое умедомление на email посредством вызова apex_mail.send. Вот на этом самом вызове и поднималось исключение:

ORA-20001: This procedure must be invoked from within an application session

Только оно не выводилось в сообщении об ошибке, а просто выводилась ошибка 404 протокола HTTP

Причем при вызове процедры payment из под пользователя SCOTT ошибка не возникала, все отрабатывало. Этой проблеме посвящена отдельная заметка.

А вот как выглядела запись об ошибке в лог-файле orcl_s000_1847.trc:

Embedded PL/SQL Gateway: MODPLSQL-00261: modplsql: /apex/payment HTTP-404 Failed to execute target procedure ORA-20001: This procedure must be invoked from within an application session.
ORA-06512: at "APEX_040200.WWV_FLOW_MAIL", line 373
ORA-06512: at "APEX_040200.WWV_FLOW_MAIL", line 410
ORA-06512: at "APEX_040200.WWV_FLOW_MAIL_API", line 69
ORA-06512: at "SCOTT.SEND_EMAIL", line 64
ORA-06512: at "SCOTT.TRG_MYTABLE$UPDATE", line 3
ORA-04088: error during execution of trigger 'SCOTT.TRG_MYTABLE$UPDATE'
ORA-06512: at "SCOTT.PAYMENT", line 33
ORA-06512: at line 35
Embedded PL/SQL Gateway: modplsql: (wpdenv.c,708) script_name='/apex' path_info='/payment'script_prefix='' dad_name='apex'

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

Доработанный текст процедуры должен выглядеть как то так:

CREATE OR REPLACE PROCEDURE SCOTT.PAYMENT(
  notification_type IN VARCHAR2 := NULL,
  operation_id IN VARCHAR2 := NULL,
  amount IN VARCHAR2 := NULL,
  withdraw_amount IN VARCHAR2 := NULL,
  currency IN VARCHAR2 := NULL,
  datetime IN VARCHAR2 := NULL,
  sender IN VARCHAR2 := NULL,
  codepro IN VARCHAR2 := NULL,
  operation_label IN VARCHAR2 := NULL,
  sha1_hash IN VARCHAR2 := NULL,
  test_notification IN VARCHAR2 := NULL,
  unaccepted IN VARCHAR2 := NULL,
  label IN VARCHAR2 := NULL
) IS
  v_err VARCHAR2(4000);
BEGIN

  /* некий код обрабатывающий параметры */
 
  -- При работе с деньгами лучше все логировать, даже если все прошло успешно.
  INSERT INTO log_table (data) VALUES (
    'notification_type='|| notification_type ||' '||
    'operation_id='||      operation_id ||' '||
    'amount='||            amount ||' '||
    'withdraw_amount='||   withdraw_amount ||' '||
    'currency='||          currency ||' '||
    'datetime='||          datetime ||' '||
    'sender='||            sender ||' '||
    'codepro='||           codepro ||' '||
    'label='||             label ||' '||
    'operation_label='||   operation_label ||' '||
    'sha1_hash='||         sha1_hash ||' '||
    'test_notification='|| test_notification ||' '||
    'unaccepted='||        unaccepted ||' '||
    'v_hash='||            v_hash
  );

EXCEPTION WHEN OTHERS THEN
  v_err := SQLERRM;
  INSERT INTO log_table (data) VALUES (v_err||' '||
    'notification_type='|| notification_type ||' '||
    'operation_id='||      operation_id ||' '||
    'amount='||            amount ||' '||
    'withdraw_amount='||   withdraw_amount ||' '||
    'currency='||          currency ||' '||
    'datetime='||          datetime ||' '||
    'sender='||            sender ||' '||
    'codepro='||           codepro ||' '||
    'label='||             label ||' '||
    'operation_label='||   operation_label ||' '||
    'sha1_hash='||         sha1_hash ||' '||
    'test_notification='|| test_notification ||' '||
    'unaccepted='||        unaccepted ||' '||
    'v_hash='||            v_hash);
END;

 

Т.е. Best practices при разработке API - обрабатывайте исключения и логируйте вызовы. В противном случае вы потратите уйму времени на анализ проблем. Конечно это нужно делать без фанатизма, а то система начнет тормозить только из-за ведения логов. Но на этапе разработки и опытной эксплуатации это точно нужно делать.

Надесь, что анатомия моих ошибок позволит Вам сэкномить драгоценное время.

release 1.0