PluginProbe
SePay Gateway / trunk
SePay Gateway vtrunk
1.1.23 1.1.22 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 All 39 releases
sepay-gateway / sepay-gateway.php

sepay-gateway.php in SePay Gateway trunk, at sepay-gateway.php

545 lines 18.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 * Plugin Name: SePay Gateway
4 * Plugin URI: https://docs.sepay.vn/woocommerce.html
5 * Description: SePay - Giải pháp tự động xác nhận thanh toán chuyển khoản ngân hàng
6 * Author: SePay Team
7 * Author URI: https://sepay.vn/
8 * Version: 1.1.23
9 * Requires Plugins: woocommerce
10 * Text Domain: sepay-gateway
11 * License: GNU General Public License v3.0
12 */
13
14 use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry;
15 use Automattic\WooCommerce\Enums\OrderStatus;
16 use Automattic\WooCommerce\Utilities\FeaturesUtil;
17
18 if (!defined('ABSPATH')) {
19 exit;
20 }
21
22 if (! defined('SEPAY_API_URL')) {
23 define('SEPAY_API_URL', 'https://my.sepay.vn');
24 }
25
26 if (! defined('SEPAY_WC_API_URL')) {
27 define('SEPAY_WC_API_URL', 'https://my.sepay.vn');
28 }
29
30 add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'sepay_add_action_links');
31
32 function sepay_add_action_links($links): array
33 {
34 $plugin_links = array(
35 '<a href="' . admin_url('admin.php?page=wc-settings&tab=checkout&section=sepay') . '">Cài đặt</a>',
36 );
37
38 return array_merge($plugin_links, $links);
39 }
40
41 add_action('plugins_loaded', 'sepay_init_gateway_class');
42
43 function sepay_missing_wc_notice()
44 {
45 $install_url = wp_nonce_url(
46 add_query_arg(
47 [
48 'action' => 'install-plugin',
49 'plugin' => 'woocommerce',
50 ],
51 admin_url('update.php')
52 ),
53 'install-plugin_woocommerce'
54 );
55
56 $admin_notice_content = sprintf(
57 '%1$sWooCommerce chưa được kích hoạt.%2$s Plugin %3$sWooCommerce%4$s phải được kích hoạt để SePay Gateway có thể hoạt động. Vui lòng %5$scài đặt & kích hoạt WooCommerce &raquo;%6$s',
58 '<strong>',
59 '</strong>',
60 '<a href="http://wordpress.org/extend/plugins/woocommerce/">',
61 '</a>',
62 '<a href="' . esc_url($install_url) . '">',
63 '</a>'
64 );
65
66 echo '<div class="error">';
67 echo '<p>' . wp_kses_post($admin_notice_content) . '</p>';
68 echo '</div>';
69 }
70
71 add_filter('woocommerce_payment_gateways', 'sepay_add_gateway_class');
72
73 function sepay_add_gateway_class($gateways)
74 {
75 $gateways[] = 'WC_Gateway_SePay';
76 return $gateways;
77 }
78
79 function sepay_init_gateway_class()
80 {
81 if (!class_exists('WooCommerce')) {
82 add_action('admin_notices', 'sepay_missing_wc_notice');
83 return;
84 }
85
86 require_once dirname(__FILE__) . '/includes/class-wc-gateway-sepay.php';
87 require_once dirname(__FILE__) . '/includes/class-wc-sepay-api.php';
88
89 add_action('wp_ajax_nopriv_sepay_check_order_status', 'sepay_check_order_status');
90 add_action('wp_ajax_sepay_check_order_status', 'sepay_check_order_status');
91 add_action('wp_ajax_setup_sepay_webhook', 'handle_setup_sepay_webhook');
92
93 function handle_setup_sepay_webhook()
94 {
95 if (!current_user_can('manage_options')) {
96 wp_send_json_error(['message' => 'Bạn không có quyền thực hiện hành động này.']);
97 }
98
99 if (!isset($_POST['_wpnonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_wpnonce'])), 'sepay_webhook_setup')) {
100 wp_send_json_error(['message' => 'Invalid nonce verification']);
101 }
102
103 $bank_account_id = isset($_POST['bank_account_id']) ? sanitize_text_field(wp_unslash($_POST['bank_account_id'])) : null;
104 $sub_account = isset($_POST['sub_account']) ? sanitize_text_field(wp_unslash($_POST['sub_account'])) : null;
105
106 $api = new WC_SePay_API();
107
108 if (!$bank_account_id || (!$sub_account && $api->is_required_sub_account($bank_account_id))) {
109 wp_send_json_error(['message' => 'Thiếu thông tin tài khoản ngân hàng hoặc tài khoản VA.']);
110 }
111
112 $settings = get_option('woocommerce_sepay_settings', []);
113
114 $api_token = get_option('wc_sepay_webhook_api_key');
115
116 if (empty($api_token)) {
117 $api_token = $settings['api_key'] ?? null;
118 }
119
120 $webhook_url = home_url('/wp-json/sepay-gateway/v');
121
122 $webhooks = $api->get_webhooks([
123 'webhook_url' => $webhook_url,
124 'api_key' => $api_token,
125 ]);
126
127 $webhook_id = isset($webhooks[0]['id']) ? $webhooks[0]['id'] : null;
128
129 $response = $api->create_webhook($bank_account_id, $webhook_id, $api_token);
130
131 if ($response['status'] !== 'success') {
132 wp_send_json_error(['message' => 'Có lỗi xảy ra khi tạo webhook. Vui lòng thử lại sau.']);
133 }
134
135 $pay_code_prefixes = $api->get_pay_code_prefixes(false);
136
137 if (empty($pay_code_prefixes)) {
138 wp_send_json_error(['message' => 'Không tìm thấy prefix cho mã thanh toán.']);
139 }
140
141 $pay_code_prefix = $pay_code_prefixes[0];
142
143 $needs_update = $pay_code_prefix['suffix_from'] !== 1 || $pay_code_prefix['suffix_to'] < 10;
144
145 if ($needs_update) {
146 try {
147 $response = $api->update_company_configurations([
148 'payment_code_formats' => [
149 [
150 'prefix' => $pay_code_prefix['prefix'],
151 'suffix_from' => 1,
152 'suffix_to' => $pay_code_prefix['suffix_to'] < 10 ? 10 : $pay_code_prefix['suffix_to'],
153 'character_type' => 'NumberAndLetter',
154 'is_active' => 1,
155 ],
156 ],
157 ]);
158
159 if (is_wp_error($response)) {
160 wp_send_json_error(['message' => 'Có lỗi xảy ra khi cập nhật mã thanh toán.']);
161 }
162 } catch (Exception $e) {
163 wp_send_json_error(['message' => 'Có lỗi xảy ra khi cập nhật mã thanh toán.']);
164 }
165 }
166
167 $settings['pay_code_prefix'] = $pay_code_prefix['prefix'];
168
169 $settings['enabled'] = 'yes';
170 $settings['bank_account'] = $bank_account_id;
171 $settings['sub_account'] = $sub_account;
172
173 $settings['title'] = 'SePay';
174 $settings['description'] = 'Thanh toán qua chuyển khoản ngân hàng với QR Code (VietQR). Tự động xác nhận thanh toán qua <a href="https://sepay.vn" target="_blank">SePay</a>.';
175 $settings['logo'] = plugin_dir_url(__FILE__) . 'assets/images/sepay-logo.png';
176
177 update_option('woocommerce_sepay_settings', $settings);
178 wp_send_json_success(['message' => 'Webhook đã được tạo thành công!']);
179 }
180
181 function sepay_check_order_status()
182 {
183 if (!isset($_POST['orderID'])) {
184 wp_send_json(['status' => false], 400);
185 }
186
187 $order_id = absint($_POST['orderID']);
188 $order = wc_get_order($order_id);
189
190 if (!$order) {
191 wp_send_json(['status' => false], 404);
192 }
193
194 $nonce = isset($_POST['order_nonce']) ? sanitize_text_field(wp_unslash($_POST['order_nonce'])) : '';
195 if (!wp_verify_nonce($nonce, 'sepay_check_order_' . $order->get_order_key())) {
196 wp_send_json(['status' => false], 403);
197 }
198
199 $supplied_key = isset($_POST['order_key']) ? sanitize_text_field(wp_unslash($_POST['order_key'])) : '';
200 $current_user_id = get_current_user_id();
201 $order_user_id = $order->get_user_id();
202 $owns_order = ($order_user_id && $order_user_id === $current_user_id)
203 || ($supplied_key !== '' && hash_equals((string) $order->get_order_key(), $supplied_key));
204
205 if (!$owns_order) {
206 wp_send_json(['status' => false], 403);
207 }
208
209 $downloads = [];
210
211 if ($order->has_downloadable_item() && $order->is_download_permitted()) {
212 foreach ($order->get_items() as $item) {
213 $product_id = $item->get_product_id();
214 $product = wc_get_product($product_id);
215
216 $itemDownloads = array_map(function ($download) use ($product, $product_id) {
217 return [
218 'id' => $download['id'],
219 'product_id' => $product_id,
220 'product_name' => $product->get_data()['name'],
221 'name' => $download['name'],
222 'downloads_remaining' => $download['downloads_remaining'],
223 'download_url' => $download['download_url'],
224 'access_expires' => $download['access_expires'],
225 ];
226 }, array_values($item->get_item_downloads()));
227 $downloads = array_merge($downloads, $itemDownloads);
228 }
229 }
230
231 wp_send_json([
232 'status' => true,
233 'order_status' => $order->get_status(),
234 'downloads' => $downloads,
235 ]);
236 }
237
238 add_action('rest_api_init', function () {
239 register_rest_route('sepay-gateway/v1', '/add-payment', [
240 'methods' => 'POST',
241 'callback' => 'sepay_api',
242 'permission_callback' => '__return_true',
243 ]);
244
245 register_rest_route('sepay-gateway/v2', '/add-payment', [
246 'methods' => 'POST',
247 'callback' => 'sepay_api',
248 'permission_callback' => '__return_true',
249 ]);
250 });
251
252 function sepay_api(WP_REST_Request $request): array
253 {
254 $parameters = $request->get_json_params();
255 $authorization = $request->get_header('Authorization');
256
257 $api_key = null;
258
259 if ($authorization) {
260 $arr1 = explode(' ', $authorization);
261
262 if (count($arr1) == 2 && $arr1[0] === 'Apikey' && strlen($arr1[1]) >= 10) {
263 $api_key = $arr1[1];
264 }
265 }
266
267 if (!ctype_alnum($api_key) || strlen($api_key) < 10) {
268 return [
269 'success' => false,
270 'message' => 'Invalid API Key format',
271 ];
272 }
273
274 $api = new WC_SePay_API();
275 $payment_gateways = WC_Payment_Gateways::instance();
276 /** @var WC_Payment_Gateway $sepay_gateway */
277 $sepay_gateway = $payment_gateways->payment_gateways()['sepay'];
278
279 $webhook_api_key = get_option('wc_sepay_webhook_api_key') ? get_option('wc_sepay_webhook_api_key') : $sepay_gateway->get_option('api_key');
280
281 if (!is_string($webhook_api_key) || $webhook_api_key === '' || !hash_equals((string) $webhook_api_key, (string) $api_key)) {
282 return [
283 'success' => false,
284 'message' => 'Invalid API Key',
285 ];
286 }
287
288 if (! is_array($parameters)) {
289 return [
290 'success' => false,
291 'message' => 'Invalid JSON request',
292 ];
293 }
294
295 if (
296 !isset($parameters['accountNumber'])
297 || !isset($parameters['gateway'])
298 || !isset($parameters['code'])
299 || !isset($parameters['transferType'])
300 || !isset($parameters['transferAmount'])
301 ) {
302 return [
303 'success' => false,
304 'message' => 'Not enough required parameters',
305 ];
306 }
307
308 if ($parameters['transferType'] !== 'in') {
309 return [
310 'success' => false,
311 'message' => 'transferType must be in',
312 ];
313 }
314
315 $s_order_id = str_replace($sepay_gateway->get_option('pay_code_prefix'), '', $parameters['code']);
316
317 if (!is_numeric($s_order_id)) {
318 return [
319 'success' => false,
320 'message' => 'Order not found',
321 ];
322 }
323
324 $s_order_id = intval($s_order_id);
325
326 global $wpdb;
327 $lock_name = 'sepay_webhook_order_' . $s_order_id;
328 $lock_acquired = (int) $wpdb->get_var($wpdb->prepare('SELECT GET_LOCK(%s, 0)', $lock_name));
329
330 if ($lock_acquired !== 1) {
331 return [
332 'success' => false,
333 'message' => 'Order is currently being processed by another request',
334 ];
335 }
336
337 try {
338 $order = wc_get_order($s_order_id);
339
340 if (!$order) {
341 return [
342 'success' => false,
343 'message' => 'Order not found',
344 ];
345 }
346
347 $order_status = $order->get_status();
348
349 if (in_array($order_status, ['completed', 'processing'])) {
350 return [
351 'success' => false,
352 'message' => 'This order has already been completed before!',
353 ];
354 }
355
356 $order_total = (int) $order->get_total();
357 $transfer_amount = (int) $parameters['transferAmount'];
358
359 if ($order_total <= 0) {
360 return [
361 'success' => false,
362 'message' => 'order_total is <= 0',
363 ];
364 }
365
366 $order_note = sprintf(
367 "SePay: Đã nhận thanh toán <b>%s</b> vào tài khoản <b>%s</b> tại ngân hàng <b>%s</b> vào lúc <b>%s</b>",
368 wc_price($transfer_amount),
369 $parameters['accountNumber'],
370 $parameters['gateway'],
371 $parameters['transactionDate']
372 );
373
374 $order_status_when_completed = $sepay_gateway->get_option('order_when_completed') ?: OrderStatus::PROCESSING;
375 if ($order_total === $transfer_amount) {
376 if (in_array($order_status_when_completed, array_keys($sepay_gateway->getWcOrderStatuses()))) {
377 $order->update_status($order_status_when_completed);
378 } else {
379 $order->payment_complete();
380 }
381
382 wc_reduce_stock_levels($s_order_id);
383 $order_note = sprintf(
384 '%s. Trạng thái đơn hàng được chuyển từ %s sang %s',
385 $order_note,
386 wc_get_order_status_name($order_status),
387 wc_get_order_status_name($order_status_when_completed)
388 );
389 } else if ($order_total > $transfer_amount) {
390 $under_payment = wc_price($order_total - $transfer_amount);
391 $order_note = "$order_note. Khách hàng thanh toán THIẾU: <b>$under_payment</b>";
392 } else {
393 $over_payment = wc_price($transfer_amount - $order_total);
394 $order_note = "$order_note. Khách hàng thanh toán THỪA: <b>$over_payment</b>";
395 }
396
397 $order->add_order_note($order_note, false);
398
399 return [
400 'success' => true,
401 ];
402 } finally {
403 $wpdb->query($wpdb->prepare('SELECT RELEASE_LOCK(%s)', $lock_name));
404 }
405 }
406 }
407
408 add_action('before_woocommerce_init', 'sepay_declare_woocommerce_support');
409
410 add_action('wp_ajax_sepay_get_bank_accounts', 'get_bank_accounts_ajax');
411 add_action('wp_ajax_sepay_get_bank_sub_accounts', 'get_bank_sub_accounts_ajax');
412 add_action('wp_ajax_sepay_get_pay_code_prefixes', 'get_paycode_prefix_ajax');
413
414 function get_bank_accounts_ajax()
415 {
416 if (!check_ajax_referer('sepay_admin', '_wpnonce', false) || !current_user_can('manage_woocommerce')) {
417 wp_send_json_error('Unauthorized');
418 }
419
420 try {
421 $api = new WC_SePay_API();
422 $accounts = $api->get_bank_accounts(false);
423 wp_send_json_success($accounts);
424 } catch (Exception $e) {
425 wp_send_json_error($e->getMessage());
426 }
427 }
428
429 function get_paycode_prefix_ajax()
430 {
431 if (!check_ajax_referer('sepay_admin', '_wpnonce', false) || !current_user_can('manage_woocommerce')) {
432 wp_send_json_error('Unauthorized');
433 }
434
435 $api = new WC_SePay_API();
436 $prefixes = $api->get_pay_code_prefixes(false);
437 wp_send_json_success($prefixes);
438 }
439
440 function get_bank_sub_accounts_ajax()
441 {
442 if (!check_ajax_referer('sepay_admin', '_wpnonce', false) || !current_user_can('manage_woocommerce')) {
443 wp_send_json_error(['message' => 'Bạn không có quyền thực hiện hành động này.']);
444 }
445
446 $bank_account_id = isset($_POST['bank_account_id']) ? sanitize_text_field(wp_unslash($_POST['bank_account_id'])) : null;
447
448 if (!$bank_account_id) {
449 wp_send_json_error(['message' => 'Thiếu ID tài khoản ngân hàng.']);
450 }
451
452 $api = new WC_SePay_API();
453 $sub_accounts = $api->get_bank_sub_accounts($bank_account_id);
454
455 if (empty($sub_accounts)) {
456 wp_send_json_error(['message' => 'Không tìm thấy tài khoản ảo.']);
457 }
458
459 wp_send_json_success($sub_accounts);
460 }
461
462 function sepay_declare_woocommerce_support()
463 {
464 if (class_exists('\Automattic\WooCommerce\Utilities\FeaturesUtil')) {
465 FeaturesUtil::declare_compatibility('cart_checkout_blocks', __FILE__);
466 }
467 }
468
469 add_action('woocommerce_blocks_loaded', 'sepay_woocommerce_block_support');
470
471 function sepay_woocommerce_block_support()
472 {
473 if (class_exists('Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType')) {
474 require_once dirname(__FILE__) . '/includes/class-wc-sepay-blocks-support.php';
475
476 add_action(
477 'woocommerce_blocks_payment_method_type_registration',
478 function (PaymentMethodRegistry $payment_method_registry) {
479 $payment_method_registry->register(new WC_SePay_Blocks_Support());
480 }
481 );
482 }
483 }
484
485 add_action('admin_enqueue_scripts', 'sepay_add_scripts');
486
487 function sepay_add_scripts()
488 {
489 $script_path = plugin_dir_path(__FILE__) . 'assets/js/main.js';
490
491 if (file_exists($script_path)) {
492 $script_version = filemtime($script_path);
493 } else {
494 $script_version = '';
495 }
496
497 wp_register_script(
498 'sepay-option-js',
499 plugin_dir_url(__FILE__) . 'assets/js/main.js',
500 ['jquery'],
501 $script_version,
502 true
503 );
504
505 wp_localize_script('sepay-option-js', 'sepay_admin_vars', [
506 'nonce' => wp_create_nonce('sepay_admin'),
507 ]);
508
509 wp_enqueue_script('sepay-option-js');
510 }
511
512 register_activation_hook(__FILE__, 'sepay_activate');
513
514 function sepay_activate()
515 {
516 set_transient('wc_sepay_activation_redirect', true, 30);
517 }
518
519 add_action('admin_init', 'sepay_redirect');
520
521 function sepay_redirect()
522 {
523 if (get_transient('wc_sepay_activation_redirect')) {
524 delete_transient('wc_sepay_activation_redirect');
525 if (!isset($_GET['page']) || $_GET['page'] !== 'wc-settings' || !isset($_GET['section']) || $_GET['section'] !== 'sepay') {
526 wp_safe_redirect(admin_url('admin.php?page=wc-settings&tab=checkout&section=sepay&oauth2=1'));
527 exit;
528 }
529 }
530 }
531
532 add_action('upgrader_process_complete', 'sepay_clear_cache_after_update', 10, 2);
533
534 function sepay_clear_cache_after_update($upgrader_object, $options)
535 {
536 if ($options['action'] === 'update' && $options['type'] === 'plugin') {
537 foreach ($options['plugins'] as $plugin) {
538 if (plugin_basename(__FILE__) === $plugin) {
539 delete_transient('wc_sepay_bank_accounts');
540 break;
541 }
542 }
543 }
544 }
545