· 4 years ago · Nov 25, 2020, 12:14 PM
1<?php
2if (!class_exists('ConfigurablePaymentHandler')) {
3 $path = MODX_CORE_PATH. 'components/mspaymentprops/ConfigurablePaymentHandler.class.php';
4 if (is_readable($path)) {
5 require_once $path;
6 }
7}
8
9/**
10 * Class for handling requests to WebPay API
11 */
12class WebPay extends ConfigurablePaymentHandler
13{
14 public const PREFIX = 'ms2_payment_';
15
16 public const OPTION_STORE_NAME = 'store_name';
17 public const OPTION_STORE_ID = 'store_id';
18 public const OPTION_SECRET_KEY = 'secret_key';
19 public const OPTION_LOGIN = 'login';
20 public const OPTION_PASSWORD = 'password';
21 public const OPTION_CHECKOUT_URL = 'checkout_url';
22 public const OPTION_GATE_URL = 'gate_url';
23 public const OPTION_LANGUAGE = 'language';
24 public const OPTION_VERSION = 'version';
25 public const OPTION_CURRENCY = 'currency';
26
27 public const OPTION_DEVELOPER_MODE = 'developer_mode';
28 public const OPTION_CHECKOUT_URL_TEST = 'checkout_url_test';
29 public const OPTION_GATE_URL_TEST = 'gate_url_test';
30
31 public const OPTION_SUCCESS_STATUS = 'success_status';
32 public const OPTION_FAILURE_STATUS = 'failure_status';
33 public const OPTION_SUCCESS_PAGE = 'success_page';
34 public const OPTION_FAILURE_PAGE = 'failure_page';
35 public const OPTION_UNPAID_PAGE = 'unpaid_page';
36
37 /**
38 * @return string
39 */
40 public static function getPrefix(): string
41 {
42 return self::PREFIX . strtolower(__CLASS__);
43 }
44
45 /**
46 * WebPay constructor.
47 * @param xPDOObject $object
48 * @param array $config
49 */
50 function __construct(xPDOObject $object, $config = [])
51 {
52 parent::__construct($object, $config);
53
54 $this->config = $config;
55 }
56
57 /**
58 * @param msOrder $order
59 * @return array|string
60 * @throws ReflectionException
61 */
62 public function send(msOrder $order)
63 {
64 if (!$link = $this->getPaymentLink($order)) {
65 return $this->error('Token and redirect url can not be requested. Please, look at error log.');
66 }
67
68 return $this->success('', ['redirect' => $link]);
69 }
70
71 /**
72 * @param msOrder $order
73 * @return string
74 * @throws ReflectionException
75 */
76 public function getPaymentLink(msOrder $order)
77 {
78 /** @var msPayment $payment */
79 $payment = $order->getOne('Payment');
80
81 /** @var msDelivery $delivery */
82 $delivery = $order->getOne('Delivery');
83
84 /** @var modUser $user */
85 $user = $order->getOne('User');
86 if ($user) {
87 /** @var modUserProfile $user */
88 $user = $user->getOne('Profile');
89 }
90
91 $this->config = $this->getProperties($payment);
92 $this->adjustCheckoutUrls();
93
94 $this->config['return_url'] = rtrim($this->getMODX()->getOption('site_url'), '/')
95 . $this->getMODX()->getOption('assets_url') . 'components/mspwebpay/webpay.php';
96
97 if (empty($this->config[self::OPTION_STORE_NAME])) {
98 $this->config[self::OPTION_STORE_NAME] = $this->modx->getOption('site_name', null);
99 }
100
101 $seed = md5(substr(md5(time()), 5, 10));
102
103 $request = [
104 'wsb_seed' => $seed,
105 'wsb_storeid' => $this->config[self::OPTION_STORE_ID],
106 'wsb_order_num' => $order->get('id'),
107 'wsb_test' => $this->config[self::OPTION_DEVELOPER_MODE],
108 'wsb_currency_id' => $this->config[self::OPTION_CURRENCY],
109 'wsb_total' => $order->get('cost'),
110
111 '*scart' => '',
112 'wsb_store' => $this->config[self::OPTION_STORE_NAME],
113 'wsb_version' => $this->config[self::OPTION_VERSION],
114 'wsb_language_id' => $this->config[self::OPTION_LANGUAGE],
115 'wsb_return_url' => $this->config['return_url'] . '?action=success',
116 'wsb_cancel_return_url' => $this->config['return_url'] . '?action=cancel',
117 'wsb_notify_url' => $this->config['return_url'] . '?action=notify',
118 'wsb_shipping_name' => $delivery->get('name'),
119 'wsb_shipping_price' => $delivery->get('price'),
120 'wsb_email' => $user->get('email')
121 ];
122
123 $products = $this->modx->getCollection(msOrderProduct::class, ['order_id' => $order->get('id')]);
124
125 $i = 0;
126 foreach ($products as $product) {
127 /** @var msOrderProduct $product */
128 $request["wsb_invoice_item_name[$i]"] = $product->get('name');
129 $request["wsb_invoice_item_quantity[$i]"] = $product->get('count');
130 $request["wsb_invoice_item_price[$i]"] = $product->get('price');
131 $i++;
132 }
133
134 $signatureParts = array_intersect_key($request, array_flip([
135 'wsb_seed',
136 'wsb_storeid',
137 'wsb_order_num',
138 'wsb_test',
139 'wsb_currency_id',
140 'wsb_total'
141 ]));
142 $signatureParts[] = $this->config[self::OPTION_SECRET_KEY];
143
144 $request['wsb_signature'] = sha1(implode('', $signatureParts));
145
146 return $this->config['return_url'] . '?' . http_build_query(['action' => 'payment', 'order' => $order->get('id'), 'request' => json_encode($request)]);
147 }
148
149 public function adjustCheckoutUrls(): void
150 {
151 if ($this->config[self::OPTION_DEVELOPER_MODE]) {
152 $this->config[self::OPTION_CHECKOUT_URL] = $this->config[self::OPTION_CHECKOUT_URL_TEST];
153 $this->config[self::OPTION_GATE_URL] = $this->config[self::OPTION_GATE_URL_TEST];
154 }
155 }
156
157 /**
158 * @param msOrder $order
159 * @param array $params
160 * @return array|string|void
161 */
162 public function receive(msOrder $order, $params = [])
163 {
164 switch ($params['action']) {
165 case 'success':
166 if (empty($params['wsb_tid'])) {
167 $this->paymentError("Could not get transaction id. Process stopped.");
168 }
169
170 $transaction_id = $params['wsb_tid'];
171 $postdata = '*API=&API_XML_REQUEST=' . urlencode('<?xml version="1.0" encoding="ISO-8859-1"?><wsb_api_request><command>get_transaction</command><authorization><username>' . $this->config['login'] . '</username><password>' . md5($this->config['password']) . '</password></authorization><fields><transaction_id>' . $transaction_id . '</transaction_id></fields></wsb_api_request>');
172
173 $curl = curl_init($this->config['gate_url']);
174 curl_setopt($curl, CURLOPT_HEADER, 0);
175 curl_setopt($curl, CURLOPT_POST, 1);
176 curl_setopt($curl, CURLOPT_POSTFIELDS, $postdata);
177 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 1);
178 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
179 $response = curl_exec($curl);
180 curl_close($curl);
181
182 $xml = simplexml_load_string($response);
183
184 if ((string)$xml->status === 'success') {
185 $fields = (array)$xml->fields;
186
187 $crc = md5($fields['transaction_id'] . $fields['batch_timestamp'] . $fields['currency_id'] . $fields['amount'] . $fields['payment_method'] . $fields['payment_type'] . $fields['order_id'] . $fields['rrn'] . $this->config['secret']);
188
189 if ($crc === $fields['wsb_signature'] && in_array($fields['payment_type'], [1, 4], true)) {
190 $miniShop2 = $this->modx->getService('miniShop2');
191 @$this->modx->context->key = 'mgr';
192 $miniShop2->changeOrderStatus($order->get('id'), 2);
193 } else {
194 $this->paymentError('Transaction with id ' . $transaction_id . ' is not valid.');
195 }
196 } else {
197 $this->paymentError('Could not check transaction with id ' . $transaction_id);
198 }
199 break;
200 case 'notify':
201 $crc = md5($params['batch_timestamp'] . $params['currency_id'] . $params['amount'] . $params['payment_method'] . $params['order_id'] . $params['site_order_id'] . $params['transaction_id'] . $params['payment_type'] . $params['rrn'] . $this->config['secret']);
202 if ($crc === $params['wsb_signature'] && in_array($params['payment_type'], [1, 4], true)) {
203 $miniShop2 = $this->modx->getService('miniShop2');
204 @$this->modx->context->key = 'mgr';
205 $miniShop2->changeOrderStatus($order->get('id'), 2);
206 header("HTTP/1.0 200 OK");
207 exit;
208 } else {
209 $this->paymentError('Transaction with id ' . $params['transaction_id'] . ' is not valid.');
210 }
211 break;
212 case 'cancel':
213 $miniShop2 = $this->modx->getService('miniShop2');
214 @$this->modx->context->key = 'mgr';
215 $miniShop2->changeOrderStatus($order->get('id'), 4);
216 break;
217 }
218 }
219
220 /**
221 * @param $text
222 * @param array $request
223 */
224 public function paymentError($text, $request = [])
225 {
226 $this->log(sprintf('%s, request: %s', $text, print_r($request, 1)));
227
228 header('HTTP/1.0 400 Bad Request');
229 die('ERROR: ' . $text);
230 }
231}
232