0

I want to use Authorize.net SIM payment method in Symfony using payum.org. There is no official gateway for it but there is one in omnipay: omnipay-authorizenet. There is also omnipay-bridge in payum so it is possible to use omnipay gateways in payum.

So I use this setup and after submitting the authorize.net form I get the error:

[date] request.CRITICAL: Uncaught PHP Exception Omnipay\Common\Exception\InvalidRequestException: "Incorrect hash" at .../authorize/vendor/omnipay/authorizenet/src/Message/SIMCompleteAuthorizeRequest.php line 42 {"exception":"[object] (Omnipay\\Common\\Exception\\InvalidRequestException(code: 0): Incorrect hash at .../authorize/vendor/omnipay/authorizenet/src/Message/SIMCompleteAuthorizeRequest.php:42)"} []

BUT this is NOT because of the generated hashes being incorrect - it is because capture url is called second time without the POST data.

On a clean installation of Symfony2 with 3 packages:

composer.json:

"payum/payum-bundle": "0.15.*",
"omnipay/authorizenet": "~2.0",
"payum/omnipay-bridge": "*@stable"

config.yml:

payum:
    security:
        token_storage:
            AppBundle\Entity\PaymentToken: { doctrine: orm }

    storages:
        AppBundle\Entity\Payment: { doctrine: orm }

    gateways:
        authorizeGateway:
            omnipay_offsite:
                type: AuthorizeNet_SIM
                options:
                    hashSecret: 'Simon'
                    ApiLoginId: 'xxx'
                    transactionkey: 'xxx'
                    testMode: false
                    developerMode: true

Controller:

/**
 * @Route("/prepare", name="prepare")
 */
public function prepareAction()
{
    $gatewayName = 'authorizeGateway';

    $storage = $this->get('payum')->getStorage('AppBundle\Entity\Payment');

    $payment = $storage->create();
    $payment->setNumber(uniqid());
    $payment->setCurrencyCode('USD');
    $payment->setTotalAmount(1);
    $payment->setDescription('A description');
    $payment->setClientId('anId');
    $payment->setClientEmail('foo@example.com');
    $storage->update($payment);

    $captureToken = $this->get('payum.security.token_factory')->createCaptureToken(
        $gatewayName,
        $payment,
        'done' // the route to redirect after capture
    );

    return $this->redirect($captureToken->getTargetUrl());
}

/**
 * @Route("/done", name="done")
 */
public function doneAction(Request $request)
{
    ...
}

Going to /prepare shows a redirecting to authorize.net page for a second and I'm redirected to external test.authorize.net/gateway/transact.dll (on https) page where I specify card number (test card number) and expiration date in the future. Submitting this form gives:

An error occurred while trying to report this transaction to the merchant. An e-mail has been sent to the merchant informing them of the error. The following is the result of the attempt to charge your credit card.

This transaction has been approved.

It is advisable for you to contact the merchant to verify that you will receive the product or service.

I'm getting the email about Merchant Email Receipt and the one about the error:

Authorize.Net Developer Center Merchant,

Your script timed out while we were trying to post transaction results to it.
Transaction ID: XXX

Transaction Result: This transaction has been approved.

The transaction is processed correctly, the capture script is called, hashes match and then the capture is called again without post data - then hashes don't match and authorize displays error.

Requests that are made from symfony profiler:

Token   IP                  Method  URL                                                                 Time                                Status
fe39ec  198.241.162.104     GET     .../payment/capture/vVgoUCPtgCOglv6rLwhIbUp64RZ_oIql1_KDpWjdrdk     Tue, 17 Nov 2015 09:47:36 +0100     500
bba47c  198.241.162.104     GET     .../payment/capture/vVgoUCPtgCOglv6rLwhIbUp64RZ_oIql1_KDpWjdrdk     Tue, 17 Nov 2015 09:47:36 +0100     200
c95b83  198.241.162.104     POST    .../payment/capture/vVgoUCPtgCOglv6rLwhIbUp64RZ_oIql1_KDpWjdrdk     Tue, 17 Nov 2015 09:47:36 +0100     302
a87347  myip                GET     .../payment/capture/vVgoUCPtgCOglv6rLwhIbUp64RZ_oIql1_KDpWjdrdk     Tue, 17 Nov 2015 09:47:30 +0100     200
c95d57  myip                GET     .../prepare                                                         Tue, 17 Nov 2015 09:47:29 +0100     302 

From what i see when we call /prepare we get redirected to capture right away this goes to authorize's form. Then after a few seconds (when credit card data is filled in and submitted) authorize (different ip) makes post request to capture. This is 302 redirect (and probably should be a SIM response with javascript code to go back to our page?). Capture is called secod time with GET and calculated hashes don't match - this is 500 response - authorize stays on their url and shows the error message. Done script is never called.

What can be the issue? It's difficult to debug this further because there is payum, omnipay-bridge, omnipay, authorize combined.

Im testing this on the environment accessible from the internet with account on http://developer.authorize.net/ with test mode off.

UPDATE:

If I add notify token to the controller, like this:

/**
 * @Route("/prepare", name="prepare")
 */
