}
75 | */
76 | async function getTotalPriceFromServer() {
77 | if (
78 | ( typeof configAdminUrlAjax !== 'undefined' ) &&
79 | ( typeof configAdminUrlAjax.location !== 'undefined' ) &&
80 | ( typeof configAdminUrlAjax.nonce !== 'undefined')
81 | ) {
82 | try {
83 | const response = await $.ajax(
84 | {
85 | url: configAdminUrlAjax.location,
86 | type: 'POST',
87 | data: {
88 | 'nonce': configAdminUrlAjax.nonce,
89 | 'action': 'get_updated_total_price',
90 | }
91 | }
92 | );
93 |
94 | // Check if totalPrice is a valid number
95 | if ( ( typeof response.totalPrice === 'number' ) && ! isNaN( response.totalPrice ) ) {
96 | // Round the number to avoid floating-point precision issues
97 | return Math.round( response.totalPrice ) / 100;
98 | } else {
99 | // Handle invalid totalPrice
100 | debugDirect( 'Invalid or non-numeric total price received: ' + JSON.stringify( response.totalPrice ), debugStatus );
101 | return 0;
102 | }
103 | } catch ( error ) {
104 | console.error( 'Error trying to get the total price from the server side', error );
105 | return 0;
106 | }
107 | } else {
108 | debugDirect( 'Values for configAdminUrlAjax not defined', debugStatus );
109 | return 0;
110 | }
111 | }
112 |
113 | /**
114 | * Check if the total price has been changed in the checkout page
115 | *
116 | * @returns {void}
117 | */
118 | async function checkActualTotalPrice() {
119 | try {
120 | const totalNumber = await getTotalPriceFromServer();
121 | if ( totalNumber > 0 ) {
122 | // Check if configGooglePay and configApplePay are defined
123 | // before attempting to access their properties
124 | if (
125 | ( typeof configGooglePay !== 'undefined' ) &&
126 | ( typeof configGooglePay.totalPrice !== 'undefined' ) &&
127 | ( totalNumber !== configGooglePay.totalPrice )
128 | ) {
129 | configGooglePay.totalPrice = totalNumber;
130 | }
131 |
132 | if (
133 | ( typeof configApplePay !== 'undefined' ) &&
134 | ( typeof configApplePay.totalPrice !== 'undefined' ) &&
135 | ( totalNumber !== configApplePay.totalPrice )
136 | ) {
137 | configApplePay.totalPrice = totalNumber;
138 | }
139 | debugDirect( 'Total price is ' + totalNumber.toFixed( 2 ), debugStatus, 'log' );
140 | } else {
141 | debugDirect( 'Total price was not provided by the server', debugStatus, 'warn' );
142 | }
143 | } catch ( error ) {
144 | console.error( 'Error fetching the total price from getTotalPriceFromServer()', error );
145 | } finally {
146 | $( '.gpay-button' ).prop( 'disabled', false );
147 | $( '.apple-pay-button' ).prop( 'disabled', false );
148 | }
149 | }
150 |
151 | /**
152 | * Initialize the class to launch Google Pay and Apple Pay
153 | * as direct payment methods
154 | */
155 | new GoogleApplePayDirectHandler();
156 |
157 | /**
158 | * Initialize the listening to the Select2 events
159 | */
160 | select2Validation();
161 |
162 | /**
163 | * Listen to the ajaxComplete event to check if the total price
164 | * has been changed in the checkout page
165 | * and enable the Google Pay and Apple Pay buttons
166 | */
167 | $( document ).ajaxComplete(
168 | function( e, xhr, settings ) {
169 | if ( settings.url.indexOf( '?wc-ajax=update_order_review' ) !== -1 ) {
170 | $( document ).one(
171 | 'update_checkout',
172 | () => {
173 | $( '.gpay-button' ).prop( 'disabled', true );
174 | $( '.apple-pay-button' ).prop( 'disabled', true );
175 | }
176 | );
177 | $( document ).one(
178 | 'updated_checkout',
179 | () => {
180 | checkActualTotalPrice();
181 | // Remove the orphan error messages from the notice group
182 | validatorInstance.removeOrphanErrorMessages();
183 | // Re-initialize select2 validation after checkout is updated
184 | select2Validation();
185 | }
186 | );
187 | $( document ).on(
188 | 'payment_method_selected',
189 | () => {
190 | $( '.gpay-button' ).prop( 'disabled', false );
191 | $( '.apple-pay-button' ).prop( 'disabled', false );
192 | }
193 | );
194 | new GoogleApplePayDirectHandler();
195 | }
196 | }
197 | );
198 | }
199 | );
200 | })( jQuery );
201 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "multisafepay/woocommerce",
3 | "description": "A new WooCommerce plugin",
4 | "type": "wordpress-plugin",
5 | "version": "6.9.0",
6 | "license": "GPL-3.0-or-later",
7 | "minimum-stability": "RC",
8 | "keywords" : [ "wordpress", "multisafepay" ],
9 | "require": {
10 | "multisafepay/php-sdk": "^5.17",
11 | "nyholm/psr7": "^1.4",
12 | "psr/http-client": "^1.0"
13 | },
14 | "provide": {
15 | "psr/http-client-implementation": "1.0"
16 | },
17 | "require-dev": {
18 | "phpunit/phpunit" : "^8.5",
19 | "squizlabs/php_codesniffer": "3.*",
20 | "wp-coding-standards/wpcs": "^2.3",
21 | "woocommerce/woocommerce-sniffs": "^0.1",
22 | "object-calisthenics/phpcs-calisthenics-rules": "^3.7",
23 | "phpro/grumphp": "^1.0",
24 | "yoast/phpunit-polyfills": "^1.0",
25 | "phpstan/phpstan": "^1.5",
26 | "php-stubs/woocommerce-stubs": "^6.0",
27 | "slevomat/coding-standard": "^6.4"
28 | },
29 | "autoload": {
30 | "psr-4": {
31 | "MultiSafepay\\WooCommerce\\": "src/"
32 | }
33 | },
34 | "autoload-dev": {
35 | "psr-4": {
36 | "MultiSafepay\\WooCommerce\\Tests\\Fixtures\\": "tests/fixtures/"
37 | }
38 | },
39 | "scripts": {
40 | "phpcs": "@php vendor/bin/phpcs --standard=phpcs.xml .",
41 | "phpcbf": "@php vendor/bin/phpcbf --standard=phpcs.xml .",
42 | "run-grumphp": "@php vendor/bin/grumphp run --tasks=phpcs,phpunit",
43 | "phpunit": "@php vendor/bin/phpunit",
44 | "phpstan": "@php vendor/bin/phpstan analyse --configuration=tests/phpstan/phpstan.neon --memory-limit 1G --error-format github"
45 | },
46 | "config": {
47 | "allow-plugins": {
48 | "dealerdirect/phpcodesniffer-composer-installer": true,
49 | "phpro/grumphp": true,
50 | "php-http/discovery": false
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/index.php:
--------------------------------------------------------------------------------
1 | activate( $network_wide );
63 | }
64 | register_activation_hook( __FILE__, 'activate_multisafepay' );
65 |
66 | /**
67 | * Init plugin
68 | *
69 | * @see https://developer.wordpress.org/plugins/hooks/
70 | * @return void
71 | */
72 | function init_multisafepay() {
73 | if ( ! function_exists( 'is_plugin_active' ) ) {
74 | require_once ABSPATH . 'wp-admin/includes/plugin.php';
75 | }
76 | if ( is_plugin_active( 'woocommerce/woocommerce.php' ) ) {
77 | $plugin = new Main();
78 | $plugin->init();
79 | }
80 | }
81 |
82 | /**
83 | * Wait for WooCommerce to load
84 | *
85 | * @return void
86 | */
87 | function action_woocommerce_loaded() {
88 | init_multisafepay();
89 | }
90 | add_action( 'woocommerce_loaded', 'action_woocommerce_loaded', 10, 1 );
91 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "multisafepay-blocks",
3 | "version": "1.0.0",
4 | "description": "Use the MultiSafepay plugin to accept payments in WooCommerce blocks.",
5 | "main": "index.js",
6 | "keywords": [
7 | "MultiSafepay"
8 | ],
9 | "author": {
10 | "name": "MultiSafepay",
11 | "homepage": "https://multisafepay.com/"
12 | },
13 | "license": "GPL-3.0-or-later",
14 | "devDependencies": {
15 | "@wordpress/scripts": "^28.1.0",
16 | "@wordpress/dependency-extraction-webpack-plugin": "^6.1.0"
17 | },
18 | "scripts": {
19 | "build": "wp-scripts build"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Blocks/BlocksController.php:
--------------------------------------------------------------------------------
1 | register( new BasePaymentMethodBlocks() );
23 | }
24 | );
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Client/MultiSafepayClient.php:
--------------------------------------------------------------------------------
1 | logger = $logger ?? new Logger();
27 | }
28 |
29 | /**
30 | * Sends a request using wp_remote_request for the given PSR-7 request (RequestInterface)
31 | * and returns a PSR-7 response (ResponseInterface).
32 | *
33 | * @param RequestInterface $request
34 | * @return ResponseInterface
35 | * @throws Exception
36 | */
37 | public function sendRequest( RequestInterface $request ): ResponseInterface {
38 | $request->getBody()->rewind();
39 | $args = $this->get_headers_from_request_interface( $request );
40 |
41 | try {
42 | $response_data = wp_remote_request( $request->getUri()->__toString(), $args );
43 | if ( is_wp_error( $response_data ) ) {
44 | throw new Exception( $response_data->get_error_message() );
45 | }
46 | } catch ( Exception $exception ) {
47 | $this->logger->log_error( 'Error when process request via MultiSafepayClient: ' . $exception->getMessage() );
48 | throw new Exception( $exception->getMessage() );
49 | }
50 |
51 | $body = wp_remote_retrieve_body( $response_data );
52 | $response = new Response( $response_data['response']['code'], $response_data['headers']->getAll(), $body, '1.1', null );
53 | $response->getBody()->rewind();
54 | return $response;
55 | }
56 |
57 | /**
58 | * Return an array of headers to be used in wp_remote_request
59 | *
60 | * @param RequestInterface $request
61 | * @return array
62 | */
63 | private function get_headers_from_request_interface( RequestInterface $request ): array {
64 | $args = array(
65 | 'method' => $request->getMethod(),
66 | 'body' => $request->getBody()->getContents(),
67 | 'httpversion' => $request->getProtocolVersion(),
68 | 'timeout' => 30,
69 | );
70 | foreach ( $request->getHeaders() as $name => $value ) {
71 | $args['headers'][ $name ] = $value[0];
72 | }
73 | return $args;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/Exceptions/MissingDependencyException.php:
--------------------------------------------------------------------------------
1 | missing_plugin_names = $missing_plugins;
27 | }
28 |
29 | /**
30 | * Get the list of all missing plugins
31 | *
32 | * @return array
33 | */
34 | public function get_missing_plugin_names(): array {
35 | return $this->missing_plugin_names;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Exceptions/index.php:
--------------------------------------------------------------------------------
1 | brand = $brand;
26 | parent::__construct( $payment_method );
27 | }
28 |
29 | /**
30 | * @return string
31 | */
32 | public function get_payment_method_gateway_code(): string {
33 | return $this->payment_method->getId();
34 | }
35 |
36 | /**
37 | * @return string
38 | */
39 | public function get_payment_method_title(): string {
40 | return $this->brand['name'];
41 | }
42 |
43 | /**
44 | * @return string
45 | */
46 | public function get_payment_method_icon(): string {
47 | return $this->brand['icon_urls']['large'];
48 | }
49 |
50 | /**
51 | * @return string
52 | */
53 | public function get_payment_method_id(): string {
54 | return PaymentMethodService::get_legacy_woocommerce_payment_gateway_ids( $this->brand['id'] );
55 | }
56 |
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/PaymentMethods/Base/BaseGiftCardPaymentMethod.php:
--------------------------------------------------------------------------------
1 | get_option( $this->get_payment_method_id() . '_gift_card_max_amount_updated', false ) ) {
25 | $this->update_option( 'max_amount', '' );
26 | $this->update_option( $this->get_payment_method_id() . '_gift_card_max_amount_updated', '1' );
27 | $this->max_amount = '';
28 | }
29 | }
30 |
31 | /**
32 | * @param WC_Order $order
33 | * @return bool
34 | */
35 | public function can_refund_order( $order ) {
36 | return false;
37 | }
38 |
39 | /**
40 | * Return if payment component is enabled.
41 | *
42 | * @return bool
43 | */
44 | public function is_payment_component_enabled(): bool {
45 | return false;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/PaymentMethods/Base/BasePaymentMethodBlocks.php:
--------------------------------------------------------------------------------
1 | get_multisafepay_payment_methods_from_api();
43 | foreach ( $multisafepay_payment_methods as $multisafepay_payment_method ) {
44 | $woocommerce_payment_gateways = array();
45 |
46 | if ( isset( $multisafepay_payment_method['type'] ) && ( 'coupon' === $multisafepay_payment_method['type'] ) ) {
47 | $woocommerce_payment_gateways[] = new BaseGiftCardPaymentMethod( new PaymentMethod( $multisafepay_payment_method ) );
48 | }
49 |
50 | if ( isset( $multisafepay_payment_method['type'] ) && ( 'payment-method' === $multisafepay_payment_method['type'] ) ) {
51 | $woocommerce_payment_gateways[] = new BasePaymentMethod( new PaymentMethod( $multisafepay_payment_method ) );
52 | foreach ( $multisafepay_payment_method['brands'] as $brand ) {
53 | if ( ! empty( $brand['allowed_countries'] ) ) {
54 | $woocommerce_payment_gateways[] = new BaseBrandedPaymentMethod( new PaymentMethod( $multisafepay_payment_method ), $brand );
55 | }
56 | }
57 | }
58 |
59 | foreach ( $woocommerce_payment_gateways as $woocommerce_payment_gateway ) {
60 | // Include direct payment methods without components just in the checkout page of the frontend context
61 | if (
62 | $woocommerce_payment_gateway->check_direct_payment_methods_without_components() &&
63 | ! $woocommerce_payment_gateway->admin_editing_checkout_page()
64 | ) {
65 | $this->gateways[] = $woocommerce_payment_gateway;
66 | }
67 |
68 | if ( ( 'redirect' === $woocommerce_payment_gateway->get_payment_method_type() ) && $woocommerce_payment_gateway->is_available() ) {
69 | $this->gateways[] = $woocommerce_payment_gateway;
70 | }
71 | }
72 | }
73 | $was_printed = true;
74 | }
75 | }
76 |
77 | /**
78 | * Returns an array of script handles to enqueue for
79 | * this payment method in the frontend context
80 | *
81 | * @return string[]
82 | */
83 | public function get_payment_method_script_handles(): array {
84 | static $was_printed = false;
85 |
86 | if ( ! $was_printed ) {
87 | $asset_path = MULTISAFEPAY_PLUGIN_DIR_PATH . '/assets/public/js/multisafepay-blocks/build/index.asset.php';
88 | $dependencies = array();
89 |
90 | if ( is_file( $asset_path ) ) {
91 | $asset = require $asset_path;
92 | $dependencies = is_array( $asset ) && isset( $asset['dependencies'] ) ? $asset['dependencies'] : $dependencies;
93 | }
94 |
95 | wp_register_script(
96 | 'multisafepay-payment-methods-blocks',
97 | MULTISAFEPAY_PLUGIN_URL . '/assets/public/js/multisafepay-blocks/build/index.js',
98 | $dependencies,
99 | MULTISAFEPAY_PLUGIN_VERSION,
100 | true
101 | );
102 |
103 | wp_localize_script( 'multisafepay-payment-methods-blocks', 'multisafepay_gateways', $this->get_payment_method_data() );
104 | $was_printed = true;
105 | }
106 |
107 | return array( 'multisafepay-payment-methods-blocks' );
108 | }
109 |
110 | /**
111 | * Returns an array of key=>value pairs of data
112 | * made available to the payment methods script.
113 | *
114 | * @return array
115 | */
116 | public function get_payment_method_data(): array {
117 | $payment_methods_data = array();
118 | foreach ( $this->gateways as $gateway ) {
119 | $payment_methods_data[] = array(
120 | 'id' => $gateway->get_payment_method_id(),
121 | 'title' => $gateway->get_title(),
122 | 'description' => $gateway->get_description(),
123 | 'is_admin' => is_admin(),
124 | );
125 | }
126 |
127 | return $payment_methods_data;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/PaymentMethods/Base/BaseRefunds.php:
--------------------------------------------------------------------------------
1 | get_transaction_manager();
42 |
43 | /** @var WC_Order $order */
44 | $order = wc_get_order( $order_id );
45 |
46 | // Get meta multisafepay_transaction_id
47 | $multisafepay_transaction_id = $order->get_meta( 'multisafepay_transaction_id', true );
48 |
49 | /** @var TransactionResponse $multisafepay_transaction */
50 | $multisafepay_transaction = $transaction_manager->get(
51 | ! empty( $multisafepay_transaction_id ) ? $multisafepay_transaction_id : $order->get_order_number()
52 | );
53 |
54 | if ( $multisafepay_transaction->requiresShoppingCart() ) {
55 | /** @var RefundRequest $refund_request */
56 | $refund_request = $transaction_manager->createRefundRequest( $multisafepay_transaction );
57 |
58 | $refunds = $order->get_refunds();
59 | $refund_merchant_item_id = reset( $refunds )->id;
60 |
61 | $cart_item = new CartItem();
62 | $cart_item->addName( __( 'Refund', 'multisafepay' ) )
63 | ->addQuantity( 1 )
64 | ->addUnitPrice( MoneyUtil::create_money( (float) $amount, $order->get_currency() )->negative() )
65 | ->addMerchantItemId( 'refund_id_' . $refund_merchant_item_id )
66 | ->addTaxRate( 0 );
67 |
68 | $refund_request->getCheckoutData()->addItem( $cart_item );
69 | }
70 |
71 | if ( ! $multisafepay_transaction->requiresShoppingCart() ) {
72 | $refund_request = new RefundRequest();
73 | $refund_request->addDescriptionText( $reason );
74 | $refund_request->addMoney( MoneyUtil::create_money( (float) $amount, $order->get_currency() ) );
75 | }
76 |
77 | try {
78 | $error = null;
79 | $transaction_manager->refund( $multisafepay_transaction, $refund_request );
80 | } catch ( Exception | ClientExceptionInterface | ApiException $exception ) {
81 | $error = __( 'Error:', 'multisafepay' ) . htmlspecialchars( $exception->getMessage() );
82 | $this->logger->log_error( 'Error during refund: ' . $error . ' Refund request : ' . wp_json_encode( $refund_request->getData() ) );
83 | }
84 |
85 | if ( ! $error ) {
86 | /* translators: %1$: The currency code. %2$ The transaction amount */
87 | $note = sprintf( __( 'Refund of %1$s%2$s has been processed successfully.', 'multisafepay' ), get_woocommerce_currency_symbol( $order->get_currency() ), $amount );
88 | $this->logger->log_info( $note );
89 | $order->add_order_note( $note );
90 | return true;
91 | }
92 |
93 | if ( get_option( 'multisafepay_debugmode', false ) ) {
94 | /* translators: %1$: The order ID. %2$ The PSP transaction ID */
95 | $message = sprintf( __( 'Refund for Order ID: %1$s with transactionId: %2$s gives message: %3$s.', 'multisafepay' ), $order_id, $multisafepay_transaction->getTransactionId(), $error );
96 | $this->logger->log_warning( $message );
97 | }
98 |
99 | return false;
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/src/PaymentMethods/Base/index.php:
--------------------------------------------------------------------------------
1 | logger = $logger ?? new Logger();
39 | $this->api_token_manager = ( new SdkService() )->get_api_token_manager();
40 | }
41 |
42 | /**
43 | * Returns a MultiSafepay ApiToken
44 | *
45 | * @return string
46 | */
47 | public function get_api_token(): string {
48 | if ( null === $this->api_token_manager ) {
49 | return '';
50 | }
51 |
52 | $cached_api_token = get_transient( 'multisafepay_api_token' );
53 | if ( false !== $cached_api_token ) {
54 | return $cached_api_token;
55 | }
56 |
57 | try {
58 | $api_token = $this->api_token_manager->get()->getApiToken();
59 | } catch ( ApiException | ClientExceptionInterface $exception ) {
60 | $this->logger->log_error( $exception->getMessage() );
61 | return '';
62 | }
63 |
64 | set_transient( 'multisafepay_api_token', $api_token, self::EXPIRATION_TIME_FOR_API_TOKEN_REQUEST );
65 |
66 | return $api_token;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Services/CustomerService.php:
--------------------------------------------------------------------------------
1 | logger = $logger ?? new Logger();
34 | }
35 |
36 | /**
37 | * @param WC_Order $order
38 | * @return CustomerDetails
39 | */
40 | public function create_customer_details( WC_Order $order ): CustomerDetails {
41 | $customer_address = $this->create_address(
42 | $order->get_billing_address_1(),
43 | $order->get_billing_address_2(),
44 | $order->get_billing_country(),
45 | $order->get_billing_state(),
46 | $order->get_billing_city(),
47 | $order->get_billing_postcode()
48 | );
49 |
50 | return $this->create_customer(
51 | $customer_address,
52 | $order->get_billing_email(),
53 | $order->get_billing_phone(),
54 | $order->get_billing_first_name(),
55 | $order->get_billing_last_name(),
56 | $order->get_customer_ip_address() ? $order->get_customer_ip_address() : '',
57 | $order->get_customer_user_agent() ? $order->get_customer_user_agent() : '',
58 | $order->get_billing_company(),
59 | $this->should_send_customer_reference( $order->get_payment_method() ) ? (string) $order->get_customer_id() : null,
60 | $this->get_customer_browser_info()
61 | );
62 | }
63 |
64 | /**
65 | * Return browser information
66 | *
67 | * @return array|null
68 | */
69 | protected function get_customer_browser_info(): ?array {
70 | $browser = sanitize_text_field( wp_unslash( $_POST['browser'] ?? '' ) );
71 |
72 | if ( ! empty( $browser ) ) {
73 | return json_decode( $browser, true );
74 | }
75 |
76 | return null;
77 | }
78 |
79 | /**
80 | * @param WC_Order $order
81 | * @return CustomerDetails
82 | */
83 | public function create_delivery_details( WC_Order $order ): CustomerDetails {
84 | $delivery_address = $this->create_address(
85 | $order->get_shipping_address_1(),
86 | $order->get_shipping_address_2(),
87 | $order->get_shipping_country(),
88 | $order->get_shipping_state(),
89 | $order->get_shipping_city(),
90 | $order->get_shipping_postcode()
91 | );
92 |
93 | return $this->create_customer(
94 | $delivery_address,
95 | $order->get_billing_email(),
96 | $order->get_billing_phone(),
97 | $order->get_shipping_first_name(),
98 | $order->get_shipping_last_name(),
99 | '',
100 | '',
101 | $order->get_shipping_company()
102 | );
103 | }
104 |
105 | /**
106 | * @param Address $address
107 | * @param string $email_address
108 | * @param string $phone_number
109 | * @param string $first_name
110 | * @param string $last_name
111 | * @param string $ip_address
112 | * @param string $user_agent
113 | * @param null|string $company_name
114 | * @param null|string $customer_id
115 | * @param null|array $browser
116 | * @return CustomerDetails
117 | */
118 | protected function create_customer(
119 | Address $address,
120 | string $email_address,
121 | string $phone_number,
122 | string $first_name,
123 | string $last_name,
124 | string $ip_address,
125 | string $user_agent,
126 | string $company_name = null,
127 | string $customer_id = null,
128 | ?array $browser = null
129 | ): CustomerDetails {
130 | $customer_details = new CustomerDetails();
131 | $customer_details
132 | ->addAddress( $address )
133 | ->addEmailAddress( new EmailAddress( $email_address ) )
134 | ->addFirstName( $first_name )
135 | ->addLastName( $last_name )
136 | ->addPhoneNumber( new PhoneNumber( $phone_number ) )
137 | ->addLocale( $this->get_locale() )
138 | ->addCompanyName( $company_name ?? '' );
139 |
140 | if ( ! empty( $ip_address ) ) {
141 | try {
142 | $customer_details->addIpAddress( new IpAddress( $ip_address ) );
143 | } catch ( InvalidArgumentException $invalid_argument_exception ) {
144 | $this->logger->log_warning( 'Invalid Customer IP address: ' . $invalid_argument_exception->getMessage() );
145 | }
146 | }
147 |
148 | if ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
149 | try {
150 | $customer_details->addForwardedIp( new IpAddress( sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) ) );
151 | } catch ( InvalidArgumentException $invalid_argument_exception ) {
152 | $this->logger->log_warning( 'Invalid Forwarded IP address: ' . $invalid_argument_exception->getMessage() );
153 | }
154 | }
155 |
156 | if ( ! empty( $user_agent ) ) {
157 | $customer_details->addUserAgent( $user_agent );
158 | }
159 |
160 | if ( ! empty( $customer_id ) ) {
161 | $customer_details->addReference( $customer_id );
162 | }
163 |
164 | if ( ! empty( $browser ) ) {
165 | $customer_details->addData( $browser );
166 | }
167 |
168 | return $customer_details;
169 | }
170 |
171 | /**
172 | * @param string $address_line_1
173 | * @param string $address_line_2
174 | * @param string $country
175 | * @param string $state
176 | * @param string $city
177 | * @param string $zip_code
178 | * @return Address
179 | */
180 | protected function create_address(
181 | string $address_line_1,
182 | string $address_line_2,
183 | string $country,
184 | string $state,
185 | string $city,
186 | string $zip_code
187 | ): Address {
188 | $address_parser = new AddressParser();
189 | $address = $address_parser->parse( $address_line_1, $address_line_2 );
190 |
191 | $street = $address[0];
192 | $house_number = $address[1];
193 |
194 | $customer_address = new Address();
195 | return $customer_address
196 | ->addStreetName( $street )
197 | ->addHouseNumber( $house_number )
198 | ->addState( $state )
199 | ->addCity( $city )
200 | ->addCountry( new Country( $country ) )
201 | ->addZipCode( $zip_code );
202 | }
203 |
204 | /**
205 | * Return customer locale
206 | *
207 | * @return string
208 | */
209 | public function get_locale(): string {
210 | $locale = get_locale() ?? self::DEFAULT_LOCALE;
211 | return apply_filters( 'multisafepay_customer_locale', $locale );
212 | }
213 |
214 | /**
215 | * Customer reference only needs to be sent when a payment token is being used, or
216 | * when a payment tokens needs to be created.
217 | *
218 | * @param string $payment_method_id
219 | * @return bool
220 | */
221 | protected function should_send_customer_reference( string $payment_method_id ): bool {
222 | if ( ! isset( $_POST[ $payment_method_id . '_payment_component_tokenize' ] ) ) {
223 | return false;
224 | }
225 |
226 | if ( ! (bool) $_POST[ $payment_method_id . '_payment_component_tokenize' ] ) {
227 | return false;
228 | }
229 |
230 | return true;
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/src/Services/OrderService.php:
--------------------------------------------------------------------------------
1 | customer_service = new CustomerService();
43 | $this->shopping_cart_service = new ShoppingCartService();
44 | $this->payment_method_service = new PaymentMethodService();
45 | }
46 |
47 | /**
48 | * @param WC_Order $order
49 | * @param string $gateway_code
50 | * @param string $type
51 | * @return OrderRequest
52 | */
53 | public function create_order_request( WC_Order $order, string $gateway_code, string $type ): OrderRequest {
54 | $order_request = new OrderRequest();
55 | $order_request
56 | ->addOrderId( $order->get_order_number() )
57 | ->addMoney( MoneyUtil::create_money( (float) ( $order->get_total() ), $order->get_currency() ) )
58 | ->addGatewayCode( $gateway_code )
59 | ->addType( $type )
60 | ->addPluginDetails( $this->create_plugin_details() )
61 | ->addDescriptionText( $this->get_order_description_text( $order->get_order_number() ) )
62 | ->addCustomer( $this->customer_service->create_customer_details( $order ) )
63 | ->addPaymentOptions( $this->create_payment_options( $order ) )
64 | ->addSecondsActive( $this->get_seconds_active() )
65 | ->addSecondChance( ( new SecondChance() )->addSendEmail( (bool) get_option( 'multisafepay_second_chance', false ) ) )
66 | ->addData( array( 'var2' => $order->get_id() ) );
67 |
68 | if ( $order->needs_shipping_address() && $order->has_shipping_address() ) {
69 | $order_request->addDelivery( $this->customer_service->create_delivery_details( $order ) );
70 | }
71 |
72 | if ( ! get_option( 'multisafepay_disable_shopping_cart', false ) || in_array( $gateway_code, GatewaysSdk::SHOPPING_CART_REQUIRED_GATEWAYS, true ) ) {
73 | $order_request->addShoppingCart( $this->shopping_cart_service->create_shopping_cart( $order, $order->get_currency(), $gateway_code ) );
74 | }
75 |
76 | if ( ! empty( $_POST[ $order->get_payment_method() . '_payment_component_payload' ] ) ) {
77 | $payment_method_id = $order->get_payment_method();
78 | $payment_component_payload_key = $payment_method_id . '_payment_component_payload';
79 | $payment_component_payload = sanitize_text_field( wp_unslash( $_POST[ $payment_component_payload_key ] ?? '' ) );
80 | if ( ! empty( $payment_component_payload ) ) {
81 | $order_request->addType( 'direct' );
82 | $order_request->addData(
83 | array(
84 | 'payment_data' => array(
85 | 'payload' => $payment_component_payload,
86 | ),
87 | )
88 | );
89 | }
90 | }
91 |
92 | $payment_token = sanitize_text_field( wp_unslash( $_POST['payment_token'] ?? '' ) );
93 | if ( ! empty( $payment_token ) && ( ( 'APPLEPAY' === $gateway_code ) || ( 'GOOGLEPAY' === $gateway_code ) ) ) {
94 | $order_request->addType( 'direct' );
95 | $order_request->addGatewayInfo( ( new Wallet() )->addPaymentToken( $payment_token ) );
96 | }
97 |
98 | $order_request = $this->add_none_tax_rate( $order_request );
99 |
100 | return apply_filters( 'multisafepay_order_request', $order_request );
101 | }
102 |
103 | /**
104 | * @return PluginDetails
105 | */
106 | protected function create_plugin_details(): PluginDetails {
107 | $plugin_details = new PluginDetails();
108 | global $wp_version;
109 | return $plugin_details
110 | ->addApplicationName( 'Wordpress-WooCommerce' )
111 | ->addApplicationVersion( 'WordPress version: ' . $wp_version . '. WooCommerce version: ' . WC_VERSION )
112 | ->addPluginVersion( MULTISAFEPAY_PLUGIN_VERSION )
113 | ->addShopRootUrl( get_bloginfo( 'url' ) );
114 | }
115 |
116 | /**
117 | * @param WC_Order $order
118 | * @return PaymentOptions
119 | */
120 | private function create_payment_options( WC_Order $order ): PaymentOptions {
121 | $payment_options = new PaymentOptions();
122 | $payment_options->addNotificationUrl( get_rest_url( get_current_blog_id(), 'multisafepay/v1/notification' ) );
123 |
124 | $cancel_endpoint = ( get_option( 'multisafepay_redirect_after_cancel', 'cart' ) === 'cart' ? '' : wc_get_checkout_url() );
125 | $cancel_url = wp_specialchars_decode( $order->get_cancel_order_url( $cancel_endpoint ) );
126 |
127 | if ( is_wc_endpoint_url( 'order-pay' ) ) {
128 | $cancel_url = wp_specialchars_decode( $order->get_checkout_payment_url() );
129 | }
130 |
131 | $payment_options->addCancelUrl( $cancel_url );
132 | $payment_options->addRedirectUrl( $order->get_checkout_order_received_url() );
133 | if ( ! apply_filters( 'multisafepay_post_notification', true ) ) {
134 | $payment_options->addNotificationUrl( add_query_arg( 'wc-api', 'multisafepay', home_url( '/' ) ) );
135 | $payment_options->addNotificationMethod( 'GET' );
136 | }
137 | return $payment_options;
138 | }
139 |
140 | /**
141 | * Return the order description.
142 | *
143 | * @param string $order_number
144 | * @return string $order_description
145 | */
146 | protected function get_order_description_text( $order_number ): string {
147 | /* translators: %s: order id */
148 | $order_description = sprintf( __( 'Payment for order: %s', 'multisafepay' ), $order_number );
149 | if ( get_option( 'multisafepay_order_request_description', false ) ) {
150 | $order_description = str_replace( '{order_number}', $order_number, get_option( 'multisafepay_order_request_description', false ) );
151 | }
152 | return $order_description;
153 | }
154 |
155 | /**
156 | * Return the time active in seconds defined in the plugin settings page
157 | *
158 | * @return int
159 | */
160 | protected function get_seconds_active(): int {
161 | $time_active = get_option( 'multisafepay_time_active', '30' );
162 | $time_active_unit = get_option( 'multisafepay_time_unit', 'days' );
163 | if ( 'days' === $time_active_unit ) {
164 | $time_active = $time_active * 24 * 60 * 60;
165 | }
166 | if ( 'hours' === $time_active_unit ) {
167 | $time_active = $time_active * 60 * 60;
168 | }
169 | return $time_active;
170 | }
171 |
172 | /**
173 | * This method add a tax rate of 0, in case is not being created automatically by the shopping cart.
174 | * This is required to process refunds, based on shopping cart items
175 | *
176 | * @param OrderRequest $order_request
177 | * @return OrderRequest
178 | */
179 | public function add_none_tax_rate( OrderRequest $order_request ): OrderRequest {
180 | if ( $order_request->getShoppingCart() === null ) {
181 | return $order_request;
182 | }
183 | if ( $order_request->getCheckoutOptions()->getTaxTable() === null ) {
184 | return $order_request;
185 | }
186 | $shopping_cart = $order_request->getShoppingCart()->getData();
187 | if ( isset( $shopping_cart['items'] ) ) {
188 | foreach ( $shopping_cart['items'] as $item ) {
189 | if ( '0' === $item['tax_table_selector'] ) {
190 | return $order_request;
191 | }
192 | }
193 | }
194 | $tax_rate = ( new TaxRate() )->addRate( 0 );
195 | $tax_rule = ( new TaxRule() )->addTaxRate( $tax_rate )->addName( '0' );
196 | $order_request->getCheckoutOptions()->getTaxTable()->addTaxRule( $tax_rule );
197 | return $order_request;
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/src/Services/PaymentComponentService.php:
--------------------------------------------------------------------------------
1 | sdk_service = new SdkService();
35 | $this->api_token_service = new ApiTokenService();
36 | $this->payment_method_service = new PaymentMethodService();
37 | }
38 |
39 | /**
40 | * Return the arguments required when payment component needs to be initialized
41 | *
42 | * @param BasePaymentMethod $woocommerce_payment_gateway
43 | * @param bool $validate_checkout
44 | * @return array
45 | */
46 | public function get_payment_component_arguments( BasePaymentMethod $woocommerce_payment_gateway, bool $validate_checkout = false ): array {
47 | $payment_component_arguments = array(
48 | 'debug' => (bool) get_option( 'multisafepay_debugmode', false ),
49 | 'env' => $this->sdk_service->get_test_mode() ? 'test' : 'live',
50 | 'ajax_url' => admin_url( 'admin-ajax.php' ),
51 | 'nonce' => wp_create_nonce( 'payment_component_arguments_nonce' ),
52 | 'api_token' => $this->api_token_service->get_api_token(),
53 | 'orderData' => array(
54 | 'currency' => get_woocommerce_currency(),
55 | 'amount' => ( $this->get_total_amount() * 100 ),
56 | 'customer' => array(
57 | 'locale' => strtoupper( substr( ( new CustomerService() )->get_locale(), 0, 2 ) ),
58 | 'country' => ( WC()->customer )->get_billing_country(),
59 | ),
60 | 'payment_options' => array(
61 | 'template' => array(
62 | 'settings' => array(
63 | 'embed_mode' => 1,
64 | ),
65 | 'merge' => true,
66 | ),
67 | 'settings' => array(
68 | 'connect' => array(
69 | 'issuers_display_mode' => 'select',
70 | ),
71 | ),
72 | ),
73 | ),
74 | 'gateway' => $woocommerce_payment_gateway->get_payment_method_gateway_code(),
75 | 'qr_supported' => $woocommerce_payment_gateway->is_qr_enabled() || $woocommerce_payment_gateway->is_qr_only_enabled(),
76 | );
77 |
78 | // Payment Component Template ID.
79 | $template_id = get_option( 'multisafepay_payment_component_template_id', false );
80 | if ( ! empty( $template_id ) ) {
81 | $payment_component_arguments['orderData']['payment_options']['template_id'] = $template_id;
82 | }
83 |
84 | // Tokenization and recurring model
85 | if ( $woocommerce_payment_gateway->is_tokenization_enabled() && is_user_logged_in() ) {
86 | $payment_component_arguments['recurring'] = array(
87 | 'model' => 'cardOnFile',
88 | 'tokens' => $this->sdk_service->get_payment_tokens(
89 | (string) get_current_user_id(),
90 | sanitize_text_field( $woocommerce_payment_gateway->get_payment_method_gateway_code() )
91 | ),
92 | );
93 | }
94 |
95 | // Payment Component QR
96 | if ( $validate_checkout ) {
97 | $qr_checkout_manager = new QrCheckoutManager();
98 | if ( $qr_checkout_manager->validate_checkout_fields() ) {
99 | if ( $woocommerce_payment_gateway->is_qr_enabled() ) {
100 | $payment_component_arguments['orderData']['payment_options']['settings']['connect']['qr'] = array( 'enabled' => 1 );
101 | }
102 | if ( $woocommerce_payment_gateway->is_qr_only_enabled() ) {
103 | $payment_component_arguments['orderData']['payment_options']['settings']['connect']['qr'] = array(
104 | 'enabled' => 1,
105 | 'qr_only' => 1,
106 | );
107 | }
108 | }
109 | }
110 |
111 | return $payment_component_arguments;
112 | }
113 |
114 | /**
115 | * Return the arguments required when payment component needs to be initialized via a WP AJAX request
116 | *
117 | * @return void
118 | */
119 | public function refresh_payment_component_config() {
120 | $payment_component_arguments_nonce = sanitize_key( $_POST['nonce'] ?? '' );
121 | if ( ! wp_verify_nonce( wp_unslash( $payment_component_arguments_nonce ), 'payment_component_arguments_nonce' ) ) {
122 | wp_send_json( array() );
123 | }
124 | $gateway_id = sanitize_key( $_POST['gateway_id'] ?? '' );
125 | $woocommerce_payment_gateway = $this->payment_method_service->get_woocommerce_payment_gateway_by_id( $gateway_id );
126 | $validate_checkout_fields = ( $woocommerce_payment_gateway->is_payment_component_enabled() && $woocommerce_payment_gateway->is_qr_enabled() || $woocommerce_payment_gateway->is_qr_only_enabled() );
127 | $payment_component_arguments = $this->get_payment_component_arguments( $woocommerce_payment_gateway, $validate_checkout_fields );
128 | wp_send_json( $payment_component_arguments );
129 | }
130 |
131 | /**
132 | * Return the total amount of the cart or order
133 | *
134 | * @return float
135 | */
136 | private function get_total_amount(): float {
137 | $total_amount = ( WC()->cart ) ? (float) WC()->cart->get_total( '' ) : null;
138 |
139 | if ( is_wc_endpoint_url( 'order-pay' ) ) {
140 | $order_id = absint( get_query_var( 'order-pay' ) );
141 | if ( 0 < $order_id ) {
142 | $order = wc_get_order( $order_id );
143 | if ( $order ) {
144 | $total_amount = (float) $order->get_total();
145 | }
146 | }
147 | }
148 |
149 | return $total_amount;
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/Services/Qr/QrCustomerService.php:
--------------------------------------------------------------------------------
1 | create_address(
31 | $customer[ $type ]['address_1'],
32 | $customer[ $type ]['address_2'],
33 | $customer[ $type ]['country'],
34 | $customer[ $type ]['state'] ?? '',
35 | $customer[ $type ]['city'] ?? '',
36 | $customer[ $type ]['postcode']
37 | );
38 |
39 | return $this->create_customer(
40 | $customer_address,
41 | $customer[ $type ]['email'] ?? $customer['billing']['email'],
42 | $customer[ $type ]['phone'] ?? $customer['billing']['phone'],
43 | $customer[ $type ]['first_name'] ?? $customer['billing']['first_name'],
44 | $customer[ $type ]['last_name'] ?? $customer['billing']['last_name'],
45 | ( new QrOrder() )->get_customer_ip_address() ?? '',
46 | ( new QrOrder() )->get_user_agent() ?? '',
47 | $customer[ $type ]['company'] ?? $customer['billing']['company'],
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Services/Qr/QrOrderService.php:
--------------------------------------------------------------------------------
1 | qr_customer_service = new QrCustomerService();
51 | $this->qr_shopping_cart_service = new QrShoppingCartService();
52 | $this->logger = new Logger();
53 | }
54 |
55 | /**
56 | * @param string $order_id
57 | * @param string $gateway_code
58 | * @param string $payload
59 | * @param array $checkout_fields
60 | * @return OrderRequest
61 | * @throws InvalidArgumentException
62 | */
63 | public function get_order_request(
64 | string $order_id,
65 | string $gateway_code,
66 | string $payload,
67 | array $checkout_fields
68 | ): OrderRequest {
69 | $cart = WC()->cart;
70 | $order_request = new OrderRequest();
71 | $order_request
72 | ->addOrderId( $order_id )
73 | ->addMoney( MoneyUtil::create_money( (float) $cart->get_total( 'edit' ), get_woocommerce_currency() ) )
74 | ->addGatewayCode( $gateway_code )
75 | ->addType( OrderRequest::DIRECT_TYPE )
76 | ->addPluginDetails( $this->create_plugin_details() )
77 | ->addDescriptionText( $this->get_order_description_text( $order_id ) )
78 | ->addCustomer( $this->qr_customer_service->create_customer_details_from_cart( $checkout_fields['customer'] ) )
79 | ->addPaymentOptions( $this->create_payment_options( $this->generate_token( $order_id ) ) )
80 | ->addSecondsActive( $this->get_seconds_active() )
81 | ->addSecondChance( ( new SecondChance() )->addSendEmail( (bool) get_option( 'multisafepay_second_chance', false ) ) )
82 | ->addData( array( 'var2' => $order_id ) );
83 |
84 | if ( $this->is_filled_shipping_address( $checkout_fields ) && WC()->cart->needs_shipping() ) {
85 | $order_request->addDelivery( $this->qr_customer_service->create_customer_details_from_cart( $checkout_fields['customer'], 'shipping' ) );
86 | }
87 |
88 | if ( ! get_option( 'multisafepay_disable_shopping_cart', false ) || in_array( $gateway_code, GatewaysSdk::SHOPPING_CART_REQUIRED_GATEWAYS, true ) ) {
89 | $order_request->addShoppingCart( $this->qr_shopping_cart_service->create_shopping_cart( $cart, get_woocommerce_currency() ) );
90 | }
91 |
92 | if ( ! empty( $payload ) ) {
93 | $order_request->addData(
94 | array(
95 | 'payment_data' => array(
96 | 'gateway' => $gateway_code,
97 | 'payload' => $payload,
98 | ),
99 | )
100 | );
101 | }
102 |
103 | $order_request = $this->add_none_tax_rate( $order_request );
104 |
105 | return apply_filters( 'multisafepay_order_request', $order_request );
106 | }
107 |
108 | /**
109 | * Check if the shipping address is filled
110 | *
111 | * @param array $checkout_fields
112 | * @return bool
113 | */
114 | public function is_filled_shipping_address( array $checkout_fields ): bool {
115 | $filled_fields = $checkout_fields['customer']['shipping'] ?? array();
116 | return ! empty( $filled_fields['address_1'] ) || ! empty( $filled_fields['address_2'] );
117 | }
118 |
119 | /**
120 | * @param string $token
121 | * @return PaymentOptions
122 | */
123 | public function create_payment_options( string $token ): PaymentOptions {
124 | $redirect_cancel_url = add_query_arg( 'token', $token, get_rest_url( get_current_blog_id(), 'multisafepay/v1/qr-balancer' ) );
125 | $payment_options = new PaymentOptions();
126 | $payment_options->addNotificationUrl( get_rest_url( get_current_blog_id(), 'multisafepay/v1/qr-notification' ) );
127 | $payment_options->addCancelUrl( $redirect_cancel_url );
128 | $payment_options->addRedirectUrl( $redirect_cancel_url );
129 | $payment_options->addSettings( array( 'qr' => array( 'enabled' => true ) ) );
130 |
131 | return $payment_options;
132 | }
133 |
134 | /**
135 | * Generate a token that will be used on validation of QR related endpoints
136 | *
137 | * @param string $order_id
138 | * @return string
139 | */
140 | public function generate_token( string $order_id ) {
141 | $token = wp_generate_password( 32, false );
142 | set_transient( 'multisafepay_token_' . $order_id, $token, 86400 );
143 | return $token;
144 | }
145 |
146 | /**
147 | * Check if the QR data is valid
148 | *
149 | * @param array $qr_data
150 | * @param string $order_id
151 | * @return bool
152 | */
153 | public function is_valid_qr_data( array $qr_data, string $order_id ): bool {
154 | $required_fields = array(
155 | 'image' => $qr_data['qr']['image'] ?? null,
156 | 'token' => $qr_data['qr']['params']['token'] ?? null,
157 | 'order_id' => $qr_data['order_id'] ?? null,
158 | );
159 |
160 | foreach ( $required_fields as $field => $value ) {
161 | if ( empty( $value ) || ( ( 'order_id' === $field ) && ( (string) $value !== $order_id ) ) ) {
162 | return false;
163 | }
164 | }
165 |
166 | return true;
167 | }
168 |
169 | /**
170 | * Generate a unique order ID for cart-based transactions
171 | *
172 | * @return string
173 | */
174 | public function generate_unique_order_id(): string {
175 | return 'QR-' . uniqid( '', true );
176 | }
177 |
178 | /**
179 | * Activate the QR code for the order
180 | *
181 | * @param BasePaymentMethod $payment_gateway
182 | * @param string $payload
183 | * @param array $checkout_fields
184 | * @return array
185 | * @throws InvalidArgumentException
186 | */
187 | public function place_order( BasePaymentMethod $payment_gateway, string $payload, array $checkout_fields ): array {
188 | $order_id = $this->generate_unique_order_id();
189 |
190 | // Create a transient using order ID and the checkout fields
191 | set_transient( 'multisafepay_qr_order_' . $order_id, $checkout_fields, self::EXPIRATION_TIME_CHECKOUT_QR_DATA );
192 |
193 | $order_request = $this->get_order_request(
194 | $order_id,
195 | $payment_gateway->get_payment_method_gateway_code(),
196 | $payload,
197 | $checkout_fields
198 | );
199 |
200 | $sdk = new SdkService();
201 | $transaction_manager = $sdk->get_transaction_manager();
202 |
203 | try {
204 | $transaction = $transaction_manager->create( $order_request );
205 | } catch ( ApiException | ClientExceptionInterface $exception ) {
206 | $this->logger->log_error( $exception->getMessage() );
207 | wc_add_notice( __( 'There was a problem processing your payment using a QR code. Please try again later or contact with us.', 'multisafepay' ), 'error' );
208 | return array();
209 | }
210 |
211 | $payment_data = $transaction->getData();
212 |
213 | $this->logger->log_info( 'Start MultiSafepay transaction for the order ID ' . $order_id . ' on ' . date( 'd/m/Y H:i:s' ) . ' with payment data ' . wp_json_encode( $payment_data ) );
214 |
215 | if ( $this->is_valid_qr_data( $payment_data, $order_id ) ) {
216 | return $payment_data;
217 | }
218 |
219 | $this->logger->log_error( 'QR code was not correct' );
220 |
221 | return array();
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/src/Services/Qr/QrPaymentComponentService.php:
--------------------------------------------------------------------------------
1 | payment_method_service = new PaymentMethodService();
34 | $this->logger = new Logger();
35 | }
36 |
37 | /**
38 | * Place a MultiSafepay transaction with QR code
39 | *
40 | * @return void
41 | */
42 | public function set_multisafepay_qr_code_transaction(): void {
43 | $payment_component_arguments_nonce = sanitize_key( $_POST['nonce'] ?? '' );
44 | if ( ! wp_verify_nonce( wp_unslash( $payment_component_arguments_nonce ), 'payment_component_arguments_nonce' ) ) {
45 | wp_send_json( array() );
46 | }
47 | $gateway_id = sanitize_key( $_POST['gateway_id'] ?? '' );
48 | $payload = sanitize_text_field( wp_unslash( $_POST['payload'] ?? '' ) );
49 |
50 | if ( empty( $gateway_id ) || empty( $payload ) ) {
51 | return;
52 | }
53 | $woocommerce_payment_gateway = $this->payment_method_service->get_woocommerce_payment_gateway_by_id( $gateway_id );
54 | if ( ! is_null( $woocommerce_payment_gateway ) &&
55 | ( $woocommerce_payment_gateway->is_qr_enabled() || $woocommerce_payment_gateway->is_qr_only_enabled() )
56 | ) {
57 | try {
58 | $qr_checkout_manager = new QrCheckoutManager();
59 | $checkout_fields = $qr_checkout_manager->get_checkout_data();
60 | $multisafepay_qr_code_transaction_data = array();
61 |
62 | if ( $qr_checkout_manager->validate_checkout_fields() ) {
63 | $shopping_cart_order_service = new QrOrderService();
64 | $multisafepay_qr_code_transaction_data = $shopping_cart_order_service->place_order( $woocommerce_payment_gateway, $payload, $checkout_fields );
65 | }
66 |
67 | wp_send_json( $multisafepay_qr_code_transaction_data );
68 |
69 | } catch ( Exception | InvalidArgumentException $exception ) {
70 | $this->logger->log_error( 'Arguments for QR could not be collected: ' . $exception->getMessage() );
71 | }
72 | }
73 | }
74 |
75 |
76 | /**
77 | * Return redirect URL where meta value key is 'multisafepay_transaction_id' and value is $order_id
78 | *
79 | * @return void
80 | */
81 | public function get_qr_order_redirect_url(): void {
82 | $payment_component_arguments_nonce = sanitize_key( $_POST['nonce'] ?? '' );
83 | if ( ! wp_verify_nonce( wp_unslash( $payment_component_arguments_nonce ), 'payment_component_arguments_nonce' ) ) {
84 | wp_send_json( array() );
85 | }
86 |
87 | $order_id = sanitize_text_field( wp_unslash( $_POST['order_id'] ?? '' ) );
88 |
89 | for ( $count = 0; $count < 5; $count++ ) {
90 | // Get WooCommerce Order ID where meta value key is 'multisafepay_transaction_id' and value is $order_id
91 | $woocommerce_order_id = Order::get_order_id_by_multisafepay_transaction_id_key( $order_id );
92 |
93 | if ( ! $woocommerce_order_id ) {
94 | sleep( 2 );
95 | continue;
96 | }
97 |
98 | $woocommerce_order = wc_get_order( $woocommerce_order_id );
99 |
100 | wp_send_json(
101 | array(
102 | 'success' => true,
103 | 'redirect_url' => $woocommerce_order->get_checkout_order_received_url(),
104 | )
105 | );
106 | }
107 |
108 | wp_send_json(
109 | array(
110 | 'success' => true,
111 | 'redirect_url' => ( get_option( 'multisafepay_redirect_after_cancel', 'cart' ) === 'cart' ) ?
112 | wc_get_cart_url() :
113 | wc_get_checkout_url(),
114 | )
115 | );
116 |
117 | exit();
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/Services/Qr/QrShoppingCartService.php:
--------------------------------------------------------------------------------
1 | logger = $logger ?? new Logger();
33 | }
34 |
35 | /**
36 | * Create a shopping cart from a WooCommerce cart
37 | *
38 | * @param WC_Cart $cart
39 | * @param string $currency
40 | * @return ShoppingCart
41 | * @throws InvalidArgumentException
42 | */
43 | public function create_shopping_cart( WC_Cart $cart, string $currency ): ShoppingCart {
44 | // Recalculate totals is needed, to ensure all discounts, fees, etc are applied
45 | $cart->calculate_totals();
46 |
47 | // Is vat exempt
48 | $is_vat_exempt = $this->is_order_vat_exempt( $cart );
49 |
50 | $cart_items = array();
51 |
52 | foreach ( $cart->get_cart() as $cart_item ) {
53 | $cart_items[] = $this->create_cart_item( $cart_item, $currency, $is_vat_exempt );
54 | }
55 |
56 | // Add shipping as an item if applicable
57 | if ( $cart->needs_shipping() ) {
58 | $cart_items[] = $this->create_shipping_cart_item( $cart, $currency, $is_vat_exempt );
59 | }
60 |
61 | // Add fees as items if applicable
62 | foreach ( $cart->get_fees() as $fee ) {
63 | $cart_items[] = $this->create_fee_cart_item( $fee, $currency, $is_vat_exempt );
64 | }
65 |
66 | // Add coupons as items if applicable
67 | foreach ( $cart->get_coupons() as $coupon ) {
68 | if (
69 | ( get_option( 'woocommerce_smart_coupon_apply_before_tax', 'no' ) !== 'yes' ) &&
70 | in_array( $coupon->get_discount_type(), $this->get_types_of_coupons_not_applied_at_item_level(), true )
71 | ) {
72 | $cart_items[] = $this->create_coupon_cart_item( $coupon, $currency );
73 | }
74 | }
75 |
76 | $shopping_cart = new ShoppingCart( $cart_items );
77 |
78 | $this->logger->log_info( wp_json_encode( $shopping_cart->getData() ) );
79 |
80 | return $shopping_cart;
81 | }
82 |
83 | /**
84 | * Create a cart item from the WooCommerce shopping cart item
85 | *
86 | * @param array $cart_item
87 | * @param string $currency
88 | * @param bool $is_vat_exempt
89 | * @return CartItem
90 | * @throws InvalidArgumentException
91 | */
92 | private function create_cart_item( array $cart_item, string $currency, bool $is_vat_exempt = false ): CartItem {
93 | $product = $cart_item['data'];
94 | $item = new CartItem();
95 | $product_name = $product->get_name();
96 | $product_price = (float) $cart_item['line_subtotal'] / $cart_item['quantity'];
97 |
98 | // If product price without discount get_subtotal() is not the same as product price with discount
99 | // Then a percentage coupon has been applied to this item
100 | if ( (float) $cart_item['line_subtotal'] !== (float) $cart_item['line_total'] ) {
101 | $discount = (float) $cart_item['line_subtotal'] - (float) $cart_item['line_total'];
102 | // translators: %1$s is the discount amount, %2$s is the currency
103 | $product_name .= sprintf( __( ' - Coupon applied: - %1$s %2$s', 'multisafepay' ), number_format( $discount, 2, '.', '' ), $currency );
104 | $product_price = (float) $cart_item['line_total'] / (int) $cart_item['quantity'];
105 | }
106 |
107 | return $item->addName( $product_name )
108 | ->addQuantity( (int) $cart_item['quantity'] )
109 | ->addMerchantItemId( $product->get_sku() ? (string) $product->get_sku() : (string) $product->get_id() )
110 | ->addUnitPrice( MoneyUtil::create_money( $product_price, $currency ) )
111 | ->addTaxRate( $this->get_item_tax_rate_from_cart( $cart_item, $is_vat_exempt ) );
112 | }
113 |
114 | /**
115 | * Get the tax rate for a cart item
116 | *
117 | * @param array $cart_item
118 | * @param bool $is_vat_exempt
119 | * @return float
120 | */
121 | private function get_item_tax_rate_from_cart( array $cart_item, bool $is_vat_exempt = false ): float {
122 | if ( ! wc_tax_enabled() ) {
123 | return 0;
124 | }
125 |
126 | if ( $is_vat_exempt ) {
127 | return 0.00;
128 | }
129 |
130 | $product = $cart_item['data'];
131 |
132 | if ( 'taxable' !== $product->get_tax_status() ) {
133 | return 0;
134 | }
135 |
136 | $tax_class = $product->get_tax_class();
137 | $tax_rates = WC_Tax::get_rates( $tax_class );
138 |
139 | switch ( count( $tax_rates ) ) {
140 | case 0:
141 | $tax_rate = 0;
142 | break;
143 | case 1:
144 | $tax = reset( $tax_rates );
145 | $tax_rate = $tax['rate'];
146 | break;
147 | default:
148 | $price_including_tax = wc_get_price_including_tax( $product );
149 | $price_excluding_tax = wc_get_price_excluding_tax( $product );
150 | $tax_rate = ( ( $price_including_tax / $price_excluding_tax ) - 1 ) * 100;
151 | break;
152 | }
153 |
154 | return $tax_rate;
155 | }
156 |
157 | /**
158 | * Create a shipping item from the WooCommerce shopping cart
159 | *
160 | * @param WC_Cart $cart
161 | * @param string $currency
162 | * @param bool $is_vat_exempt
163 | * @return ShippingItem
164 | */
165 | public function create_shipping_cart_item( WC_Cart $cart, string $currency, bool $is_vat_exempt = false ): ShippingItem {
166 | $shipping_item = new ShippingItem();
167 | return $shipping_item->addName( __( 'Shipping', 'multisafepay' ) )
168 | ->addQuantity( 1 )
169 | ->addUnitPrice( MoneyUtil::create_money( (float) $cart->get_shipping_total(), $currency ) )
170 | ->addMerchantItemId( 'msp-shipping' )
171 | ->addTaxRate( $this->get_shipping_tax_rate( $cart, $is_vat_exempt ) );
172 | }
173 |
174 | /**
175 | * Get the tax rate for shipping
176 | *
177 | * @param WC_Cart $cart
178 | * @param bool $is_vat_exempt
179 | * @return float
180 | */
181 | private function get_shipping_tax_rate( WC_Cart $cart, bool $is_vat_exempt = false ): float {
182 | if ( ! wc_tax_enabled() ) {
183 | return 0;
184 | }
185 |
186 | if ( $is_vat_exempt ) {
187 | return 0.00;
188 | }
189 |
190 | $shipping_total = $cart->get_shipping_total();
191 | $shipping_taxes = $cart->get_shipping_taxes();
192 |
193 | if ( empty( $shipping_taxes ) || ( 0.00 === $shipping_total ) ) {
194 | return 0;
195 | }
196 |
197 | $total_tax = array_sum( $shipping_taxes );
198 |
199 | return ( (float) $total_tax * 100 ) / (float) $shipping_total;
200 | }
201 |
202 | /**
203 | * Create a fee item from the WooCommerce shopping cart
204 | *
205 | * @param stdClass $fee
206 | * @param string $currency
207 | * @param bool $is_vat_exempt
208 | * @return CartItem
209 | */
210 | private function create_fee_cart_item( stdClass $fee, string $currency, bool $is_vat_exempt = false ): CartItem {
211 | $fee_item = new CartItem();
212 | return $fee_item->addName( $fee->name )
213 | ->addQuantity( 1 )
214 | ->addMerchantItemId( (string) $fee->id )
215 | ->addUnitPrice( MoneyUtil::create_money( (float) $fee->amount, $currency ) )
216 | ->addTaxRate( $this->get_fee_tax_rate( $fee, $is_vat_exempt ) );
217 | }
218 |
219 | /**
220 | * Get the tax rate for a fee item from the cart
221 | *
222 | * @param stdClass $fee
223 | * @param bool $is_vat_exempt
224 | * @return float
225 | */
226 | private function get_fee_tax_rate( stdClass $fee, bool $is_vat_exempt = false ): float {
227 | if ( ! wc_tax_enabled() ) {
228 | return 0.00;
229 | }
230 |
231 | if ( $is_vat_exempt ) {
232 | return 0.00;
233 | }
234 |
235 | if ( 0.00 === (float) $fee->amount ) {
236 | return 0.00;
237 | }
238 |
239 | if ( false === $fee->taxable ) {
240 | return 0.00;
241 | }
242 |
243 | if ( empty( $fee->total ) ) {
244 | return 0.00;
245 | }
246 |
247 | $total_tax = array_sum( $fee->tax_data );
248 |
249 | return ( (float) $total_tax * 100 ) / (float) $fee->total;
250 | }
251 |
252 | /**
253 | * Create a coupon item from the WooCommerce shopping cart
254 | *
255 | * @param WC_Coupon $coupon
256 | * @param string $currency
257 | * @return CartItem
258 | * @throws InvalidArgumentException
259 | */
260 | private function create_coupon_cart_item( WC_Coupon $coupon, string $currency ): CartItem {
261 | $coupon_item = new CartItem();
262 | return $coupon_item->addName( $coupon->get_code() )
263 | ->addQuantity( 1 )
264 | ->addMerchantItemId( (string) $coupon->get_id() )
265 | ->addUnitPrice( MoneyUtil::create_money( (float) -$coupon->get_amount(), $currency ) )
266 | ->addTaxRate( 0 );
267 | }
268 |
269 | /**
270 | * Retrieve the types of coupons that are not applied at item level
271 | *
272 | * @return array
273 | */
274 | public function get_types_of_coupons_not_applied_at_item_level(): array {
275 | return apply_filters( 'multisafepay_types_of_coupons_not_applied_at_item_level', array( 'smart_coupon' ) );
276 | }
277 |
278 | /**
279 | * Returns if order is VAT exempt via WC_Cart->get_customer()->is_vat_exempt
280 | *
281 | * @param WC_Cart $cart
282 | * @return bool
283 | */
284 | public function is_order_vat_exempt( WC_Cart $cart ): bool {
285 | $customer = $cart->get_customer();
286 |
287 | if ( null === $customer ) {
288 | return false;
289 | }
290 |
291 | return $customer->is_vat_exempt();
292 | }
293 | }
294 |
--------------------------------------------------------------------------------
/src/Services/SdkService.php:
--------------------------------------------------------------------------------
1 | api_key = $api_key ?? $this->get_api_key();
59 | $this->test_mode = $test_mode ?? $this->get_test_mode();
60 | $this->logger = $logger ?? new Logger();
61 | $psr_factory = new Psr17Factory();
62 | $client = new MultiSafepayClient();
63 | try {
64 | $this->sdk = new Sdk( $this->api_key, ( $this->test_mode ) ? false : true, $client, $psr_factory, $psr_factory );
65 | } catch ( InvalidApiKeyException $invalid_api_key_exception ) {
66 | set_transient( 'multisafepay_payment_methods', array() );
67 | $this->logger->log_error( $invalid_api_key_exception->getMessage() );
68 | }
69 | }
70 |
71 | /**
72 | * Returns if test mode is enable
73 | *
74 | * @return boolean
75 | */
76 | public function get_test_mode(): bool {
77 | return (bool) get_option( 'multisafepay_testmode', false );
78 | }
79 |
80 | /**
81 | * Returns api key set in settings page according with
82 | * the environment selected
83 | *
84 | * @return string
85 | */
86 | public function get_api_key(): string {
87 | if ( $this->get_test_mode() ) {
88 | return get_option( 'multisafepay_test_api_key', '' );
89 | }
90 | return get_option( 'multisafepay_api_key', '' );
91 | }
92 |
93 |
94 | /**
95 | * Returns gateway manager
96 | *
97 | * @return GatewayManager|WP_Error
98 | */
99 | public function get_gateway_manager() {
100 | try {
101 | return $this->sdk->getGatewayManager();
102 | } catch ( ApiException $api_exception ) {
103 | $this->logger->log_error( $api_exception->getMessage() );
104 | return new WP_Error( 'multisafepay-warning', $api_exception->getMessage() );
105 | }
106 | }
107 |
108 |
109 | /**
110 | * Returns an array of the gateways available on the merchant account
111 | *
112 | * @return Gateway[]|WP_Error
113 | */
114 | public function get_gateways() {
115 | try {
116 | return $this->get_gateway_manager()->getGateways( true );
117 | } catch ( ApiException $api_exception ) {
118 | $this->logger->log_error( $api_exception->getMessage() );
119 | return new WP_Error( 'multisafepay-warning', $api_exception->getMessage() );
120 | }
121 | }
122 |
123 | /**
124 | * Returns transaction manager
125 | *
126 | * @return TransactionManager
127 | */
128 | public function get_transaction_manager(): TransactionManager {
129 | return $this->sdk->getTransactionManager();
130 | }
131 |
132 | /**
133 | * Returns issuer manager
134 | *
135 | * @return IssuerManager
136 | */
137 | public function get_issuer_manager(): IssuerManager {
138 | return $this->sdk->getIssuerManager();
139 | }
140 |
141 |
142 | /**
143 | * @return Sdk
144 | */
145 | public function get_sdk(): Sdk {
146 | return $this->sdk;
147 | }
148 |
149 | /**
150 | * Returns api token manager
151 | *
152 | * @return ApiTokenManager
153 | */
154 | public function get_api_token_manager(): ?ApiTokenManager {
155 | if ( null === $this->sdk ) {
156 | $this->logger->log_error( 'SDK is not initialized' );
157 | return null;
158 | }
159 | return $this->sdk->getApiTokenManager();
160 | }
161 |
162 | /**
163 | * Returns a PaymentMethodManager instance
164 | *
165 | * @return PaymentMethodManager|null
166 | */
167 | public function get_payment_method_manager(): ?PaymentMethodManager {
168 | if ( null === $this->sdk ) {
169 | $this->logger->log_error( 'SDK is not initialized' );
170 | return null;
171 | }
172 | try {
173 | return $this->sdk->getPaymentMethodManager();
174 | } catch ( ApiException $api_exception ) {
175 | $this->logger->log_error( $api_exception->getMessage() );
176 | return null;
177 | }
178 | }
179 |
180 | /**
181 | * Returns an array of tokens for the given customer reference and gateway code
182 | *
183 | * @param string $customer_reference
184 | * @param string $gateway_code
185 | * @return array
186 | */
187 | public function get_payment_tokens( string $customer_reference, string $gateway_code ): array {
188 | try {
189 | $tokens = $this->sdk->getTokenManager()->getListByGatewayCodeAsArray( $customer_reference, $gateway_code );
190 | } catch ( ApiException $api_exception ) {
191 | $this->logger->log_error( $api_exception->getMessage() );
192 | return array();
193 | } catch ( ClientExceptionInterface $client_exception ) {
194 | $this->logger->log_error( $client_exception->getMessage() );
195 | return array();
196 | } catch ( Exception $exception ) {
197 | $this->logger->log_error( $exception->getMessage() );
198 | return array();
199 | }
200 |
201 | return $tokens;
202 | }
203 |
204 | /**
205 | * Return the MultiSafepay Merchant Account ID
206 | *
207 | * @return int
208 | */
209 | public function get_multisafepay_account_id(): int {
210 | try {
211 | $account_manager = $this->sdk->getAccountManager();
212 | $gateway_merchant_id = $account_manager->get()->getAccountId();
213 | } catch ( ApiException | ClientExceptionInterface | Exception $exception ) {
214 | $this->logger->log_error( 'Error when try to set the merchant credentials: ' . $exception->getMessage() );
215 | }
216 |
217 | return $gateway_merchant_id ?? 0;
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/src/Services/ValidationService.php:
--------------------------------------------------------------------------------
1 | true,
30 | 'valid' => true,
31 | 'error' => 'WC_Validation class not available',
32 | )
33 | );
34 | return;
35 | }
36 |
37 | // Use the WooCommerce validation class to validate the zip code
38 | $is_valid = WC_Validation::is_postcode( $postcode, $country );
39 |
40 | wp_send_json(
41 | array(
42 | 'success' => $is_valid,
43 | 'valid' => $is_valid,
44 | 'postcode' => $postcode,
45 | 'country' => $country,
46 | )
47 | );
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Services/index.php:
--------------------------------------------------------------------------------
1 | get_multisafepay_logs();
22 | $view_multisafepay_log_nonce = sanitize_key( $_POST['view-multisafepay-log'] ?? '' );
23 |
24 | if (
25 | ( ! empty( $view_multisafepay_log_nonce ) && wp_verify_nonce( wp_unslash( $view_multisafepay_log_nonce ), 'view-multisafepay-log' ) ) &&
26 | ! empty( $_POST['log_file'] ) && isset( $logs[ sanitize_title( wp_unslash( $_POST['log_file'] ) ) ] ) ) {
27 | $current_log = $logs[ sanitize_title( wp_unslash( $_POST['log_file'] ) ) ];
28 | // phpcs:ignore ObjectCalisthenics.ControlStructures.NoElse.ObjectCalisthenics\Sniffs\ControlStructures\NoElseSniff
29 | } elseif ( ! empty( $logs ) ) {
30 | $current_log = end( $logs );
31 | }
32 |
33 | require_once MULTISAFEPAY_PLUGIN_DIR_PATH . 'templates/partials/multisafepay-settings-logs-display.php';
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Settings/SettingsController.php:
--------------------------------------------------------------------------------
1 | base === 'woocommerce_page_multisafepay-settings' ) {
52 | wp_enqueue_style( 'multisafepay-admin-css', MULTISAFEPAY_PLUGIN_URL . '/assets/admin/css/multisafepay-admin.css', array(), MULTISAFEPAY_PLUGIN_VERSION, 'all' );
53 | }
54 | $sections = array( 'multisafepay_applepay', 'multisafepay_googlepay' );
55 | if ( isset( $_GET['section'] ) && in_array( $_GET['section'], $sections, true ) ) {
56 | wp_enqueue_script( 'multisafepay-admin-js', MULTISAFEPAY_PLUGIN_URL . '/assets/admin/js/multisafepay-admin.js', array(), MULTISAFEPAY_PLUGIN_VERSION, true );
57 | }
58 | }
59 |
60 | /**
61 | * Register the common settings page in WooCommerce menu section.
62 | *
63 | * @see https://developer.wordpress.org/reference/functions/add_submenu_page/
64 | *
65 | * @return void
66 | */
67 | public function register_common_settings_page(): void {
68 | $title = sprintf( __( 'MultiSafepay Settings v. %s', 'multisafepay' ), MULTISAFEPAY_PLUGIN_VERSION ); // phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment
69 | add_submenu_page(
70 | 'woocommerce',
71 | esc_html( $title ),
72 | __( 'MultiSafepay Settings', 'multisafepay' ),
73 | 'manage_woocommerce',
74 | 'multisafepay-settings',
75 | array( $this, 'display_multisafepay_settings' )
76 | );
77 | }
78 |
79 | /**
80 | * Display the common settings page view.
81 | *
82 | * @return void
83 | */
84 | public function display_multisafepay_settings(): void {
85 | $tab_active = $this->get_tab_active();
86 | require_once MULTISAFEPAY_PLUGIN_DIR_PATH . 'templates/multisafepay-settings-display.php';
87 | }
88 |
89 | /**
90 | * Display the support settings page view.
91 | *
92 | * @return void
93 | */
94 | public function display_multisafepay_support_section(): void {
95 | require_once MULTISAFEPAY_PLUGIN_DIR_PATH . 'templates/partials/multisafepay-settings-support-display.php';
96 | }
97 |
98 | /**
99 | * Display the status tab.
100 | *
101 | * @return void
102 | */
103 | public function display_multisafepay_status_section(): void {
104 | $status_controller = new StatusController();
105 | $status_controller->display();
106 | }
107 |
108 | /**
109 | * Display the log tab.
110 | *
111 | * @return void
112 | */
113 | public function display_multisafepay_logs_section() {
114 | $logs_controller = new LogsController();
115 | $logs_controller->display();
116 | }
117 |
118 | /**
119 | * Returns active tab defined in get variable
120 | *
121 | * @return string
122 | */
123 | private function get_tab_active(): string {
124 | if ( isset( $_GET['tab'] ) && '' !== $_GET['tab'] ) {
125 | return wp_unslash( sanitize_key( $_GET['tab'] ) );
126 | }
127 | return 'general';
128 | }
129 |
130 | /**
131 | * Register general settings in common settings page
132 | *
133 | * @return void
134 | */
135 | public function register_common_settings(): void {
136 | $settings_fields = new SettingsFields();
137 | $settings = $settings_fields->get_settings();
138 | foreach ( $settings as $tab_key => $section ) {
139 | $this->add_settings_section( $tab_key, $section['title'] );
140 | foreach ( $section['fields'] as $field ) {
141 | $this->register_setting( $field, $tab_key );
142 | $this->add_settings_field( $field, $tab_key );
143 | }
144 | }
145 | }
146 |
147 | /**
148 | * Add settings field
149 | *
150 | * @see https://developer.wordpress.org/reference/functions/add_settings_field/
151 | *
152 | * @param array $field The field
153 | * @param string $tab_key The key of the tab
154 | * @return void
155 | */
156 | private function add_settings_field( array $field, string $tab_key ): void {
157 | add_settings_field(
158 | $field['id'],
159 | $this->generate_label_for_settings_field( $field ),
160 | array( $this, 'display_field' ),
161 | 'multisafepay-settings-' . $tab_key,
162 | $tab_key,
163 | array(
164 | 'field' => $field,
165 | )
166 | );
167 | }
168 |
169 | /**
170 | * Return the label tag to be used in add_settings_field
171 | *
172 | * @param array $field The settings field array
173 | * @return string
174 | */
175 | private function generate_label_for_settings_field( array $field ): string {
176 | if ( '' === $field['tooltip'] ) {
177 | return sprintf( '', $field['id'], $field['label'] );
178 | }
179 | return sprintf( '', $field['id'], $field['label'], wc_help_tip( $field['tooltip'] ) );
180 | }
181 |
182 | /**
183 | * Filter which set the settings page and adds a screen options of WooCommerce
184 | *
185 | * @see http://hookr.io/filters/woocommerce_screen_ids/
186 | *
187 | * @param array $screen
188 | * @return array
189 | */
190 | public function set_wc_screen_options_in_common_settings_page( array $screen ): array {
191 | $screen[] = 'woocommerce_page_multisafepay-settings';
192 | return $screen;
193 | }
194 |
195 | /**
196 | * Register setting
197 | *
198 | * @see https://developer.wordpress.org/reference/functions/register_setting/
199 | *
200 | * @param array $field
201 | * @param string $tab_key
202 | * @return void
203 | */
204 | private function register_setting( array $field, string $tab_key ): void {
205 | register_setting(
206 | 'multisafepay-settings-' . $tab_key,
207 | $field['id'],
208 | array(
209 | 'type' => $field['setting_type'],
210 | 'show_in_rest' => false,
211 | 'sanitize_callback' => $field['callback'],
212 | )
213 | );
214 | }
215 |
216 | /**
217 | * Add settings section
218 | *
219 | * @see https://developer.wordpress.org/reference/functions/add_settings_section/
220 | *
221 | * @param string $section_key
222 | * @param string $section_title
223 | * @return void
224 | */
225 | private function add_settings_section( string $section_key, string $section_title ): void {
226 | add_settings_section(
227 | $section_key,
228 | $section_title,
229 | array( $this, 'display_intro_section' ),
230 | 'multisafepay-settings-' . $section_key
231 | );
232 | }
233 |
234 | /**
235 | * Callback to display the title on each settings sections
236 | *
237 | * @see https://developer.wordpress.org/reference/functions/add_settings_section/
238 | *
239 | * @param array $args
240 | * @return void
241 | */
242 | public function display_intro_section( array $args ): void {
243 | $settings_fields = new SettingsFields();
244 | $settings = $settings_fields->get_settings();
245 | if ( ! empty( $settings[ $args['id'] ]['intro'] ) ) {
246 | esc_html( (string) printf( '%s
', esc_html( $settings[ $args['id'] ]['intro'] ) ) );
247 | }
248 | }
249 |
250 | /**
251 | * Return the HTML view by field.
252 | * Is the callback function in add_settings_field
253 | *
254 | * @param array $args
255 | * @return void
256 | */
257 | public function display_field( $args ): void {
258 | $field = $args['field'];
259 | $settings_field_display = new SettingsFieldsDisplay( $field );
260 | $settings_field_display->display();
261 | }
262 |
263 | /**
264 | * Filter the settings field and return sort by sort_order key.
265 | *
266 | * @param array $settings
267 | * @return array
268 | */
269 | public function filter_multisafepay_common_settings_fields( array $settings ): array {
270 | foreach ( $settings as $key => $section ) {
271 | $sort_order = array();
272 | foreach ( $section['fields'] as $field_key => $field ) {
273 | $sort_order[ $field_key ] = $field['sort_order'];
274 | }
275 | array_multisort( $sort_order, SORT_ASC, $settings[ $key ]['fields'] );
276 | }
277 | return $settings;
278 | }
279 | }
280 |
--------------------------------------------------------------------------------
/src/Settings/SettingsFieldsDisplay.php:
--------------------------------------------------------------------------------
1 | field = $field;
28 | }
29 |
30 | /**
31 | * Get the value by setting field
32 | *
33 | * @see https://developer.wordpress.org/reference/functions/get_option/
34 | * @param array $field
35 | * @return mixed
36 | */
37 | private function get_option_by_field( array $field ) {
38 | $value = get_option( $field['id'], false );
39 | if ( ! $value ) {
40 | return $field['default'];
41 | }
42 | return esc_html( $value );
43 | }
44 |
45 | /**
46 | * Render the html for a text type input
47 | *
48 | * @param array $field
49 | * @return string
50 | */
51 | private function render_text_field( array $field ): string {
52 | $value = $this->get_option_by_field( $field );
53 | $field_id = esc_attr( $field['id'] );
54 | $placeholder = esc_attr( $field['placeholder'] );
55 | $html = '';
56 | if ( ! empty( $field['description'] ) ) {
57 | $html .= '' . $field['description'] . '
';
58 | }
59 | return $html;
60 | }
61 |
62 | /**
63 | * Render the html for a select type input
64 | *
65 | * @param array $field
66 | * @return string
67 | */
68 | private function render_select_field( array $field ): string {
69 | $value = $this->get_option_by_field( $field );
70 | $field_id = esc_attr( $field['id'] );
71 | $html = ' ';
81 | if ( ! empty( $field['description'] ) ) {
82 | $html .= '' . $field['description'] . '
';
83 | }
84 | return $html;
85 | }
86 |
87 | /**
88 | * Render the html for a checkbox type input
89 | *
90 | * @param array $field
91 | * @return string
92 | */
93 | private function render_checkbox_field( array $field ): string {
94 | $checked = ( $this->get_option_by_field( $field ) ) ? 'checked="checked"' : '';
95 | $field_id = esc_attr( $field['id'] );
96 | $placeholder = esc_attr( $field['placeholder'] );
97 | $html = '';
98 | if ( ! empty( $field['description'] ) ) {
99 | $html .= '' . $field['description'] . '
';
100 | }
101 | return $html;
102 | }
103 |
104 | /**
105 | * Render the html for a password type input
106 | *
107 | * @param array $field
108 | * @return string
109 | */
110 | private function render_password_field( array $field ): string {
111 | $value = $this->get_option_by_field( $field );
112 | $field_id = esc_attr( $field['id'] );
113 | $placeholder = esc_attr( $field['placeholder'] );
114 | $html = '';
115 | if ( ! empty( $field['description'] ) ) {
116 | $html .= '' . $field['description'] . '
';
117 | }
118 | return $html;
119 | }
120 |
121 | /**
122 | * Render the html for each type of the registered setting field
123 | *
124 | * @return void
125 | */
126 | public function display(): void {
127 | $html = '';
128 | switch ( $this->field['type'] ) {
129 | case 'text':
130 | $html .= $this->render_text_field( $this->field );
131 | break;
132 | case 'select':
133 | $html .= $this->render_select_field( $this->field );
134 | break;
135 | case 'checkbox':
136 | $html .= $this->render_checkbox_field( $this->field );
137 | break;
138 | case 'password':
139 | $html .= $this->render_password_field( $this->field );
140 | break;
141 | }
142 |
143 | echo wp_kses( $html, EscapeUtil::get_allowed_html_tags() );
144 | }
145 |
146 | }
147 |
--------------------------------------------------------------------------------
/src/Settings/StatusController.php:
--------------------------------------------------------------------------------
1 | get_multisafepay_system_status_report();
20 | $plain_text_status_report = $system_report->get_plain_text_system_status_report();
21 | require_once MULTISAFEPAY_PLUGIN_DIR_PATH . 'templates/partials/multisafepay-settings-status-display.php';
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Settings/ThirdPartyCompatibility.php:
--------------------------------------------------------------------------------
1 | declare_hpos_compatibility();
20 | $this->declare_blocks_compatibility();
21 | }
22 |
23 | /**
24 | * Declare compatibility with high performance order storage features
25 | *
26 | * @return void
27 | */
28 | public function declare_hpos_compatibility(): void {
29 | if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
30 | \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
31 | 'custom_order_tables',
32 | MULTISAFEPAY_PLUGIN_DIR_PATH . 'multisafepay.php'
33 | );
34 | }
35 | }
36 |
37 | /**
38 | * Declare compatibility with block-based checkout features
39 | *
40 | * @return void
41 | */
42 | public function declare_blocks_compatibility(): void {
43 | if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
44 | \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
45 | 'cart_checkout_blocks',
46 | MULTISAFEPAY_PLUGIN_DIR_PATH . 'multisafepay.php'
47 | );
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Settings/index.php:
--------------------------------------------------------------------------------
1 | activate_plugin_single_site();
26 | }
27 | if ( $network_wide ) {
28 | $this->activate_plugin_all_sites();
29 | }
30 | }
31 |
32 | /**
33 | * Check if dependencies are not active and return fatal error
34 | * for a single site.
35 | *
36 | * @return void
37 | */
38 | private function activate_plugin_single_site(): void {
39 | try {
40 | $dependency_checker = new DependencyChecker();
41 | $dependency_checker->check();
42 | } catch ( MissingDependencyException $missing_dependency_exception ) {
43 | $dependencies = implode( ', ', $missing_dependency_exception->get_missing_plugin_names() );
44 | $message = sprintf( __( 'Missing dependencies: %s. Please install these extensions to use the MultiSafepay WooCommerce plugin', 'multisafepay' ), $dependencies ); // phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment
45 | die( esc_html( $message ) );
46 | }
47 | }
48 |
49 | /**
50 | * Check if dependencies are not active and return fatal error
51 | * for a network.
52 | *
53 | * @return void
54 | */
55 | private function activate_plugin_all_sites(): void {
56 | $blog_ids = $this->get_blogs_ids();
57 | foreach ( $blog_ids as $blog_id ) {
58 | switch_to_blog( $blog_id );
59 | $this->activate_plugin_single_site();
60 | restore_current_blog();
61 | }
62 | }
63 |
64 | /**
65 | * Return all sites ids
66 | *
67 | * @return array
68 | */
69 | private function get_blogs_ids(): array {
70 | $args = array(
71 | 'fields' => 'ids',
72 | );
73 | $blogs_ids = get_sites( $args );
74 | return $blogs_ids;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Utils/CustomLinks.php:
--------------------------------------------------------------------------------
1 | ' . __( 'Settings', 'multisafepay' ) . '',
21 | '' . __( 'Docs & Support', 'multisafepay' ) . '',
22 | );
23 | return array_merge( $custom_links, $links );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Utils/DependencyChecker.php:
--------------------------------------------------------------------------------
1 | 'woocommerce/woocommerce.php',
15 | );
16 |
17 | /**
18 | * Check if there is a dependency missing to make the plugin work
19 | *
20 | * @throws MissingDependencyException
21 | * @return void
22 | */
23 | public function check(): void {
24 | $missing_plugins = $this->get_missing_plugins_list();
25 | if ( ! empty( $missing_plugins ) ) {
26 | throw new MissingDependencyException( $missing_plugins );
27 | }
28 | }
29 |
30 | /**
31 | * Return the keys of all missing plugins
32 | *
33 | * @return array
34 | */
35 | private function get_missing_plugins_list(): array {
36 | return array_keys( array_filter( self::REQUIRED_PLUGINS, array( $this, 'is_plugin_inactive' ) ) );
37 | }
38 |
39 | /**
40 | * Check if a certain plugin is inactive
41 | *
42 | * @param string $plugin_path
43 | * @return boolean
44 | */
45 | private function is_plugin_inactive( string $plugin_path ): bool {
46 | if ( ! is_plugin_active( $plugin_path ) ) {
47 | return true;
48 | }
49 |
50 | return false;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Utils/EscapeUtil.php:
--------------------------------------------------------------------------------
1 | array(
20 | 'name' => array(),
21 | 'id' => array(),
22 | 'type' => array(),
23 | 'placeholder' => array(),
24 | 'value' => array(),
25 | 'checked' => true,
26 | ),
27 | 'p' => array(
28 | 'class' => array(),
29 | ),
30 | 'select' => array(
31 | 'name' => array(),
32 | 'id' => array(),
33 | ),
34 | 'option' => array(
35 | 'value' => array(),
36 | 'selected' => true,
37 | ),
38 | );
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Utils/Hpos.php:
--------------------------------------------------------------------------------
1 | version ) ) {
29 | return WC()->version;
30 | }
31 | return null;
32 | }
33 |
34 | /**
35 | * Check if the WooCommerce version is compatible with HPOS
36 | *
37 | * @return bool
38 | */
39 | public static function is_active(): bool {
40 | return ( 'yes' === get_option( 'woocommerce_custom_orders_table_enabled', 'no' ) ) &&
41 | version_compare( self::get_woocommerce_version(), self::HPOS_RELEASE_VERSION, '>=' );
42 | }
43 |
44 | /**
45 | * @param WC_Order $order
46 | * @param string $key
47 | * @param string $value
48 | *
49 | * @return bool|int
50 | */
51 | public static function update_meta( WC_Order $order, string $key, string $value ) {
52 | if ( self::is_active() ) {
53 | $order->update_meta_data( $key, $value );
54 | return $order->save();
55 | }
56 |
57 | return update_post_meta( $order->get_id(), $key, $value );
58 | }
59 |
60 | /**
61 | * Get the order meta-data
62 | *
63 | * @param WC_Order $order
64 | * @param string $key
65 | * @param bool $single
66 | *
67 | * @return mixed
68 | */
69 | public static function get_meta( WC_Order $order, string $key, bool $single = true ) {
70 | if ( self::is_active() ) {
71 | return $order->get_meta( $key );
72 | }
73 |
74 | return get_post_meta( $order->get_id(), $key, $single );
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Utils/Internationalization.php:
--------------------------------------------------------------------------------
1 | actions = array();
29 | $this->filters = array();
30 | }
31 |
32 | /**
33 | * Add a new action to the collection to be registered with WordPress.
34 | *
35 | * @param string $hook The name of the WordPress action that is being registered.
36 | * @param object $component A reference to the instance of the object on which the action is defined.
37 | * @param string $callback The name of the function defined on the $component.
38 | * @param int $priority The priority at which the function should be fired.
39 | * @param int $accepted_args The number of arguments that should be passed to the $callback.
40 | * @return void
41 | */
42 | public function add_action( string $hook, $component, string $callback, int $priority = 10, int $accepted_args = 1 ): void {
43 | $this->actions = $this->add( $this->actions, $hook, $component, $callback, $priority, $accepted_args );
44 | }
45 |
46 | /**
47 | * Add a new filter to the collection to be registered with WordPress.
48 | *
49 | * @param string $hook The name of the WordPress filter that is being registered.
50 | * @param object $component A reference to the instance of the object on which the filter is defined.
51 | * @param string $callback The name of the function defined on the $component.
52 | * @param int $priority The priority at which the function should be fired.
53 | * @param int $accepted_args The number of arguments that should be passed to the $callback.
54 | * @return void
55 | */
56 | public function add_filter( string $hook, $component, string $callback, int $priority = 10, int $accepted_args = 1 ) {
57 | $this->filters = $this->add( $this->filters, $hook, $component, $callback, $priority, $accepted_args );
58 | }
59 |
60 | /**
61 | * A utility function that is used to register the actions and hooks into a single
62 | * collection.
63 | *
64 | * @param array $hooks The collection of hooks that is being registered (that is, actions or filters).
65 | * @param string $hook The name of the WordPress filter that is being registered.
66 | * @param object $component A reference to the instance of the object on which the filter is defined.
67 | * @param string $callback The name of the function definition on the $component.
68 | * @param int $priority The priority at which the function should be fired.
69 | * @param int $accepted_args The number of arguments that should be passed to the $callback.
70 | * @return array The collection of actions and filters registered with WordPress.
71 | */
72 | private function add( array $hooks, string $hook, $component, string $callback, int $priority, int $accepted_args ) {
73 | $hooks[] = array(
74 | 'hook' => $hook,
75 | 'component' => $component,
76 | 'callback' => $callback,
77 | 'priority' => $priority,
78 | 'accepted_args' => $accepted_args,
79 | );
80 | return $hooks;
81 | }
82 |
83 | /**
84 | * Register the filters and actions with WordPress.
85 | *
86 | * @return void
87 | */
88 | public function init() {
89 | foreach ( $this->filters as $hook ) {
90 | add_filter( $hook['hook'], array( $hook['component'], $hook['callback'] ), $hook['priority'], $hook['accepted_args'] );
91 | }
92 | foreach ( $this->actions as $hook ) {
93 | add_action( $hook['hook'], array( $hook['component'], $hook['callback'] ), $hook['priority'], $hook['accepted_args'] );
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/Utils/Logger.php:
--------------------------------------------------------------------------------
1 | logger = $logger ?? wc_get_logger();
23 | }
24 |
25 | /**
26 | * Log method for emergency level
27 | * System is unusable.
28 | * Example:
29 | *
30 | * @param string $message
31 | */
32 | public function log_emergency( string $message ) {
33 | $this->logger->log( 'emergency', $message, array( 'source' => 'multisafepay' ) );
34 | }
35 |
36 | /**
37 | * Log method for alert level
38 | * Action must be taken immediately.
39 | * Example: Entire website down, database unavailable, etc.
40 | *
41 | * @param string $message
42 | */
43 | public function log_alert( string $message ) {
44 | $this->logger->log( 'alert', $message, array( 'source' => 'multisafepay' ) );
45 | }
46 |
47 | /**
48 | * Log method for critical level
49 | * Critical conditions.
50 | * Example: Unexpected exceptions.
51 | *
52 | * @param string $message
53 | */
54 | public function log_critical( string $message ) {
55 | $this->logger->log( 'critical', $message, array( 'source' => 'multisafepay' ) );
56 | }
57 |
58 | /**
59 | * Log method for error level
60 | * Error conditions
61 | * Example: Set to shipped or invoiced an order, which is on initialized status
62 | *
63 | * @param string $message
64 | */
65 | public function log_error( string $message ) {
66 | $this->logger->log( 'error', $message, array( 'source' => 'multisafepay' ) );
67 | }
68 |
69 | /**
70 | * Log method for warning level
71 | * Exceptional occurrences that are not errors because do not lead to a complete failure of the application.
72 | * Example: Entire website down, database unavailable, etc.
73 | *
74 | * @param string $message
75 | */
76 | public function log_warning( string $message ) {
77 | $this->logger->log( 'warning', $message, array( 'source' => 'multisafepay' ) );
78 | }
79 |
80 | /**
81 | * Log method for notice level
82 | * Normal but significant events.
83 | * Example: A notification has been processed using a payment method different than the one registered when the order was created.
84 | *
85 | * @param string $message
86 | */
87 | public function log_notice( string $message ) {
88 | $this->logger->log( 'notice', $message, array( 'source' => 'multisafepay' ) );
89 | }
90 |
91 | /**
92 | * Log method for info level
93 | * Interesting events.
94 | * Example: The payment link of a transaction
95 | *
96 | * @param string $message
97 | */
98 | public function log_info( string $message ) {
99 | if ( get_option( 'multisafepay_debugmode', false ) ) {
100 | $this->logger->log( 'info', $message, array( 'source' => 'multisafepay' ) );
101 | }
102 | }
103 |
104 | /**
105 | * Log method for debug level
106 | * Detailed debug information: Denotes specific and detailed information of every action.
107 | * Example: The trace of every action registered in the system.
108 | *
109 | * @param string $message
110 | */
111 | public function log_debug( string $message ) {
112 | if ( get_option( 'multisafepay_debugmode', false ) ) {
113 | $this->logger->log( 'debug', $message, array( 'source' => 'multisafepay' ) );
114 | }
115 | }
116 |
117 | /**
118 | * Return an array of logs filenames
119 | *
120 | * @return array
121 | */
122 | private function get_logs(): array {
123 | return WC_Log_Handler_File::get_log_files();
124 | }
125 |
126 | /**
127 | * Return an array of logs that belongs to MultiSafepay
128 | *
129 | * @return array
130 | */
131 | public function get_multisafepay_logs(): array {
132 | $logs = $this->get_logs();
133 | foreach ( $logs as $key => $log ) {
134 | if ( strpos( $log, 'multisafepay' ) === false ) {
135 | unset( $logs[ $key ] );
136 | }
137 | }
138 | return $logs;
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/Utils/MoneyUtil.php:
--------------------------------------------------------------------------------
1 | get_payment_method(), 'multisafepay_' ) !== false ) {
20 | return true;
21 | }
22 |
23 | return false;
24 | }
25 |
26 | /**
27 | * Add a note to the order
28 | *
29 | * @param WC_Order $order
30 | * @param string $message
31 | * @param bool $on_debug
32 | * @return void
33 | */
34 | public static function add_order_note( WC_Order $order, string $message, bool $on_debug = false ): void {
35 | if ( $on_debug && ! get_option( 'multisafepay_debugmode', false ) ) {
36 | return;
37 | }
38 |
39 | $order->add_order_note( $message );
40 | }
41 |
42 | /**
43 | * Return WooCommerce Order ID where meta value key is 'multisafepay_transaction_id' and value is $order_id
44 | *
45 | * @param string $order_id
46 | * @return false|mixed|\WC_Order
47 | *
48 | * @phpcs:disable WordPress.DB.SlowDBQuery
49 | */
50 | public static function get_order_id_by_multisafepay_transaction_id_key( string $order_id ) {
51 | $orders = wc_get_orders(
52 | array(
53 | 'limit' => 1,
54 | 'meta_key' => 'multisafepay_transaction_id',
55 | 'meta_value' => $order_id,
56 | 'return' => 'ids',
57 | )
58 | );
59 | return ! empty( $orders ) ? $orders[0] : false;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Utils/QrOrder.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | description ) { ?>
4 | description ); ?>
5 |
6 |
7 | payment_component ) { ?>
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/templates/multisafepay-settings-display.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | ';
20 | echo '
' . esc_html__( 'Order Status', 'multisafepay' ) . '
';
21 | echo '
';
26 | echo '
';
27 | break;
28 | case 'options':
29 | echo '';
30 | echo '
' . esc_html__( 'Options', 'multisafepay' ) . '
';
31 | echo '';
36 | echo '';
37 | break;
38 | case 'logs':
39 | $this->display_multisafepay_logs_section();
40 | break;
41 | case 'support':
42 | $this->display_multisafepay_support_section();
43 | break;
44 | case 'status':
45 | $this->display_multisafepay_status_section();
46 | break;
47 | case 'general':
48 | default:
49 | echo '';
50 | echo '
' . esc_html__( 'Account', 'multisafepay' ) . '
';
51 | echo '';
56 | echo '';
57 | break;
58 | }
59 | ?>
60 |
61 |
--------------------------------------------------------------------------------
/templates/partials/multisafepay-settings-logs-display.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
29 |
30 |
31 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/templates/partials/multisafepay-settings-status-display.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | |
20 |
21 |
22 |
23 | $value ) { ?>
24 |
25 |
26 | :
27 | |
28 |
29 |
30 | |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/templates/partials/multisafepay-settings-support-display.php:
--------------------------------------------------------------------------------
1 |
2 |
89 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require( 'path' );
2 | const defaultConfig = require( '@wordpress/scripts/config/webpack.config' );
3 |
4 | module.exports = {
5 | ...defaultConfig,
6 | entry: {
7 | 'index': path.resolve( __dirname, 'assets/public/js/multisafepay-blocks/src/index.js' ),
8 | },
9 | output: {
10 | path: path.resolve( __dirname, 'assets/public/js/multisafepay-blocks/build' ),
11 | filename: '[name].js',
12 | },
13 | };
14 |
--------------------------------------------------------------------------------