diff --git a/MAINTAINING.md b/MAINTAINING.md index 0a7c301..303fda5 100644 --- a/MAINTAINING.md +++ b/MAINTAINING.md @@ -28,6 +28,11 @@ Other: - External WC PG dev guide: https://www.skyverge.com/blog/how-to-create-a-simple-woocommerce-payment-gateway/ - WP get_options() functions: https://developer.wordpress.org/reference/functions/get_option/ +### Attention +- Due to the feature of "custom order_id suffix to prevent duplicated order_id", order_id input and output may need to be handled non-traditionally, look for `@TAG: order-suffix-separator` in the code comments. e.g: + - when sending order_id to Midtrans, it may need to go thru func `WC_Midtrans_Utils::generate_non_duplicate_order_id` + - when receiving order_id, it may need to go thru func `WC_Midtrans_Utils::check_and_restore_original_order_id` + ### Separted Payment Buttons To implement separated payment buttons (separate WC payment gateway) for each of Midtrans' supported payment methods, the following implementations are made: - within `/class/sub-specific-buttons` those are the class files diff --git a/README.md b/README.md index 0c470f3..d1dea89 100644 --- a/README.md +++ b/README.md @@ -8,21 +8,23 @@ Also [Available on Wordpress plugin store](https://wordpress.org/plugins/midtran ### Description -This plugin will allow secure online payment on your WooCommerce store, without your customer ever need to leave your WooCommerce store! With beautiful responsive payment interface built-in. -Midtrans  is an online payment gateway. They strive to make payments simple for both the merchant and customers. -Support various online payment channel. -Support WooCommerce v3 & v2. +This plugin will allow secure online payment on your WooCommerce store, without your customer ever need to leave your WooCommerce store! + +Midtrans-WooCommerce is official plugin from [Midtrans](https://midtrans.com). Midtrans is an online payment gateway. We strive to make payments simple & secure for both the merchant and customers. Support various online payment channel. Support WooCommerce v3 & v2. + +Please follow [this step by step guide](https://docs.midtrans.com/en/snap/with-plugins?id=wordpress-woocommerce) for complete configuration. If you have any feedback or request, please [do let us know here](https://docs.midtrans.com/en/snap/with-plugins?id=feedback-and-request). Payment Method Feature: * Credit card fullpayment and other payment methods. -* Bank transfer, internet banking for various banks +* E-wallet, Bank transfer, internet banking for various banks * Credit card Online & offline installment payment. * Credit card BIN, bank transfer, and other channel promo payment. * Credit card MIGS acquiring channel. * Custom expiry. * Two-click & One-click feature. -* Midtrans Snap all payment method fullpayment. +* Midtrans Snap all supported payment method. +* Optional: Separated specific payment buttons with its own icons. ### Installation @@ -89,7 +91,7 @@ You can customize icon that will be shown on payment buttons, from the plugin co All available values for the field: ``` -credit_card.png, gopay.png, shopeepay.png, qris.png, other_va.png, bni_va.png, bri_va.png, bca_va.png, permata_va.png, echannel.png, alfamart.png, indomaret.png, akulaku.png, bca_klikpay.png, cimb_clicks.png, danamon_online.png, midtrans.png +midtrans.png, credit_card.png, gopay.png, shopeepay.png, qris.png, other_va.png, bni_va.png, bri_va.png, bca_va.png, permata_va.png, echannel.png, alfamart.png, indomaret.png, akulaku.png, bca_klikpay.png, cimb_clicks.png, danamon_online.png ``` Or refer to [payment-methods folder](/public/images/payment-methods) to see the list of all available file names. The image file will be loaded from that folder. @@ -136,11 +138,13 @@ If you are a developer or know how to customize Wordpress, this section may be u This plugin have few available [WP hooks](https://developer.wordpress.org/plugins/hooks/): - filter: `midtrans_snap_params_main_before_charge` (1 params) - - For if you want to modify Snap API JSON param on the main gateway, before transaction is created on Midtrans side. + - For if you want to modify Snap API JSON param on the main gateway, before transaction is created on Midtrans side. The $params is PHP Array representation of [Snap API JSON param](https://snap-docs.midtrans.com/#request-body-json-parameter) - action: `midtrans_after_notification_payment_complete` (2 params) - For if you want to perform action/update WC Order object when the payment is declared as complete upon Midtrans notification received. - action: `midtrans_on_notification_received` (2 params) - For if you want to perform action/update WC Order object upon Midtrans notification received. +- action: `midtrans-handle-valid-notification` (1 params) + - For if you want to perform something upon valid Midtrans notification received. Note: this is legacy hook, better use the hook above. Example implementation: ```php @@ -174,6 +178,8 @@ function my_midtrans_on_notif_hook( $order, $midtrans_notification ) { For reference on where/which file to apply that code example, [refer here](https://blog.nexcess.net/the-right-way-to-add-custom-functions-to-your-wordpress-site/). +Note: for `midtrans_after_notification_payment_complete` & `midtrans_on_notification_received` hooks, if you are using [custom "WC Order Status on Payment Paid"](https://docs.midtrans.com/en/snap/with-plugins?id=advanced-customize-woocommerce-order-status-upon-payment-paid) config, the final WC Order status value can get overridden by that config. As that config is executed last. + #### Customizing Snap API parameters diff --git a/abstract/abstract.midtrans-gateway.php b/abstract/abstract.midtrans-gateway.php index 67da586..8beb199 100644 --- a/abstract/abstract.midtrans-gateway.php +++ b/abstract/abstract.midtrans-gateway.php @@ -131,16 +131,26 @@ public function process_refund($order_id, $amount = null, $reason = '') { */ public function refund( $order, $order_id, $amount, $reason ) { $refund_params = array( + // @TODO: careful with this order_id here, which does not get deduplicated treatment 'refund_key' => 'RefundID' . $order_id . '-' . current_time('timestamp'), 'amount' => $amount, 'reason' => $reason ); try { - $response = WC_Midtrans_API::createRefund($order_id, $refund_params, $this->id); + if(strpos($this->id, 'midtrans_sub') !== false){ + // for sub separated gateway buttons, use main gateway plugin id instead + $this->id = 'midtrans'; + } + // @TODO: call refund API with transaction_id instead of order_id to avoid id not found for suffixed order_id. $order->get_transaction_id(); + $transaction_id = $order->get_transaction_id() + ? $order->get_transaction_id() + : $order_id; + $response = WC_Midtrans_API::createRefund($transaction_id, $refund_params, $this->id); } catch (Exception $e) { $this->setLogError( $e->getMessage() ); - $error_message = strpos($e->getMessage(), '412') ? $e->getMessage() . ' Note: Refund via Midtrans only for specific payment method, please consult to your midtrans PIC for more information' : $e->getMessage(); + // error_log(var_export($e,1)); + $error_message = strpos($e->getMessage(), '412') ? $e->getMessage() . ' Note: Refund via Midtrans API only available on some payment methods, and if the payment status is eligible. Please consult to your midtrans PIC for more information' : $e->getMessage(); return $error_message; } @@ -334,6 +344,7 @@ public function getResponseTemplate( $order ) { * @return WC_Order_Refund|WP_Error */ public function midtrans_refund( $order_id, $refund_amount, $refund_reason, $isFullRefund = false ) { + $order_id = WC_Midtrans_Utils::check_and_restore_original_order_id(); $order = wc_get_order( $order_id ); if( ! is_a( $order, 'WC_Order') ) { return; @@ -386,6 +397,20 @@ public function midtrans_refund( $order_id, $refund_amount, $refund_reason, $isF return $refund; } + /** + * Custom helper function to set customer web session cookies of WC order's finish_url + * That will be used by finish url handler to redirect customer to upon finish url is reached + * Cookies is used to strictly allow only the transacting-customer + * to access the order's finish url + * @TAG: finish_url_user_cookies + * @param WC_Order $order WC Order instance of the current transaction + */ + public function set_finish_url_user_cookies( $order ) { + $cookie_name = 'wc_midtrans_last_order_finish_url'; + $order_finish_url = $order->get_checkout_order_received_url(); + setcookie($cookie_name, $order_finish_url); + } + /** * Custom helper function to write messages to WP/WC error log. * @TODO: refactor name to make it more descriptive? diff --git a/class/class.midtrans-gateway-api.php b/class/class.midtrans-gateway-api.php index cb44afd..7b9ab9a 100644 --- a/class/class.midtrans-gateway-api.php +++ b/class/class.midtrans-gateway-api.php @@ -87,12 +87,47 @@ public static function get_environment() { * @return void */ public static function fetchAndSetMidtransApiConfig( $plugin_id="midtrans" ) { + if(strpos($plugin_id, 'midtrans_sub') !== false){ + // for sub separated gateway buttons, use main gateway plugin id instead + $plugin_id = 'midtrans'; + } self::fetchAndSetCurrentPluginOptions( $plugin_id ); Midtrans\Config::$isProduction = (self::get_environment() == 'production') ? true : false; Midtrans\Config::$serverKey = self::get_server_key(); Midtrans\Config::$isSanitized = true; } + /** + * Same as createSnapTransaction, but it will auto handle exception + * 406 duplicated order_id exception from Snap API, by calling WC_Midtrans_Utils::generate_non_duplicate_order_id + * @param object $order the WC Order instance. + * @param array $params Payment options. + * @param string $plugin_id ID of the plugin class calling this function + * @return object Snap response (token and redirect_url). + * @throws Exception curl error or midtrans error. + */ + public static function createSnapTransactionHandleDuplicate( $order, $params, $plugin_id="midtrans") { + try { + $response = self::createSnapTransaction($params, $plugin_id); + } catch (Exception $e) { + // Handle: Snap order_id duplicated, retry with suffixed order_id + if( strpos($e->getMessage(), 'transaction_details.order_id sudah digunakan') !== false) { + self::setLogRequest( $e->getMessage().' - Attempt to auto retry with suffixed order_id', $plugin_id ); + // @TAG: order-id-suffix-handling + $params['transaction_details']['order_id'] = + WC_Midtrans_Utils::generate_non_duplicate_order_id($params['transaction_details']['order_id']); + $response = self::createSnapTransaction($params, $plugin_id); + + // store the suffixed order id to order metadata + // @TAG: order-id-suffix-handling-meta + $order->update_meta_data('_mt_suffixed_midtrans_order_id', $params['transaction_details']['order_id']); + } else { + throw $e; + } + } + return $response; + } + /** * Create Snap Token. * @param array $params Payment options. diff --git a/class/class.midtrans-gateway-installment.php b/class/class.midtrans-gateway-installment.php index 864450c..52c116d 100755 --- a/class/class.midtrans-gateway-installment.php +++ b/class/class.midtrans-gateway-installment.php @@ -88,7 +88,7 @@ function process_payment( $order_id ) { // Empty the cart because payment is initiated. $woocommerce->cart->empty_cart(); try { - $snapResponse = WC_Midtrans_API::createSnapTransaction( $params, $this->id ); + $snapResponse = WC_Midtrans_API::createSnapTransactionHandleDuplicate( $order, $params, $this->id ); } catch (Exception $e) { $this->setLogError( $e->getMessage() ); WC_Midtrans_Utils::json_print_exception( $e, $this ); @@ -105,7 +105,8 @@ function process_payment( $order_id ) { $order->update_meta_data('_mt_payment_snap_token',$snapResponse->token); $order->update_meta_data('_mt_payment_url',$snapResponse->redirect_url); $order->save(); - + // set wc order's finish_url on user's session cookie + $this->set_finish_url_user_cookies($order); if(property_exists($this,'enable_immediate_reduce_stock') && $this->enable_immediate_reduce_stock == 'yes'){ wc_reduce_stock_levels($order); } diff --git a/class/class.midtrans-gateway-installmentoff.php b/class/class.midtrans-gateway-installmentoff.php index 9f9e3d6..b1bd030 100755 --- a/class/class.midtrans-gateway-installmentoff.php +++ b/class/class.midtrans-gateway-installmentoff.php @@ -117,7 +117,7 @@ public function process_payment( $order_id ) { // Empty the cart because payment is initiated. $woocommerce->cart->empty_cart(); try { - $snapResponse = WC_Midtrans_API::createSnapTransaction( $params, $this->id ); + $snapResponse = WC_Midtrans_API::createSnapTransactionHandleDuplicate( $order, $params, $this->id ); } catch (Exception $e) { $this->setLogError( $e->getMessage() ); WC_Midtrans_Utils::json_print_exception( $e, $this ); @@ -135,6 +135,8 @@ public function process_payment( $order_id ) { $order->update_meta_data('_mt_payment_snap_token',$snapResponse->token); $order->update_meta_data('_mt_payment_url',$snapResponse->redirect_url); $order->save(); + // set wc order's finish_url on user's session cookie + $this->set_finish_url_user_cookies($order); if(property_exists($this,'enable_immediate_reduce_stock') && $this->enable_immediate_reduce_stock == 'yes'){ // Reduce item stock on WC, item also auto reduced on order `pending` status changes wc_reduce_stock_levels($order); diff --git a/class/class.midtrans-gateway-notif-handler.php b/class/class.midtrans-gateway-notif-handler.php index 176a6a9..37040bd 100644 --- a/class/class.midtrans-gateway-notif-handler.php +++ b/class/class.midtrans-gateway-notif-handler.php @@ -50,6 +50,21 @@ public function doEarlyAckResponse() { return $raw_notification; } + /** + * Redirect transacting-user to the finish url set when they were checking out + * if they were the authorized transacting-user, they will have the finish_url on cookie + * @TAG: finish_url_user_cookies + */ + public function checkAndRedirectUserToFinishUrl(){ + if(isset($_COOKIE['wc_midtrans_last_order_finish_url'])){ + // authorized transacting-user + wp_redirect($_COOKIE['wc_midtrans_last_order_finish_url']); + }else{ + // else, unauthorized user, redirect to shop homepage by default. + wp_redirect( get_permalink( wc_get_page_id( 'shop' ) ) ); + } + } + /** * getPluginOptions * @param string $plugin_id plugin id of the paid order @@ -85,11 +100,19 @@ public function handleMidtransNotificationRequest() { $sanitizedPost['response'] = isset($_POST['response'])? sanitize_text_field($_POST['response']): null; + // @TAG: order-id-suffix-handling + $sanitized['order_id'] = + WC_Midtrans_Utils::check_and_restore_original_order_id($sanitized['order_id']); + // check whether the request is POST or GET, // @TODO: refactor this conditions, this doesn't quite represent conditions for a POST request if(empty($sanitized['order_id']) && empty($sanitizedPost['id']) && empty($sanitized['id']) && empty($sanitizedPost['response'])) { // Request is POST, proceed to create new notification, then update the payment status $raw_notification = $this->doEarlyAckResponse(); + + // @TAG: order-id-suffix-handling + $raw_notification['order_id'] = + WC_Midtrans_Utils::check_and_restore_original_order_id($raw_notification['order_id']); // Get WooCommerce order $wcorder = wc_get_order( $raw_notification['order_id'] ); // exit if the order id doesn't exist in WooCommerce dashboard @@ -107,7 +130,11 @@ public function handleMidtransNotificationRequest() { $midtrans_notification = WC_Midtrans_API::getStatusFromMidtransNotif( $plugin_id ); // If notification verified, handle it if (in_array($midtrans_notification->status_code, array(200, 201, 202, 407))) { - if (wc_get_order($midtrans_notification->order_id) != false) { + // @TAG: order-id-suffix-handling + $order_id = + WC_Midtrans_Utils::check_and_restore_original_order_id($midtrans_notification->order_id); + // @TODO: relocate this check into the function itself, to prevent unnecessary double DB query load + if (wc_get_order($order_id) != false) { do_action( "midtrans-handle-valid-notification", $midtrans_notification, $plugin_id ); } } @@ -126,23 +153,24 @@ public function handleMidtransNotificationRequest() { if( !empty($sanitized['order_id']) && !empty($sanitized['status_code']) && $sanitized['status_code'] <= 200) { $order_id = $sanitized['order_id']; // error_log($this->get_return_url( $order )); //debug - $order = new WC_Order( $order_id ); - wp_redirect($order->get_checkout_order_received_url()); + $this->checkAndRedirectUserToFinishUrl(); } // if or pending/challenge else if( !empty($sanitized['order_id']) && !empty($sanitized['transaction_status']) && $sanitized['status_code'] == 201) { - $order_id = $sanitized['order_id']; - $order = new WC_Order( $order_id ); - $plugin_id = $order->get_payment_method(); + try { + $order_id = $sanitized['order_id']; + $order = new WC_Order( $order_id ); + $plugin_id = $order->get_payment_method(); - $plugin_options = $this->getPluginOptions($plugin_id); - if( array_key_exists('ignore_pending_status',$plugin_options) - && $plugin_options['ignore_pending_status'] == 'yes' - ){ - wp_redirect( get_permalink( wc_get_page_id( 'shop' ) ) ); - exit; - } - wp_redirect($order->get_checkout_order_received_url()); + $plugin_options = $this->getPluginOptions($plugin_id); + if( array_key_exists('ignore_pending_status',$plugin_options) + && $plugin_options['ignore_pending_status'] == 'yes' + ){ + wp_redirect( get_permalink( wc_get_page_id( 'shop' ) ) ); + exit; + } + } catch (Exception $e) { } // catch if order not exist on WC + $this->checkAndRedirectUserToFinishUrl(); } //if deny, redirect to order checkout page again else if( !empty($sanitized['order_id']) && !empty($sanitized['transaction_status']) && $sanitized['status_code'] >= 202){ @@ -154,10 +182,14 @@ public function handleMidtransNotificationRequest() { // if customer redirected from async payment with POST `response` (CIMB clicks, etc) } else if ( !empty($sanitizedPost['response']) ){ $responses = json_decode( stripslashes($sanitizedPost['response']), true); + + // @TAG: order-id-suffix-handling + $responses['order_id'] = + WC_Midtrans_Utils::check_and_restore_original_order_id($responses['order_id']); $order = new WC_Order( $responses['order_id'] ); // if async payment paid if ( $responses['status_code'] == 200) { - wp_redirect($order->get_checkout_order_received_url()); + $this->checkAndRedirectUserToFinishUrl(); } // if async payment not paid else { @@ -177,11 +209,14 @@ public function handleMidtransNotificationRequest() { // But actually, BCA Klikpay already handled on finish-url-page.php, evaluate if this still needed $plugin_id = wc_get_order( $sanitized['id'] )->get_payment_method(); $midtrans_notification = WC_Midtrans_API::getMidtransStatus($id, $plugin_id); - $order_id = $midtrans_notification->order_id; + + // @TODO remove this order_id? seems unused + // @TAG: order-id-suffix-handling + $order_id = + WC_Midtrans_Utils::check_and_restore_original_order_id($midtrans_notification->order_id); // if async payment paid if ($midtrans_notification->transaction_status == 'settlement'){ - $order = new WC_Order( $order_id ); - wp_redirect($order->get_checkout_order_received_url()); + $this->checkAndRedirectUserToFinishUrl(); } // if async payment not paid else { @@ -204,10 +239,10 @@ public function handleMidtransNotificationRequest() { */ public function handleMidtransValidNotificationRequest( $midtrans_notification, $plugin_id = 'midtrans' ) { global $woocommerce; - - $order = new WC_Order( $midtrans_notification->order_id ); + // @TAG: order-id-suffix-handling + $order_id = WC_Midtrans_Utils::check_and_restore_original_order_id($midtrans_notification->order_id); + $order = new WC_Order( $order_id ); $order->add_order_note(__('Midtrans HTTP notification received: '.$midtrans_notification->transaction_status.'. Midtrans-'.$midtrans_notification->payment_type,'midtrans-woocommerce')); - $order_id = $midtrans_notification->order_id; // allow merchant-defined custom action function to perform action on $order upon notif handling do_action( 'midtrans_on_notification_received', $order, $midtrans_notification ); @@ -294,8 +329,11 @@ public function validateRefundNotif( $midtrans_notification ) { } $refund_request = $midtrans_notification->refunds[$lastArrayIndex]; + // @TAG: order-id-suffix-handling + $order_id = + WC_Midtrans_Utils::check_and_restore_original_order_id($midtrans_notification->order_id); // Validate the refund doesn't charge twice by the refund last index - $order_notes = wc_get_order_notes(array('order_id' => $midtrans_notification->order_id)); + $order_notes = wc_get_order_notes(array('order_id' => $order_id)); foreach($order_notes as $value) { if (strpos($value->content, $refund_request->refund_key ) !== false) { return false; @@ -312,10 +350,13 @@ public function validateRefundNotif( $midtrans_notification ) { * @return void */ public function checkAndHandleWCSubscriptionTxnNotif( $midtrans_notification, $order ) { + // @TAG: order-id-suffix-handling + $order_id = + WC_Midtrans_Utils::check_and_restore_original_order_id($midtrans_notification->order_id); // Process if this is a subscription transaction - if ( wcs_order_contains_subscription( $midtrans_notification->order_id ) || wcs_is_subscription( $midtrans_notification->order_id ) || wcs_order_contains_renewal( $midtrans_notification->order_id ) ) { + if ( wcs_order_contains_subscription( $order_id ) || wcs_is_subscription( $order_id ) || wcs_order_contains_renewal( $order_id ) ) { // if not subscription and wc status pending, don't process (because that's a recurring transaction) - if ( wcs_order_contains_renewal( $midtrans_notification->order_id) && $order->get_status() == 'pending' ) { + if ( wcs_order_contains_renewal( $order_id) && $order->get_status() == 'pending' ) { return false; } $subscriptions = wcs_get_subscriptions_for_order( $order, array( 'order_type' => 'any' ) ); diff --git a/class/class.midtrans-gateway-paymentrequest.php b/class/class.midtrans-gateway-paymentrequest.php index afb6288..e9573d9 100755 --- a/class/class.midtrans-gateway-paymentrequest.php +++ b/class/class.midtrans-gateway-paymentrequest.php @@ -81,7 +81,7 @@ function process_payment( $order_id ) { // Empty the cart because payment is initiated. $woocommerce->cart->empty_cart(); try { - $snapResponse = WC_Midtrans_API::createSnapTransaction( $params, $this->id ); + $snapResponse = WC_Midtrans_API::createSnapTransactionHandleDuplicate( $order, $params, $this->id ); } catch (Exception $e) { $this->setLogError( $e->getMessage() ); WC_Midtrans_Utils::json_print_exception( $e, $this ); @@ -98,6 +98,8 @@ function process_payment( $order_id ) { $order->update_meta_data('_mt_payment_snap_token',$snapResponse->token); $order->update_meta_data('_mt_payment_url',$snapResponse->redirect_url); $order->save(); + // set wc order's finish_url on user's session cookie + $this->set_finish_url_user_cookies($order); if(property_exists($this,'enable_immediate_reduce_stock') && $this->enable_immediate_reduce_stock == 'yes'){ wc_reduce_stock_levels($order); } diff --git a/class/class.midtrans-gateway-promo.php b/class/class.midtrans-gateway-promo.php index 6ffc717..df9178c 100755 --- a/class/class.midtrans-gateway-promo.php +++ b/class/class.midtrans-gateway-promo.php @@ -112,7 +112,7 @@ function process_payment( $order_id ) { $woocommerce->cart->empty_cart(); try { - $snapResponse = WC_Midtrans_API::createSnapTransaction( $params, $this->id ); + $snapResponse = WC_Midtrans_API::createSnapTransactionHandleDuplicate( $order, $params, $this->id ); } catch (Exception $e) { $this->setLogError( $e->getMessage() ); WC_Midtrans_Utils::json_print_exception( $e, $this ); @@ -130,6 +130,9 @@ function process_payment( $order_id ) { $order->update_meta_data('_mt_payment_url',$snapResponse->redirect_url); $order->save(); + // set wc order's finish_url on user's session cookie + $this->set_finish_url_user_cookies($order); + if(property_exists($this,'enable_immediate_reduce_stock') && $this->enable_immediate_reduce_stock == 'yes'){ wc_reduce_stock_levels($order); } diff --git a/class/class.midtrans-gateway-subscription.php b/class/class.midtrans-gateway-subscription.php index 4941837..096f28b 100644 --- a/class/class.midtrans-gateway-subscription.php +++ b/class/class.midtrans-gateway-subscription.php @@ -109,7 +109,7 @@ function process_payment( $order_id ) { // Empty the cart because payment is initiated. $woocommerce->cart->empty_cart(); try { - $snapResponse = WC_Midtrans_API::createSnapTransaction( $params, $this->id ); + $snapResponse = WC_Midtrans_API::createSnapTransactionHandleDuplicate( $order, $params, $this->id ); } catch (Exception $e) { $this->setLogError( $e->getMessage() ); WC_Midtrans_Utils::json_print_exception( $e, $this ); diff --git a/class/class.midtrans-gateway.php b/class/class.midtrans-gateway.php index 1299592..9d96737 100755 --- a/class/class.midtrans-gateway.php +++ b/class/class.midtrans-gateway.php @@ -132,7 +132,7 @@ public function process_payment_helper( $order_id, $options = false ) { // allow merchant-defined custom filter function to modify snap $params $params = apply_filters( 'midtrans_snap_params_main_before_charge', $params ); try { - $snapResponse = WC_Midtrans_API::createSnapTransaction( $params, $this->id ); + $snapResponse = WC_Midtrans_API::createSnapTransactionHandleDuplicate( $order, $params, $this->id ); } catch (Exception $e) { $this->setLogError( $e->getMessage() ); WC_Midtrans_Utils::json_print_exception( $e, $this ); @@ -151,6 +151,9 @@ public function process_payment_helper( $order_id, $options = false ) { $order->update_meta_data('_mt_payment_url',$snapResponse->redirect_url); $order->save(); + // set wc order's finish_url on user's session cookie + $this->set_finish_url_user_cookies($order); + // @TODO: default to yes or remove this options: enable_immediate_reduce_stock if(property_exists($this,'enable_immediate_reduce_stock') && $this->enable_immediate_reduce_stock == 'yes'){ // Reduce item stock on WC, item also auto reduced on order `pending` status changes @@ -196,7 +199,7 @@ protected function getDefaultTitle () { * @return string */ protected function getSettingsDescription() { - return __('Secure payment via Midtrans that accept various payment methods, with mobile friendly built-in interface, or (optionally) redirection. This is the main payment button, 1 single button for multiple available payments methods. Please follow "how-to configure guide" here.', 'midtrans-woocommerce'); + return __('Secure payment via Midtrans that accept various payment methods, with mobile friendly built-in interface, or (optionally) redirection. This is the main payment button, 1 single button for multiple available payments methods. Please follow "how-to configure guide" here. Any feedback & request let us know here.', 'midtrans-woocommerce'); } /** diff --git a/class/class.midtrans-utils.php b/class/class.midtrans-utils.php index 4cb0926..7de6a0f 100644 --- a/class/class.midtrans-utils.php +++ b/class/class.midtrans-utils.php @@ -130,5 +130,40 @@ function wp_get_inline_script_tag($javascript, $attributes = array()){ } } + /** + * In case Snap API return 406 duplicate order ID, this helper func will generate + * new order id that is to prevent duplicate, by adding suffix on the order_id string + * @TAG: order-suffix-separator + * @param string the original WC order_id + * @return string the non duplicate order_id added with suffix + */ + public static function generate_non_duplicate_order_id($order_id){ + $suffix_separator = '-wc-mdtrs-'; + $date = new DateTime(); + $unix_timestamp = $date->getTimestamp(); + + $non_duplicate_order_id = $order_id.$suffix_separator.$unix_timestamp; + return $non_duplicate_order_id; + } + + /** + * Retrieve original WC order_id from a non duplicate order_id produced by function above: + * generate_non_duplicate_order_id. This will check if the suffix separator exist, + * and split it to get the original order_id. + * @TAG: order-suffix-separator + * @param string any order_id either original or non duplicate version + * @return string the original WC order_id + */ + public static function check_and_restore_original_order_id($non_duplicate_order_id){ + $suffix_separator = '-wc-mdtrs-'; + $original_order_id = $non_duplicate_order_id; + if(strpos($non_duplicate_order_id, $suffix_separator) !== false){ + $splitted_order_id_strings = explode($suffix_separator, $non_duplicate_order_id); + // only return the left-side of the separator, ignore the rest + $original_order_id = $splitted_order_id_strings[0]; + } + return $original_order_id; + } + } ?> \ No newline at end of file diff --git a/class/midtrans-admin-settings.php b/class/midtrans-admin-settings.php index 38f6836..f12b0f8 100644 --- a/class/midtrans-admin-settings.php +++ b/class/midtrans-admin-settings.php @@ -64,7 +64,7 @@ 'notification_url_display' => array( 'title' => __( 'Notification URL value', 'midtrans-woocommerce' ), 'type' => 'title', - 'description' => __( 'After you have filled required config above, don\'t forget to scroll to bottom and click Save Changes button.

