Hide Payment Method WooCommerce after Failure of first try - php

I am trying to hide the payment method once user click place order using payment method lets say 'B' which returns in failure of payment.
public function process_payment( $order_id ) {
global $woocommerce;
$order = new WC_Order( $order_id );
if($this->api->somemethod()){
// Payment complete
$order->payment_complete();
return array(
'result' => 'success',
'redirect' => $this->get_return_url( $order )
);
}
else {
wc_add_notice( pll__('Payment error: You cannot use this payment method, Choose another method '), 'error' );
return;
}
}
I have used following filter
add_filter( 'woocommerce_payment_gateways', 'add_gateway_cb' );
But it only works on page reload. I want something which works with Ajax process payment or at least triggers the payment methods to reload upon failure.
Alternatively:
I can use custom JS on place order click.

Related

How to reload checkout page if payment failed in Woocommerce

I have a custom payment gateway where I need to reload checkout page if payment failed.
Reason:
When Card details submitted the payment gateway generates a card token which I need to process the payment but we can use the card token only once with a request.
What I need:
Currently, I am just showing the error message and a return when payment is failed.
if($payment_status['status']){
$order->update_status( 'on-hold', __( "ABC Payment Done\n", 'stackoverflow' ) );
wc_reduce_stock_levels($order_id);
WC()->cart->empty_cart();
return array(
'result' => 'success',
'redirect' => $this->get_return_url( $order )
);
}else{
wc_add_notice( $payment_status['message'] , 'error' );
return;
}
How can I reload/refresh the page if payment failed? S user can enter the card details again and we can process the payment.
Or any other suggestion?
It depends if you want it sync or async.
Sync way:
header("Refresh:0");
It refreshes your current page. You can specify also the page you want:
header("Refresh:0; url=another_page.php");
Async way:
Generate the content dynamically, clean all changes and tokens.
Fksjii is true.
However, it might be cleaner to have a specified page that generates token code.
With an XHR request you can get it, then submit every data with an XHR and wait for a callback.
However, if you can't edit JS code you might do
if ($failed) {
unset($_REQUEST);
Header("Refresh: 0");
exit;
}
Do not forget to clean $_GET or $_POST data before reloading your page.
It is always very important to exit even after a redirection sent in the headers. Indeed, headers are sent but code continue to execute and it could create some bugs.
You need return the array for example:
if ( $payment_status['status'] ) {
$order->update_status( 'on-hold', __( "ABC Payment Done\n", 'stackoverflow' ) );
wc_reduce_stock_levels($order_id);
WC()->cart->empty_cart();
return array(
'result' => 'success',
'redirect' => $this->get_return_url( $order )
);
} else {
$order->update_status( 'failed', __( 'Payment error:', 'woocommerce' ) . $this->get_option( 'error_message' ) );
wc_add_notice( $payment_status['message'] , 'error' );
WC()->cart->empty_cart();
return array(
'result' => 'failure',
'redirect' => WC()->cart->get_checkout_url()
);
}
That code will redirect to the checkout page. Hope help you.

AJAX add custom product order meta data from single product page

