customer notification not send after status update

  • Posts: 330
  • Thank you received: 24
2 weeks 6 days ago #372154

-- url of the page with the problem -- : www.cultuuroudenbosch.nl/
-- HikaShop version -- : 6.5.0
-- Joomla version -- : 6.1.1
-- PHP version -- : 8.4

Hi,
We are using RO Payments for handling the payments and the return to HikaShop.

In here we set the status of the order to "Shipped" because it's about digital tickets.
I have updated the setting to always notify the customer when updating an order.

The problem is that this e-mail is not send when the status is updated via RO Payments but is when done manually.
Checked with Roland and he tells me that when the test are done and the statuses meet the requirements he execute this code :
$history->notified = 1;

Now can you help us figure out if anything is wrong with this approach?

Please Log in or Create an account to join the conversation.

  • Posts: 85765
  • Thank you received: 14071
  • MODERATOR
2 weeks 6 days ago #372156

Hi,

Setting notified = 1 is the right idea, the customer status email is gated on that flag, but it only goes out if the call to modifyOrder is done in a way that actually saves the order. Two things need to be true, and the second one is the most likely reason it works manually but not through RO Payments.

1. The history object (with notified = 1) has to be passed as the third argument of modifyOrder, for example $this->modifyOrder($order_id, 'Shipped', $history). HikaShop rebuilds the history record from that argument, so anything set directly on $order->history is ignored. The simplest equivalent is to pass true as the third argument:

$this->modifyOrder($order_id, 'Shipped', true)

true maps straight to notified = 1.

2. modifyOrder has to be called with the integer order id, not the order object. This is the important one. modifyOrder only saves the order (and therefore only sends the customer email) when the first argument is an id and not an object. Passing the order object is meant for the order creation flow, where the order is saved later by the controller. For a status change on an existing order, passing the object means no save happens, so the customer notification is never sent. That matches what you see: the manual backend update sends the mail, the RO Payments update does not.

So could you ask Roland to check that he calls modifyOrder with the integer order id (not the order object), and passes the notification as the third argument (true, or the history object with notified = 1). With that, the Shipped status email will be sent like it is for a manual update.

One side note so there is no confusion: the email parameter and the "payment notification email" setting control a separate notification that goes to the shop owner, not to the customer. The customer status email is the notified flag on the history, which is what you already identified.

Please Log in or Create an account to join the conversation.

  • Posts: 270
  • Thank you received: 37
  • Hikamarket Multivendor Hikaserial Subscription Hikashop Business
2 weeks 6 days ago #372162

useless response from me after Nicolas' message, that's why I deleted it, sorry :whistle:

Last edit: 2 weeks 6 days ago by oxido. Reason: useless response from me after Nicolas' message

Please Log in or Create an account to join the conversation.

  • Posts: 330
  • Thank you received: 24
2 weeks 1 day ago #372188

Hi Nicolas,

Roland added some extra logging to check what is done. This comes from the logging when doing a successful payment:

