title: How to finalize an order when implementing offsite payment gateway using Sylius 0.16 and Payum
body: <p>I've been trying to wrap my head around on how to implement a classic offline payment gateway using Sylius (sylius/sylius-standard, v0.16).</p>
  
  <p>So far I got this in my bundle's build() method:</p>
  
  <pre><code>public function build(ContainerBuilder $container)
  {
      /** @var PayumExtension $payum */
      $payum = $container-&gt;getExtension('payum');
      $payum-&gt;addGatewayFactory(new PayWayOffsiteGatewayFactory());
  }
  </code></pre>
  
  <p>Here's my config.yml:</p>
  
  <pre><code>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%
  </code></pre>
  
  <p>The %compayway.*% fields are configured okay ofcourse, skipping this part.
  Then I got this in my PayWayOffsiteGatewayFactory class:</p>
  
  <pre><code>class PayWayOffsiteGatewayFactory extends AbstractGatewayFactory
  {
      /**
       * {@inheritdoc}
       */
      public function getName()
      {
          return 'payway_offsite';
      }
  
      /**
       * {@inheritdoc}
       */
      public function addConfiguration(ArrayNodeDefinition $builder)
      {
          parent::addConfiguration($builder);
  
          $builder-&gt;children()
              -&gt;scalarNode('secret_key')-&gt;isRequired()-&gt;cannotBeEmpty()-&gt;end()
              -&gt;scalarNode('shop_id')-&gt;isRequired()-&gt;cannotBeEmpty()-&gt;end()
              -&gt;scalarNode('username')-&gt;isRequired()-&gt;cannotBeEmpty()-&gt;end()
              -&gt;scalarNode('password')-&gt;isRequired()-&gt;cannotBeEmpty()-&gt;end()
              -&gt;booleanNode('sandbox')-&gt;defaultTrue()-&gt;end()
              -&gt;end();
      }
  
      /**
       * {@inheritDoc}
       */
      protected function getPayumGatewayFactoryClass()
      {
          return 'WebBurza\PaymentBundle\Payum\PayWay\PayWayGatewayFactory';
      }
  
      /**
       * {@inheritDoc}
       */
      protected function getComposerPackage()
      {
          return 'webburza/tcompayway';
      }
  }
  </code></pre>
  
  <p>And the PayWayGatewayFactory class: </p>
  
  <pre><code>class PayWayGatewayFactory extends GatewayFactory
  {
      /**
       * {@inheritDoc}
       * @throws \Payum\Core\Exception\InvalidArgumentException
       */
      protected function populateConfig(ArrayObject $config)
      {
          $config-&gt;defaults(array(
              'payum.factory_name'            =&gt; 'payway_offsite',
              'payum.factory_title'           =&gt; 'PayWay Offsite',
              'payum.action.capture'          =&gt; new CaptureOffsiteAction(),
              'payum.action.capture_null'     =&gt; new CaptureOffsiteNullAction(),
              'payum.action.status'           =&gt; new StatusAction(),
              'payum.action.convert_payment'  =&gt; new ConvertPaymentAction(),
          ));
  
          if (false == $config['payum.api']) {
              $config['payum.default_options'] = array(
                  'secret_key'    =&gt; '',
                  'shop_id'       =&gt; '',
                  'username'      =&gt; '',
                  'password'      =&gt; '',
                  'sandbox'       =&gt; true
              );
              $config-&gt;defaults($config['payum.default_options']);
              $config['payum.required_options'] = [
                  'secret_key',
                  'shop_id',
                  'username',
                  'password',
                  'sandbox'
              ];
  
              $config['payum.api'] = function (ArrayObject $config) {
  
                  $config-&gt;validateNotEmpty($config['payum.required_options']);
  
                  $api = new Api([
                      'secret_key'=&gt; $config['secret_key'],
                      'shop_id'   =&gt; $config['shop_id'],
                      'username'  =&gt; $config['username'],
                      'password'  =&gt; $config['password'],
                      'sandbox'   =&gt; $config['sandbox'],
                  ], $config['payum.http_client']);
  
                  return $api;
  
              };
          }
      }
  }
  </code></pre>
  
  <p>So far, I'm under the impression all this is more-less okay (correct me if I'm wrong). I've managed to get to my CaptureOffsiteAction's execute method:</p>
  
  <pre><code>public function execute($captureRequest)
      {
          RequestNotSupportedException::assertSupports($this, $captureRequest);
          $model = ArrayObject::ensureArrayObject($captureRequest-&gt;getModel());
  
          $httpRequest = new GetHttpRequest();
          $this-&gt;gateway-&gt;execute($httpRequest);
  
          if ($httpRequest-&gt;method === 'POST') {
              $status = new GetHumanStatus($model);
  
              if (isset($httpRequest-&gt;request['pgw_transaction_id']) &amp;&amp; $httpRequest-&gt;request['pgw_transaction_id'] &gt; 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-&gt;getToken()-&gt;getTargetUrl();
          $model['failureUrl'] = $captureRequest-&gt;getToken()-&gt;getTargetUrl();
          $offsiteUrl = $this-&gt;api-&gt;getOffsiteUrl();
  
          $data    = $this-&gt;api-&gt;prepareOffsitePayment($model-&gt;toUnsafeArray());
          $headers = ['application/x-www-form-urlencoded'];
  
          throw new HttpPostRedirect(
              $offsiteUrl,
              $data,
              200,
              $headers
          );
  
      }
  </code></pre>
  
  <p>First, lets see if I got this right - once I start the capture process (execute method) - things work as expected, I end up in the bottom part of the method, creating success and failure URLs, preparing offsite payment, hash and all that and eventually end up on the payment gateway via HttpPostRedirect with correct order id, price etc. This part is okay.</p>
  
  <p>After paying, upon returning to success url, I again end up in CaptureOffsiteAction::execute and check for pgw_transaction_id - if this is set, it means i got back from payment and I should detect either a successfully paid order or cancelled/failed payment.</p>
  
  <p><strong>Questions are:</strong></p>
  
  <ul>
  <li><p>in case of successful payment - how do I 'close' the order (convert cart to order, mark payment as captured, etc.?)</p></li>
  <li><p>I see in other payment gateways that there's plenty other methos - lets say im interested in only offsite payment via post-redirect - what does StatusAction do? And when is it called?</p></li>
  </ul>
  
  <p>So far I found Payum (and PayumBundle) very confusing, I've managed to implement 2 offsite gateways in under a day using OmniPay (on other, non-Sylius related projects) but can't seem to solve this Payum thing. :(</p>
  
  <p>Any help appreciated, thanks in advance!</p>