1 <?php
2
3
4 class YaMoneyPayment extends PaymentMethod {
5 static $db = array(
6 'TestMode' => 'Boolean',
7 'Password' => 'Varchar(150)',
8 'ShopID' => 'Varchar',
9 'SCID' => 'Varchar',
10 'BaseURL' => 'Varchar(100)',
11
12
13 'ConnectionType' => "Enum('HTTPS, PKCS7, EMAIL')",
14
15 'TestPassword' => 'Varchar(150)',
16 'TestShopID' => 'Varchar',
17 'TestSCID' => 'Varchar',
18 'TestBaseURL' => 'Varchar(100)',
19
20 'PaymentType' => "Enum('PC, AC, MC, GP, WM, SB, MP, AB')",
21
22 'TaxSystem' => "Enum('1, 2, 3, 4, 5, 6')",
23
24 'PaymentSubject' => 'Varchar',
25 'PaymentMode' => 'Varchar',
26 );
27
28 protected static $cert_file_path = '/cart_payment/pem/payment_center_2016.cer';
29 protected static $CA_file_path = '/cart_payment/pem/yamoney_center_2014.pem';
30
31 static function set_cert_file_path($path) {
32 self::$cert_file_path = $path;
33 }
34
35 static function get_cert_file_path() {
36 return Director::baseFolder() . self::$cert_file_path;
37 }
38
39 static function set_CA_file_path($path) {
40 self::$cert_file_path = $path;
41 }
42
43 static function get_CA_file_path() {
44 return Director::baseFolder() . self::$CA_file_path;
45 }
46
47 static $paymentMethodIcons = array(
48 'cart_payment/img/yandex/YaPayment_AC.png',
49 'cart_payment/img/yandex/YaPayment_MC.png',
50 'cart_payment/img/yandex/YaPayment_PC.png',
51 'cart_payment/img/yandex/YaPayment_SB.png',
52 'cart_payment/img/yandex/YaPayment_WM.png',
53 'cart_payment/img/yandex/YaPayment_GP.png',
54 );
55
56 static $defaults = array(
57 'BaseURL' => 'https://money.yandex.ru/eshop.xml',
58 'TestBaseURL' => 'https://demomoney.yandex.ru/eshop.xml',
59 'PaymentType' => 'PC',
60 );
61
62 static $payment_subject = array(
63 'commodity' => 'Товар',
64 'excise' => 'Подакцизный товар',
65 'job' => 'Работа',
66 'service' => 'Услуга',
67 'gambling_bet' => 'Ставка в азартной игре',
68 'gambling_prize' => 'Выигрыш в азартной игре',
69 'lottery' => 'Лотерейный билет',
70 'lottery_prize' => 'Выигрыш в лотерею',
71 'intellectual_activity' => 'Результаты интеллектуальной деятельности',
72 'payment' => 'Платеж',
73 'agent_commission' => 'Агентское вознаграждение',
74 'composite' => 'Несколько вариантов',
75 'another' => 'Другое',
76 );
77
78 static $payment_mode = array(
79 'full_prepayment' => 'Полная предоплата',
80 'partial_prepayment' => 'Частичная предоплата',
81 'advance' => 'Аванс',
82 'full_payment' => 'Полный расчет',
83 'partial_payment' => 'Частичный расчет и кредит',
84 'credit' => 'Кредит',
85 'credit_payment' => 'Выплата по кредиту',
86 );
87
88 89 90 91
92 93 94 95 96
97 static function getPaymentMethod() {
98 return DataObject::get_one('YaMoneyPayment', 'Active = 1');
99 }
100
101
102 static function parse_orderID($orderID) {
103 if (strpos($orderID, '-')) {
104 $data = explode('-', $orderID);
105 return (int)$data[1];
106 }
107 return (int)$orderID;
108 }
109
110 function onBeforeWrite() {
111 parent::onBeforeWrite();
112 if (!$this->ID || $this->isChanged('PaymentType')) {
113 $this->Title = _t('YaMoneyPayment.db_' . $this->PaymentType);
114 }
115 }
116
117 function getPaymentTypes() {
118 $rs = array();
119 foreach($this->dbObject('PaymentType')->enumValues() as $type) {
120 $rs[$type] = _t('YaMoneyPayment.db_' . $type, $type);
121 }
122 return $rs;
123 }
124
125 function getConnectionTypes() {
126 $rs = array();
127 foreach($this->dbObject('ConnectionType')->enumValues() as $type) {
128 $rs[$type] = _t('YaMoneyPayment.type_' . $type, $type);
129 }
130 return $rs;
131 }
132
133 function getTaxSystems() {
134 $rs = array();
135 foreach($this->dbObject('TaxSystem')->enumValues() as $type) {
136 $rs[$type] = _t('YaMoneyPayment.TaxSystem_' . $type, $type);
137 }
138 return $rs;
139 }
140
141 function getPaymentHandler() {
142 return 'YaMoneyPayment_Handler';
143 }
144
145 function getCMSFields() {
146 $fields = parent::getCMSFields();
147 $fields->findOrMakeTab('Root.Settings', _t('PaymentMethod.tab_Settings','Settings'));
148
149 $fields->addFieldToTab('Root.Settings', new DropdownField('PaymentType', $this->fieldLabel('PaymentType'), $this->getPaymentTypes()));
150 $fields->addFieldToTab('Root.Settings', new DropdownField('ConnectionType', $this->fieldLabel('ConnectionType'), $this->getConnectionTypes()));
151 $fields->addFieldToTab('Root.Settings', new DropdownField('TaxSystem', $this->fieldLabel('TaxSystem'), $this->getTaxSystems()));
152
153 $fields->addFieldToTab('Root.Settings', new DropdownField('PaymentSubject', $this->fieldLabel('PaymentSubject'), self::$payment_subject, "", null, _t('YaMoneyPayment.Select')));
154 $fields->addFieldToTab('Root.Settings', new DropdownField('PaymentMode', $this->fieldLabel('PaymentMode'), self::$payment_mode, "", null, _t('YaMoneyPayment.Select')));
155
156
157 $fields->addFieldToTab('Root.Settings', new HeaderField('WorkSettings', _t('PaymentMethod.WorkSettings', 'Work Settings')));
158 $fields->addFieldToTab('Root.Settings', $fields->dataFieldByName('Password'));
159 $fields->addFieldToTab('Root.Settings', $fields->dataFieldByName('ShopID'));
160 $fields->addFieldToTab('Root.Settings', $fields->dataFieldByName('SCID'));
161 $fields->addFieldToTab('Root.Settings', $fields->dataFieldByName('BaseURL'));
162
163 $fields->addFieldToTab('Root.Settings', new HeaderField('TestSettings', _t('PaymentMethod.TestSettings', 'Test Settings')));
164 $fields->addFieldToTab('Root.Settings', $fields->dataFieldByName('TestPassword'));
165 $fields->addFieldToTab('Root.Settings', $fields->dataFieldByName('TestShopID'));
166 $fields->addFieldToTab('Root.Settings', $fields->dataFieldByName('TestSCID'));
167 $fields->addFieldToTab('Root.Settings', $fields->dataFieldByName('TestBaseURL'));
168
169 return $fields;
170 }
171
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
201
202 function Password() {
203 return ($this->TestMode) ? $this->TestPassword : $this->Password;
204 }
205
206 function ShopID() {
207 return ($this->TestMode) ? $this->TestShopID : $this->ShopID;
208 }
209
210 function SCID() {
211 return ($this->TestMode) ? $this->TestSCID : $this->SCID;
212 }
213
214 function BaseURL() {
215 return ($this->TestMode) ? $this->TestBaseURL : $this->BaseURL;
216 }
217
218
219 function processPayment($payment) {
220 if ($payment->Status != 'Failure' && $payment->Status != 'Success') {
221 $payment->Status = 'Pending';
222 $payment->write();
223
224 $link = $this->paymentLink($payment->ID);
225
226 return new Payment_Processing($link);
227 } else {
228
229 }
230 }
231
232
233 function getClearPaymentLink($payment) {
234 return Director::absoluteURL(YaMoneyPayment_Handler::payment_link($payment->HashLink));
235 }
236
237 238 239 240 241
242 function getForm($payment) {
243 if (!$payment->PaidObject())
244 return false;
245
246
247
248 $orderCost = $payment->Amount;
249 $successURL = Director::absoluteURL(YaMoneyPayment_Handler::success_link());
250 $errorURL = Director::absoluteURL(YaMoneyPayment_Handler::error_link());
251 $html = '<form id="PaymentForm" action="'.$this->BaseURL().'" method="post" enctype="application/x-www-form-urlencoded">';
252 $html .= '<input type="hidden" name="shopId" value="'.$this->ShopID().'" />';
253 $html .= '<input type="hidden" name="scid" value="'.$this->SCID().'" />';
254 $html .= '<input type="hidden" name="sum" value="'.$orderCost.'" />';
255 $html .= '<input type="hidden" name="customerNumber" value="order_'.$payment->PaidForID.'" />';
256
257
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
275
276
277 $html .= '<input name="paymentType" value="'.$payment->PaymentType()->PaymentType.'" type="hidden"/>';
278 $html .= '<input name="orderNumber" value="'.$payment->PaidForID.'-'.$payment->ID.'" type="hidden"/>';
279
280
281 if ($this->UseOnlineKassa) {
282 $order = $payment->PaidObject();
283 $contact = ($order->Email ? $order->Email : $order->Phone);
284 $checkData = array(
285 'customerContact' => $contact,
286 'taxSystem' => $this->TaxSystem,
287 );
288
289
290 if (singleton('Product')->hasMethod('getVAT')) {
291 $sc = SiteConfig::current_site_config();
292 $items = array();
293 foreach($order->Items() as $item) {
294 $itm = array();
295 $itm['quantity'] = $item->Quantity;
296 $itm['price'] = array();
297 $itm['price']['amount'] = $item->ItemPrice;
298 $vat = $item->getProduct()->getVAT();
299 $itm['tax'] = $vat->YaKassaCode;
300 $itm['text'] = $item->Title;
301
302
303 if (trim($this->PaymentSubject) && trim($this->PaymentMode)) {
304 $itm['paymentMethodType'] = $this->PaymentMode;
305 $itm['paymentSubjectType'] = $this->PaymentSubject;
306 }
307 $items[] = $itm;
308 }
309 if ($order->ShippingMethodID && ($shippingMethod = $order->ShippingMethod())) {
310 $itm = array();
311 $itm['quantity'] = 1;
312 $itm['price'] = array();
313 $itm['price']['amount'] = $order->ShippingTotal;
314 $vat = $item->getProduct()->getVAT();
315 $itm['tax'] = (($sc->DefaultVATID && ($defaultVAT = $sc->DefaultVAT())) ? $defaultVAT->YaKassaCode : 1);
316 $itm['text'] = $shippingMethod->Name;
317 $items[] = $itm;
318 }
319 $checkData['items'] = $items;
320 $html .= '<input name="ym_merchant_receipt" value=\''.json_encode($checkData, JSON_UNESCAPED_UNICODE).'\' type="hidden"/>';
321 }
322 }
323 $html .= '<input name="shopSuccessURL" value="'.$successURL.'" type="hidden"/>';
324 $html .= '<input name="shopFailURL" value="'.$errorURL.'" type="hidden"/>';
325 $html .= '<input type="submit" value="Оплатить" />';
326
327
328 $html .= '</form>';
329 return $html;
330
331 332 333 334 335 336 337
338 }
339
340
341 function getRequestSignature($post) {
342 return strtoupper(md5($post['action'].';'.$post['orderSumAmount'].';'.$post['orderSumCurrencyPaycash'].';'.$post['orderSumBankPaycash'].';'.$post['shopId'].';'.$post['invoiceId'].';'.$post['customerNumber'].';'.$this->Password()));
343 }
344
345 346 347 348
349 function completePayment($payment) {
350 if ($payment->Status != 'Success') {
351 $payment->Status = 'Success';
352 $payment->Message = _t('YaMoneyPayment.DONE', 'Заказ оплачен');
353 $payment->write();
354 }
355 }
356 }
357
358 359 360
361 class YaMoneyPayment_Handler extends Payment_Handler {
362
363 static $URLSegment = 'ya_money';
364
365
366 static function payment_link($hash) {
367 return self::$URLSegment . '/payment_form?hash=' . $hash;
368 }
369
370 static function success_link() {
371 return self::$URLSegment . '/success';
372 }
373
374 static function error_link() {
375 return self::$URLSegment . '/error';
376 }
377
378 function payment_form() {
379 if (isset($_GET['hash'])) {
380 $hash = Convert::raw2sql($_GET['hash']);
381 $payment = DataObject::get_one('Payment', "HashLink = '$hash'");
382 if ($payment) {
383 $formData = $payment->PaymentType()->getForm($payment);
384 if ($formData) {
385 return $this->customise(array(
386 'Form' => $formData
387 ))->renderWith(array('YaMoneyPayment_form'));
388
389 }
390 }
391 }
392 return false;
393 }
394
395 396 397 398 399
400 function callBack($post) {
401 $response = false;
402 $yaPaymentMethod = YaMoneyPayment::getPaymentMethod();
403 if (!$yaPaymentMethod) {
404 return false;
405 }
406 $requestData = false;
407
408 switch($yaPaymentMethod->ConnectionType) {
409 case 'PKCS7':
410 $requestData = $this->decrypt_proc($post->getBody());
411 break;
412 case 'HTTPS':
413 $requestData = $post->postVars();
414 break;
415 }
416 if ($requestData && isset($requestData['action'])) {
417 if ($requestData) {
418 if ($requestData['action'] == 'checkOrder') {
419 $response = $this->checkOrder($requestData);
420 }
421 if ($requestData['action'] == 'paymentAviso') {
422 $response = $this->paymentAviso($requestData);
423 }
424 if ($response) {
425 header("Content-Type: application/xml; charset=utf-8");
426 echo $response;
427 }
428 }
429 }
430 }
431
432 433 434 435 436
437 function getRequiredParams() {
438 $params = array('action', 'shopId', 'orderNumber', 'customerNumber', 'orderSumAmount', 'paymentType', 'orderSumCurrencyPaycash', 'orderSumBankPaycash', 'invoiceId');
439 $yaPaymentMethod = YaMoneyPayment::getPaymentMethod();
440 if ($yaPaymentMethod) {
441 if ($yaPaymentMethod->ConnectionType == 'HTTPS') {
442 $params[] = 'md5';
443 }
444 return $params;
445 }
446 return false;
447 }
448
449 450 451 452 453 454 455 456
457 function checkOrder($post) {
458 $code = 0;
459 $message = '';
460 $params = $this->getRequiredParams();
461 if ($params) {
462 foreach($params as $param) {
463 if (!isset($post[$param])) {
464 $code = 200;
465 $message = 'Не указаны все обязательные параметры';
466 }
467 }
468 } else {
469 $code = 200;
470 $message = 'Не указаны все обязательные параметры';
471 }
472 if ($post['action'] != 'checkOrder') {
473 $code = 200;
474 $message = 'Неправильный параметр action';
475 }
476 if (!$code) {
477 if ($payment = DataObject::get_by_id('Payment', YaMoneyPayment::parse_orderID($post['orderNumber']))) {
478 $payment->PaymentResponse = serialize($post);
479 $payment->YaMoneyPaymentType = $post['paymentType'];
480 $payment->TransactionID = $post['invoiceId'];
481 $payment->write();
482 $paymentType = $payment->PaymentType();
483 if (($paymentType->ConnectionType == 'HTTPS') && $post['md5'] != $paymentType->getRequestSignature($post)) {
484 $code = 1;
485 $message = 'Проверка хэша неудачна';
486 }
487 if (!($post['shopId'] == $paymentType->ShopID()) || !($post['customerNumber'] == ('order_'.$payment->PaidForID))) {
488 $code = 100;
489 $message = 'Неверные параметры заказа';
490 }
491 } else {
492 $code = 100;
493 $message = 'Неверные параметры заказа';
494 }
495 }
496 if (!$code) {
497 $response = '<?xml version="1.0" encoding="UTF-8"?>'."\n".'<checkOrderResponse performedDatetime="'.date('c').'" code="0" invoiceId="'.$post['invoiceId'].'" shopId="'.$paymentType->ShopID().'" orderSumAmount="'.$post['orderSumAmount'].'"/>'."\n";
498 } else {
499 $response = '<?xml version="1.0" encoding="UTF-8"?>'."\n".'<checkOrderResponse performedDatetime="'.date('c').'" code="'.$code.'" invoiceId="'.$post['invoiceId'].'" shopId="'.$paymentType->ShopID().'" message="'.$message.'"/>';
500 }
501 return $response;
502 }
503
504
505 506 507 508 509 510 511 512 513
514 function paymentAviso($post) {
515 $code = 0;
516 $message = '';
517 $params = $this->getRequiredParams();
518 if ($params) {
519 foreach($params as $param) {
520 if (!isset($post[$param])) {
521 $code = 200;
522 $message = 'Не указаны все обязательные параметры';
523 }
524 }
525 } else {
526 $code = 200;
527 $message = 'Не указаны все обязательные параметры';
528 }
529 if ($post['action'] != 'paymentAviso') {
530 $code = 200;
531 $message = 'Неправильный параметр action';
532 }
533 if (!$code) {
534 if ($payment = DataObject::get_by_id('Payment', YaMoneyPayment::parse_orderID($post['orderNumber']))) {
535 $payment->PaymentResponse = serialize($post);
536 $payment->YaMoneyPaymentType = $post['paymentType'];
537 $payment->write();
538 $paymentType = $payment->PaymentType();
539 if (($paymentType->ConnectionType == 'HTTPS') && $post['md5'] != $paymentType->getRequestSignature($post)) {
540 $code = 1;
541 $message = 'Проверка хэша неудачна';
542 }
543 if (!($post['shopId'] == $paymentType->ShopID()) || !($post['customerNumber'] == ('order_'.$payment->PaidForID))) {
544 $code = 100;
545 $message = 'Неверные параметры заказа';
546 }
547 } else {
548 $code = 100;
549 $message = 'Неверные параметры заказа';
550 }
551 }
552 if (!$code) {
553
554 $paymentType->completePayment($payment);
555 $response = '<?xml version="1.0" encoding="UTF-8"?>'."\n".'<paymentAvisoResponse performedDatetime="'.date('c').'" code="0" invoiceId="'.$post['invoiceId'].'" shopId="'.$paymentType->ShopID().'"/>';
556 } else {
557 $response = '<?xml version="1.0" encoding="UTF-8"?>'."\n".'<paymentAvisoResponse performedDatetime="'.date('c').'" code="'.$code.'" invoiceId="'.$post['invoiceId'].'" shopId="'.$paymentType->ShopID().'" message="'.$message.'"/>';
558 }
559 return $response;
560 }
561
562 563 564
565 function success() {
566 if (isset($_GET['orderNumber'])) {
567 $paymentID = YaMoneyPayment::parse_orderID($_GET['orderNumber']);
568 if (($payment = DataObject::get_by_id('Payment', $paymentID)) && ($order = DataObject::get_by_id('Order', $payment->PaidForID))) {
569 $link = false;
570 if (class_exists('CheckoutPage')) {
571 $link = CheckoutPage::find_link('order/' . $order->HashLink);
572 }
573 return singleton('Page_Controller')->customise(array(
574 'Order' => $order,
575 'OrderLink' => $link,
576 ))->renderWith(array('YaMoneyPayment_success', 'Page' ));
577 }
578 }
579 }
580
581 582 583
584 function error() {
585 if (isset($_GET['orderNumber'])) {
586 $paymentID = YaMoneyPayment::parse_orderID($_GET['orderNumber']);
587 if (($payment = DataObject::get_by_id('Payment', $paymentID)) && ($order = DataObject::get_by_id('Order', $payment->PaidForID))) {
588 $link = false;
589 if (class_exists('CheckoutPage')) {
590 $link = CheckoutPage::find_link('order/' . $order->HashLink);
591 }
592 return singleton('Page_Controller')->customise(array(
593 'Order' => $order,
594 'OrderLink' => $link,
595 ))->renderWith(array('YaMoney_error', 'Page'));
596 }
597 }
598 }
599
600
601 private function decrypt_proc($data){
602
603 $descriptorspec = array(
604 0 => array("pipe", "r"),
605 1 => array("pipe", "w"),
606 2 => array("pipe", "w"),
607 );
608 $yaPaymentMethod = YaMoneyPayment::getPaymentMethod();
609 $certFilePath = YaMoneyPayment::get_cert_file_path();
610 $CAfilePath = YaMoneyPayment::get_CA_file_path();
611 if (!$certFilePath || !is_file($certFilePath)) {
612
613 user_error(_t('YaMoneyPayment.NoCertificate', 'No certificate'), E_USER_ERROR);
614 return false;
615 }
616 $process = proc_open(
617 'openssl smime -verify -inform PEM -nointern -certfile '.$certFilePath.' -CAfile '.$CAfilePath, $descriptorspec, $pipes);
618
619 if (is_resource($process)) {
620 fwrite($pipes[0], $data);
621 fclose($pipes[0]);
622 $xml = stream_get_contents($pipes[1]);
623 $sm = simplexml_load_string($xml);
624 if ($sm) {
625 $rrs = array();
626 $rrs['action'] = str_replace('Request','',$sm->getName());
627 foreach($sm->attributes() as $name=>$attr) {
628 $rrs[(string)$name] = (string)$attr;
629 }
630 fclose($pipes[1]);
631 $resCode = proc_close($process);
632 if ($resCode != 0) {
633 if ($resCode == 2 || $resCode == 4){
634 return false;
635 }
636 }
637 return $rrs;
638 }
639 }
640 return false;
641 }
642 }
643
644
645
646
647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668
[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the
Silverstripe Forums to ask development related questions.
-