Разработка ПО для приема платежей в системе CyberPlat и интеграция с АСР Fastcom
Разработка ПО для приема платежей в системе CyberPlat и интеграция с АСР Fastcom
Необходимость в разработке шлюза между платежной системой "Киберплат" и АСР Fastcom возникла в далеком 2008 году. На тот момент уже было реализовано ПО для взаимодействия с системами FreeCash и Assist. Полученный при реализации платежных шлюзов опыт позволил выбрать архитектуру, снижающую нагрузку на сервер биллинга Fastcom, ядром которого является СУБД Oracle, и повысить безопасность инфраструктуры приема платежей. Все платежные шлюзы были размещены на отдельном сервере под управлением ОС FreeBSD, в качестве web-сервера был выбран Apache 2.2 + suEXEC. Для обеспечения подключений к СУБД Oracle должна быть установлена библиотека OCI8. Платежный шлюз реализовала на PHP с использованием модуля расширения php_oci8.
ОАО «Киберплат» осуществляет организацию приема платежей в режиме on-line. Схема приема платежей:
Изначальная реализация шлюза предусматривала возможность проведения платежей за услуги доступа в интернет и кабельное телевидение, однако в 2011 году добавились требования по приему авансовых платежей новых абонентов без проверки номера договора (договор в процессе оформления) и поддержкой новой системы нумерации договоров с сохранением старой нумерации.
Итак, в соответствии с требованиями технического задания провайдера необходимо было обеспечить поддержку следующих типов платежей:
Тип 0. Название: «Авансовый платеж новых абонентов».
Данные для предварительной проверки: в поле additional передается ФИО плательщика, номер договора не обязателен, минимальная сумма платежа - 10 рублей, максимальная сумма платежа - 10000 рублей.
Тип 1. Название: «Интернет».
Данные для предварительной проверки: допустимый набор символов - 0123456789, минимальное число символов - 8, максимальное количество символов - 11, префиксов символов нет, весь диапазон разрешен, минимальная сумма платежа - 10 рублей, максимальная сумма платежа - 10000 рублей.
Тип 2. Название: «Кабельное телевидение».
Данные для предварительной проверки: допустимый набор символов - 0123456789, минимальное число символов - 8, максимальное количество символов - 11, префиксов символов нет, весь диапазон разрешен, минимальная сумма платежа - 10 рублей, максимальная сумма платежа - 10000 рублей.
По каждому типу платежа должна быть реализована возможность поиска абонента, проверка состояния платежа и занесение платежа в базу данных Fastcom.
Справка по операторам, обслуживаемым в системе CyberPlat, содержит подробную информацию о параметрах платежей каждого оператора в соответствии с протоколом взаимодействия CyberPlat.
В 2008 году аутентификация платежной системы могла осуществляться по выбору: базовая HTTP аутентификация, с использованием клиентского SSL сертификата X.509, однако впоследствии аутентификация с использованием сертификата стала обязательным условием подключения к системе Киберплат.
Скрипт для приема платежей в системе Киберплат - cyberplat.php:
<?php /* ################################################################## Скрипт cyberplat.php - пример приёма платежей для взаимодействия с платежной системой «CyberPlat». ################################################################## */ PutEnv("ORACLE_SID=fst"); PutEnv("NLS_LANG=AMERICAN_AMERICA.CL8MSWIN1251"); PutEnv("ORACLE_HOME=/usr/local/oracle8-client/"); PutEnv("TNS_ADMIN=/www/oracle/"); $DATE=""; //Реквизиты платежной системы Cyberplat в биллинге Фастком $auth_username=""; $auth_password=""; ///////////////////////////////////////////////////////////////////// /* function Answer - ответы на запросы, возвращаются в виде XML-сообщений. */ function Answer($status, $message){ GLOBAL $PAYMENT_ID; GLOBAL $DATE; GLOBAL $ACTION; $replacement = "$1-$2-$3T$4:$5:$6"; $pay_d= preg_replace('/^(20\d{2})-(0?[1-9]|1[0-2])-(0?[1-9]|[12][0-9]|3[01]) ([0-9]{2}):([0-9]{2}):([0-9]{2})$/', $replacement, $DATE); if ($status == 0 and ($ACTION == 'payment' or $ACTION == 'cancel' or $ACTION == 'status')) { $string =<<<END <?xml version="1.0" encoding="windows-1251"?> <response> <code>$status</code> <authcode>$PAYMENT_ID</authcode> <date>$pay_d</date> <message>$message</message> </response> END; }elseif(($ACTION == 'check' and $status == 0) or ($status != 0)){ $string =<<<END <?xml version="1.0" encoding="windows-1251"?> <response> <code>$status</code> <message>$message</message> </response> END; } print "$string"; exit(); } ///////////////////////////////////////////////////////////////////// /* function Check_contract - проверка возможности проведения платежа по номеру абонента и сумме платежа */ function Check_contract($cid, $summ_pay, $type, $con){ GLOBAL $PAY_MAXSUM; GLOBAL $PAY_CONTRACT; GLOBAL $CLIENT_ID; $PAY_CONTRACT = ""; $CLIENT_ID = ""; if (($summ_pay < 10) or ($summ_pay > $PAY_MAXSUM)){ Answer("3",""); } $query = "SELECT cc.* FROM ct_t_contract cc where contract_no='$cid'"; $stid = oci_parse($con, $query); if (!$stid){ Answer("-3",""); } oci_define_by_name($stid, "ID", $f_id); oci_define_by_name($stid, "CLIENT_ID", $cl_id); $r = oci_execute($stid); if (!$r) { Answer("-3",""); } if (oci_fetch($stid)) { $PAY_CONTRACT=$f_id; $CLIENT_ID=$cl_id; oci_free_statement($stid); return 1; } else { oci_free_statement($stid); Answer("2",""); } } ///////////////////////////////////////////////////////////////////// /* function Check_pay - проверка наличия платежа в биллинге */ function Check_pay($p_id, $con){ GLOBAL $PAYMENT_ID; GLOBAL $DATE; $PAYMENT_ID=""; $query = "SELECT ID, TO_CHAR(PAY_DATE,'YYYY-MM-DD HH24:MI:SS') PAY_DATE FROM pm_t_payment pm WHERE pm.payment_no = '$p_id'"; $stid = oci_parse($con, $query); if (!$stid){ Answer("-3",""); } oci_define_by_name($stid, "ID", $PAYMENT_ID); oci_define_by_name($stid, "PAY_DATE", $pay_date); $r = oci_execute($stid); if (!$r) { Answer("-3",""); } if (oci_fetch($stid)) { oci_free_statement($stid); $DATE=$pay_date; return 1; }else{ oci_free_statement($stid); return 0; } } ///////////////////////////////////////////////////////////////////// /* function GetValue - поиск данных объекта договора в биллинге Фастком */ function GetValue($objid, $objtype, $propname, $con){ $query = "SELECT v.value FROM ct_t_value v, ct_t_property p where v.objprop_id = p.id and p.code = '$propname' and p.objecttype_code = '$objtype' and v.controbj_id = '$objid'"; $stid = oci_parse($con, $query); if (!$stid){ Answer("-3",""); } oci_define_by_name($stid, "VALUE", $val); $r = oci_execute($stid); if (!$r) { Answer("-3",""); } oci_fetch($stid); oci_free_statement($stid); return $val; } ///////////////////////////////////////////////////////////////////// /* function Аuthenticate - аутентификация платежной системы Cyberplat в биллинге и поиск значений, необходимых для занесения платежа в базу данных */ function Аuthenticate($con,$username,$password){ GLOBAL $CONTRACT_NO; //Идентификатор платежной системы Cyberplat в биллинге GLOBAL $ENTERPRISE_ID; GLOBAL $PAY_MAXSUM; //Максимальная сумма платежа в биллинге GLOBAL $PAY_FORM; GLOBAL $CODE; GLOBAL $CURRENCY_CODE; $CONTRACT_NO=""; $ENTERPRISE_ID=""; $PAY_FORM=""; $CODE=""; $CURRENCY_CODE=""; $query = "SELECT v.controbj_id from ct_t_value v, ct_t_property p WHERE v.value = '$username' and v.objprop_id = p.id and p.code = 'USERNAME' and p.objecttype_code = 'PAY_RECEIVE'"; $stid = oci_parse($con, $query); if (!$stid){ Answer("-3",""); } oci_define_by_name($stid, "CONTROBJ_ID", $c_id); $r = oci_execute($stid); if (!$r) { Answer("-3",""); } if (!oci_fetch($stid)) { Answer("-3",""); } oci_free_statement($stid); if (GetValue($c_id,'PAY_RECEIVE','PASSWORD',$con) <> $password){ Answer("10","Unauthorized"); } else { $query_ct = "SELECT ct.* FROM CT_T_CONTRACT ct, CT_T_OBJECT ob WHERE ct.id = ob.contract_id AND ob.id = '$c_id'"; $stid_ct = oci_parse($con, $query_ct); if (!$stid_ct){ Answer("-3",""); } oci_define_by_name($stid_ct, "CONTRACT_NO", $c_no); oci_define_by_name($stid_ct, "ENTERPRISE_ID", $c_enterprise); $r_ct = oci_execute($stid_ct); if (!$r_ct) { Answer("-3",""); } while (oci_fetch($stid_ct)) { $CONTRACT_NO=$c_no; $ENTERPRISE_ID=$c_enterprise; } } oci_free_statement($stid_ct); $PAY_MAXSUM=GetValue($c_id,'PAY_RECEIVE','MAX_SUMMA',$con); if(empty($PAY_MAXSUM)){ $PAY_MAXSUM=10000; } $paydoccode=GetValue($c_id,'PAY_RECEIVE','PAY_DOCUMENT',$con); $query_paydoc = "SELECT * FROM RF_T_PAYDOCTYPE WHERE code='$paydoccode'"; $stid_paydoc = oci_parse($con, $query_paydoc); if (!$stid_paydoc){ Answer("-3",""); } oci_define_by_name($stid_paydoc, "PAY_FORM", $pay_form); oci_define_by_name($stid_paydoc, "CODE", $code); oci_define_by_name($stid_paydoc, "CURRENCY_CODE", $currency_code); $r_paydoc = oci_execute($stid_paydoc); if (!$r_paydoc) { Answer("-3",""); } while (oci_fetch($stid_paydoc)) { $PAY_FORM=$pay_form; $CODE=$code; $CURRENCY_CODE=$currency_code; } oci_free_statement($stid_paydoc); return 1; } ///////////////////////////////////////////////////////////////////// /* function Payment - проведение платежа в базе данных Fastcom. $p_no - идентификатор платежа в базе данных АСР "Фастком". $pay_summ - сумма платежа. $p_add - номер договора абонента или ФИО для авансовых платежей. $con - идентификатор соединения к базе данных oracle. $type - тип платежа (0 - авансовый платеж новых абонентов, 1 - платеж за интернет, 2 - платеж за кабельное телевидение). */ function Payment($p_no, $pay_summ, $p_add, $con,$type) { GLOBAL $CODE; GLOBAL $CLIENT_ID; GLOBAL $PAY_CONTRACT; GLOBAL $ENTERPRISE_ID; GLOBAL $CURRENCY_CODE; GLOBAL $PAY_FORM; GLOBAL $PAYMENT_ID; //id вставляемой записи в базу данных Фастком GLOBAL $DATE; $PAYMENT_ID=""; $query_s = "select PM_Q_PAYMENT.NEXTVAL from dual"; $stid = oci_parse($con, $query_s); if (!$stid){ Answer("-3",""); } oci_define_by_name($stid, "NEXTVAL", $PAYMENT_ID); $ex_s = oci_execute($stid); if (!$ex_s) { Answer("-3",""); } if (!oci_fetch($stid)) { Answer("-3",""); } oci_free_statement($stid); $query_i = oci_parse($con, "INSERT INTO PM_T_PAYMENT (ID, PAYMENT_NO, PAY_DATE, PAYFORM, REST, SUMMA, YEARMONTH, PAYTYPE_CODE, CLIENT_ID, CONTRACT_ID, IS_PROCEED, FIRST_PROCEED_SUMMA, OWNER_ID, WHO_INPUT, REST4ADV, IS_NEED_ACCEPT, CURRENCY_CODE, COMPUTE_RATE_ODDS, DIRECTION, NOTE) VALUES ($PAYMENT_ID, '$p_no', to_date('$DATE', 'YYYY-MM-DD HH24:MI:SS'), '$PAY_FORM', $pay_summ, $pay_summ, TRUNC(to_date('$DATE', 'YYYY-MM-DD HH24:MI:SS'),'MONTH'), '$CODE', '$CLIENT_ID', '$PAY_CONTRACT', 'N', $pay_summ, $ENTERPRISE_ID, 'USER', $pay_summ, 'Y', '$CURRENCY_CODE', 'N', 'In', '$p_add')"); if (!$query_i){ Answer("-3",""); } $ex_i=oci_execute($query_i); if (!$ex_i){ Answer("-3",""); }else { if($type<>0){ $query_proc = "begin bh_p_operations.pay($PAYMENT_ID); end;"; //подтверждение платежа (расход) $stid_proc = oci_parse($con, $query_proc); $ex_proc = oci_execute($stid_proc); oci_free_statement($stid_proc); } Answer("0",""); } } ///////////////////////////////////////////////////////////////////// /* function Secure_Number - проверка на принадлежность числам */ function Secure_Number($str){ if (preg_match("/^[0-9]+$/", $str) and (strlen($str) <= 11)){ return 1; } else return 0; } ///////////////////////////////////////////////////////////////////// /* function Secure_Num_Text - проверка наличия текста и чисел */ function Secure_Num_Text($str){ if (preg_match("/^[a-zA-Z0-9]+$/", $str) and (strlen($str) <= 20)){ return 1; } else return 0; } ///////////////////////////////////////////////////////////////////// /* function Secure_Amount - проверка суммы платежа */ function Secure_Amount($str){ if (preg_match("/^[\d]+(\.\d{1,2})*$/", $str)){ return 1; } else return 0; } ///////////////////////////////////////////////////////////////////// /* function Secure_Number_Pay - проверка номера платежа */ function Secure_Number_Pay($str){ if (preg_match("/^[0-9]+$/", $str)){ return 1; } else return 0; } ///////////////////////////////////////////////////////////////////// /* function Secure_Additional - проверка ФИО в поле additional */ function Secure_Additional($str){ if (preg_match("/^[0-9\-\sА-Яа-я]+$/", $str) and (strlen($str) <= 60)){ return 1; } else return 0; } ///////////////////////////////////////////////////////////////////// $ACTION = $_GET['action']; //Тип запроса: payment, status, check, cancel $con=oci_connect("username", "password", "fst"); //Соединение с oracle if (!$con) { Answer("-3",""); } /* Базовая аутентификация Apache $auth_username=""; $auth_password=""; if(!isset($_SERVER['PHP_AUTH_USER'])){ Header("WWW-Authenticate: Basic realm=\"Payment gate\""); Header("HTTP/1.0 401 Unauthorized"); exit(); } elseif (!isset($_SERVER['PHP_AUTH_PW'])) { Header("WWW-Authenticate: Basic realm=\"Payment gate\""); Header("HTTP/1.0 401 Unauthorized"); exit(); } else { $auth_username=$_SERVER['PHP_AUTH_USER']; $auth_password =$_SERVER['PHP_AUTH_PW']; } if(!Secure_Num_Text($auth_username)){ Answer("10","Unauthorized"); } if(!Secure_Num_Text($auth_password)){ Answer("10","Unauthorized"); } */ Аuthenticate($con,$auth_username,$auth_password); if ($ACTION == "payment"){ //Проведение платежа, занесение в базу данных АСР "Фастком" $type = $_GET['type']; //Тип платежа $date_payment =urldecode($_GET['date']); //Дата и время платежа if (preg_match('/^(20\d{2})-(0?[1-9]|1[0-2])-(0?[1-9]|[12][0-9]|3[01])T([0-9]{2}):([0-9]{2}):([0-9]{2})$/', $date_payment)) { $replacement = "$1-$2-$3 $4:$5:$6"; $DATE= preg_replace('/^(20\d{2})-(0?[1-9]|1[0-2])-(0?[1-9]|[12][0-9]|3[01])T([0-9]{2}):([0-9]{2}):([0-9]{2})$/', $replacement, $date_payment); }else { Answer('5','');} $pay_id = $_GET['receipt']; //Номер платежа if(!Secure_Number_Pay($pay_id)){ Answer("4",""); } $pay_amount = $_GET['amount']; //Сумма платежа if(!Secure_Amount($pay_amount)){ Answer("3",""); } $payment_no="cyberplat".$pay_id."@".$CONTRACT_NO; //Идентификатор платежа в базе данных АСР "Фастком" if (($type == 1) or ($type == 2)){//платеж за интернет или кабельное телевидение $account = $_GET['number']; //Номер абонента if(!Secure_Number($account)){ Answer("2",""); } if (Check_contract($account,$pay_amount,$type,$con)){ if (!Check_pay($payment_no, $con)){//проверка наличия в базе данных платежа Payment($payment_no, $pay_amount, $account, $con,$type);//занесение платежа в базу данных Фастком }else { Answer("0","");//платеж успешно проведен } } }elseif($type == 0){ //авансовый платеж новых абонентов $additional = nl2br(htmlspecialchars(urldecode($_GET['additional']))); //FIO if(!Secure_Additional($additional)){ Answer("-1",""); } setlocale (LC_CTYPE, array ('ru_RU.CP1251', 'rus_RUS.1251')); $additional_u=strtoupper($additional); $PAY_CONTRACT = ""; $CLIENT_ID = ""; if (!Check_pay($payment_no, $con)){//проверка наличия в базе данных платежа Payment($payment_no, $pay_amount, $additional_u, $con,$type);//занесение платежа в базу данных Фастком } else { Answer("0","");//платеж успешно проведен } }else Answer("-2",""); //Неверное значение типа платежа } elseif ($ACTION == "cancel"){ //Удаление платежа. Не реализовано. $pay_id = $_GET['receipt']; //Номер платежа if(!Secure_Number_Pay($pay_id)){ Answer("4",""); } Answer("9",""); } elseif ($ACTION == "status"){ //Проверка статуса платежа (поиск платежа) $pay_id = $_GET['receipt']; //Номер платежа if(!Secure_Number_Pay($pay_id)){ Answer("4",""); } $payment_no="cyberplat".$pay_id."@".$CONTRACT_NO; if (Check_pay($payment_no, $con)){ //Проверка наличия в базе данных платежа Answer("0",""); } Answer("6",""); } elseif ($ACTION == "check") { //Проверка номера абонента и суммы платежа $type = $_GET['type']; //Тип платежа $pay_amount = $_GET['amount']; //Сумма платежа if(!Secure_Amount($pay_amount)){ Answer("3",""); } if (($type == 1) or ($type == 2)){ //Платеж за интернет или кабельное телевидение $account = $_GET['number']; //Номер абонента if(!Secure_Number($account)){ Answer("2",""); } if (Check_contract($account,$pay_amount,$type,$con)){ Answer("0",""); } }elseif($type == 0){ //Авансовый платеж новых абонентов. Так как абонент при проверке еще не занесен в базу данных, то результат проверки - всегда успех $additional = nl2br(htmlspecialchars(urldecode($_GET['additional']))); //ФИО нового абонента, у которого договор в процессе оформления if(!Secure_Additional($additional)){ Answer("2",""); } Answer("0",""); }else Answer("-2",""); //Неверное значение типа платежа } else { //Неверный тип запроса Answer("1",""); } $committed = oci_commit($con); oci_close($con); ?>
Список расшифровок ответов для некоторых кодов возврата:
-3 - внутренняя ошибка провайдера услуг;
-2 - неверное значение типа платежа (type);
-1 - неверный формат дополнительного параметра (additional);
0 - нет ошибки, операция прошла успешно;
1 - неизвестный тип запроса (action);
2 - абонент не найден;
3 - неверная сумма платежа;
4 - неверное значение номера платежа;
5 - неверное значение даты;
6 - платеж не найден;
9 - платеж не может быть отменен;
10 - прочие ошибки (в поле message содержится описание ошибки).
|
Нажимая кнопку «Сохранить», я подтверждаю свою дееспособность, согласие на получение информации от NetK, согласие на обработку персональных данных в соответствии с Политикой конфиденциальности и Пользовательским соглашением.