zotapay / zota-for-woocommerce

@@ -113,14 +113,18 @@
Loading
113 113
	// Enqueue scripts.
114 114
	add_action( 'admin_enqueue_scripts', 'zota_admin_enqueue_scripts' );
115 115
116 -
	// WooCommerce settings tab.
116 +
	// Hook filters and actions.
117 +
	add_filter( 'woocommerce_register_shop_order_post_statuses', array( '\Zota\Zota_WooCommerce\Includes\Order', 'register_shop_order_post_statuses' ) );
118 +
	add_filter( 'woocommerce_valid_order_statuses_for_payment_complete', array( '\Zota\Zota_WooCommerce\Includes\Order', 'valid_order_statuses_for_payment_complete' ) );
119 +
	add_filter( 'wc_order_statuses', array( '\Zota\Zota_WooCommerce\Includes\Order', 'order_statuses' ) );
117 120
	add_filter( 'woocommerce_settings_tabs_array', array( '\Zota\Zota_WooCommerce\Includes\Settings', 'settings_tab' ), 50 );
118 121
	add_action( 'woocommerce_settings_tabs_' . ZOTA_WC_PLUGIN_ID, array( '\Zota\Zota_WooCommerce\Includes\Settings', 'settings_show' ) );
119 122
	add_action( 'woocommerce_update_options_' . ZOTA_WC_PLUGIN_ID, array( '\Zota\Zota_WooCommerce\Includes\Settings', 'settings_update' ) );
120 123
	add_action( 'woocommerce_save_settings_' . ZOTA_WC_PLUGIN_ID, array( '\Zota\Zota_WooCommerce\Includes\Settings', 'save_settings' ) );
121 -
	add_action( 'woocommerce_admin_field_icon', array( '\Zota\Zota_WooCommerce\Includes\Settings', 'field_icon' ), 10, 1 );
122 -
	add_action( 'woocommerce_admin_field_remove_payment_method', array( '\Zota\Zota_WooCommerce\Includes\Settings', 'field_remove_payment_method' ), 10, 1 );
124 +
	add_action( 'woocommerce_admin_field_icon', array( '\Zota\Zota_WooCommerce\Includes\Settings', 'field_icon' ) );
125 +
	add_action( 'woocommerce_admin_field_remove_payment_method', array( '\Zota\Zota_WooCommerce\Includes\Settings', 'field_remove_payment_method' ) );
123 126
	add_action( 'wp_ajax_add_payment_method', array( '\Zota\Zota_WooCommerce\Includes\Settings', 'add_payment_method' ) );
127 +
	add_action( 'woocommerce_admin_order_totals_after_total', array( '\Zota\Zota_WooCommerce\Includes\Order', 'add_total_row' ) );
124 128
125 129
	// Initialize.
126 130
	require_once ZOTA_WC_PATH . '/includes/class-zota-woocommerce.php';
@@ -143,14 +147,16 @@
Loading
143 147
	);
144 148
145 149
	// Settings shortcut on plugins page.
146 -
	add_filter( 'plugin_action_links_zota-for-woocommerce/zota-for-woocommerce.php', 'wc_gateway_zota_settings_button', 10, 1 );
150 +
	add_filter( 'plugin_action_links_zota-for-woocommerce/zota-for-woocommerce.php', 'wc_gateway_zota_settings_button' );
147 151
148 152
	// Add column OrderID on order list.
149 -
	add_filter( 'manage_edit-shop_order_columns', array( '\Zota\Zota_WooCommerce\Includes\Order', 'admin_columns' ), 10, 1 );
153 +
	add_filter( 'manage_edit-shop_order_columns', array( '\Zota\Zota_WooCommerce\Includes\Order', 'admin_columns' ) );
150 154
	add_action( 'manage_shop_order_posts_custom_column', array( '\Zota\Zota_WooCommerce\Includes\Order', 'admin_column_order_id' ), 10, 2 );
151 155
152 156
	// Scheduled check for pending payments.
153 -
	add_action( 'zota_scheduled_order_status', array( '\Zota\Zota_WooCommerce\Includes\Order', 'check_status' ), 10, 1 );
157 +
	add_action( 'zota_scheduled_order_status', array( '\Zota\Zota_WooCommerce\Includes\Order', 'check_status' ) );
158 +
	add_action( 'woocommerce_order_status_cancelled', array( '\Zota\Zota_WooCommerce\Includes\Order', 'delete_expiration_time' ) );
159 +
	add_action( 'woocommerce_order_status_cancelled', array( '\Zota\Zota_WooCommerce\Includes\Order', 'set_expired' ) );
154 160
}
155 161
156 162
/**

@@ -194,6 +194,9 @@
Loading
194 194
195 195
		// Traits.
196 196
		require_once $this->tests_dir . '/framework/traits/trait-wc-rest-api-complex-meta.php';
197 +
198 +
		// Tests helper
199 +
		require_once __DIR__ . '/class-tests-helper.php';
197 200
	}
198 201
199 202
	/**

@@ -0,0 +1,106 @@
Loading
1 +
<?php
2 +
3 +
/**
4 +
 * @package Zota_Woocommerce
5 +
 */
6 +
7 +
/**
8 +
 * Class Tests_Helper.
9 +
 */
