PluginUs.Net - Business Tools for WooCommerce and WordPress

[realize your idea - make your dreams come true]
Botoscope is currently in early access

Support Forum

You need to log-in to create request (topic) to the support

Follow-up on MYR to USD checkout conversion – Converting based on MYR fixed pricing & shipping rates

The support doesn work on Saturdays and Sundays, so some Friday requests can be answered on Monday. If you have problems with registration ask help on contact us page please
If you not got email within 24~36 business hours, firstly check your spam box, and if no any email from the support there - back to the forum and read answer here. DO NOT ANSWER ON EMAILS [noreply@pluginus.net] FROM THE FORUM!! Emails are just for your info, all answers should be published only here.
The support doesn work on Saturdays and Sundays, so some Friday requests can be answered on Monday.

Hi FOX Support Team,

Thank you so much for the detailed explanation and the solution provided in this thread (referring to topic). The snippet to switch MYR visitors to USD upon checkout works smoothly to bypass PayPal's CURRENCY_NOT_SUPPORTED issue!

However, I have encountered a specific scenario regarding fixed prices and shipping rates:

On our site, we use FOX's "Individual fixed prices rules for each product" (and custom shipping rates per currency/region) because our local product pricing and shipping costs for Malaysia (MYR) differ from our standard USD pricing for other global regions.

When the snippet switches the currency from MYR to USD at checkout, WooCommerce automatically pulls the product's default USD fixed price and USD shipping rate, rather than taking the MYR fixed price / shipping amount and converting it into USD based on the exchange rate.

My Question: Is it possible to modify the snippet so that when the checkout switches to USD, it calculates the order total by converting the MYR fixed prices and MYR shipping costs into USD (using the exchange rate), instead of applying the default USD fixed product prices and shipping rules?

Any advice or code adjustments on how to achieve this would be greatly appreciated!

Best regards,

Bennett

Hello Bennett

Good to hear the first snippet solved the PayPal error. What you ran into now is not a bug, it is what fixed prices are for, so let me explain the mechanics first and then give you two ways forward.

Why the MYR prices disappear when the currency switches

A fixed price is an absolute value that belongs to one currency. It is stored per product as its own value and it is never passed through the exchange rate, that is the whole point of the feature. So when the checkout switches the visitor to USD, the plugin does exactly what it is designed to do: it looks for the USD fixed price and uses it. The MYR value is not converted, because a fixed price is not meant to be converted.

Fixed shipping works the same way, and there is one more detail there. The shipping conversion only runs when the active currency differs from the shop base currency. Once the checkout switches to your base currency, the plugin stops touching shipping rates at all and WooCommerce uses the plain rate you configured in the shipping zone.

So what you are asking for goes against the design of the feature you are using. That does not mean it cannot be done, but it does mean there is no setting for it.

Option one, the approach without any code

The real problem is that you are using the CURRENCY as a marker for the REGION. That is why everything falls apart the moment the currency changes.

The plugin has a separate mechanism for exactly this case: per country prices, in the same product price block where you set the fixed prices. A price defined there is bound to the country, not to the currency, and unlike a fixed price it does go through the exchange rate. So one Malaysian price stays Malaysian and is shown converted in whatever currency the visitor is looking at, including after the checkout switches them to USD.

To use it you would enable the geolocation price option in the plugin settings, and move your Malaysian values from the fixed price fields to the per country fields. Important: as long as a fixed price exists for the active currency it wins, so the MYR fixed prices have to be removed for this to take effect.

We have not tested this against your particular setup, so please try it on a staging copy first. If it fits, you need no custom code at all, and this is by far the healthier solution.

This covers products only. Shipping would still need the custom route below.

Option two, convert the order instead of the cart

If the per country prices do not fit your business, there is a second approach, and it is cleaner than trying to intercept prices at the checkout.

Instead of switching the currency before the order is created, you let the whole cart and checkout stay in MYR. All your Malaysian fixed prices and your MYR shipping rates are then applied by the plugin normally, with no interference. The order is created in MYR, and immediately after that, before it reaches PayPal, the order amounts are converted to USD by the exchange rate and the order currency is changed.

This works because of the order of operations inside WooCommerce. The checkout validates the form, creates the order, fires woocommerce_checkout_order_processed, and only then calls the payment gateway. We verified this against WooCommerce 11.0.1: the order is created at line 1411 of class-wc-checkout.php, the hook fires at line 1427, and the gateway is called afterwards. The block based checkout follows the same sequence through its own hook. So the gateway simply reads an order that is already in USD and knows nothing about the conversion. Nothing is faked and no JavaScript is involved.

Before you use this, one question that decides whether it will work at all

Which PayPal plugin are you running, and where do the PayPal buttons appear? If PayPal is a normal gateway that you select on the checkout page and the payment starts after you press Place Order, this approach works.