Copy and use this recommended Notification URL '.$this->get_main_notification_url().' into "Midtrans Dashboard > Settings > Configuration > Notification Url". This will allow your WooCommerce to receive Midtrans payment status, which auto sync the payment status.','midtrans-woocommerce'), + 'description' => __( 'After you have filled required config above, don\'t forget to scroll to bottom and click Save Changes button.

Copy and use this recommended Notification URL '.$this->get_main_notification_url().' into "Midtrans Dashboard > Settings > Configuration > Notification Url". This will allow your WooCommerce to receive Midtrans payment status, which auto sync the payment status.','midtrans-woocommerce'), ), 'label_config_separator' => array( 'title' => __( 'II. Payment Buttons Appereance Section - Optional', 'midtrans-woocommerce' ), @@ -87,7 +87,7 @@ 'sub_payment_method_image_file_names_str' => array( 'title' => __( 'Button Icons', 'midtrans-woocommerce' ), 'type' => 'text', - 'description' => __( 'You can input multiple payment method names separated by coma (,).
See all available values here, you can copy paste the value, and adjust as needed. Also support https:// url to external image.', 'midtrans-woocommerce' ), + 'description' => __( 'You can input multiple payment method names separated by coma (,).
See all available values here, you can copy paste the value, and adjust as needed. Also support https:// url to external image.', 'midtrans-woocommerce' ), 'placeholder' => 'midtrans.png,credit_card.png', ), 'advanced_config_separator' => array( @@ -164,7 +164,7 @@ 'type' => 'checkbox', 'label' => 'Immediately reduce item stock on Midtrans payment pop-up?', 'description' => __( 'By default, item stock only reduced if payment status on Midtrans reach pending/success (customer choose payment channel and click pay on payment pop-up). Enable this if you want to immediately reduce item stock when payment pop-up generated/displayed.', 'midtrans-woocommerce' ), - 'default' => 'yes' + 'default' => 'no' ), // @Note: only main plugin class config will be applied on notif handler, sub plugin class config will not affect it, check gateway-notif-handler.php class to fix 'ignore_pending_status' => array( diff --git a/midtrans-gateway.php b/midtrans-gateway.php index 3662d71..b6d8a8f 100644 --- a/midtrans-gateway.php +++ b/midtrans-gateway.php @@ -3,7 +3,7 @@ Plugin Name: Midtrans - WooCommerce Payment Gateway Plugin URI: https://github.com/veritrans/SNAP-Woocommerce Description: Accept all payment directly on your WooCommerce site in a seamless and secure checkout environment with Midtrans -Version: 2.30.1 +Version: 2.31.0 Author: Midtrans Author URI: http://midtrans.co.id License: GPLv2 or later diff --git a/public/images/payment-methods/qris_1.png b/public/images/payment-methods/alt_qris.png similarity index 100% rename from public/images/payment-methods/qris_1.png rename to public/images/payment-methods/alt_qris.png diff --git a/public/js/midtrans-payment-page-main.js b/public/js/midtrans-payment-page-main.js index 739d9de..cf0ad3b 100644 --- a/public/js/midtrans-payment-page-main.js +++ b/public/js/midtrans-payment-page-main.js @@ -3,6 +3,21 @@ ;(function( $, window, document ) { var payButton = document.getElementById("pay-button"); + /** + * JS version of func `check_and_restore_original_order_id` of class `WC_Midtrans_Utils` + * @TAG: order-suffix-separator + */ + function check_and_restore_original_order_id(non_duplicate_order_id){ + var suffix_separator = '-wc-mdtrs-'; + var original_order_id = non_duplicate_order_id; + if(non_duplicate_order_id && non_duplicate_order_id.indexOf(suffix_separator)>0){ + var splitted_order_id_strings = non_duplicate_order_id.split(suffix_separator); + // only return the left-side of the separator, ignore the rest + original_order_id = splitted_order_id_strings[0]; + } + return original_order_id; + } + function MixpanelTrackResult(token, merchant_id, cms_name, cms_version, plugin_name, plugin_version, status, result) { var eventNames = { pay: 'pg-pay', @@ -71,6 +86,9 @@ if(wc_midtrans.is_using_map_finish_url){ var finish_url = result.finish_redirect_url; } else { + // @TODO: `&order_id=` param may no longer needed, since we use finish_url_user_cookies + // @TAG: order-id-suffix-handling + result.order_id = check_and_restore_original_order_id(result.order_id); var finish_url = wc_midtrans.finish_url+"&order_id="+result.order_id+"&status_code="+result.status_code+"&transaction_status="+result.transaction_status; } window.location = finish_url; @@ -81,6 +99,8 @@ if (result.fraud_status == 'challenge'){ // if challenge redirect to finish payButton.innerHTML = "Loading..."; + // @TAG: order-id-suffix-handling + result.order_id = check_and_restore_original_order_id(result.order_id); window.location = wc_midtrans.finish_url+"&order_id="+result.order_id+"&status_code="+result.status_code+"&transaction_status="+result.transaction_status; } @@ -88,6 +108,8 @@ // prevent redirect var pending_url = '#'; } else { + // @TAG: order-id-suffix-handling + result.order_id = check_and_restore_original_order_id(result.order_id); var pending_url = wc_midtrans.pending_url+"&order_id="+result.order_id+"&status_code="+result.status_code+"&transaction_status="+result.transaction_status; // redirect to thank you page window.location = pending_url; @@ -106,6 +128,8 @@ MixpanelTrackResult(SNAP_TOKEN, MERCHANT_ID, CMS_NAME, CMS_VERSION, PLUGIN_NAME, PLUGIN_VERSION, 'error', result); // console.log(result?result:'no result'); payButton.innerHTML = "Loading..."; + // @TAG: order-id-suffix-handling + result.order_id = check_and_restore_original_order_id(result.order_id); window.location = wc_midtrans.error_url+"&order_id="+result.order_id+"&status_code="+result.status_code+"&transaction_status="+result.transaction_status; }, onClose: function(){ diff --git a/readme.txt b/readme.txt index 942e359..ede9605 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: yocki, rizdaprasetya Tags: midtrans, snap, payment, payment-gateway, credit-card, commerce, e-commerce, woocommerce, veritrans Requires at least: 3.9.1 Tested up to: 5.8 -Stable tag: 2.30.1 +Stable tag: 2.31.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -13,21 +13,21 @@ Midtrans-WooCommerce is plugin for Midtrans, Indonesian Payment Gateway. Brings This plugin will allow secure online payment on your WooCommerce store, without your customer ever need to leave your WooCommerce store! -Midtrans-WooCommerce is official plugin from [Midtrans](https://midtrans.com), Indonesian Payment Gateway. Brings safety and highly dedicated to customer experience (UX) to WooCommerce.Support various online payment channel. -Support WooCommerce v3 & v2. +Midtrans-WooCommerce is official plugin from [Midtrans](https://midtrans.com). Midtrans is an online payment gateway. We strive to make payments simple & secure for both the merchant and customers. Support various online payment channel. Support WooCommerce v3 & v2. -Please follow [this step by step guide](https://docs.midtrans.com/en/snap/with-plugins?id=wordpress-woocommerce) for complete configuration. +Please follow [this step by step guide](https://docs.midtrans.com/en/snap/with-plugins?id=wordpress-woocommerce) for complete configuration. If you have any feedback or request, please [do let us know here](https://docs.midtrans.com/en/snap/with-plugins?id=feedback-and-request). Payment Method Feature: * Credit card fullpayment and other payment methods. -* Bank transfer, internet banking for various banks +* E-wallet, Bank transfer, internet banking for various banks * Credit card Online & offline installment payment. * Credit card BIN, bank transfer, and other channel promo payment. * Credit card MIGS acquiring channel. * Custom expiry. * Two-click & One-click feature. -* Midtrans Snap all payment method fullpayment. +* Midtrans Snap all supported payment method. +* Optional: Separated specific payment buttons with its own icons. == Installation == @@ -72,6 +72,12 @@ The best way please email to support@midtrans.com, but bugs can be reported in o == Changelog == += 2.31.0 - 2021-08-26 = +* handle duplicated Snap order_id (incase WP is reinstalled, or DB restored) by auto-adding suffix +* improvement on finish url redirect flow, to prevent issue +* handle uncaught error on finish url +* immediate-reduce-stock disabled by default + = 2.30.1 - 2021-08-09 = * prevent issue "cannot inherit abstract function" on outdated PHP v5.0.0 - v5.3.8 & v7.0.0 - v7.1.x * minor description improvement @@ -255,6 +261,12 @@ The best way please email to support@midtrans.com, but bugs can be reported in o == Upgrade Notice == += 2.31.0 - 2021-08-26 = +* handle duplicated Snap order_id (incase WP is reinstalled, or DB restored) by auto-adding suffix +* improvement on finish url redirect flow, to prevent issue +* handle uncaught error on finish url +* immediate-reduce-stock disabled by default + = 2.30.1 - 2021-08-09 = * prevent issue "cannot inherit abstract function" on outdated PHP v5.0.0 - v5.3.8 & v7.0.0 - v7.1.x * minor description improvement @@ -409,6 +421,6 @@ Support additional feature like installment, MIGS acq, and bin promo. == Get Help == * [Midtrans WooCommerce Configuration Guide](https://docs.midtrans.com/en/snap/with-plugins?id=wordpress-woocommerce) * [Midtrans registration](https://account.midtrans.com/register) +* [Midtrans Support Contact](https://midtrans.com/id/contact-us) * [Midtrans Documentation](https://docs.midtrans.com) -* [Midtrans Snap API Documentation](https://snap-docs.midtrans.com) * [Midtrans-WooCommerce Wiki](https://github.com/veritrans/SNAP-Woocommerce/wiki) \ No newline at end of file