Payum / PayumBundle

Payum offers everything you need to work with payments. From simplest use cases to very advanced ones.
https://payum.forma-pro.com/
MIT License
565 stars 143 forks source link

Skeleton : Cannot find right api for the action App\Acme\Action\CaptureAction #514

Closed martygraphy closed 4 years ago

martygraphy commented 4 years ago

Hi ! I encounter a problem when I want to create a custom paserelle on symgony 4.4

I created a skeleton in my application. I followed the whole installation process but I get this error when my CaptureAction class is called : Cannot find right api for the action App\Ogone\Action\CaptureAction

Moreover I am obliged to create this gateway by this way because the ETS Ogone payment bundle is not functional on Symfony 4.4.

composer.json

        "payum/core": "^1.6",
        "payum/offline": "^1.6",
        "payum/payum": "^1.6",
        "payum/payum-bundle": "^2.4",

services.yml

    App\Ogone\OgoneGatewayFactory:
        class: Payum\Core\Bridge\Symfony\Builder\GatewayFactoryBuilder
        arguments: [App\Ogone\OgoneGatewayFactory]
        tags:
            - { name: payum.gateway_factory_builder, factory: ogone }

payum.yml

payum:
    security:
        token_storage:
            App\Entity\Payment\PaymentToken: { doctrine: orm }
    storages:
        App\Entity\Payment\Payment: { doctrine: orm }
    gateways:
        ogone:
            factory: ogone
            pspid: "ogone.pspid"
            prod: "ogone.prod"
            prefix: "ogone.prefix"
            security_sha1_in: "ogone.security_sha1_in"
            security_sha1_out: "ogone.security_sha1_out"

OgoneGatewayFactory.php

use App\Ogone\Action\AuthorizeAction;
use App\Ogone\Action\CancelAction;
use App\Ogone\Action\CaptureAction;
use App\Ogone\Action\ConvertPaymentAction;
use App\Ogone\Action\NotifyAction;
use App\Ogone\Action\RefundAction;
use App\Ogone\Action\StatusAction;
use Payum\Core\Bridge\Spl\ArrayObject;
use Payum\Core\GatewayFactory;

class OgoneGatewayFactory extends GatewayFactory
{
    protected function populateConfig(ArrayObject $config)
    {
        $config->defaults(
            [
                'payum.factory_name' => 'ogone',
                'payum.factory_title' => 'Ogone',
                'payum.action.capture' => new CaptureAction(),
                'payum.action.authorize' => new AuthorizeAction(),
                'payum.action.refund' => new RefundAction(),
                'payum.action.cancel' => new CancelAction(),
                'payum.action.notify' => new NotifyAction(),
                'payum.action.status' => new StatusAction(),
                'payum.action.convert_payment' => new ConvertPaymentAction(),
            ]
        );

        if (false === $config['payum.api']) {
            $config['payum.default_options'] = [
                'pspid' => '',
                'prod' => false,
                'prefix' => '',
                'security_sha1_in' => '',
                'security_sha1_out' => '',
            ];
            $config->defaults($config['payum.default_options']);
            $config['payum.required_options'] = ['pspid', 'security_sha1_in', 'security_sha1_out'];

            $config['payum.api'] = function (ArrayObject $config) {
                $config->validateNotEmpty($config['payum.required_options']);

                return new Api((array) $config,
                    $config['payum.http_client'],
                    $config['httplug.message_factory']
                );
            };
        }
    }
}

BaseApiAwraeAction.php

use App\Ogone\Api;
use Payum\Core\Action\ActionInterface;
use Payum\Core\ApiAwareInterface;
use Payum\Core\ApiAwareTrait;
use Payum\Core\GatewayAwareInterface;
use Payum\Core\GatewayAwareTrait;

abstract class BaseApiAwareAction implements ActionInterface, GatewayAwareInterface, ApiAwareInterface
{
    use GatewayAwareTrait;
    use ApiAwareTrait;

    public function __construct()
    {
        $this->apiClass = Api::class;
    }
}

CaptureAction.php

class CaptureAction extends BaseApiAwareAction
{