If you use WooCommerce PayPal Payments with the smart buttons, and the PayPal button appears on the cart page or the product page, it will not work. Those buttons create the PayPal order from the cart before a WooCommerce order exists at all, so our code runs too late. Handling that case means working inside that plugin's own filters, which is a different job.

Please also tell us how your per currency shipping rates are configured. Our fixed shipping fields exist only on the Flat rate method. If your rates come from somewhere else, the shipping part of this needs a different answer.

The code

Add this to the functions.php of your child theme. Check the gateway id list against your site first.

// Convert an order from MYR to USD after it is created but before it is
// sent to PayPal. Prices and shipping are calculated by the plugin in MYR
// as usual, only the resulting order amounts are converted.
add_action( 'woocommerce_checkout_order_processed', 'my_woocs_convert_order_for_paypal', 5, 3 );
add_action( 'woocommerce_store_api_checkout_order_processed', 'my_woocs_convert_order_for_paypal_blocks', 5, 1 );

function my_woocs_convert_order_for_paypal_blocks( $order ) {
	my_woocs_convert_order_for_paypal( $order->get_id(), array(), $order );
}

function my_woocs_convert_order_for_paypal( $order_id, $posted_data = array(), $order = null ) {

	$from_currency = 'MYR';
	$to_currency   = 'USD';

	// Gateway ids this conversion applies to. Check the real id on your site.
	$gateways = array( 'paypal', 'ppec_paypal', 'ppcp-gateway' );

	global $WOOCS;

	if ( ! is_object( $WOOCS ) ) {
		return;
	}

	if ( ! $order ) {
		$order = wc_get_order( $order_id );
	}

	if ( ! $order ) {
		return;
	}

	// Never convert the same order twice.
	if ( $order->get_meta( '_woocs_converted_from' ) ) {
		return;
	}

	if ( $order->get_currency() !== $from_currency ) {
		return;
	}

	if ( ! in_array( $order->get_payment_method(), $gateways, true ) ) {
		return;
	}

	$currencies = $WOOCS->get_currencies();

	if ( empty( $currencies[ $from_currency ]['rate'] ) || empty( $currencies[ $to_currency ]['rate'] ) ) {
		return;
	}

	$rate_from = floatval( $currencies[ $from_currency ]['rate'] );
	$rate_to   = floatval( $currencies[ $to_currency ]['rate'] );

	if ( $rate_from <= 0 || $rate_to <= 0 ) {
		return;
	}

	// Rates are relative to the shop base currency, so the value goes
	// through the base: MYR -> base -> USD.
	$k        = $rate_to / $rate_from;
	$decimals = wc_get_price_decimals();

	$original_total = $order->get_total();

	foreach ( $order->get_items( array( 'line_item', 'fee' ) ) as $item ) {
		$item->set_subtotal( wc_format_decimal( $item->get_subtotal() * $k, $decimals ) );
		$item->set_total( wc_format_decimal( $item->get_total() * $k, $decimals ) );
		$item->save();
	}

	foreach ( $order->get_items( 'shipping' ) as $item ) {
		$item->set_total( wc_format_decimal( $item->get_total() * $k, $decimals ) );
		$item->save();
	}

	foreach ( $order->get_items( 'coupon' ) as $item ) {
		$item->set_discount( wc_format_decimal( $item->get_discount() * $k, $decimals ) );
		$item->set_discount_tax( wc_format_decimal( $item->get_discount_tax() * $k, $decimals ) );
		$item->save();
	}

	$order->set_currency( $to_currency );

	$order->update_meta_data( '_woocs_converted_from', $from_currency );
	$order->update_meta_data( '_woocs_conversion_rate', $k );
	$order->update_meta_data( '_woocs_original_total', $original_total );

	// Taxes are recalculated from the tax rates, the order total is summed
	// from the already converted line items.
	$order->calculate_taxes();
	$order->calculate_totals( false );

	$order->add_order_note(
		sprintf(
			'Converted from %s %s to %s for the PayPal payment. Rate used: %s',
			$original_total,
			$from_currency,
			$to_currency,
			$k
		)
	);

	$order->save();
}

What to expect from it

The customer keeps seeing MYR through the whole cart and checkout, and sees USD for the first time on the PayPal page. Please put a clear line on your checkout telling Malaysian customers that the payment is processed in USD. Without it you will get complaints and disputes, and that is a real risk, not a formality.

The USD amount will never match the MYR amount neatly, because the rate is live.

The order, the confirmation email and the thank you page will all be in USD after the conversion. The original currency, the original total and the rate used are stored on the order and written into the order notes, so you can always reconstruct what happened.

Where we stand on this

To be straight with you: this is custom development for your specific business setup, not part of the plugin, and we are not able to guarantee it. We can give you the idea and the mechanics, which is what you have above. We have not tested it against partial refunds, fixed amount coupons, subscriptions, invoicing plugins or your accounting workflow. Test it on a staging copy with real orders before you put it on the live shop.