[2026-06-08 13:45:20] Send customer to URL: www.mollie.com/checkout/select-method/LfVv44peTFeP4ZWM29LSJ
[2026-06-08 13:45:27] == Start Payment provider processing
[2026-06-08 13:45:27] Found log ID: 104
[2026-06-08 13:45:27] Query string: pid=nkpy8QKMI9TL7JFC0ecrJEDGXoFblPEipwXhC6G9f7QpRwnu7L
[2026-06-08 13:45:27] Get transaction status
[2026-06-08 13:45:27] Customer processing
[2026-06-08 13:45:27] [Customer] Query string: pid=nkpy8QKMI9TL7JFC0ecrJEDGXoFblPEipwXhC6G9f7QpRwnu7L
[2026-06-08 13:45:27] [Customer] Is transaction processed: No
[2026-06-08 13:45:27] [Customer] Going to sleep
[2026-06-08 13:45:27] Received payment status: paid
[2026-06-08 13:45:27] Has chargebacks: No
[2026-06-08 13:45:27] Received card: ideal
[2026-06-08 13:45:28] Payment sequence: oneoff
[2026-06-08 13:45:28] Processing OK status
[2026-06-08 13:45:28] Payment has SUCCESS status
[2026-06-08 13:45:28] Current order status P
[2026-06-08 13:45:28] Send customer change status email
[2026-06-08 13:45:28] {"order_number":"B-00111","order_id":111,"order_total":"37.50000","order_status":"shipped","user_email":"xxxx@xxxx.nl","order_comment":"Transactie nummer: tr_LfVv44peTFeP4ZWM29LSJ"}
[2026-06-08 13:45:28] Send admin order payment email
[2026-06-08 13:45:28] Setting payment as processed
[2026-06-08 13:45:28] Start Notify extension
[2026-06-08 13:45:28] Result status is valid
[2026-06-08 13:45:28] Starting payment notification
[2026-06-08 13:45:28] Order status is created
[2026-06-08 13:45:28] Get status code for result SUCCESS and profile mollie
[2026-06-08 13:45:28] Status code RO Payments: C
[2026-06-08 13:45:28] Status code RO Hikashop: shipped
[2026-06-08 13:45:28] Work with status: shipped
[2026-06-08 13:45:28] Set History notified to 1
[2026-06-08 13:45:28] History notified is 1
[2026-06-08 13:45:28] History details
[2026-06-08 13:45:28] {"notified":1,"amount":37.5,"type":"payment","data":"Transactie nummer: mollie_6a26c76e4cd25"}
[2026-06-08 13:45:28] Modifying order 111 to status shipped
[2026-06-08 13:45:28] [Customer] Waking up
[2026-06-08 13:45:28] [Customer] Result: SUCCESS
[2026-06-08 13:45:28] [Customer] Is transaction processed: Yes
[2026-06-08 13:45:28] [Customer] Payment already processed
[2026-06-08 13:45:28] [Customer] Payment result is SUCCESS
[2026-06-08 13:45:28] [Customer] Payment status is valid
[2026-06-08 13:45:28] Redirect to: xxx.xxxxxx.nl/index.php?option=com_hikas...EipwXhC6G9f7QpRwnu7L
[2026-06-08 13:46:27] == Start Payment provider processing
[2026-06-08 13:46:27] Found log ID: 104
[2026-06-08 13:46:27] Query string: pid=nkpy8QKMI9TL7JFC0ecrJEDGXoFblPEipwXhC6G9f7QpRwnu7L
[2026-06-08 13:46:27] == End Payment provider processing
[2026-06-08 13:48:26] == Start Payment provider processing
[2026-06-08 13:48:26] Found log ID: 104
[2026-06-08 13:48:26] Query string: pid=nkpy8QKMI9TL7JFC0ecrJEDGXoFblPEipwXhC6G9f7QpRwnu7L
[2026-06-08 13:48:26] == End Payment provider processing
J! Info
Request
Session
Profile
Queries19
5.109MB
305ms

Hope this is giving some inspiration on the matter :-)

Please Log in or Create an account to join the conversation.

  • Posts: 85765
  • Thank you received: 14071
  • MODERATOR
2 weeks 1 day ago #372191

Hi,

There are actually two modifyOrder methods and they do not take the arguments in the same order, which is almost certainly what is happening here.

- The payment plugin one (called as $this->modifyOrder(...) from a plugin that extends hikashopPaymentPlugin) takes the history as its 3rd argument:
$this->modifyOrder($order_id, 'shipped', $history, $email);
That is the form you see in the example plugin and in our documentation and what I was referring to in my previous message here.

- The order class one (hikashop_get('class.order')->modifyOrder(...)) takes the payment method name as its 3rd argument and the history as its 4th:
hikashop_get('class.order')->modifyOrder($order_id, 'shipped', 'ro_payments', $history);

So if RO Payments calls the order class directly with the history as the 3rd argument (the usual payment-plugin form), the history object lands in the payment-name slot and the real history stays empty, so the notified flag is lost. The order is still saved and the status still changes to shipped, but no customer email goes out, which matches your logs exactly (your own history object is correctly set to notified = 1, but it never reaches the notification code).

If you are calling the order class, move the flag to the 4th argument:

hikashop_get('class.order')->modifyOrder($order_id, 'shipped', 'ro_payments', true);
// or, with your history object (history->notified = 1):
hikashop_get('class.order')->modifyOrder($order_id, 'shipped', 'ro_payments', $history);

If RO Payments is itself a HikaShop payment plugin and uses $this->modifyOrder, then the 3rd-argument form is correct and the flag is fine; in that case the email would be empty for another reason.

Please Log in or Create an account to join the conversation.

  • Posts: 330
  • Thank you received: 24
2 weeks 19 hours ago #372196

Hi,