    /**
     * {@inheritdoc}
     *
     * @param Capture $request
     */
    public function execute($request)
    {
        RequestNotSupportedException::assertSupports($this, $request);

        $model = ArrayObject::ensureArrayObject($request->getModel());

        if (null !== $model['CODE']) {
            return;
        }

        if (false === $model['CLIENTUSERAGENT']) {
            $this->gateway->execute($httpRequest = new GetHttpRequest());
            $model['CLIENTUSERAGENT'] = $httpRequest->userAgent;
        }
        if (false === $model['CLIENTIP']) {
            $this->gateway->execute($httpRequest = new GetHttpRequest());
            $model['CLIENTIP'] = $httpRequest->clientIp;
        }

        $cardFields = ['CARDCODE', 'CARDCVV', 'CARDVALIDITYDATE', 'CARDFULLNAME'];
        if (false === $model->validateNotEmpty($cardFields, false) && false === $model['ALIAS']) {
            try {
                $obtainCreditCard = new ObtainCreditCard($request->getToken());
                $obtainCreditCard->setModel($request->getFirstModel());
                $obtainCreditCard->setModel($request->getModel());
                $this->gateway->execute($obtainCreditCard);
                $card = $obtainCreditCard->obtain();

                if ($card->getToken()) {
                    $model['ALIAS'] = $card->getToken();
                } else {
                    $model['CARDVALIDITYDATE'] = SensitiveValue::ensureSensitive($card->getExpireAt()->format('m-y'));
                    $model['CARDCODE'] = SensitiveValue::ensureSensitive($card->getNumber());
                    $model['CARDFULLNAME'] = SensitiveValue::ensureSensitive($card->getHolder());
                    $model['CARDCVV'] = SensitiveValue::ensureSensitive($card->getSecurityCode());
                }
            } catch (RequestNotSupportedException $e) {
                throw new LogicException('Credit card details has to be set explicitly or there has to be an action that supports ObtainCreditCard request.');
            }
        }

        //instruction must have an alias set (e.g oneclick payment) or credit card info.
        if (false === ($model['ALIAS'] || $model->validateNotEmpty($cardFields, false))) {
            throw new LogicException('Either credit card fields or its alias has to be set.');
        }

        $result = $this->api->payment($model->toUnsafeArray());
        $model->replace((array) $result);
    }

    /**
     * {@inheritdoc}
     */
    public function supports($request)
    {
        return
            $request instanceof Capture &&
            $request->getModel() instanceof \ArrayAccess
        ;
    }

}

Api.php

namespace App\Ogone;

use Http\Message\MessageFactory;
use Payum\Core\Bridge\Spl\ArrayObject;
use Payum\Core\Exception\Http\HttpException;
use Payum\Core\Exception\LogicException;
use Payum\Core\HttpClientInterface;
use Psr\Http\Message\ResponseInterface;

class Api
{
    const  CODE_INVALID_OGONE_PAYMENT = '0';
    const  CODE_PAYMENT_CANCELED_BY_CUSTOMER = '1';
    const  CODE_AUTHORIZATION_REFUSED = '2';

    const  CODE_ORDER_STORED = '4';
    const  CODE_STORED_WAITING_EXTERNAL_RESULT = '40';
    const  CODE_WAITING_FOR_CLIENT_PAYMENT = '41';
    const  CODE_WAITING_AUTHENTICATION = '46';

    const  CODE_AUTHORIZED = '5';
    const  CODE_AUTHORIZED_WAITING_EXTERNAL_RESULT = '50';
    const  CODE_AUTHORISATION_WAITING = '51';
    const  CODE_AUTHORIZATION_NOT_KNOWN = '52';
    const  CODE_STAND_BY = '55';
    const  CODE_OK_WITH_SCHEDULED_PAYMENTS = '56';
    const  CODE_NOT_OK_WITH_SCHEDULED_PAYMENTS = '57';
    const  CODE_AUTHORIZATION_TO_BE_REQUEST_MANUALLY = '59';

    const  CODE_AUTHORIZED_AND_CANCELLED = '6';
    const  CODE_AUTHOR_DELETION_WAITING = '61';
    const  CODE_AUTHOR_DELETION_UNCERTAIN = '62';
    const  CODE_AUTHOR_DELETION_REFUSED = '63';
    const  CODE_AUTHORIZED_AND_CANCEL = '64';

    const  CODE_PAYMENT_DELETED_CANCELED = '7';
    const  CODE_PAYMENT_DELETION_PENDING = '71';
    const  CODE_PAYMENT_DELETION_UNCERTAIN = '72';
    const  CODE_PAYMENT_DELETION_REFUSED = '73';
    const  CODE_PAYMENT_DELETED = '74';

    const  CODE_REFUNDED_PAYMENT = '8';
    const  CODE_REFUND_PENDING = '81';
    const  CODE_REFUND_UNCERTAIN = '82';
    const  CODE_REFUND_REFUSED = '83';
    const  CODE_REFUND = '84';
    const  CODE_REFUND_PROCESSED_BY_MERCHANT = '85';

    const  CODE_PAYMENT_REQUESTED = '9';
    const  CODE_PAYMENT_PROCESSING = '91';
    const  CODE_PAYMENT_UNCERTAIN = '92';
    const  CODE_PAYMENT_REFUSED = '93';
    const  CODE_REFUND_DECLINED_BY_ACQUIRER = '94';
    const  CODE_PAYMENT_PROCESSED_BY_MERCHANT = '95';
    const  CODE_REFUND_REVERSED = '96';
    const  CODE_PAYMENT_BEING_PROCESSED = '99';

    /**
     * The "payment" function is the basic function that allows collecting from a cardholder.
     * This operation collects money directly.
     */
    const OPERATION_PAYMENT = 'payment';

    /**
     * The "authorization" function allows "freezing" temporarily the funds in a cardholder's bank
     * account for 7 days. This application does not debit it.
     * This type of operation is mainly used in the world of physical goods ("retail") when the merchant
     * decides to debit his customer at merchandise shipping time.
     */
    const OPERATION_AUTHORIZATION = 'authorization';