I've built an add to cart process that successfully creates a "personalisation" field when the item is added to the cart. Using the standard 'add to cart' button, the page reloads and all is well. I wanted to improve the UX by making the basket update via AJAX, which I've implemented and again working ok.
However, this stops the cart item data meta from registering because the request is not defined - the hook isn't able to grab input values from the page any more. The hook I'm using to define and write the meta fields to is 'woocommerce_add_cart_item_data'
I tried creating my own separate AJAX function to define the content of the field separately, using $_SESSION instead of $_REQUEST (inspired by this answer -> how to pass ajax data to woocommerce filter?)
Unfortunately this seemingly fires after 'woocommerce_add_cart_item_data' has run, so the meta data does not come through (in fact it ends up showing on the next product added, since the variable is defined late and not accessed until the 2nd product is added).
I'd really appreciate some help with either:
(1) Amending the code so 'woocommerce_add_cart_item_data' is able to take data from the AJAX add to cart action I made, or
(2) Amend my custom AJAX call so session data is able to be applied in a timely order (unsure if this is achievable based on what I've seen so far).
// Create AJAX add to cart button
function add_cart_btn($prod) {
echo apply_filters( 'woocommerce_loop_add_to_cart_link',
sprintf( 'Add to bag',
esc_url( $prod->add_to_cart_url() ),
esc_attr( $prod->id ),
esc_attr( $prod->get_sku() ),
$prod->is_purchasable() ? 'add_to_cart_button' : '',
esc_attr( $prod->product_type ),
esc_html( $prod->add_to_cart_text() )
),
$prod );
}
add_cart_btn($product);
// functions.php
/* Saves field data */
function save_add_custom_info_field( $cart_item_data, $product_id ) {
if( isset( $_REQUEST['custom_info_message'] ) ) {
// *** $_REQUEST['custom_info_message'] not defined ***
$cart_item_data[ 'custom_info_message' ] = $_REQUEST['custom_info_message'];
$cart_item_data['unique_key'] = md5( microtime().rand() );
}
return $cart_item_data;
}
add_action( 'woocommerce_add_cart_item_data', 'save_add_custom_info_field', 10, 2 );
/* Renders field entry on cart and checkout */
function render_mssg_meta_on_cart_and_checkout( $cart_data, $cart_item = null ) {
$custom_items = array();
if( !empty( $cart_data ) ) {
$custom_items = $cart_data;
}
if( isset( $cart_item['custom_info_message'] ) ) {
$custom_items[] = array( "name" => 'Personalisation / Customisation', "value" => $cart_item['custom_info_message'] );
}
return $custom_items;
}
add_filter( 'woocommerce_get_item_data',
'render_mssg_meta_on_cart_and_checkout', 10, 2 );
// *** Attempted solution with AJAX - Use $_SESSION['message'] in 'save_add_custom_info_field' function above instead of $_REQUEST... delivered after 'woocommerce_add_cart_item_data' so doesn't work
function set_customised_message() {
$message = $_POST['message'];
define( 'DOING_AJAX', true );
if ( ! defined( 'WP_ADMIN' ) ) {
define( 'WP_ADMIN', true );
}
session_start();
$_SESSION['message'] = $message;
echo $message;
die();
}
I expect custom meta to show on the cart/checkout pages, but it isn't due to the variable being either $cart_item['custom_info_message'] being undefined, or session variable from AJAX call not being available.
I've managed to solve this by updating the product meta data after it is added to the cart (via AJAX) - amending set_customised_message function above. Note the AJAX call required a timeout delay of 1s in order to work.
Leaving it here in case anyone else is having a similar issue.
function set_customised_message() {
$cart = WC()->cart->cart_contents;
foreach( $cart as $cart_item_id=>$cart_item ) {
echo $cart_item['unique_key'];
$cart_item['custom_info_message'] = $message;
echo $cart_item['custom_info_message'];
WC()->cart->cart_contents[$cart_item_id] = $cart_item;
}
WC()->cart->set_session();
}
Thanks to https://pluginrepublic.com/how-to-update-existing-woocommerce-cart-meta-data/ for this solution

Remove cancel button from WooCommerce My account Orders conditionally

I want to make sure the cancel button is not visible in my-account> my-order when 'Payment Method Title' is 'Npay'.
The 'Npay' is an external payment gateway and does not work with the commerce. Therefore, payment cancellation must be done externally only.
add_filter('woocommerce_my_account_my_orders_actions', 'remove_my_cancel_button', 10, 2);
function remove_my_cancel_button($actions, $order){
if ( $payment_method->has_title( 'Npay' ) ) {
unset($actions['cancel']);
return $actions;
}
}
To remove the cancel button from My account Orders, we use the following:
add_filter('woocommerce_my_account_my_orders_actions', 'remove_myaccount_orders_cancel_button', 10, 2);
function remove_myaccount_orders_cancel_button( $actions, $order ){
unset($actions['cancel']);
return $actions;
}
But to remove the cancel button from My account Orders based on the payment title, you will use the WC_Order method get_payment_method_title() like:
add_filter('woocommerce_my_account_my_orders_actions', 'remove_myaccount_orders_cancel_button', 10, 2);
function remove_myaccount_orders_cancel_button( $actions, $order ){
if ( $order->get_payment_method_title() === 'Npay' ) {
unset($actions['cancel']);
}
return $actions;
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
The main variable argument $actions need to be returned at the end outside the IF statement

woocommerce_checkout_order_processed hook executing function twice

I have attached a function to the woocommerce_checkout_order_processed hook:
//check if woocommerce is acive
if (in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins')))) {
add_action('woocommerce_checkout_order_processed', 'wc_on_place_order');
}
The wc_on_place_order function is to be executed after the user clicks on the PLACE ORDER button. However, it's so odd that the function is executed twice.
My wc_on_place_order function calls an external api written in C#:
function wc_on_place_order( $order_id ) {
global $wpdb;
// get order object and order details
$order = new WC_Order( $order_id );
// get product details
$items = $order->get_items();
//return $items;
$products = array();
foreach ($items as $item) {
array_push($products,
array('userid' => $order->user_id, 'descr' => $item['name'], 'amt' => (float)$item['line_total'])
);
}
//passing $products to external api using `curl_exec`
. . . .
//on successful call, the page should be showing an `alert`, however, it does not
// the handle response
if (strpos($response,'ERROR') !== false) {
print_r($response);
} else {
echo "<script type='text/javascript'>alert($response)</script>";
}
}
After debugging on C# API, I noticed that it the service is being called twice, thus, the checkout is being saved twice to the API database.
Is there something wrong with the wc_on_place_order function or is woocommerce_checkout_order_processed called twice when clicking the PLACE ORDER?
Interestingly, adding return $items after $items = $order->get_items() somehow, the C# api was only called once:
// get product details
$items = $order->get_items();
return $items; //this line
Why is that so?
One more question I would like to ask, is woocommerce_checkout_order_processed the right hook I should use? I have been searching the web for the correct hook to use and it seems that woocommerce_checkout_order_processed is used in the most post. I can't use the woocommerce_thankyou hook as it is also calling the API if I refresh the page.
Any idea will be really appreciated.
EDIT:
I used woocommerce_after_checkout_validation hook which fires after pre-validations on checkout. I can't remember though why woocommerce_checkout_order_processed is being fired twice but I just changed some kind of settings in WooCommerce options page. I can't remember which.
Useful Links from the Comments:
Visual Representation of the WooCommerce hooks
WordPress Action References
I always use the hook woocommerce_payment_complete This will fire as the name suggests after the order has been paid.
function order_payment_complete( $order_id ){
$order = wc_get_order( $order_id );
/* Insert your code */
}
add_action( 'woocommerce_payment_complete', 'order_payment_complete' );

Filter WooCommerce email shipping text value

I'm attempting to use the 'woocommerce_order_shipping_to_display' filter to show 'Free' in the WooCommerce email table when no shipping charges will be incurred. In the image, I'm trying to get 'Flat Rate' to display as 'Free'. My PHP is moderate, and I can't get my code quite there. Does anyone see what I could be missing?
My function breaks the actual email so I can't see if it's working or not. When the function is in place, and I resend a processing email, instead of resending, it opens in the same browser window without anything below the main email body table data (screenshot).
/* return custom text on email when shipping is free */
add_filter( 'woocommerce_order_shipping_to_display', 'filter_email_shipping_text', 10, 2 );
function filter_email_shipping_text( $shipping, $this ) {
global $woocommerce;
if ( $this->get_shipping_method() ) {
$shipping = __( 'Free', 'woocommerce' );
}
return $shipping;
}
Shipping Email
Broken Post Attempt
UPDATED
I've scavenged for a few similar cases, and I've rewritten the function below. This still doesn't work, but I think I needed to initialise the WC_Order class to tell the function which order to reference. Can anyone help me finish it off?
/* return custom text on email when shipping is free */
add_filter( 'woocommerce_order_shipping_to_display', 'filter_email_shipping_text', 10 );
function filter_email_shipping_text( $shipping ) {
global $wcdn;
$order = new WC_Order($wcdn->print->order_id);
if ( $order->get_shipping_method() || $order->order_shipping = 0 ) {
$shipping = sprintf(__( 'Free', 'woocommerce' ));
}
return $shipping;
}
CONCLUSION
I was able to fix my function and it now works how intended. This is the working function and filter in case anyone need to replicate.
/* return custom text on email when shipping is free */
add_filter( 'woocommerce_order_shipping_to_display', 'filter_email_shipping_text', 10, 2 );
function filter_email_shipping_text( $shipping, $order_id ) {
global $woocommerce, $post;
$order = new WC_Order( $order_id );
if ( $order->order_shipping == 0 ) {
$shipping = sprintf(__( 'Free!', 'woocommerce' ));
}
return $shipping;
}

Categories