10 +
class Tests_Helper {
11 +
12 +
	/**
13 +
	 * Enable payment gateways.
14 +
	 *
15 +
	 * @param  string $payment_method_id WC Payment method id.
16 +
	 * @param  string $currency       Shop currency.
17 +
	 * @param  array  $settings       ZotaPay general settings.
18 +
	 * @param  array  $payment_method Payment method settings.
19 +
	 */
20 +
	public static function setUp( $payment_method_id, $currency = 'USD', $settings = array(), $payment_method = array() ) {
21 +
		update_option( 'woocommerce_currency', $currency );
22 +
23 +
		$gateway_settings = [
24 +
			'testmode' => isset( $settings['testmode'] ) ? $settings['testmode'] : 'yes',
25 +
			'test_merchant_id' => isset( $settings['test_merchant_id'] ) ? $settings['test_merchant_id'] : 'dummy_merchant_id',
26 +
			'test_merchant_secret_key' => isset( $settings['test_merchant_secret_key'] ) ? $settings['test_merchant_secret_key'] : 'dummy_merchant_secret_key',
27 +
			'logging' => isset( $settings['logging'] ) ? $settings['logging'] : 'no',
28 +
		];
29 +
30 +
		$payment_methods = [ $payment_method_id ];
31 +
		$payment_method_settings = [
32 +
			'enabled' => isset( $payment_method['enabled'] ) ? $payment_method['enabled'] : 'yes',
33 +
			'title' => isset( $payment_method['title'] ) ? $payment_method['title'] : 'Credit Card (Zota)',
34 +
			'description' => isset( $payment_method['description'] ) ? $payment_method['description'] : 'Pay with your credit card via Zota.',
35 +
			'test_endpoint' => isset( $payment_method['test_endpoint'] ) ? $payment_method['test_endpoint'] : 'dummy_endpoint',
36 +
			'endpoint' => isset( $payment_method['endpoint'] ) ? $payment_method['endpoint'] : 'dummy_endpoint',
37 +
			'icon' => isset( $payment_method['icon'] ) ? $payment_method['icon'] : '',
38 +
		];
39 +
40 +
		update_option( 'woocommerce_' . ZOTA_WC_GATEWAY_ID . '_settings', $gateway_settings );
41 +
		update_option( 'woocommerce_' . $payment_method_id . '_settings', $payment_method_settings, true );
42 +
		update_option( 'zotapay_payment_methods', $payment_methods, false );
43 +
44 +
		WC()->session = null;
45 +
46 +
		$wc_payment_gateways = WC_Payment_Gateways::instance();
47 +
		$wc_payment_gateways->init();
48 +
	}
49 +
50 +
51 +
	public static function create_order( $product, $qty, $gateway ) {
52 +
		WC_Helper_Shipping::create_simple_flat_rate();
53 +
54 +
		$order_data = array(
55 +
			'status'        => 'pending',
56 +
			'customer_id'   => 1,
57 +
			'customer_note' => '',
58 +
			'total'         => '',
59 +
		);
60 +
61 +
		$_SERVER['REMOTE_ADDR'] = '127.0.0.1'; // Required, else wc_create_order throws an exception.
62 +
		$order                  = wc_create_order( $order_data );
63 +
64 +
		// Add order products.
65 +
		$item = new WC_Order_Item_Product();
66 +
		$item->set_props(
67 +
			array(
68 +
				'product'  => $product,
69 +
				'quantity' => 1,
70 +
				'subtotal' => wc_get_price_excluding_tax( $product, array( 'qty' => $qty ) ),
71 +
				'total'    => wc_get_price_excluding_tax( $product, array( 'qty' => $qty ) ),
72 +
			)
73 +
		);
74 +
		$item->save();
75 +
		$order->add_item( $item );
76 +
77 +
		// Set billing address.
78 +
		$order->set_billing_first_name( 'Jeroen' );
79 +
		$order->set_billing_last_name( 'Sormani' );
80 +
		$order->set_billing_company( 'WooCompany' );
81 +
		$order->set_billing_address_1( 'WooAddress' );
82 +
		$order->set_billing_address_2( '' );
83 +
		$order->set_billing_city( 'WooCity' );
84 +
		$order->set_billing_state( 'NY' );
85 +
		$order->set_billing_postcode( '12345' );
86 +
		$order->set_billing_country( 'US' );
87 +
		$order->set_billing_email( 'admin@example.org' );
88 +
		$order->set_billing_phone( '555-32123' );
89 +
90 +
		// Set payment gateway.
91 +
		$payment_gateways = WC()->payment_gateways->payment_gateways();
92 +
93 +
		$order->set_payment_method( $payment_gateways[ $gateway ] );
94 +
95 +
		// Set totals.
96 +
		$order->set_shipping_total( 0 );
97 +
		$order->set_discount_total( 0 );
98 +
		$order->set_discount_tax( 0 );
99 +
		$order->set_cart_tax( 0 );
100 +
		$order->set_shipping_tax( 0 );
101 +
		$order->set_total( wc_get_price_excluding_tax( $product, array( 'qty' => $qty ) ) );
102 +
		$order->save();
103 +
104 +
		return $order;
105 +
	}
106 +
}

@@ -15,40 +15,8 @@
Loading
15 15
	 */
16 16
	public function setUp() {
17 17
		parent::setUp();
18 -
19 -
		/**
20 -
		 * TODO: We should probably move these in a global helper / setUp method.
21 -
		 */
22 -
		update_option( 'woocommerce_currency', 'USD' );
23 -
24 -
		$settings = [
25 -
			'testmode' => 'yes',
26 -
			'test_merchant_id' => 'dummy_merchant_id',
27 -
			'test_merchant_secret_key' => 'dummy_merchant_secret_key',
28 -
			'logging' => 'no',
29 -
		];
30 -
31 -
		$payment_method_id = ZOTA_WC_GATEWAY_ID . '_' . uniqid();
32 -
		$payment_methods = [ $payment_method_id ];
33 -
		$payment_method_settings = [
34 -
			'enabled' => 'yes',
35 -
			'title' => 'Credit Card (Zota)',
36 -
			'description' => 'Pay with your credit card via Zota.',
37 -
			'test_endpoint' => 'dummy_endpoint',
38 -
			'endpoint' => 'dummy_endpoint',
39 -
			'icon' => '',
40 -
		];
41 -
42 -
		update_option( 'woocommerce_' . ZOTA_WC_GATEWAY_ID . '_settings', $settings );
43 -
		update_option( 'woocommerce_' . $payment_method_id . '_settings', $payment_method_settings, true );
44 -
		update_option( 'zotapay_payment_methods', $payment_methods, false );
45 -
46 -
		$this->payment_method = $payment_method_id;
47 -
48 -
		WC()->session = null;
49 -
50 -
		$wc_payment_gateways = WC_Payment_Gateways::instance();
51 -
		$wc_payment_gateways->init();
18 +
		$this->payment_method = ZOTA_WC_GATEWAY_ID . '_' . uniqid();
19 +
		Tests_Helper::setUp( $this->payment_method );
52 20
	}