    /**
     * The "capture" function allows collecting funds from a cardholder after an authorization
     * ("authorization" function). This capture can take place within 7 days after the authorization.
     */
    const OPERATION_CAPTURE = 'capture';

    /**
     * This dual function is directly managed by the system:
     * - Refund: Consists of returning the already collected funds to a cardholder
     * - Cancellation: Consists of not sending a payment transaction as compensation.
     */
    const OPERATION_REFUND = 'refund';

    /**
     * The "credit" function allows sending funds to a cardholder.
     */
    const OPERATION_CREDIT = 'credit';

    /**
     * @var HttpClientInterface
     */
    protected $client;

    /**
     * @var MessageFactory
     */
    protected $messageFactory;

    /**
     * @var array
     */
    protected $options = [
        'pspid' => null,
        'security_sha1_in' => null,
        'security_sha1_out' => null,
    ];

    /**
     * Api constructor.
     * @param array $options
     * @param HttpClientInterface $client
     * @param MessageFactory $messageFactory
     */
    public function __construct(array $options, HttpClientInterface $client, MessageFactory $messageFactory)
    {
        $options = ArrayObject::ensureArrayObject($options);
        $options->defaults($this->options);
        $options->validateNotEmpty(['pspid', 'security_sha1_in', 'security_sha1_out']);
        $this->options = $options;
        $this->client = $client;
        $this->messageFactory = $messageFactory;
    }

    /**
     * @return ResponseInterface
     */
    public function payment(array $params)
    {
        $params['OPERATIONTYPE'] = static::OPERATION_PAYMENT;

        $this->addGlobalParams($params);

        return $this->doRequest([
            'method' => 'payment',
            'params' => $params,
        ]);
    }

    /**
     * @param $method
     *
     * @return ResponseInterface
     */
    protected function doRequest($fields)
    {
        $headers = [
            'Content-Type' => 'application/x-www-form-urlencoded',
        ];

        $request = $this->messageFactory->createRequest('POST', $this->getApiEndpoint(), $headers, http_build_query($fields));

        $response = $this->client->send($request);

        if (false == ($response->getStatusCode() >= 200 && $response->getStatusCode() < 300)) {
            throw HttpException::factory($request, $response);
        }

        $result = json_decode($response->getBody()->getContents());
        if (null === $result) {
            throw new LogicException("Response content is not valid json: \n\n{$response->getBody()->getContents()}");
        }

        return $result;
    }

    protected function addGlobalParams(array &$params)
    {
        $params['PSPID'] = $this->options['pspid'];
    }

    /**
     * @return string
     */
    protected function getApiEndpoint()
    {
        return 'https://secure.ogone.com/ncol/test/orderstandard_UTF8.asp';
    }
}

What I don't understand is that payum detects my gateway when I run the command: bin/console debug:payum:gateway

Ogone (Payum\Core\Gateway):
        Actions:
        Payum\Core\Bridge\Symfony\Action\GetHttpRequestAction
        Payum\Core\Bridge\Symfony\Action\ObtainCreditCardAction
        Payum\Core\Action\CapturePaymentAction
        Payum\Core\Action\AuthorizePaymentAction
        Payum\Core\Action\PayoutPayoutAction
        Payum\Core\Action\ExecuteSameRequestWithModelDetailsAction
        Payum\Core\Bridge\Twig\Action\RenderTemplateAction
        Payum\Core\Action\GetCurrencyAction
        Payum\Core\Action\GetTokenAction
        App\Ogone\Action\CaptureAction
        App\Ogone\Action\AuthorizeAction
        App\Ogone\Action\RefundAction
        App\Ogone\Action\CancelAction
        App\Ogone\Action\NotifyAction
        App\Ogone\Action\StatusAction
        App\Ogone\Action\ConvertPaymentAction

        Extensions:
        Payum\Bundle\PayumBundle\Profiler\PayumCollector
        Payum\Core\Extension\GenericTokenFactoryExtension
        Payum\Core\Bridge\Psr\Log\LoggerExtension
        Payum\Core\Bridge\Psr\Log\LogExecutedActionsExtension
        Payum\Core\Extension\StorageExtension
                Storage: Payum\Core\Bridge\Doctrine\Storage\DoctrineStorage
                Model: App\Entity\Payment\Payment
        Payum\Core\Extension\StorageExtension
                Storage: Payum\Core\Bridge\Doctrine\Storage\DoctrineStorage
                Model: App\Entity\Payment\PaymentDetails
        Payum\Core\Extension\EndlessCycleDetectorExtension

        Apis:
        Payum\Core\Bridge\Httplug\HttplugClient

But he doesn't detect my Api class, it detect only HttplugClient .

Do you have an idea about this problem ?

thank you in advance.

martygraphy commented 4 years ago

Sorry it's my fault, I replaced if (false == $config['payum.api']) { with if (false == $config['payum.api']) { I close the ticket

alister commented 3 years ago

The issue was weak vs strong equivalency - if (false === $config['payum.api']) { vs if (false == $config['payum.api']) {. The array-value is likely null at that point, but the code (existing gateway configs, and other skeleton/sample code) is old, written before stricter-type-safety was generally used).