Roland is using this:
$this->modifyOrder as payment plugin.

This code has not been changed for a long time.

He would also like to know what the conditions are for sending this e-mail with the (in this case) serial tickets. Is this just that "notified" ?

Please Log in or Create an account to join the conversation.

  • Posts: 85765
  • Thank you received: 14071
  • MODERATOR
2 weeks 15 hours ago #372197

Hi,

To answer your direct question first: for a status change, the customer email is sent when three things are true, not just "notified":
1. the history "notified" flag is set (which you confirmed),
2. no plugin clears the send flag during the onAfterOrderUpdate event,
3. the order has a customer email address to send to.

HikaSerial hooks onAfterOrderUpdate too, but it only assigns the serials there; it does not clear the send flag. At send time it adds the serials into the status email rather than blocking it. So a serial-ticket order has no extra email condition compared to a normal order, it should send like any other.

Since "notified" is confirmed and HikaSerial is not blocking it, the cleanest next step is to log the email decision so we see exactly where it is lost. In administrator/components/com_hikashop/classes/order.php, find the order update branch (search for "history_notified" near "onAfterOrderUpdate") and add the four hikashop_writeToLog lines marked NEW:

			$send_email = @$order->history->history_notified;
			hikashop_writeToLog('EMAILDEBUG order '.@$order->order_id.': update branch reached, notified='.var_export($send_email,true).', status='.@$order->order_status);   // NEW
			$app->triggerEvent('onAfterOrderUpdate', array( &$order, &$send_email) );
			hikashop_writeToLog('EMAILDEBUG order '.@$order->order_id.': after onAfterOrderUpdate, send_email='.var_export($send_email,true));   // NEW

			$historyClass->addRecord($order);

			if(!$send_email) {
				hikashop_writeToLog('EMAILDEBUG order '.@$order->order_id.': SUPPRESSED, send_email empty after the plugins');   // NEW
				return $order->order_id;
			}

