index.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. <?php
  2. // including API OpenPayz
  3. // change the path according to your realities
  4. include ("../../libs/api.openpayz.php");
  5. class PrivatMultiserv extends PaySysProto {
  6. /**
  7. * Predefined stuff
  8. */
  9. const PATH_CONFIG = 'config/privat_multiserv.ini';
  10. /**
  11. * Paysys specific predefines
  12. * If you need multiple instances of this paysys for somehow -
  13. * just add a numeric index to HASH_PREFIX and PAYSYS constants, like:
  14. * PB_MULTISERV_1_, PB_MULTISERV_2_, PB_MULTISERV_n_
  15. * PB_MULTISERV_1, PB_MULTISERV_2, PB_MULTISERV_n
  16. * or distinguish it in any other way, suitable for you
  17. */
  18. const HASH_PREFIX = 'PB_MULTISERV_';
  19. const PAYSYS = 'PB_MULTISERV';
  20. const PB_XML_XSITYPE_DEBTPACK = 'DebtPack';
  21. const PB_XML_XSITYPE_GATEWAY = 'Gateway';
  22. /**
  23. * Placeholder for a "payment_method" GET parameter
  24. *
  25. * @var string
  26. */
  27. protected $paymentMethod = '';
  28. /**
  29. * Placeholder for available payment methods
  30. *
  31. * @var array
  32. */
  33. protected $paymentMethodsAvailable = array('Search', 'Check', 'Pay');
  34. /**
  35. * Placeholder for payment sum amount from PB requests
  36. *
  37. * @var double
  38. */
  39. protected $paymentSum = 0;
  40. /**
  41. * Subscriber's virtual payment ID
  42. *
  43. * @var string
  44. */
  45. protected $subscriberVirtualID = '';
  46. /**
  47. * Subscriber's login from PrivatBank
  48. *
  49. * @var string
  50. */
  51. protected $subscriberLogin = '';
  52. /**
  53. * Paysys merchant credentials from CONTRAGENT EXT INFO module
  54. *
  55. * @var string
  56. */
  57. protected $merchantCreds = '';
  58. /**
  59. * Transaction reference string we return to PB on "Check" and receive on "Pay"
  60. *
  61. * @var string
  62. */
  63. protected $pbTransactReference = '';
  64. /**
  65. * ID attribute of data section on "Pay" request
  66. *
  67. * @var string
  68. */
  69. protected $pbPaymentID = '';
  70. /**
  71. * ServiceCode attribute of ServiceGroup section on "Pay" request
  72. *
  73. * @var string
  74. */
  75. protected $pbServiceCode = '';
  76. /**
  77. * CompanyCode attribute of ServiceGroup section on "Pay" request
  78. *
  79. * @var string
  80. */
  81. protected $pbCompanyCode = '';
  82. /**
  83. * Contains received by listener preprocessed request data
  84. *
  85. * @var array
  86. */
  87. protected $receivedXML = array();
  88. /**
  89. * List of possible error codes
  90. *
  91. * @var array
  92. */
  93. protected $errorCodes = array(
  94. 2 => 'Subscriber not found',
  95. 7 => 'Transaction duplicate'
  96. );
  97. /**
  98. * Preloads all required configuration, sets needed object properties
  99. *
  100. * @return void
  101. */
  102. public function __construct() {
  103. parent::__construct(self::PATH_CONFIG);
  104. $this->setOptions();
  105. }
  106. /**
  107. * Validates gets PrivatBank merchant ID and password from contragents ext info by Ubilling agent ID
  108. *
  109. * @return array
  110. */
  111. protected function getMerchantCredsByPaySysName() {
  112. $this->merchantCreds = $this->getUBAgentDataExten('', self::PAYSYS);
  113. return ($this->merchantCreds);
  114. }
  115. /**
  116. * Returns XML response head according to conditions
  117. *
  118. * @return string
  119. */
  120. protected function getXMLResponseHead() {
  121. if ($this->paymentMethod == 'Search') {
  122. $transferTagClose = '';
  123. $inlineDataTagClose = '';
  124. $xsitype = self::PB_XML_XSITYPE_DEBTPACK;
  125. $attribute = 'billPeriod="' . date("Ym") . '"';
  126. } else {
  127. $transferTagClose = "\n </Transfer>";
  128. $inlineDataTagClose = ' /';
  129. $xsitype = self::PB_XML_XSITYPE_GATEWAY;
  130. $attribute = 'reference="' . ($this->paymentMethod == 'Check'
  131. ? PaySysProto::genRandNumString()
  132. : $this->pbTransactReference) . '"';
  133. }
  134. $xmlHead = '
  135. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  136. <Transfer xmlns="http://debt.privatbank.ua/Transfer" interface="Debt" action="' . $this->paymentMethod . '">
  137. <Data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="' . $xsitype . '" ' . $attribute . $inlineDataTagClose . '>'
  138. . $transferTagClose;
  139. return ($xmlHead);
  140. }
  141. /**
  142. * Returns XML response PayerInfoBlock
  143. *
  144. * @param $lsAttrAdd
  145. *
  146. * @return string
  147. */
  148. protected function getXMLPayerInfoBlock($lsAttrAdd = true) {
  149. $realname = $this->getUserRealnames($this->subscriberLogin);
  150. $address = $this->getUserAddresses($this->subscriberLogin, $this->addressCityDisplay);
  151. $mobile = $this->getUserCellPhone($this->subscriberLogin);
  152. $mobile = (empty($mobile) ? '' : $mobile[$this->subscriberLogin][0]);
  153. $lsAttr = ($lsAttrAdd ? ' ls="' . $this->subscriberVirtualID . '"' : '');
  154. $xmlPayerBlock = '
  155. <PayerInfo billIdentifier="' . $this->subscriberVirtualID . '"' . $lsAttr . '>
  156. <Fio>' . (empty($realname) ? 'unspecified' : $realname) . '</Fio>
  157. <Phone>' . (empty($mobile) ? 'unspecified' : $mobile) . '</Phone>
  158. <Address>' . (empty($address) ? 'unspecified' : $address) . '</Address>
  159. </PayerInfo>
  160. ';
  161. return ($xmlPayerBlock);
  162. }
  163. /**
  164. * Returns XML response DebtServiceBlock
  165. *
  166. * @return string
  167. */
  168. protected function getXMLServiceGroupBlock() {
  169. $userdata = $this->getUserStargazerData($this->subscriberLogin);
  170. $userBalance = ($this->subscriberBalanceDecimals < 0)
  171. ? $userdata['Cash'] : (($this->subscriberBalanceDecimals == 0)
  172. ? intval($userdata['Cash'], 10) : round($userdata['Cash'], $this->subscriberBalanceDecimals, PHP_ROUND_HALF_EVEN));
  173. $xmlServiceGroupBlock = '
  174. <ServiceGroup>
  175. ';
  176. foreach ($this->merchantCreds as $io => $eachMerch) {
  177. $tmpCompanyCode = $eachMerch['internal_paysys_id'];
  178. $tmpServiceCode = $eachMerch['internal_paysys_srv_id'];
  179. $tmpServiceName = $eachMerch['paysys_token'];
  180. $tmpPercent = $eachMerch['paysys_secret_key'];
  181. $tmpAmountToPay = ($tmpPercent / 100) * $this->paymentSum;
  182. $tmpDebtServiceBlock = '
  183. <DebtService serviceCode="' . $tmpServiceCode . '" >
  184. <CompanyInfo>
  185. <CompanyCode>' . $tmpCompanyCode . '</CompanyCode>
  186. </CompanyInfo>
  187. <DebtInfo amountToPay="' . $tmpAmountToPay . '">
  188. <Balance>' . $userBalance . '</Balance>
  189. </DebtInfo>
  190. <ServiceName>' . $tmpServiceName . '</ServiceName>
  191. ' . $this->getXMLPayerInfoBlock() . '
  192. </DebtService>
  193. ';
  194. $xmlServiceGroupBlock.= $tmpDebtServiceBlock;
  195. }
  196. $xmlServiceGroupBlock.= '
  197. </ServiceGroup>
  198. ';
  199. return ($xmlServiceGroupBlock);
  200. }
  201. /**
  202. * Extracts some essential data, like VirtualID, payment sum, transaction reference ID
  203. * from a certain PB request, as their payload quite differs
  204. *
  205. * @param $paymentMethod
  206. *
  207. * @return void
  208. */
  209. protected function getEssentialDataFromPBRequest() {
  210. switch ($this->paymentMethod) {
  211. case 'Search':
  212. if (!empty($this->receivedXML['Transfer']['Data']['Unit']) and
  213. is_array($this->receivedXML['Transfer']['Data']['Unit'])) {
  214. $tmpArr = $this->receivedXML['Transfer']['Data']['Unit'];
  215. foreach ($tmpArr as $io => $eachAttr) {
  216. if (!empty($eachAttr['name']) and !empty($eachAttr['value'])) {
  217. if ($eachAttr['name'] == 'bill_identifier') {
  218. $this->subscriberVirtualID = $eachAttr['value'];
  219. } elseif ($eachAttr['name'] == 'summ') {
  220. $this->paymentSum = $eachAttr['value'];
  221. }
  222. }
  223. }
  224. }
  225. break;
  226. case 'Check':
  227. case 'Pay':
  228. if (!empty($this->receivedXML['Transfer']['Data']['PayerInfo_attr']['billIdentifier'])) {
  229. $this->subscriberVirtualID = $this->receivedXML['Transfer']['Data']['PayerInfo_attr']['billIdentifier'];
  230. }
  231. if (!empty($this->receivedXML['Transfer']['Data']['TotalSum'])) {
  232. $this->paymentSum = $this->receivedXML['Transfer']['Data']['TotalSum'];
  233. }
  234. if ($this->paymentMethod == 'Pay') {
  235. if (!empty($this->receivedXML['Transfer']['Data']['CompanyInfo']['CheckReference'])) {
  236. $this->pbTransactReference = $this->receivedXML['Transfer']['Data']['CompanyInfo']['CheckReference'];
  237. }
  238. if (!empty($this->receivedXML['Transfer']['Data_attr']['id'])) {
  239. $this->pbPaymentID = $this->receivedXML['Transfer']['Data_attr']['id'];
  240. }
  241. if (!empty($this->receivedXML['Transfer']['Data']['ServiceGroup']['Service_attr'])) {
  242. $this->pbServiceCode = $this->receivedXML['Transfer']['Data']['ServiceGroup']['Service_attr'];
  243. }
  244. if (!empty($this->receivedXML['Transfer']['Data']['CompanyInfo']['UnitCode'])) {
  245. $this->pbCompanyCode = $this->receivedXML['Transfer']['Data']['CompanyInfo']['UnitCode'];
  246. }
  247. }
  248. break;
  249. }
  250. }
  251. protected function replySearch() {
  252. $xmlReply = $this->getXMLResponseHead();
  253. $xmlReply.= $this->getXMLPayerInfoBlock(false);
  254. $xmlReply.= $this->getXMLServiceGroupBlock();
  255. $xmlReply.= '
  256. </Data>
  257. </Transfer>
  258. ';
  259. $xmlReply = trim($xmlReply);
  260. die($xmlReply);
  261. }
  262. protected function replyCheck() {
  263. $xmlReply = $this->getXMLResponseHead();
  264. die($xmlReply);
  265. }
  266. protected function replyPay() {
  267. $opHash = self::HASH_PREFIX . $this->pbTransactReference;
  268. $opHashData = $this->getOPTransactDataByHash($opHash);
  269. if (empty($opHashData)) {
  270. $srvName = '';
  271. $merchCreds = $this->getMerchantCredsByPaySysName();
  272. foreach ($merchCreds as $io => $eachMerch) {
  273. if ($eachMerch['internal_paysys_srv_id'] == $this->pbServiceCode and
  274. $eachMerch['internal_paysys_id'] == $this->pbCompanyCode) {
  275. $srvName = $eachMerch['paysys_token'];
  276. }
  277. }
  278. //push transaction to database
  279. op_TransactionAdd($opHash, $this->paymentSum, $this->subscriberVirtualID,
  280. self::PAYSYS,
  281. self::PAYSYS . ': [' . $this->pbPaymentID . '] - ' . $srvName);
  282. op_ProcessHandlers();
  283. $xmlReply = $this->getXMLResponseHead();
  284. die($xmlReply);
  285. } else {
  286. $this->replyError(400, 'TRANSACTION_ALREADY_EXISTS', 7);
  287. }
  288. }
  289. /**
  290. * Sets HTTP headers before reply
  291. */
  292. protected function setHTTPHeaders() {
  293. header('Content-Type: text/xml; charset=UTF-8');
  294. }
  295. /**
  296. * Returns XML error reply
  297. *
  298. * @param $errorCode
  299. *
  300. * @return false|string
  301. */
  302. protected function replyError($errorCode = 400, $errorMsg = 'SOMETHING WENT WRONG', $xmlTplCode = 0) {
  303. if (empty($xmlTplCode)) {
  304. die($errorCode . ' - ' . $errorMsg);
  305. } else {
  306. $xmlTplMsg = $this->errorCodes[$xmlTplCode];
  307. $xmlErrorTemplate = '
  308. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  309. <Transfer xmlns="http://debt.privatbank.ua/Transfer" interface="Debt" action="' . $this->paymentMethod . '">
  310. <Data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ErrorInfo" code="' . $xmlTplCode . '">
  311. <Message>' . $xmlTplMsg . '</Message>
  312. </Data>
  313. </Transfer>';
  314. die($xmlErrorTemplate);
  315. }
  316. }
  317. /**
  318. * Processes requests
  319. */
  320. protected function processRequests() {
  321. $this->getEssentialDataFromPBRequest();
  322. if (empty($this->subscriberVirtualID)) {
  323. $this->replyError(422, 'SUBSCRIBER_ID_UNSPECIFIED');
  324. }
  325. if (empty($this->paymentSum)) {
  326. $this->replyError(422, 'PAYMENT_AMOUNT_UNSPECIFIED');
  327. }
  328. if ($this->paymentMethod == 'Pay' and empty($this->pbTransactReference)) {
  329. $this->replyError(422, 'TRANSACTION_REFERENCE_ID_UNSPECIFIED');
  330. }
  331. $opCustomersAll = op_CustomersGetAll();
  332. if (empty($opCustomersAll[$this->subscriberVirtualID])) {
  333. $this->replyError(404, 'SUBSCRIBER_NOT_FOUND', 2);
  334. }
  335. $this->subscriberLogin = $opCustomersAll[$this->subscriberVirtualID];
  336. switch ($this->paymentMethod) {
  337. case 'Search':
  338. $this->getMerchantCredsByPaySysName();
  339. if (empty($this->merchantCreds)) {
  340. $this->replyError(422, 'MERCHANT_EXTINFO_ABSENT');
  341. }
  342. $this->replySearch();
  343. break;
  344. case 'Check':
  345. $this->replyCheck();
  346. break;
  347. case 'Pay':
  348. $this->replyPay();
  349. break;
  350. default:
  351. $this->replyError(422, 'PAYMENT_METHOD_UNKNOWN');
  352. }
  353. }
  354. /**
  355. * Listen to your heart when he's calling for you
  356. * Listen to your heart, there's nothing else you can do
  357. *
  358. * @return void
  359. */
  360. public function listen() {
  361. $rawRequest = file_get_contents('php://input');
  362. $this->receivedXML = xml2array($rawRequest);
  363. $this->setHTTPHeaders();
  364. if (empty($this->receivedXML) or empty($this->receivedXML['Transfer']['Data']) or empty($this->receivedXML['Transfer_attr'])) {
  365. $this->replyError(400, 'PAYLOAD_EMPTY_OR_INCONSISTENT');
  366. } else {
  367. $this->paymentMethod = (empty($this->receivedXML['Transfer_attr']['action']) ? '' : trim($this->receivedXML['Transfer_attr']['action']));
  368. if (in_array($this->paymentMethod, $this->paymentMethodsAvailable)) {
  369. $this->processRequests();
  370. } else {
  371. $this->replyError(422, 'PAYMENT_METHOD_UNKNOWN');
  372. }
  373. }
  374. }
  375. }
  376. $frontend = new PrivatMultiserv();
  377. $frontend->listen();