53 21
54 22
	/**

@@ -33,6 +33,70 @@
Loading
33 33
	public static $requiring_states = array( 'AU', 'CA', 'US' );
34 34
35 35
36 +
	/**
37 +
	 * Register additional order statuses.
38 +
	 *
39 +
	 * @param  array $order_statuses WC Order statuses.
40 +
	 * @return array
41 +
	 */
42 +
	public static function register_shop_order_post_statuses( $order_statuses ) {
43 +
		$zota_order_statuses = array(
44 +
			'wc-partial-payment' => array(
45 +
				'label'                     => _x( 'Partial Payment', 'Order status', 'zota-woocommerce' ),
46 +
				'public'                    => false,
47 +
				'exclude_from_search'       => false,
48 +
				'show_in_admin_all_list'    => true,
49 +
				'show_in_admin_status_list' => true,
50 +
				/* translators: %s: number of orders */
51 +
				'label_count'               => _n_noop( 'Partial Payment <span class="count">(%s)</span>', 'Partial Payment <span class="count">(%s)</span>', 'zota-woocommerce' ),
52 +
			),
53 +
			'wc-overpayment'    => array(
54 +
				'label'                     => _x( 'Overpayment', 'Order status', 'zota-woocommerce' ),
55 +
				'public'                    => false,
56 +
				'exclude_from_search'       => false,
57 +
				'show_in_admin_all_list'    => true,
58 +
				'show_in_admin_status_list' => true,
59 +
				/* translators: %s: number of orders */
60 +
				'label_count'               => _n_noop( 'Overpayment <span class="count">(%s)</span>', 'Overpayment <span class="count">(%s)</span>', 'zota-woocommerce' ),
61 +
			),
62 +
		);
63 +
64 +
		return apply_filters( 'wc_gateway_zota_register_shop_order_post_statuses', array_merge( $order_statuses, $zota_order_statuses ) );
65 +
	}
66 +
67 +
68 +
	/**
69 +
	 * Register additional order statuses.
70 +
	 *
71 +
	 * @param  array $order_statuses WC Order statuses.
72 +
	 * @return array
73 +
	 */
74 +
	public static function valid_order_statuses_for_payment_complete( $order_statuses ) {
75 +
		$zota_order_statuses = array(
76 +
			'partial-payment',
77 +
			'overpayment',
78 +
		);
79 +
80 +
		return array_merge( $order_statuses, $zota_order_statuses );
81 +
	}
82 +
83 +
84 +
	/**
85 +
	 * Add to list of WC Order statuses.
86 +
	 *
87 +
	 * @param  array $order_statuses WC Order statuses.
88 +
	 * @return array
89 +
	 */
90 +
	public static function order_statuses( $order_statuses ) {
91 +
		$zota_order_statuses = array(
92 +
			'wc-partial-payment' => _x( 'Partial Payment', 'Order status', 'zota-woocommerce' ),
93 +
			'wc-overpayment' => _x( 'Overpayment', 'Order status', 'zota-woocommerce' ),
94 +
		);
95 +
96 +
		return array_merge( $order_statuses, $zota_order_statuses );
97 +
	}
98 +
99 +
36 100
	/**
37 101
	 * Prepare customer state.
38 102
	 *
@@ -200,43 +264,265 @@
Loading
200 264
201 265
202 266
	/**
203 -
	 * Process order status response.
267 +
	 * Get order extra data.
204 268
	 *
205 -
	 * @param  int                  $order_id Order ID.
206 -
	 * @param  \Zotapay\ApiResponse $response Response Status.
269 +
	 * @param  \Zotapay\ApiCallback $callback API Response.
270 +
	 * @return array|false
271 +
	 */
272 +
	public static function get_extra_data( $callback ) {
273 +
		// Check extra data.
274 +
		if ( empty( $callback->getExtraData() ) ) {
275 +
			return false;
276 +
		}
277 +
278 +
		return $callback->getExtraData();
279 +
	}
280 +
281 +
282 +
	/**
283 +
	 * Is order amount changed.
284 +
	 *
285 +
	 * @param  \Zotapay\ApiCallback $callback API Response.
286 +
	 * @return array|false
287 +
	 */
288 +
	public static function amount_changed( $callback ) {
289 +
		$extra_data = self::get_extra_data( $callback );
290 +
291 +
		// If no extra data return.
292 +
		if ( empty( $extra_data ) ) {
293 +
			return false;
294 +
		}
295 +
296 +
		// Check if has amount changed key.
297 +
		if ( empty( $extra_data['amountChanged'] ) ) {
298 +
			return false;
299 +
		}
300 +
301 +
		return $extra_data['amountChanged'];
302 +
	}
303 +
304 +
305 +
	/**
306 +
	 * Add totals row for paid amount.
307 +
	 *
308 +
	 * @param int $order_id WC Order ID.
207 309
	 * @return bool
208 310
	 */
