duncanmcclean / simple-commerce

A simple, yet powerful e-commerce addon for Statamic.
https://statamic.com/addons/duncanmcclean/simple-commerce
Other
147 stars 41 forks source link

Order is overridden after saving changes to the order in the `callback` method in a custom gateway #1139

Closed morhi closed 2 months ago

morhi commented 2 months ago

Description

I created a custom provider and need to process some data from the payment provider after the provider redirects back to the checkout page using a callback URL. My idea is that the payment provider would call public function callback(Request $request): bool and I can process the payment data and mark the order as being paid, then return true to indicate that the payment was successful. However, if I do any changes within the callback function, the outer controller GatewayCallbackController overrides any changes, because it holds a different state of the order object and saves the old state of the order after calling the callback function, so that any change to the order gets overridden:

# I simplified the code to explain the issue better
class GatewayCallbackController extends BaseActionController
{
    public function index(Request $request, $gateway)
    {
        $order = Order::find($request->get('_order_id'));
        $callbackSuccess = Gateway::use($gateway['handle'])->callback($request);

        if (! $callbackSuccess) {
            return $this->withErrors($request, "Order [{$order->get('title')}] has not been marked as paid yet.");
        }

        $order->status(OrderStatus::Placed)->save();
        $this->forgetCart();
    }
}

I think doing something like this would solve my issue, because I could work on the same object:

# I simplified the code to explain the issue better
class GatewayCallbackController extends BaseActionController
{
    public function index(Request $request, $gateway)
    {
        $order = Order::find($request->get('_order_id'));

        // Also pass $order to the callback
        $callbackSuccess = Gateway::use($gateway['handle'])->callback($request, $order);

        if (! $callbackSuccess) {
            return $this->withErrors($request, "Order [{$order->get('title')}] has not been marked as paid yet.");
        }

        $order->status(OrderStatus::Placed)->save();
        $this->forgetCart();
    }
}

@duncanmcclean What are your thoughts on this? Am I doing it wrong? I think I cannot use webhooks for this with this payment provider.

Steps to reproduce

  1. Create a custom gateway
  2. Implement the callback function and modify the order
  3. Call the callback URL
  4. Any changes in the callback function get overridden

Environment

Environment Laravel Version: 11.20.0 PHP Version: 8.2.20 Composer Version: 2.7.7 Environment: local Debug Mode: ENABLED Maintenance Mode: OFF Timezone: UTC Locale: de

Cache Config: NOT CACHED Events: NOT CACHED Routes: NOT CACHED Views: CACHED

Drivers Broadcasting: log Cache: file Database: mysql Logs: stack / single Mail: smtp Queue: sync Session: file

Simple Commerce Currencies: CHF, CHF Gateways: Worldline Twint Repository: Customer: DuncanMcClean\SimpleCommerce\Customers\EntryCustomerRepository Repository: Order: DuncanMcClean\SimpleCommerce\Orders\EntryOrderRepository Repository: Product: DuncanMcClean\SimpleCommerce\Products\EntryProductRepository Shipping Methods: Free Shipping Tax Engine: DuncanMcClean\SimpleCommerce\Tax\Standard\TaxEngine

Livewire Livewire: v3.5.6

Statamic Addons: 19 Sites: 2 (Deutsch, Französisch) Stache Watcher: Enabled Static Caching: half Version: 5.22.1 PRO

Statamic Addons doefom/statamic-export: 0.4.0 duncanmcclean/simple-commerce: 7.4.2 jonassiewertsen/statamic-livewire: 3.6.0 rias/statamic-redirect: 3.7.2 ...

duncanmcclean commented 2 months ago

What's the gateway you're trying to integrate with?

We can't really change the mehod signature of the callback method to include $order since that'd cause a breaking change.

morhi commented 2 months ago

Yea makes sense. It is this product: https://worldline.com/en-ch/home/main-navigation/solutions/merchants/solutions-and-services/e-commerce/saferpay-payment-solution and we use this SDK: https://github.com/Ticketpark/SaferpayJsonApi

duncanmcclean commented 2 months ago

Looks like SaferPay might refer to webhooks as "notifications" instead... https://docs.saferpay.com/home/integration-guide/licences-and-interfaces/transaction-interface#notification

morhi commented 2 months ago

Okay, so the idea would be that the checkout function does not really do anything, but I'd need to implement the payment data processing in the webhook method and let it be called by SaferPay through async notifications?

duncanmcclean commented 2 months ago

Yeah, I think so. When possible, the webhook should be used for processing payment stuff, rather than the "callback page".

morhi commented 2 months ago

Oke, thanks. Let me try :)

morhi commented 2 months ago

Got it. It does work with the webhook using the payment notification. However, this payment provider does a GET request to the webhook, so I had to add

Route::get('!/simple-commerce/gateways/{gateway}/webhook', [\DuncanMcClean\SimpleCommerce\Http\Controllers\GatewayWebhookController::class, 'index']);

to my routes file. Thanks though, Duncan 🚀

duncanmcclean commented 2 months ago

Glad you managed to get it sorted.