public function prepareAction()
{
    $gatewayName = 'authorizeGateway';

    $storage = $this->get('payum')->getStorage('AppBundle\Entity\Payment');

    $payment = $storage->create();
    $payment->setNumber(uniqid());
    $payment->setCurrencyCode('USD');
    $payment->setTotalAmount(1); // 1.23 EUR
    $payment->setDescription('A description');
    $payment->setClientId('anId');
    $payment->setClientEmail('foo@example.com');
    $storage->update($payment);
    $captureToken = $this->get('payum.security.token_factory')->createCaptureToken(
        $gatewayName,
        $payment,
        'done' // the route to redirect after capture
    );

    $tokenFactory = $this->get('payum.security.token_factory');
    $notifyToken = $tokenFactory->createNotifyToken($gatewayName, $payment);
    $payment->setDetails(['notifyUrl' => $notifyToken->getTargetUrl()]);
    $storage->update($payment);

    return $this->redirect($captureToken->getTargetUrl());
}

I get error "Request Notify{model: ArrayObject} is not supported.":

[2015-11-17 17:46:50] request.INFO: Matched route "payum_notify_do". {"route_parameters":{"_controller":"Payum\\Bundle\\PayumBundle\\Controller\\NotifyController::doAction","payum_token":"Lv5ovrC-8vikIB9ItDVLcNfuRzjjaD_pPiE3-6VIV8Y","_route":"payum_notify_do"},"request_uri":".../payment/notify/Lv5ovrC-8vikIB9ItDVLcNfuRzjjaD_pPiE3-6VIV8Y"} []
[2015-11-17 17:46:50] security.INFO: Populated the TokenStorage with an anonymous Token. [] []
[2015-11-17 17:46:50] request.CRITICAL: Uncaught PHP Exception Payum\Core\Exception\RequestNotSupportedException: "Request Notify{model: ArrayObject} is not supported." at .../authorize/vendor/payum/core/Payum/Core/Exception/RequestNotSupportedException.php line 29 {"exception":"[object] (Payum\\Core\\Exception\\RequestNotSupportedException(code: 0): Request Notify{model: ArrayObject} is not supported. at .../authorize/vendor/payum/core/Payum/Core/Exception/RequestNotSupportedException.php:29)"} []
blahy
  • 1,294
  • 1
  • 8
  • 9
  • Try to comment this line out: https://github.com/Payum/PayumBundle/blob/master/Controller/CaptureController.php#L37 – Maksim Kotlyar Nov 17 '15 at 14:54
  • Also this line could be related, notify url (the post request) IS NOT THE SAME as return url. they must be two different urls. https://github.com/thephpleague/omnipay-authorizenet/blob/master/src/Message/SIMAuthorizeRequest.php#L18 – Maksim Kotlyar Nov 17 '15 at 14:58

1 Answers1

0

Omnipay bridge 0.15.x does not set a notifyUrl, and the omnipay gateway uses return url as notify one. When notification comes (before you are redirected) the capture token is invalidated and no longer available.

There are two solutions:

  1. Upgrade to 1.0 where notifyUrl is generated. Btw you can use omnipay gateway factory instead of omnipay_offsite.

  2. or you have to generate notify url yourself, and set it to notifyUrl

    $tokenFactory = $this->get('payum.security.token_factory'); $notifyToken = $tokenFactory->createNotifyToken($gatewayName, $payment);

    $payment->setDetails(['notifyUrl' => $notifyToken->getTargetUrl()]); $storage->update($payment);

Maksim Kotlyar
  • 3,821
  • 27
  • 31
  • Hi, thank you for the response. I went for the second option for now. I didnt have that much time to debug this but now I get other error "Request Notify{model: ArrayObject} is not supported." I updated the question with the details of how i modified the controller code following your instructions and what error i got (so the final outcome for now is still the same) – blahy Nov 17 '15 at 16:53
  • the bridge does not proxy notify request to internal omnipay gateway's method. Should be implemented – Maksim Kotlyar Nov 17 '15 at 17:13
  • Is there any project that implements this via bridge for authorize.net SIM that I can take a look at? Or documentation? PayumBundle:Notify:do is called and I understand I have to implement custom NotifyAction that should return one of the omnipay/authorize messages or call SIMGateway::completePurchase? – blahy Nov 18 '15 at 09:13
  • Nope, there is not any app, at least I am not aware of them. Yes, you are right you have to add a NotifyAction to omnipay bridge which calls either completePurchase method. and update the model with details, like this: https://github.com/Payum/OmnipayBridge/blob/master/src/Action/OffsiteCaptureAction.php#L116 – Maksim Kotlyar Nov 18 '15 at 11:37
  • I added the action service: action.notify.custom: class: AppBundle\Services\NotifyAction tags: - {name: payum.action, prepend: true, all: true } It is visible when I do payum:gateway:debug: authorizeGateway (Payum\Core\Gateway): Actions: AppBundle\Services\NotifyAction But when authorize comes back to payum_notify_do gateway doesnt have this action attached (i check $this->actions in Payum\Core\Gateway::findActionSupported), so I still get RequestNotSupportedException. Did I register the action correctly? What could be the problem that action is not called? – blahy Nov 18 '15 at 13:11
  • wired, If you see it on debug, it must be on notify too. – Maksim Kotlyar Nov 18 '15 at 13:28
  • try this tag {name: payum.action, gateway_name: authorizeGateway, prepend: true } – Maksim Kotlyar Nov 18 '15 at 13:29
  • It is being called now after the payment is made. $request is Notify instance and getModel returns PaymentToken instance. Now if you could point me into right direction on how the execute method should be implemented in this case. Is this the place where i can check the payment status and make some changes in my db? Should i call $storage->update($payment); here? And should I return the contents of SIMGateway::completePurchase? Or is it too early and my doneAction will be called eventually? – blahy Nov 18 '15 at 14:32