209 -
	public static function update_status( $order_id, $response ) {
311 +
	public static function add_total_row( $order_id ) {
210 312
		// Get the order.
211 313
		$order = wc_get_order( $order_id );
212 314
		if ( empty( $order ) ) {
213 315
			$error = sprintf(
214 316
				// translators: %s WC Order ID.
215 -
				esc_html__( 'Update status WC Order #%s not found.', 'zota-woocommerce' ),
317 +
				esc_html__( 'Add totals row WC Order #%s not found.', 'zota-woocommerce' ),
216 318
				(int) $order_id
217 319
			);
218 320
			Zotapay::getLogger()->error( $error );
219 321
			return false;
220 322
		}
221 323
222 -
		// Check response.
223 -
		if ( empty( $response ) ) {
324 +
		// Check order status.
325 +
		if ( ! in_array( $order->get_status(), array( 'partial-payment', 'overpayment' ), true ) ) {
326 +
			return;
327 +
		}
328 +
329 +
		// Set totals row label.
330 +
		$label = 'partial-payment' === $order->get_status() ? __( 'Partial Payment', 'zota-woocommerce' ) : __( 'Overpayment', 'zota-woocommerce' );
331 +
332 +
		// Amount changed.
333 +
		$zotapay_amount = $order->get_meta( '_zotapay_amount', true );
334 +
		?>
335 +
		<table class="wc-order-totals" style="border-top: 1px solid #999; margin-top:12px; padding-top:12px">
336 +
			<tr>
337 +
				<td class="label label-highlight"><?php echo esc_html( $label ); ?>: <br /></td>
338 +
				<td width="1%"></td>
339 +
				<td class="total">
340 +
					<?php echo wc_price( \floatval( $zotapay_amount ), array( 'currency' => $order->get_currency() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
341 +
				</td>
342 +
			</tr>
343 +
			<tr>
344 +
				<td>
345 +
					<span class="description">
346 +
					<?php
347 +
					if ( ! empty( $order->get_date_paid() ) ) {
348 +
						if ( $order->get_payment_method_title() ) {
349 +
							/* translators: 1: payment date. 2: payment method */
350 +
							echo esc_html( sprintf( __( '%1$s via %2$s', 'zota-woocommerce' ), $order->get_date_paid()->date_i18n( get_option( 'date_format' ) ), $order->get_payment_method_title() ) );
351 +
						} else {
352 +
							echo esc_html( $order->get_date_paid()->date_i18n( get_option( 'date_format' ) ) );
353 +
						}
354 +
					}
355 +
					?>
356 +
					</span>
357 +
				</td>
358 +
				<td colspan="2"></td>
359 +
			</tr>
360 +
		</table>
361 +
362 +
		<div class="clear"></div>
363 +
		<?php
364 +
	}
365 +
366 +
367 +
	/**
368 +
	 * Handle callback.
369 +
	 *
370 +
	 * @param  int                  $order_id Order ID.
371 +
	 * @param  \Zotapay\ApiCallback $callback Callback object.
372 +
	 * @return bool
373 +
	 */
374 +
	public static function handle_callback( $order_id, $callback ) {
375 +
		// Check callback.
376 +
		if ( empty( $callback ) ) {
224 377
			$error = sprintf(
225 378
				// translators: %s WC Order ID.
226 -
				esc_html__( 'Order status response empty for WC Order #%s.', 'zota-woocommerce' ),
379 +
				esc_html__( 'Order callback empty for WC Order #%s.', 'zota-woocommerce' ),
227 380
				(int) $order_id
228 381
			);
229 382
			Zotapay::getLogger()->error( $error );
230 383
			return false;
231 384
		}
232 385
233 -
		// If no change do nothing.
234 -
		if ( $order->get_meta( '_zotapay_status', true ) === $response->getStatus() ) {
386 +
		$response['status']                 = $callback->getStatus();
387 +
		$response['processorTransactionID'] = $callback->getProcessorTransactionID();
388 +
		$response['errorMessage']           = $callback->getErrorMessage();
389 +
390 +
		if ( self::amount_changed( $callback ) ) {
391 +
			$extra_data = $callback->getExtraData();
392 +
393 +
			$response['amountChanged']  = true;
394 +
			$response['amount']         = $callback->getAmount();
395 +
			$response['originalAmount'] = $extra_data['originalAmount'];
396 +
		}
397 +
398 +
		return self::update_status( $order_id, $response );
399 +
	}
400 +
401 +
402 +
	/**
403 +
	 * Handle amount changed
404 +
	 *
405 +
	 * @param  WC_order $order           WC Order.
406 +
	 * @param  array    $response        Response data.
407 +
	 * @return bool
408 +
	 */
409 +
	public static function handle_amount_changed( $order, $response ) {
410 +
		// Convert values to floats.
411 +
		$amount          = \floatval( $response['amount'] );
412 +
		$original_amount = \floatval( $response['originalAmount'] );
413 +
414 +
		// Add meta.
415 +
		$order->update_meta_data( '_zotapay_amount_changed', 'yes' );
416 +
		$order->update_meta_data( '_zotapay_amount', $amount ); // Sanitized already with floatval.
417 +
418 +
		// Order note.
419 +
		if ( $amount < $original_amount ) {
420 +
			$note = sprintf(
421 +
				// translators: %1$s amount paid, %2$s original order amount.
422 +
				esc_html__( 'ZotaPay order partial payment. %1$s of %2$s paid.', 'zota-woocommerce' ),
423 +
				sanitize_text_field( wc_price( $amount ) ),
424 +
				sanitize_text_field( wc_price( $original_amount ) )
425 +
			);
426 +
		} elseif ( $amount > $original_amount ) {
427 +
			$note = sprintf(
428 +
				// translators: %1$s amount paid, %2$s original order amount.
429 +
				esc_html__( 'ZotaPay order overpayment. %1$s of %2$s paid.', 'zota-woocommerce' ),
430 +
				sanitize_text_field( wc_price( $amount ) ),
431 +
				sanitize_text_field( wc_price( $original_amount ) )
432 +
			);
433 +
		} else {
434 +
			$note = esc_html__( 'ZotaPay order payment completed.', 'zota-woocommerce' );
435 +
		}
436 +
437 +
		if ( ! empty( $response['processorTransactionID'] ) ) {
438 +
			$note .= ' ' . sprintf(
439 +
				// translators: Processor Transaction ID.
440 +
				esc_html__( 'Transaction ID: %s.', 'zota-woocommerce' ),
441 +
				sanitize_text_field( $response['processorTransactionID'] )
442 +
			);
443 +
		}
444 +
445 +
		$order->add_order_note( $note );
446 +
		$order->save();
447 +
448 +
		// ZotaPay amount.
449 +
		$zotapay_amount = \floatval( $order->get_meta( '_zotapay_amount', true ) );
450 +
451 +
		// Compare amount paid against original amount.
452 +
		if ( $amount < $original_amount ) {
453 +
			// If amount is lower set order status to Partial Payment.
454 +
			$order->set_status( 'wc-partial-payment' );
455 +
			if ( ! $order->get_date_paid( 'edit' ) ) {
456 +
				$order->set_date_paid( time() );
457 +
			}
458 +
			$order->save();
459 +
		} elseif ( $amount > $original_amount ) {
460 +
			// If amount is greater set order status to Overpaid.
461 +
			$order->set_date_paid( time() );
462 +
			$order->set_status( 'wc-overpayment' );
463 +
			if ( ! $order->get_date_paid( 'edit' ) ) {
464 +
				$order->set_date_paid( time() );
465 +
			}
466 +
			$order->save();
467 +
		} else {
468 +
			// If amount equals to original amount set order payment complete.
469 +
			self::delete_expiration_time( $order->get_id() );
470 +
			$order->payment_complete();
471 +
		}
472 +
473 +
		return true;
474 +
	}
475 +
476 +
477 +
	/**
478 +
	 * Handle merchant redirect.
479 +
	 *
480 +
	 * @param  int                       $order_id Order ID.
481 +
	 * @param  \Zotapay\MerchantRedirect $redirect Redirect object.
482 +
	 * @return bool
483 +
	 */
484 +
	public static function handle_redirect( $order_id, $redirect ) {
485 +
486 +
		// Check callback.
487 +
		if ( empty( $redirect ) ) {
488 +
			$error = sprintf(
489 +
				// translators: %s WC Order ID.
490 +
				esc_html__( 'Order redirect empty for WC Order #%s.', 'zota-woocommerce' ),
491 +
				(int) $order_id
492 +
			);
493 +
			Zotapay::getLogger()->error( $error );
494 +
			return false;
495 +
		}
496 +
497 +
		$response['status']                 = $redirect->getStatus();
498 +
		$response['errorMessage']           = $redirect->getErrorMessage();
499 +
500 +
		return self::update_status( $order_id, $response );
501 +
	}
502 +
503 +
504 +
	/**
505 +
	 * Process order status response.
506 +
	 *
507 +
	 * @param  int   $order_id Order ID.
508 +
	 * @param  array $response Response data.
509 +
	 * @return bool
510 +
	 */
511 +
	public static function update_status( $order_id, $response ) {
512 +
		// Get the order.
513 +
		$order = wc_get_order( $order_id );
514 +
		if ( empty( $order ) ) {
515 +
			$error = sprintf(
516 +
				// translators: %s WC Order ID.
517 +
				esc_html__( 'Update status WC Order #%s not found.', 'zota-woocommerce' ),
518 +
				(int) $order_id
519 +
			);
520 +
			Zotapay::getLogger()->error( $error );
235 521
			return false;
236 522
		}
237 523
238 524
		// Update order meta.
239 -
		$order->update_meta_data( '_zotapay_status', sanitize_text_field( $response->getStatus() ) );
525 +
		$order->update_meta_data( '_zotapay_status', sanitize_text_field( $response['status'] ) );
240 526
		$order->update_meta_data( '_zotapay_updated', time() );
241 527
		$order->save();
242 528
@@ -248,11 +534,11 @@
Loading
248 534
		Zotapay::getLogger()->info( $message );
249 535
250 536
		// Awaiting statuses.
251 -
		if ( in_array( $response->getStatus(), array( 'CREATED', 'PENDING', 'PROCESSING' ), true ) ) {
537 +
		if ( in_array( $response['status'], array( 'CREATED', 'PENDING', 'PROCESSING' ), true ) ) {
252 538
			$note = sprintf(
253 539
				// translators: ZotaPay status.
254 540
				esc_html__( 'ZotaPay status: %s.', 'zota-woocommerce' ),
255 -
				sanitize_text_field( $response->getStatus() )
541 +
				esc_html( $response['status'] )
256 542
			);
257 543
			$order->add_order_note( $note );
258 544
			$order->save();
@@ -260,17 +546,20 @@
Loading
260 546
		}
261 547
262 548
		// Status APPROVED.
263 -
		if ( 'APPROVED' === $response->getStatus() ) {
264 -
265 -
			// Delete expiration time.
549 +
		if ( 'APPROVED' === $response['status'] ) {
266 550
			self::delete_expiration_time( $order_id );
267 551
268 -
			if ( method_exists( $response, 'getProcessorTransactionID' ) ) {
552 +
			// Check is amount changed.
553 +
			if ( isset( $response['amountChanged'] ) ) {
554 +
				return self::handle_amount_changed( $order, $response );
555 +
			}
556 +
557 +
			if ( ! empty( $response['processorTransactionID'] ) ) {
269 558
				$note = sprintf(
270 559
					// translators: %1$s ZotaPay status, %2$s Processor Transaction ID.
271 560
					esc_html__( 'ZotaPay status: %1$s, Transaction ID: %2$s.', 'zota-woocommerce' ),
272 -
					sanitize_text_field( $response->getStatus() ),
273 -
					sanitize_text_field( $response->getProcessorTransactionID() )
561 +
					sanitize_text_field( $response['status'] ),
562 +
					sanitize_text_field( $response['processorTransactionID'] )
274 563
				);
275 564
				$order->add_order_note( $note );
276 565
				$order->save();
@@ -286,7 +575,7 @@
Loading
286 575
		}
287 576
288 577
		// Status UNKNOWN send an email to ZotaPay, log error and add order note.
289 -
		if ( 'UNKNOWN' === $response->getStatus() ) {
578 +
		if ( 'UNKNOWN' === $response['status'] ) {
290 579
291 580
			// Log info.
292 581
			$log = sprintf(
@@ -299,7 +588,7 @@
Loading
299 588
			$message = sprintf(
300 589
				// translators: %1$s ZotaPay email, %2$s Status.
301 590
				esc_html__( 'You are receiving this because order has status %1$s. Please forward this email to %2$s.', 'zota-woocommerce' ),
302 -
				sanitize_text_field( $response->getStatus() ),
591 +
				sanitize_text_field( $response['status'] ),
303 592
				'support@zotapay.com'
304 593
			);
305 594
			$message .= PHP_EOL . PHP_EOL . $log;
@@ -322,20 +611,20 @@
Loading
322 611
		}
323 612
324 613
		// Final statuses with errors - DECLINED, FILTERED, ERROR.
325 -
		if ( method_exists( $response, 'getProcessorTransactionID' ) ) {
614 +
		if ( ! empty( $response['processorTransactionID'] ) ) {
326 615
			$note = sprintf(
327 616
				// translators: %1$s ZotaPay status, %2$s Processor Transaction ID, %3$s Error message.
328 617
				esc_html__( 'ZotaPay status: %1$s, Transaction ID: %2$s, Error: %3$s.', 'zota-woocommerce' ),
329 -
				sanitize_text_field( $response->getStatus() ),
330 -
				sanitize_text_field( $response->getProcessorTransactionID() ),
331 -
				sanitize_text_field( $response->getErrorMessage() )
618 +
				sanitize_text_field( $response['status'] ),
619 +
				sanitize_text_field( $response['processorTransactionID'] ),
620 +
				sanitize_text_field( $response['errorMessage'] )
332 621
			);
333 622
		} else {
334 623
			$note = sprintf(
335 624
				// translators: %1$s ZotaPay status, %2$s Error message.
336 625
				esc_html__( 'ZotaPay status: %1$s, Error: %2$s.', 'zota-woocommerce' ),
337 -
				sanitize_text_field( $response->getStatus() ),
338 -
				sanitize_text_field( $response->getErrorMessage() )
626 +
				sanitize_text_field( $response['status'] ),
627 +
				sanitize_text_field( $response['errorMessage'] )
339 628
			);
340 629
		}
341 630
		$order->update_status( 'failed', $note );
@@ -481,6 +770,22 @@
Loading
481 770
			return;
482 771
		}
483 772
773 +
		// If partial/overpayment do nothing.
774 +
		if ( in_array( $order->get_status(), array( 'partial-payment', 'overpayment' ), true ) ) {
775 +
			return;
776 +
		}
777 +
778 +
		// If expired do nothing.
779 +
		if ( ! empty( $order->get_meta( '_zotapay_expired', true ) ) ) {
780 +
			$message = sprintf(
781 +
				// translators: %s WC Order ID.
782 +
				esc_html__( 'Check status ended for expired WC Order #%s.', 'zota-woocommerce' ),
783 +
				(int) $order_id
784 +
			);
785 +
			Zotapay::getLogger()->info( $message );
786 +
			return;
787 +
		}
788 +
484 789
		$message = sprintf(
485 790
			// translators: %s WC Order ID.
486 791
			esc_html__( 'Checking expiration time for WC Order #%s.', 'zota-woocommerce' ),
@@ -488,7 +793,7 @@
Loading
488 793
		);
489 794
		Zotapay::getLogger()->info( $message );
490 795
491 -
		$zotapay_expiration    = $order->get_meta( '_zotapay_expiration', true );
796 +
		$zotapay_expiration    = intval( $order->get_meta( '_zotapay_expiration', true ) );
492 797
		$zotapay_status_checks = intval( $order->get_meta( '_zotapay_status_checks', true ) );
493 798
494 799
		$date_time    = new \DateTime();
@@ -507,7 +812,10 @@
Loading
507 812
			return;
508 813
		}
509 814
510 -
		$response = self::order_status( $order_id );
815 +
		$order_status = self::order_status( $order_id );
816 +
817 +
		$response['status']                 = $order_status->getStatus();
818 +
		$response['errorMessage']           = $order_status->getErrorMessage();
511 819
512 820
		// Update status and meta.
513 821
		if ( ! self::update_status( $order_id, $response ) ) {

@@ -0,0 +1,335 @@
Loading
1 +
<?php
2 +
3 +
/**
4 +
 * @package Zota_Woocommerce
5 +
 */
6 +
7 +
/**
8 +
 * Class WC_Tests_Payment_Gateway.
9 +
 */
10 +
class WC_Tests_Order extends WC_Unit_Test_Case {
11 +
12 +
	/**
13 +
	 * Data Array
14 +
	 *
15 +
	 * @return array
16 +
	 */
17 +
	public function getDataPartialPayment() {
18 +
		$stream = dirname( __FILE__ ) . '/data/callback-partial-payment.json';
19 +
		$fileContents = \file_get_contents( $stream );
20 +
		$data = \json_decode( $fileContents, JSON_OBJECT_AS_ARRAY );
21 +
22 +
		return array(
23 +
			array(
24 +
				array(
25 +
					'stream' => $stream,
26 +
					'data' => $data,
27 +
				),
28 +
			),
29 +
		);
30 +
	}
31 +
32 +
33 +
	/**
34 +
	 * Data Array
35 +
	 *
36 +
	 * @return array
37 +
	 */
38 +
	public function getDataOverPayment() {
39 +
		$stream = dirname( __FILE__ ) . '/data/callback-overpayment.json';
40 +
		$fileContents = \file_get_contents( $stream );
41 +
		$data = \json_decode( $fileContents, JSON_OBJECT_AS_ARRAY );
42 +
43 +
		return [
44 +
			[
45 +
				[
46 +
					'stream' => $stream,
47 +
					'data' => $data,
48 +
				],
49 +
			],
50 +
		];
51 +
	}
52 +
53 +
54 +
	/**
55 +
	 * Setup, enable payment gateways.
56 +
	 */
57 +
	public function setUp() {
58 +
		$this->payment_method = ZOTA_WC_GATEWAY_ID . '_' . uniqid();
59 +
		Tests_Helper::setUp( $this->payment_method );
60 +
	}
61 +
62 +
63 +
	/**
64 +
	 * Initialize session that some tests might have removed.
65 +
	 */
66 +
	public function tearDown() {
67 +
		parent::tearDown();
68 +
		WC()->initialize_session();
69 +
	}
70 +
71 +
72 +
	/**
73 +
	 * Create prending payment order.
74 +
	 */
75 +
	public function create_pending_payment_order( $total = 10 ) {
76 +
		WC()->initialize_session();
77 +
78 +
		$payment_gateways = WC()->payment_gateways->payment_gateways();
79 +
80 +
		$total = floatval( $total );
81 +
82 +
		$product = WC_Helper_Product::create_simple_product(
83 +
			true,
84 +
			[
85 +
				'regular_price' => $total,
86 +
				'price'         => $total,
87 +
				'tax_status' => 'none',
88 +
				'virtual' => true,
89 +
			]
90 +
		);
91 +
92 +
		WC()->cart->add_to_cart( $product->get_id() );
93 +
94 +
		$order = Tests_Helper::create_order( $product, 1, $this->payment_method );
95 +
96 +
		$data = [
97 +
			'code' => 200,
98 +
			'message' => null,
99 +
			'data' => [
100 +
				'merchantOrderID' => $order->get_id(),
101 +
				'orderID' => '1234',
102 +
				'depositUrl' => 'https://example.com',
103 +
			],
104 +
			'httpCode' => 200,
105 +
			'depositUrl' => 'https://example.com',
106 +
			'merchantOrderID' => $order->get_id(),
107 +
			'orderID' => '1234',
108 +
		];
109 +
110 +
		$mockResponse = [
111 +
			wp_json_encode( $data ),
112 +
			200,
113 +
		];
114 +
115 +
		\Zotapay\Zotapay::setMockResponse( $mockResponse );
116 +
117 +
		$zota = $payment_gateways[ $order->get_payment_method() ];
118 +
119 +
		$result = $zota->process_payment( $order->get_id() );
120 +
121 +
		/**
122 +
		 * Set $_GET here because the redirect request handler deals
123 +
		 * directly with it.
124 +
		 */
125 +
		$_GET = [
126 +
			'billingDescriptor' => '',
127 +
			'merchantOrderID' => $order->get_id(),
128 +
			'orderID' => '1234',
129 +
			'status' => 'PENDING',
130 +
		];
131 +
132 +
		$verify['status'] = isset( $_GET['status'] ) ? $_GET['status'] : '';
133 +
		$verify['orderID'] = isset( $_GET['orderID'] ) ? $_GET['orderID'] : '';
134 +
		$verify['merchantOrderID'] = isset( $_GET['merchantOrderID'] ) ? $_GET['merchantOrderID'] : '';
135 +
		$verify['merchantSecretKey'] = \Zotapay\Zotapay::getMerchantSecretKey();
136 +
137 +
		$_GET['signature'] = hash( 'sha256', \implode( '', $verify ) );
138 +
139 +
		// Trigger redirect request to thank you page.
140 +
		do_action( 'woocommerce_thankyou_' . $zota->id, $order->get_id() );
141 +
142 +
		return wc_get_order( $order->get_id() );
143 +
	}
144 +
145 +
146 +
	/**
147 +
	 * Test getting extra data.
148 +
	 *
149 +
	 * @dataProvider getDataPartialPayment
150 +
	 */
151 +
	public function test_get_extra_data( $data ) {
152 +
		// Get the callback handler.
153 +
		$callback = new \Zotapay\ApiCallback( $data['stream'] );
154 +
155 +
		// Get extra data.
156 +
		$extra_data = \Zota\Zota_WooCommerce\Includes\Order::get_extra_data( $callback );
157 +
158 +
		$this->assertTrue( is_array( $extra_data ) );
159 +
	}
160 +
161 +
162 +
	/**
163 +
	 * Test if amount changed.
164 +
	 *
165 +
	 * @dataProvider getDataPartialPayment
166 +
	 */
167 +
	public function test_amount_changed( $data ) {
168 +
		// Get the callback handler.
169 +
		$callback = new \Zotapay\ApiCallback( $data['stream'] );
170 +
171 +
		// Check if amount changed.
172 +
		$is_amount_changed = \Zota\Zota_WooCommerce\Includes\Order::amount_changed( $callback );
173 +
174 +
		$this->assertTrue( $is_amount_changed );
175 +
	}
176 +
177 +
178 +
	/**
179 +
	 * Test handle amount changed.
180 +
	 *
181 +
	 * @dataProvider getDataPartialPayment
182 +
	 */
183 +
	public function test_handle_amount_changed( $data ) {
184 +
		$original_amount = floatval( $data['data']['extraData']['originalAmount'] );
185 +
		$partial_payment = floatval( $data['data']['amount'] );
186 +
187 +
		$order = $this->create_pending_payment_order( $original_amount );
188 +
189 +
		// Get the callback handler.
190 +
		$callback = new \Zotapay\ApiCallback( $data['stream'] );
191 +
192 +
		$response['status']                 = $callback->getStatus();
193 +
		$response['processorTransactionID'] = $callback->getProcessorTransactionID();
194 +
		$response['errorMessage']           = $callback->getErrorMessage();
195 +
196 +
		$extra_data = $callback->getExtraData();
197 +
198 +
		$response['amountChanged']  = true;
199 +
		$response['amount']         = $callback->getAmount();
200 +
		$response['originalAmount'] = $extra_data['originalAmount'];
201 +
202 +
		// Handle amount changed.
203 +
		\Zota\Zota_WooCommerce\Includes\Order::handle_amount_changed( $order, $response );
204 +
205 +
		$this->assertSame( $order->get_meta( '_zotapay_amount_changed', true ), 'yes' );
206 +
		$this->assertSame( \floatval( $order->get_meta( '_zotapay_amount', true ) ), $partial_payment );
207 +
	}
208 +
209 +
210 +
	/**
211 +
	 * Test callback for partial payment.
212 +
	 *
213 +
	 * @dataProvider getDataPartialPayment
214 +
	 */
215 +
	public function test_callback_partial_payment( $data ) {
216 +
		$original_amount = floatval( $data['data']['extraData']['originalAmount'] );
217 +
		$partial_payment = floatval( $data['data']['amount'] );
218 +
219 +
		$order = $this->create_pending_payment_order( $original_amount );
220 +
221 +
		// Get the callback handler.
222 +
		$callback = new \Zotapay\ApiCallback( $data['stream'] );
223 +
224 +
		// Single callback.
225 +
		$handle = \Zota\Zota_WooCommerce\Includes\Order::handle_callback( $order->get_id(), $callback );
226 +
227 +
		// Update order meta.
228 +
		$order->add_meta_data( '_zotapay_callback', time() );
229 +
		$order->add_meta_data( '_zotapay_transaction_id', $callback->getProcessorTransactionID() );
230 +
		$order->save();
231 +
232 +
		$order = wc_get_order( $order->get_id() );
233 +
234 +
		// Check status change
235 +
		$this->assertTrue( $handle );
236 +
		$this->assertSame( $order->get_status(), 'partial-payment' );
237 +
238 +
		$order_notes = wc_get_order_notes( array( 'order_id' => $order->get_id() ) );
239 +
240 +
		// ZotaPay amount.
241 +
		$zotapay_amount = \floatval( $order->get_meta( '_zotapay_amount', true ) );
242 +
243 +
		$this->assertSame( $zotapay_amount, $partial_payment );
244 +
		$this->assertSame( $order_notes[0]->content, 'Order status changed from Pending payment to Partial Payment.' );
245 +
	}
246 +
247 +
248 +
	/**
249 +
	 * Test callback for overpayment.
250 +
	 *
251 +
	 * @dataProvider getDataOverPayment
252 +
	 */
253 +
	public function test_callback_overpayment( $data ) {
254 +
		$original_amount = floatval( $data['data']['extraData']['originalAmount'] );
255 +
		$overpayment = floatval( $data['data']['amount'] );
256 +
257 +
		$order = $this->create_pending_payment_order( $original_amount );
258 +
259 +
		// Get the callback handler.
260 +
		$callback = new \Zotapay\ApiCallback( $data['stream'] );
261 +
262 +
		// Single callback.
263 +
		$handle = \Zota\Zota_WooCommerce\Includes\Order::handle_callback( $order->get_id(), $callback );
264 +
265 +
		// Update order meta.
266 +
		$order->add_meta_data( '_zotapay_callback', time() );
267 +
		$order->add_meta_data( '_zotapay_transaction_id', $callback->getProcessorTransactionID() );
268 +
		$order->save();
269 +
270 +
		$order = wc_get_order( $order->get_id() );
271 +
272 +
		// Check status change
273 +
		$this->assertTrue( $handle );
274 +
		$this->assertSame( $order->get_status(), 'overpayment' );
275 +
276 +
		$order_notes = wc_get_order_notes( array( 'order_id' => $order->get_id() ) );
277 +
278 +
		// ZotaPay amount.
279 +
		$zotapay_amount = \floatval( $order->get_meta( '_zotapay_amount', true ) );
280 +
281 +
		$this->assertSame( $zotapay_amount, $overpayment );
282 +
		$this->assertSame( $order_notes[0]->content, 'Order status changed from Pending payment to Overpayment.' );
283 +
	}
284 +
285 +
286 +
	/**
287 +
	 * Test all cases with multiple callbacks.
288 +
	 *
289 +
	 * @dataProvider getDataPartialPayment
290 +
	 */
291 +
	public function test_amount_cahnged_with_multiple_callbacks( $data ) {
292 +
		$original_amount = floatval( $data['data']['extraData']['originalAmount'] );
293 +
		$order = $this->create_pending_payment_order( $original_amount );
294 +
295 +
		$callback_amounts = array( 2.00, 3.00, 5.00, 20.00, 1000.00, $original_amount );
296 +
297 +
		// Multiple callbacks.
298 +
		foreach ( $callback_amounts as $callback_amount ) {
299 +
			$order_id = $order->get_id();
300 +
301 +
			// Change amount.
302 +
			$stream = str_replace( '"amount": "5.00"', '"amount": "' . (string) $callback_amount . '"', $data['stream'] );
303 +
304 +
			// Get the callback handler.
305 +
			$callback = new \Zotapay\ApiCallback( $stream );
306 +
307 +
			// Single callback.
308 +
			$handle = \Zota\Zota_WooCommerce\Includes\Order::handle_callback( $order_id, $callback );
309 +
310 +
			$this->assertTrue( $handle );
311 +
312 +
			// Update order meta.
313 +
			$order->add_meta_data( '_zotapay_callback', time() );
314 +
			$order->add_meta_data( '_zotapay_transaction_id', $callback->getProcessorTransactionID() );
315 +
			$order->save();
316 +
317 +
			$order = wc_get_order( $order_id );
318 +
319 +
			$order_notes = wc_get_order_notes( array( 'order_id' => $order->get_id() ) );
320 +
321 +
			// ZotaPay amount.
322 +
			$zotapay_amount = \floatval( $order->get_meta( '_zotapay_amount', true ) );
323 +
324 +
			// Check status change
325 +
			if ( $zotapay_amount < $original_amount ) {
326 +
				$this->assertSame( 'partial-payment', $order->get_status() );
327 +
			} elseif ( $zotapay_amount > $original_amount ) {
328 +
				$this->assertSame( 'overpayment', $order->get_status() );
329 +
			} else {
330 +
				$this->assertSame( $original_amount, $zotapay_amount );
331 +
				$this->assertSame( 'processing', $order->get_status() );
332 +
			}
333 +
		}
334 +
	}
335 +
}

@@ -111,13 +111,13 @@
Loading
111 111
112 112
			// Check Processor Transaction ID.
113 113
			if ( null === $callback->getProcessorTransactionID() ) {
114 -
				$error = sprintf(
115 -
					// translators: %1$s Merchant Order ID.
116 -
					esc_html__( 'Merchant Order ID %1$s no Processor Transaction ID.', 'zota-woocommerce' ),
117 -
					$callback->getMerchantOrderID()
114 +
				Zotapay::getLogger()->info(
115 +
					sprintf(
116 +
						// translators: %1$s Merchant Order ID.
117 +
						esc_html__( 'Merchant Order ID %1$s no Processor Transaction ID.', 'zota-woocommerce' ),
118 +
						$callback->getMerchantOrderID()
119 +
					)
118 120
				);
119 -
				Zotapay::getLogger()->error( $error );
120 -
				wp_send_json_error( $error, 400 );
121 121
			}
122 122
123 123
			// Update status and add notes.
@@ -130,7 +130,7 @@
Loading
130 130
					$callback->getStatus()
131 131
				)
132 132
			);
133 -
			Order::update_status( $order_id, $callback );
133 +
			Order::handle_callback( $order_id, $callback );
134 134
135 135
			// Update order meta.
136 136
			$order->add_meta_data( '_zotapay_callback', time() );
@@ -201,7 +201,7 @@
Loading
201 201
					$redirect->getStatus()
202 202
				)
203 203
			);
204 -
			Order::update_status( $order_id, $redirect );
204 +
			Order::handle_redirect( $order_id, $redirect );
205 205
		} catch ( InvalidSignatureException $e ) {
206 206
			$error = sprintf(
207 207
				// translators: %1$s Order ID, %2$s Error message.
Files Complexity Coverage
includes 183 32.77%
tests 389 28.78%
functions.php 0 4.10%
uninstall.php 0 0.00%
zota-for-woocommerce.php 0 0.00%
Project Totals (42 files) 572 28.58%
Notifications are pending CI completion. Waiting for GitHub's status webhook to queue notifications. Push notifications now.

No yaml found.

Create your codecov.yml to customize your Codecov experience

Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading