Разрабатывая систему приема платежей, возникла необходимость получения уведомления от платежной системы о результатах обработки платежа. В качестве платежной системы была выбрана платформа Яндекс.Деньги. Данная платформа осуществляет уведомление посредством 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 - обрабатывайте исключения и логируйте вызовы. В противном случае вы потратите уйму времени на анализ проблем. Конечно это нужно делать без фанатизма, а то система начнет тормозить только из-за ведения логов. Но на этапе разработки и опытной эксплуатации это точно нужно делать.
Надесь, что анатомия моих ошибок позволит Вам сэкномить драгоценное время.