Как доработать заказ при внедрении удаленного платежного шлюза с помощью Sylius 0.16 и Payum

Я пытался понять, как реализовать классический автономный платежный шлюз с помощью Sylius (sylius/sylius-standard, v0.16).

До сих пор я получил это в методе build() моего пакета:

public function build(ContainerBuilder $container)
{
    /** @var PayumExtension $payum */
    $payum = $container->getExtension('payum');
    $payum->addGatewayFactory(new PayWayOffsiteGatewayFactory());
}

Вот мой config.yml:

sylius_payment:
    gateways:
        payway_offsite: PayWay

payum:
    gateways:
        payway_offsite:
            payway_offsite:
                secret_key: %tcompayway.secret_key%
                shop_id:    %tcompayway.shop_id%
                username:   %tcompayway.username%
                password:   %tcompayway.password%
                sandbox:    %tcompayway.sandbox%

Поля %compayway.*%, конечно же, настроены нормально, пропуская эту часть. Затем я получил это в своем классе PayWayOffsiteGatewayFactory:

class PayWayOffsiteGatewayFactory extends AbstractGatewayFactory
{
    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'payway_offsite';
    }

    /**
     * {@inheritdoc}
     */
    public function addConfiguration(ArrayNodeDefinition $builder)
    {
        parent::addConfiguration($builder);

        $builder->children()
            ->scalarNode('secret_key')->isRequired()->cannotBeEmpty()->end()
            ->scalarNode('shop_id')->isRequired()->cannotBeEmpty()->end()
            ->scalarNode('username')->isRequired()->cannotBeEmpty()->end()
            ->scalarNode('password')->isRequired()->cannotBeEmpty()->end()
            ->booleanNode('sandbox')->defaultTrue()->end()
            ->end();
    }

    /**
     * {@inheritDoc}
     */
    protected function getPayumGatewayFactoryClass()
    {
        return 'WebBurza\PaymentBundle\Payum\PayWay\PayWayGatewayFactory';
    }

    /**
     * {@inheritDoc}
     */
    protected function getComposerPackage()
    {
        return 'webburza/tcompayway';
    }
}

И класс PayWayGatewayFactory:

class PayWayGatewayFactory extends GatewayFactory
{
    /**
     * {@inheritDoc}
     * @throws \Payum\Core\Exception\InvalidArgumentException
     */
    protected function populateConfig(ArrayObject $config)
    {
        $config->defaults(array(
            'payum.factory_name'            => 'payway_offsite',
            'payum.factory_title'           => 'PayWay Offsite',
            'payum.action.capture'          => new CaptureOffsiteAction(),
            'payum.action.capture_null'     => new CaptureOffsiteNullAction(),
            'payum.action.status'           => new StatusAction(),
            'payum.action.convert_payment'  => new ConvertPaymentAction(),
        ));

        if (false == $config['payum.api']) {
            $config['payum.default_options'] = array(
                'secret_key'    => '',
                'shop_id'       => '',
                'username'      => '',
                'password'      => '',
                'sandbox'       => true
            );
            $config->defaults($config['payum.default_options']);
            $config['payum.required_options'] = [
                'secret_key',
                'shop_id',
                'username',
                'password',
                'sandbox'
            ];

            $config['payum.api'] = function (ArrayObject $config) {

                $config->validateNotEmpty($config['payum.required_options']);

                $api = new Api([
                    'secret_key'=> $config['secret_key'],
                    'shop_id'   => $config['shop_id'],
                    'username'  => $config['username'],
                    'password'  => $config['password'],
                    'sandbox'   => $config['sandbox'],
                ], $config['payum.http_client']);

                return $api;

            };
        }
    }
}

Пока у меня сложилось впечатление, что все более-менее нормально (поправьте меня, если я ошибаюсь). Мне удалось добраться до метода выполнения моего CaptureOffsiteAction:

public function execute($captureRequest)
    {
        RequestNotSupportedException::assertSupports($this, $captureRequest);
        $model = ArrayObject::ensureArrayObject($captureRequest->getModel());

        $httpRequest = new GetHttpRequest();
        $this->gateway->execute($httpRequest);

        if ($httpRequest->method === 'POST') {
            $status = new GetHumanStatus($model);

            if (isset($httpRequest->request['pgw_transaction_id']) && $httpRequest->request['pgw_transaction_id'] > 0) {
                // mark this order id as paid, convert cart to order, etc.

            } else {
                // mark as failed/cancelled, redirect back to cart
            }
            return;

        }

        $model['successUrl'] = $captureRequest->getToken()->getTargetUrl();
        $model['failureUrl'] = $captureRequest->getToken()->getTargetUrl();
        $offsiteUrl = $this->api->getOffsiteUrl();

        $data    = $this->api->prepareOffsitePayment($model->toUnsafeArray());
        $headers = ['application/x-www-form-urlencoded'];

        throw new HttpPostRedirect(
            $offsiteUrl,
            $data,
            200,
            $headers
        );

    }

