Skip to main content
deleted 1 character in body
Source Link
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.
    }
  }
}
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.
    }
  }
}
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.
    }
  }
}
added 7 characters in body
Source Link

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 but, only when a payment succeedshas 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.

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 but when a payment succeeds).
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.

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.

edited the question to be only one
Source Link
avpaderno
  • 97.9k
  • 15
  • 164
  • 284

Commerce Payment Gateway: Safely save data to the order in onNotify()

I'm new to Drupal and have to make a Offsite Payment Gateway.an off-site payment gateway (Withwith Drupal Commerce 2)

 . It all works - except sometimes it doesn't.

I believe this is due to a race condition between onReturn() and onNotify(): In onNotify() i set a property on the order and save the order. However, Drupal Commerce seems to automatically save the order in onReturn() too (out of my control). I think thatbut sometimes this saving of the order in onReturn() will overwrite the change i did to the order in onNotify(), causing the data to be lost, when onReturn() and onNotify() are being called at almost the same time.

Is there a safe way to do ->setData(...) on the $order in onNotify()? (How would you do it?)

(or can saving the order in onReturn be prevented? (is it necessary?))

Thanks for any help!


doesn't.


More detailed description of the problem: (This can be skipped)

The remote payment provider's server sends notification-requests requests to mythe server, about the status of the payment, so iI have both onReturn and onNotify in mythe 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, iI create and save mythe Payment-object 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. (veryIt's a very simplified pseudo-code +code; validation checks notaren't included here.):

class RedirectCheckout extends OffsitePaymentGatewayBase implements SupportsNotificationsInterface {

    ...

    public function onReturn(...) {
        $is_order_accepted = ...check if 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 if 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 IS OVERWRITTEN BY onReturn(), I BELIEVE.
        }
    }
}
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 iI need to save some data on the order, when an order is accepted (which is not available when the order is created - onlybut when a payment succeeds).  
(The Drupal Commerce documentation says you "don't need to (and should not)" touch the order, but iI have to save some extra data on the order, that other parts on the system expects to be there. Interesting what they mean by "(and should not)"...)

This often works. However, the two onReturn and onNotify requests from the remote server sometimes arrives at almost the same time, which iI believe leads to a race condition. (My current theory)

Unfortunately, even though i'mI am not doing anything to the order in onReturn, commerceCommerce seems to still save the order. I belivebelieve this can sometimessometime overwrite the data saved to the order by onNotify. ExampleFor example:

  • onReturn starts running, and loads the order (This is done by the commerce library itself, so iI can't do anything about this - or can i?.)
  • Now, theThe notify-request request arrives, so onNotify starts running, loads and saves the order, and returns.
  • After this, the onReturn method returns, gives back control to commerceCommerce, which saves the order again - butagain; since it loaded the order-object it BEFORE object before onNotify saved it, it overwrites whatever onNotify wrote with old data, when it saves it.

(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, if any), during the onReturn-request request.)

Is there a good way to handle this? (I.e., for example work around the race-condition to be able to save order-data in onNotify)

How would you do it?


(I have thought about Drupal's locking mechanism (would have worked, if it was possible to lock in onReturn all the way from before Drupal Commerce loads the order until after it has saved it - is that somehow possible? Perhaps it's somehow possible to reload the orderdata in onReturn (and wait for onNotify to have completed)? Or create a wrapper (custom Controller) around onReturn, which calls onReturns, but locks first?). Perhaps there are other ways of ->setData on the order which would survive, like "UPDATE commerce_order SET transaction_id = x WHERE y" instead of an object? Etc etc.)

I'm using Drupal 8.6, btw.

Commerce Payment Gateway: Safely save data to the order in onNotify()

I'm new to Drupal and have to make a Offsite Payment Gateway. (With Drupal Commerce 2)

  It all works - except sometimes it doesn't.

I believe this is due to a race condition between onReturn() and onNotify(): In onNotify() i set a property on the order and save the order. However, Drupal Commerce seems to automatically save the order in onReturn() too (out of my control). I think that sometimes this saving of the order in onReturn() will overwrite the change i did to the order in onNotify(), causing the data to be lost, when onReturn() and onNotify() are being called at almost the same time.

Is there a safe way to do ->setData(...) on the $order in onNotify()? (How would you do it?)

(or can saving the order in onReturn be prevented? (is it necessary?))

Thanks for any help!


.


More detailed description of the problem: (This can be skipped)

The remote payment provider's server sends notification-requests to my server, about the status of the payment, so i have both onReturn and onNotify in my 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 my 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 (very simplified pseudo-code + validation checks not included here):

class RedirectCheckout extends OffsitePaymentGatewayBase implements SupportsNotificationsInterface {

    ...

    public function onReturn(...) {
        $is_order_accepted = ...check if 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 if 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 IS 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 succeeds).  
(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. Interesting what they mean by "(and should not)"...)

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. (My current theory)

Unfortunately, even though i'm not doing anything to the order in onReturn, commerce seems to still save the order. I belive this can sometimes overwrite the data saved to the order by onNotify. Example:

  • onReturn starts running, and loads the order (This is done by the commerce library itself, so i can't do anything about this - or can i?)
  • Now, 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 - but since it loaded the order-object it BEFORE onNotify saved it, it overwrites whatever onNotify wrote with old data, when it saves it.

(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? (I.e. work around the race-condition to be able to save order-data in onNotify)

How would you do it?


(I have thought about Drupal's locking mechanism (would have worked, if it was possible to lock in onReturn all the way from before Drupal Commerce loads the order until after it has saved it - is that somehow possible? Perhaps it's somehow possible to reload the order in onReturn (and wait for onNotify to have completed)? Or create a wrapper (custom Controller) around onReturn, which calls onReturns, but locks first?). Perhaps there are other ways of ->setData on the order which would survive, like "UPDATE commerce_order SET transaction_id = x WHERE y" instead of an object? Etc etc.)

I'm using Drupal 8.6, btw.

Safely save data to the order in onNotify()

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 but when a payment succeeds).
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.

added 76 characters in body
Source Link
Loading
added 11 characters in body
Source Link
Loading
added 11 characters in body
Source Link
Loading
edited body
Source Link
Loading
Source Link
Loading