I'm new to Drupal and have to make an off-site payment gateway (with Drupal Commerce 2). It all works, but sometimes it doesn't.
The remote payment provider's server sends notification requests to the server, about the status of the payment, so I have both onReturn
and onNotify
in the PaymentGateway class.
Since onReturn
is not guaranteed to be called (the customer might close the browser, etc, and the provider does not necessarily send him back in my case), but onNotify
is guaranteed to be called, I create and save the Payment
object in onNotify
, not in onReturn
, when the payment is complete. (This is also what the documentation suggests to do: https://docs.drupalcommerce.org/commerce2/developer-guide/payments/create-payment-gateway/off-site-gateways/handling-ipn)
So my code looks something like this. (It's a very simplified pseudo-code; validation checks aren't included.)
class RedirectCheckout extends OffsitePaymentGatewayBase implements SupportsNotificationsInterface {
public function onReturn() {
$is_order_accepted = /* Check the remote payment provider has accepted the order */
if (!$is_order_accepted) {
throw new NeedsRedirectException()
}
// If all is good, do nothing.
}
public function onNotify() {
/** @var OrderInterface $order */
$order = /* Load the order the notification is about */
$is_order_accepted = /* Check the remote payment provider has accepted the order */
if ($is_order_accepted) {
$payment = $payment_storage->create();
$payment->save();
$order->setData('transaction_id', $transactionId);
$order->save(); // This is what sometimes overwritten by onReturn(), I believe.
}
}
}
Notice that I need to save some data on the order, when an order is accepted (which is not available when the order is created, only when a payment has succeeded).
The Drupal Commerce documentation says you "don't need to (and should not)" touch the order, but I have to save some extra data on the order that other parts on the system expects to be there.
This often works. However, the two onReturn
and onNotify
requests from the remote server sometimes arrives at almost the same time, which I believe leads to a race condition.
Unfortunately, even though I am not doing anything to the order in onReturn
, Commerce seems to still save the order. I believe this can sometime overwrite the data saved to the order by onNotify
. For example:
onReturn
starts running, and loads the order (This is done by the commerce library itself, so I can't do anything about this.)
- The notify request arrives, so
onNotify
starts running, loads and saves the order, and returns
- After this, the
onReturn
method returns, gives back control to Commerce, which saves the order again; since it loaded the order object before onNotify
saved it, it overwrites whatever onNotify
wrote with old data
(Perhaps the converse order can also be problematic, where onNotify
can overwrite any data saved to the order by Drupal Commerce behind the scenes, if any, during the onReturn
request.)
Is there a good way to handle this, for example work around the race-condition to be able to save order data in onNotify
?
I'm using Drupal 8.6.