Во-первых, давайте посмотрим, правильно ли я понял - как только я запускаю процесс захвата (метод выполнения) - все работает, как и ожидалось, я оказываюсь в нижней части метода, создавая URL-адреса успеха и неудачи, готовя внешний платеж, хеш и все такое. это и в конечном итоге окажется на платежном шлюзе через HttpPostRedirect с правильным идентификатором заказа, ценой и т. д. С этой частью все в порядке.

После оплаты, после возврата к успешному URL-адресу, я снова оказываюсь в CaptureOffsiteAction::execute и проверяю pgw_transaction_id - если он установлен, это означает, что я вернулся с платежа, и я должен обнаружить либо успешно оплаченный заказ, либо отмененный/неудачный платеж.

Вопросы:

  • в случае успешной оплаты - как мне "закрыть" заказ (преобразовать корзину в заказ, отметить платеж как захваченный и т.д.?)

  • Я вижу, что в других платежных шлюзах есть много других методов - скажем, меня интересует только оплата за пределами сайта через пост-перенаправление - что делает StatusAction? И когда он вызывается?

До сих пор я находил Payum (и PayumBundle) очень запутанными, мне удалось реализовать 2 внешних шлюза менее чем за день с помощью OmniPay (в других проектах, не связанных с Sylius), но, похоже, я не могу решить эту проблему с Payum. :(

Любая помощь приветствуется, заранее спасибо!


person Zwer    schedule 07.02.2016    source источник


Ответы (1)


Извините за поздний ответ - мне удалось выяснить, где я ошибся.

Вся идея в последовательном вызове Действий. В моем случае это будет:

  • CaptureOffsiteAction
  • СтатусДействие

Моя первая ошибка заключалась в том, что я подумал, что Payum использует только 1 действие для определенного рабочего процесса. Документация была немного запутанной (по крайней мере, мне так показалось), поэтому я не понял той части, где StatusAction фактически заботится об обработке статуса заказа по возвращении из PG.

Итак, из того, что я собрал:

  • CaptureOffsiteAction обрабатывает перенаправление на платежный шлюз и проверяет запрос обратной передачи по возврату.
  • Затем StatusAction вступает во владение и проверяет правильность статусов, чтобы пометить заказ как завершенный, новый или неудавшийся.

Вот мой метод выполнения StatusAction, например:

public function execute($request)
{
    RequestNotSupportedException::assertSupports($this, $request);

    $model = new ArrayObject($request->getModel());
    if (null === $model['EXECCODE']) {
        $request->markNew();
        return;
    }

    if (Api::STATUS_SUCCESS === $model['EXECCODE']) {
        $request->markCaptured();
        return;
    }

    $request->markFailed();
}

И вот последний метод CaptureOffsiteAction::execute:

public function execute($captureRequest)
{
    RequestNotSupportedException::assertSupports($this, $captureRequest);
    $model = ArrayObject::ensureArrayObject($captureRequest->getModel());

    $httpRequest = new GetHttpRequest();
    $this->gateway->execute($httpRequest);

    if ($httpRequest->method === 'POST') {
        // TODO: properly validate response signature here

        if (isset($httpRequest->request['pgw_transaction_id'])) {
            $model['EXECCODE'] = Api::STATUS_SUCCESS;
        } else {
            $model['EXECCODE'] = $httpRequest->request['pgw_result_code'];
        }

        return;
    }

    $model['successUrl'] = $captureRequest->getToken()->getTargetUrl();
    $model['failureUrl'] = $captureRequest->getToken()->getTargetUrl();
    $offsiteUrl = $this->api->getOffsiteUrl();

    $data    = $this->api->prepareOffsitePayment($model->toUnsafeArray(), $captureRequest);

    $headers = ['application/x-www-form-urlencoded'];

    throw new HttpPostRedirect(
        $offsiteUrl,
        $data,
        200,
        $headers
    );
}

Надеюсь, это поможет некоторым из вас! Ваше здоровье.

person Zwer    schedule 23.02.2016
comment
Мне потребовалось 2 дня, чтобы понять это и правильно внедрить Paybox. Спасибо за ваш отзыв. И не забудьте принять ваш ответ. - person Aurel; 25.02.2016