a bit further down, where the mail is sent:
			if(!empty($order->mail)) {
				$mailClass = hikashop_get('class.mail');
				hikashop_writeToLog('EMAILDEBUG order '.@$order->order_id.': dst_email='.var_export(@$order->mail->dst_email,true).', mail_name='.@$order->mail->mail_name);   // NEW
				if(!empty($order->mail->dst_email)) {
					$mailClass->sendMail($order->mail);
					hikashop_writeToLog('EMAILDEBUG order '.@$order->order_id.': sendMail done, success='.var_export(@$mailClass->mail_success,true));   // NEW
				}

Then place one order paid through RO Payments (the failing case) and change one order's status by hand in the backend (the working case with the user notification activated), and send me the EMAILDEBUG lines from the HikaShop log for both. They will tell us exactly where it diverges:

- if the "update branch reached" line is missing for the RO Payments order, then the order modification did not save through this path at all (for example the order object, not its id, was passed), and the issue is on the call side, not the email,
- if "send_email" becomes empty after onAfterOrderUpdate, a plugin is clearing it,
- if send_email stays set but dst_email is empty, the order has no recipient in that context,
- if sendMail runs with success=false, it is a mailer problem.


If that second line shows send_email becoming empty after onAfterOrderUpdate (the most likely case), a plugin is clearing it. To find exactly which one, replace that single dispatch line:
			$app->triggerEvent('onAfterOrderUpdate', array( &$order, &$send_email) );

with this block, which runs the same plugins one by one (each exactly once, so the order is still processed normally) and logs the culprit:
			$diagDispatcher = new \Joomla\Event\Dispatcher();
			foreach(JPluginHelper::getPlugin('hikashop') as $diagP) {
				$diagClass = 'plgHikashop'.ucfirst($diagP->name);
				if(!class_exists($diagClass))
					continue;
				try {
					$diagInst = new $diagClass($diagDispatcher, (array)$diagP);
					if(!method_exists($diagInst, 'onAfterOrderUpdate'))
						continue;
					$diagBefore = $send_email;
					$diagInst->onAfterOrderUpdate($order, $send_email);
					hikashop_writeToLog('EMAILDEBUG order '.@$order->order_id.': plugin "'.$diagP->name.'" -> send_email='.var_export($send_email,true).($send_email != $diagBefore ? '   <<< CLEARED BY THIS PLUGIN' : ''));
				} catch(\Throwable $diagE) {
					hikashop_writeToLog('EMAILDEBUG order '.@$order->order_id.': plugin "'.$diagP->name.'" could not be run on its own: '.$diagE->getMessage());
				}
			}

The line marked "<<< CLEARED BY THIS PLUGIN" names the culprit. A throwaway dispatcher is used so the plugins are not re-registered on the live one, and each plugin still runs exactly once, so the order processes as usual. Put the original triggerEvent line back once you have the name.

Remove the NEW lines once we have the answer.

Please Log in or Create an account to join the conversation.

  • Posts: 330
  • Thank you received: 24
2 weeks 11 hours ago #372202

Hi Nicolas,

We added the log lines and found out that it stops at step 3:

if send_email stays set but dst_email is empty, the order has no recipient in that context,

Here is the log from the test:

<h3>06.09.26 14:45:22</h3>
EMAILDEBUG order 123: update branch reached, notified=1, status=shipped

<h3>06.09.26 14:45:22</h3>
EMAILDEBUG order 123: after onAfterOrderUpdate, send_email=1

What does this mean for the next step(s)? Anything else we should check and/or test?

Regards,

Roland

Please Log in or Create an account to join the conversation.

  • Posts: 85765
  • Thank you received: 14071
  • MODERATOR
2 weeks 6 hours ago #372205

Hi Roland,

Good, that narrows it down nicely. You are right that it stops at the recipient: the status email is gated on dst_email, and an empty dst_email means no send even though send_email is 1.

Where dst_email comes from: it is only ever filled inside loadOrderNotification(), which loads the customer from the order's order_user_id and sets dst_email to customer->user_email. The important part for your case is when that function is NOT called. In the update branch of the order save, loadOrderNotification only runs when $order->mail is still empty. If $order->mail is already set on the order object at that point, HikaShop takes the other path and never builds dst_email, so it stays empty. The same applies if $order->customer is already set with a user_id matching order_user_id but without a user_email: the customer is then not reloaded and dst_email ends up empty.

That is the difference with the manual update. The backend save carries a fully loaded order with the customer (and email) already attached, so it sends. A gateway callback that passes a pre-built order object carrying its own ->mail (or a ->customer without the email) makes HikaShop reuse that object and skip the lookup that fills the recipient.

Two things to try:

1. Call modifyOrder with the integer order id only, and do not attach $order->mail or a partial $order->customer to it beforehand. With a plain id, HikaShop builds a fresh order, reloads it from the database, loads the customer from order_user_id, and fills dst_email itself. That is the cleanest fix and matches the id-not-object point from earlier in the thread.

2. To confirm which of the two cases it is, add these two lines in the same update branch where you already log (right around the loadOrderNotification call):

hikashop_writeToLog('EMAILDEBUG order '.$order->order_id.': mail_preset='.(empty($order->mail)?'no':'yes').', order_user_id='.(int)@$order->order_user_id);
// and just before the dst_email check / sendMail:
hikashop_writeToLog('EMAILDEBUG order '.$order->order_id.': dst_email= customer_email=');

If mail_preset is "yes", RO Payments is setting $order->mail before the save, which is the cause. If mail_preset is "no" but customer_email is empty, then order_user_id points to a user record with no user_email, in which case check that the order's order_user_id is the real customer record and that it has an email on file (relevant for guest or admin-created orders).

Please Log in or Create an account to join the conversation.

  • Posts: 330
  • Thank you received: 24
1 week 7 hours ago #372248

a reply from Roland :

Hi Nicholas,

> Call modifyOrder with the integer order id only

The method requires 2 arguments where the second argument is the $order_status. What should I put there?

At least I assume I am calling the modifyOrder from the payment plugin, is that correct?

Please Log in or Create an account to join the conversation.

  • Posts: 85765
  • Thank you received: 14071
  • MODERATOR
1 week 4 hours ago #372249

The second argument should be the new order status.
For example: "confirmed"
Yes, you normally what you use $this->modifyOrder() inside the main class of the payment plugin which extends from hikashopPaymentPlugin.

It's basically what I said a week ago:
- The payment plugin one (called as $this->modifyOrder(...) from a plugin that extends hikashopPaymentPlugin) takes the history as its 3rd argument:
$this->modifyOrder($order_id, 'shipped', $history, $email);
www.hikashop.com/forum/orders-management...s-update.html#372191

Please Log in or Create an account to join the conversation.

Time to create page: 0.131 seconds
Powered by Kunena Forum