thephpleague / omnipay-targetpay

TargetPay driver for the Omnipay PHP payment processing library
MIT License
14 stars 4 forks source link

Capture payment error: Invalid input given. Should be an array or instance of \Traversable #7

Closed ghost closed 8 years ago

ghost commented 8 years ago

I have an application in Symfony 2.8 where I get an error when capturing payment via payum - omnipay-bridge and omnipay/targetpay.

When I call my prepareAction I'm getting redirected to an external payment form at the targetpay website. When I cancel (cancel = pay in testmode) I'm redirected back to my site with the following exception:

Invalid input given. Should be an array or instance of \Traversable (500 Internal Server Error)

When I refresh the page I'm ending up on the final route 'bsdb_payment_membership_status' (see PaymentController) and the status of my payment is 'new'. What is wrong with my payment process?

My composer.json looks like this:

 "payum/payum-bundle": "~2.0",
  "payum/omnipay-bridge": "~1.2",
  "omnipay/targetpay": "~2.1",

My config.yml looks like this:

payum:
    security:
        token_storage:
            Bsdb\BsdbPaymentBundle\Entity\PaymentToken: { doctrine: orm }

    storages:
        Bsdb\BsdbPaymentBundle\Entity\PaymentDetails: { doctrine: orm }

    gateways:
        bsdb_membership_payment:
            factory: omnipay
            type: "TargetPay_Mrcash"
            options:
                subAccountId: 123456
                testMode: true

I have created a custom PaymentBundle with:

Entity PaymentToken

namespace Bsdb\BsdbPaymentBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Payum\Core\Model\Token;

/**
 * @ORM\Table
 * @ORM\Entity
 */
class PaymentToken extends Token
{
}

Entity PaymentDetails

namespace Bsdb\BsdbPaymentBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Payum\Core\Model\ArrayObject;
use Application\Sonata\UserBundle\Entity\User;

/**
 * @ORM\Table(name="PaymentDetails")
 * @ORM\Entity
 */
class PaymentDetails extends ArrayObject
{
    /**
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     *
     * @var integer $id
     */
    protected $id;

    /**
     * @var User $user
     *
     * @ORM\ManyToOne(targetEntity="Application\Sonata\UserBundle\Entity\User")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
     * })
     */
    protected $user;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="started_at", type="datetime", nullable=true)
     */
    protected $startedAt;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="completed_at", type="datetime", nullable=true)
     */
    protected $completedAt;

    /**
     * Set start date:
     */
    public function __construct()
    {
        $this->setStartedAt(new \DateTime());
    }

    /**
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @return User
     */
    public function getUser()
    {
        return $this->user;
    }

    /**
     * @param User $user
     */
    public function setUser($user)
    {
        $this->user = $user;
    }

    /**
     * @return \DateTime
     */
    public function getStartedAt()
    {
        return $this->startedAt;
    }

    /**
     * @param \DateTime $startedAt
     */
    public function setStartedAt($startedAt)
    {
        $this->startedAt = $startedAt;
    }

    /**
     * @return \DateTime
     */
    public function getCompletedAt()
    {
        return $this->completedAt;
    }

    /**
     * @param \DateTime $completedAt
     */
    public function setCompletedAt($completedAt)
    {
        $this->completedAt = $completedAt;
    }

    public function getDetailsString()
    {
        return json_encode($this->details);
    }
}

Controller PaymentController

namespace Bsdb\BsdbPaymentBundle\Controller;

use Application\Sonata\UserBundle\Entity\User;
use Application\Sonata\UserBundle\Event\UserEvent;
use Application\Sonata\UserBundle\Service\UserGroupAttacher;
use Application\Sonata\UserBundle\UserEvents;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Request;
use Payum\Core\Request\GetHumanStatus;
use Symfony\Component\HttpFoundation\JsonResponse;

class PaymentController extends Controller
{
    /**
     * @Route(name="bsdb_payment_membership_prepare", path="/membership/prepare")
     */
    public function prepareAction()
    {
        $paymentName = 'bsdb_membership_payment';
        $storage = $this->get('payum')->getStorage('Bsdb\BsdbPaymentBundle\Entity\PaymentDetails');
        $currentUser = $this->get('security.context')->getToken()->getUser();

        /** @var \Bsdb\BsdbPaymentBundle\Entity\PaymentDetails $paymentDetails */
        $paymentDetails = $storage->create();

        $paymentDetails['amount'] = $this->container->getParameter('premium.amount');
        $paymentDetails['description'] = $this->container->getParameter('premium.description');
        $paymentDetails['clientIp'] = $this->get('request')->getClientIp();
        $paymentDetails->setUser($currentUser);

        $storage->update($paymentDetails);

        $captureToken = $this->get('payum')->getTokenFactory()->createCaptureToken(
            $paymentName,
            $paymentDetails,
            'bsdb_payment_membership_captured' // the route to redirect after capture;
        );

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

    /**
     * @Route(name="bsdb_payment_membership_captured", path="/membership/captured")
     */
    public function captureDoneAction(Request $request)
    {
        $token = $this->get('payum')->getHttpRequestVerifier()->verify($request);
        $gateway = $this->get('payum')->getGateway($token->getGatewayName());

        $gateway->execute($status = new GetHumanStatus($token));
        $payment = $status->getFirstModel();

        if ($status->isCaptured()) {
            /** @var User $user */
            $user = $this->getUser();
            $user->setLastPremiumUpgrade(new \DateTime());
            $user->setPremiumExpiresAt(new \DateTime(sprintf('+%s days', $this->container->getParameter('premium.expiration.period'))));

            // Set group
            $entityManager = $this->getDoctrine()->getManager();
            $groupAttachter = new UserGroupAttacher($entityManager);
            $groupAttachter->attachUserGroup($user, $this->container->getParameter('premium.group'));

            // Update payment data:
            $payment->setCompletedAt(new \DateTime());

            // Save:
            $entityManager->persist($user);
            $entityManager->flush();

            // Trigger events for emails and stuff:
            $event = new UserEvent($user);
            $this->get('event_dispatcher')->dispatch(UserEvents::USER_PREMIUM_PURCHASED, $event);
        }

        return $this->redirect($this->generateUrl('bsdb_payment_membership_status', array('status' => $status->getValue())));
    }

    /**
     * @Route(name="bsdb_payment_membership_status", path="/membership/status/{status}")
     * @Template()
     */
    public function paidAction($status)
    {
        return array('status' => $status);
    }

} 

Thank you for your expertise!

delatbabel commented 8 years ago

OK so my first comment is that this isn't actually a support forum. It's a bug tracker, for reporting identified bugs. Especially in the case of integrating with Payum, there's probably not a lot we can do to help. The official Omnipay support forum is in fact StackOverflow so I suggest you re-post your question there, and make sure that it is tagged with both payum and omnipay tags.

My second comment is that the error message doesn't give us enough information to track down what's going on. I suggest you go through the symfony log files to see where the error is being generated, and perhaps add some debug / logging code of your own to see what's going into whatever function is expecting an array and not receiving one. This doesn't specifically look like something wrong inside Omnipay but without a bit of a stack trace it's hard to determine what's going on.

Sorry I can't give you better information.