XF 2.2 How to manipulate payment return

FoxSecrets

Active member
In getPurchaseObject method, the returnUrl and cancelUrl are added to purchase object which the user is redirected after the payment process.

I'd like to manipulate this return.. I got 2 questions

1) How to update status field of some table according to the return url?

2) How to directly add a popup message according to the return url, is there any attribute to buildLink?

Code:
    public function getPurchaseFromRequest(\XF\Http\Request $request, \XF\Entity\User $purchaser, &$error = null)
    {
        $profileId = $request->filter('payment_profile_id', 'uint');
        .........
        /** @var \MyAddon\Service\Purchase\Create $creator */
        $creator = \XF::service('MyAddon:Purchase\Create', $item);

        $purchase = $creator->save();

        return $this->getPurchaseObject($paymentProfile, $purchase, $purchaser);
    }

    ..............

    public function getPurchaseObject(\XF\Entity\PaymentProfile $paymentProfile, $purchasable, \XF\Entity\User $purchaser)
    {
        $purchase = new Purchase();

        $purchase->description          = "xxxxxxxxxxx";
        $purchase->title                = "xxxxxxxxxxx";
        ...........
        $router = \XF::app()->router('public');
        $purchase->returnUrl = $router->buildLink('canonical:success-url/' . $purchasable->item_id);
        $purchase->cancelUrl = $router->buildLink('canonical:fail-url/' . $purchasable->item_id);

        return $purchase;
    }
 
I've extended class XF/Payment/AbstractProvider to change 'completeTransaction' method, however it's not recognizing (still reading the original one).

Is there anything special in payment process? Can someone explain me how it works?
 
Looking at payment_callback file, it enters in if (!$handler->validateCallback($state)) , just set the status code, but I need to run $handler->completeTransaction($state) ... how to do that? this file has no class to override this process...

Anyone can help me please?


payment_callback.php

Code:
if (!$handler->validateCallback($state))
{
  $response->httpCode($state->httpCode ?: 403);
}
.................
else
{
  $handler->setProviderMetadata($state);
  $handler->getPaymentResult($state);
  $handler->completeTransaction($state);
}
 
I changed the state on completePurchase method as I wanted, but just database is changed and the page remains outdated because had load previously.

What's the best way to reload the page in the end of it?

Code:
    public function completePurchase(CallbackState $state)
    {
        ................

        switch ($paymentResult) {
            case CallbackState::PAYMENT_RECEIVED:
            case CallbackState::PAYMENT_REINSTATED:

                $purchase->status = 'completed';
                $purchase->save();

                $state->logType = 'payment';

                if ($paymentResult == CallbackState::PAYMENT_RECEIVED) {
                    $state->logMessage = 'Payment received, purchase validated.';
                } else {
                    $state->logMessage = 'Reversal cancelled, purchase revalidated.';
                }

                break;
        }
    }
 
Hey @Jeremy P , can you give me a hand with this topic?

After the purchase I got a positive response, I can dump data into payment_callback.php but it does not go into the completePurchase method to update the database information. How can I achieve this?
 
It's not clear to me what you're trying to do. The payment_callback.php file is for processing webhooks for payment gateways. Users never see it and the asynchronous nature of webhooks mean they might be received much later than the user initiating the payment.
 
When a payment is completed it will call the completePurchase method of your purchasable object, where you can implement whatever logic you'd like. Again, this happens in response to a webhook and not a visitor request.
 
I believe my webhook is correctly set, the payment is done and returned to the forum, however it's not calling the method of my purchasable object. Is there any documentation related to the payment process?
 
You can check the payment provider log in the control to see if the webhook was processed successfully. Is this using a core payment provider?
 
Yes, I'm using Stripe. I checked and there is no log, but how can there be no log if the payment returns the success url?
 
The user is returned to the success URL after carrying out the payment flow, initiating the transaction. The webhooks are sent by the payment provider later, after the transaction has been processed (whether it succeeds or fails). This usually happens pretty quickly after the payment is initiated, but can happen later as it is entirely at the discretion of the payment provider.

You can check with Stripe to see if the webhooks were sent, but if there's nothing in the logs then they were never received by XF.
 
I've noticed something related to the Stripe webhook verification with the signing secret. When I remove it, it works, otherwise it doesn't.
When it pass the log is " Action Error: Webhook received from Stripe could not be verified as being valid."
1713451975098.webp
And these are the states (using stripe cli):
1713452054973.webp

Can you explain what's going on? Is this related to Xenforo or Stripe part?
 
Does this log an error in the payment provider log saying the webhook could not be verified? I don't think we've had other reports of this and we use the Stripe SDK directly to perform the verification, so I can only really suggest triple checking the signing secret settings.
 
Still investigating the issue, but while the process is working (without the signing secret), how to update a table field and reload page?
I tried to update in completePurchase method, but it didn't work. How can I achieve that? Where does this method return?
 
\XF\Purchasable\AbstractPurchasable::completePurchase is called by \XF\Payment\AbstractProvider::completeTransaction (assuming the payment was successful or reinstated). It doesn't return anything (void). A bit of an aside, but I'd highly recommend using an IDE or LSP plugin to help navigate and make sense of the code. You can use it to find where methods are called or defined.

You won't be able to refresh the visitor's page when the payment is completed though, since it's in response to a webhook and not the visitor.
 
I already use VSCode and Inteliphense, but sometimes I get confused with some classes, am still newbie on programming. I got your point, so I'll move the user to another page on success return. Thanks!!
 
I already use VSCode and Inteliphense, but sometimes I get confused with some classes, am still newbie on programming.
Just as a tip, F12 on a method will take you to the definition and Shift+F12 on a method will show you everywhere it's referenced (called or overwritten). These can be really useful for making sense of the control flow.
 
Top Bottom