title: Symfony - Payum - Adding funds to user's balance
body: <p>I have a Symfony 3.4 project, I wanted to integrate PayumBundle for Paypal Express Checkout, What I'm trying to achieve is the following:
  A user can add funds to their account using Paypal Express Checkout, once they fill in the amount they wanted to add, I check if the payment went through and I increase the user's balance with that amount.
  A user can buy "Orders" from the website as well, if they did that, the amount of the order will be deducted from their balance.
  and here is what I have set up for now:</p>
  
  <p>Config.yml:</p>
  
  <pre><code>payum:
  security:
      token_storage:
          AppBundle\Entity\PaymentToken: { doctrine: orm }
  storages:
      AppBundle\Entity\Payment: { doctrine: orm }
      AppBundle\Entity\PaymentDetails: { doctrine: orm }
  gateways:
      paypal_express_checkout_default_gateaway:
          factory: paypal_express_checkout
          username:  '**************.email.com'
          password:  '********************'
          signature: '***************'
          sandbox: true
  </code></pre>
  
  <p>My PaymentDetails class:</p>
  
  <pre><code>    /**
       * @ORM\Table(name="payum_payment_details")
       * @ORM\Entity
       */
      class PaymentDetails extends ArrayObject
      {
          /**
           * @ORM\Column(name="id", type="integer")
           * @ORM\Id
           * @ORM\GeneratedValue(strategy="IDENTITY")
           */
          protected $id;
          /**
           * @return int
           */
  
  
          /**
           * One PaymentDetails has One AccountBalanceTransaction.
           * @ORM\OneToOne(targetEntity="AppBundle\Entity\AccountBalanceTransaction", mappedBy="paymentDetails")
           */
          private $accountBalanceTransaction;
  
          /**
           * Many PaymentDetails have One User.
           * @ORM\ManyToOne(targetEntity="Application\Sonata\UserBundle\Entity\User", inversedBy="paymentDetails")
           * @ORM\JoinColumn(name="user_id", referencedColumnName="id")
           */
          private $user;
  
      // getters and setter removed
  
  }
  </code></pre>
  
  <p>AccountBalanceTransaction class:</p>
  
  <pre><code>/**
   * @ORM\Entity
   * @ORM\Table(name="account_balance_transaction")
   */
  class AccountBalanceTransaction
  {
      public function __construct()
      {
          $this-&gt;setDate(new \DateTime());
          $this-&gt;paymentDetails = new ArrayCollection();
      }
  
  
      /**
       * @ORM\Id
       * @ORM\GeneratedValue
       * @ORM\Column(type="integer")
       */
      private $id;
  
      /**
       * @ORM\Column(type="decimal", precision=7, scale=2, nullable=true)
       */
      private $credit;
  
      /**
       * @ORM\Column(type="decimal", precision=7, scale=2, nullable=true)
       */
      private $debit;
  
      /**
       * @ORM\Column(type="decimal", precision=7, scale=2)
       */
      private $balance;
  
      /**
       * @ORM\Column(type="datetime")
       */
      private $date;
  
      /**
       * @ORM\Column(type="text", nullable=true)
       */
      private $description;
  
      /**
       * One AccountBalanceTransaction Might have One PaymentDetails.
       * @ORM\OneToOne(targetEntity="AppBundle\Entity\PaymentDetails", inversedBy="accountBalanceTransaction")
       * @ORM\JoinColumn(name="payment_details_id", referencedColumnName="id", nullable=true)
       */
      private $paymentDetails;
  
  
      /**
       * One AccountBalanceTransaction Might have One Order.
       * @ORM\OneToOne(targetEntity="AppBundle\Entity\Order", inversedBy="accountBalanceTransaction")
       * @ORM\JoinColumn(name="order_id", referencedColumnName="id", nullable=true)
       */
      private $order;
  
      /**
       * Many AccountBalanceTransactions have One User.
       * @ORM\ManyToOne(targetEntity="Application\Sonata\UserBundle\Entity\User", inversedBy="accountBalanceTransactions")
       * @ORM\JoinColumn(name="user_id", referencedColumnName="id")
       */
      private $user;
  }
  </code></pre>
  
  <p>And finally, this is my PaymentController class:</p>
  
  <pre><code>class PaymentController extends Controller
  {
  
   /**
       * @Extra\Route(
       *   "/prepare_add_funds",
       *   name="paypal_express_checkout_prepare_add_funds"
       * )
       *
       * @Extra\Template(":payment:prepare.html.twig")
       */
      public function prepareSimplePurchaseAndDoctrineOrmAction(Request $request)
      {
          $user = $this-&gt;getUser();
          if (!is_object($user) || !$user instanceof UserInterface) {
              throw new AccessDeniedException('This user does not have access to this section.');
          }
  
          $gatewayName = 'paypal_express_checkout_default_gateaway';
          $form = $this-&gt;createPurchaseForm();
          $form-&gt;handleRequest($request);
          if ($form-&gt;isValid()) {
              $data = $form-&gt;getData();
              $storage = $this-&gt;get('payum')-&gt;getStorage(PaymentDetails::class);
              /** @var $payment PaymentDetails */
              $payment = $storage-&gt;create();
              $amount = $data['amount'];
              $payment['PAYMENTREQUEST_0_CURRENCYCODE'] = 'USD';
              $payment['PAYMENTREQUEST_0_AMT'] = $amount;
              $storage-&gt;update($payment);
              $captureToken = $this-&gt;get('payum')-&gt;getTokenFactory()-&gt;createCaptureToken(
                  $gatewayName,
                  $payment,
                  'paypal_express_checkout_done_add_funds'
              );
              $payment['INVNUM'] = $payment-&gt;getId();
              $payment-&gt;setUser($user);
              $storage-&gt;update($payment);
  
              return $this-&gt;redirect($captureToken-&gt;getTargetUrl());
          }
  
          return array(
              'form' =&gt; $form-&gt;createView(),
              'gatewayName' =&gt; $gatewayName
          );
      }
  
      /**
       * @Extra\Route(
       *   "/payment/details",
       *   name="paypal_express_checkout_done_add_funds"
       * )
       */
  
      public function viewAction(Request $request)
      {
          $user = $this-&gt;getUser();
          if (!is_object($user) || !$user instanceof UserInterface) {
              throw new AccessDeniedException('This user does not have access to this section.');
          }
  
  
  
          $token = $this-&gt;get('payum')-&gt;getHttpRequestVerifier()-&gt;verify($request);
          $gateway = $this-&gt;get('payum')-&gt;getGateway($token-&gt;getGatewayName());
          try {
              $gateway-&gt;execute(new Sync($token));
          } catch (RequestNotSupportedException $e) {}
  
          $gateway-&gt;execute($status = new GetHumanStatus($token));
          $payment = $status-&gt;getFirstModel();
  
          if ($user-&gt;getId() != || $payment-&gt;getUser()-&gt;getId()) {
              throw new AccessDeniedException('This user does not have access to this section.');
          }
  
          if ($status-&gt;isCaptured()) {
              $entityManager = $this-&gt;getDoctrine()-&gt;getManager();
              $amount = $details['AMT'];
  
              // check if this paymentDetails already has an AccountBalanceTransaction
              $accountBalanceTransaction = $this-&gt;getDoctrine()
                  -&gt;getRepository(AccountBalanceTransaction::class)
                  -&gt;findOneBy(['paymentDetails' =&gt; $payment-&gt;getId()]);
              // if not then create a new one and increase the user's balance
              if($accountBalanceTransaction === null){
                  $accountBalanceTransaction = new AccountBalanceTransaction();
                  $accountBalanceTransaction-&gt;setUser($user);
                  $accountBalanceTransaction-&gt;setPaymentDetails($payment);
                  $accountBalanceTransaction-&gt;setCredit($amount);
                  $accountBalanceTransaction-&gt;setDescription('User added funds with Paypal.');
                  $accountBalanceTransaction-&gt;setBalance($user-&gt;getBalance() + $amount);
  
                  $entityManager-&gt;persist($accountBalanceTransaction);
                  $entityManager-&gt;flush();
  
  
                  $user-&gt;setBalance($user-&gt;getBalance() + $amount);
                  $entityManager-&gt;persist($user);
                  $entityManager-&gt;flush();
              }
  
              $refundToken = $this-&gt;get('payum')-&gt;getTokenFactory()-&gt;createRefundToken(
                  $token-&gt;getGatewayName(),
                  $status-&gt;getFirstModel(),
                  $request-&gt;getUri()
              );
          }
  
          return $this-&gt;render('payment/view.html.twig', array(
              'status' =&gt; $status-&gt;getValue(),
              'refundToken' =&gt; $refundToken
          ));
      }
  
  
      /**
       * @return \Symfony\Component\Form\FormInterface
       */
      protected function createPurchaseForm()
      {
          return $this-&gt;createFormBuilder()
              -&gt;add('amount', null, array(
                  'data' =&gt; 100,
                  'constraints' =&gt; array(new Range(array('max' =&gt; 10000)))
              ))
              -&gt;getForm()
              ;
      }
  }
  </code></pre>
  
  <p>I'm using FosUserBundle for users management.</p>
  
  <p>This kind of works, but I'm not really happy with it, I think there is a better way to do it, but since I'm new to Payum and payments integration in general, so I'll need someone else's help.</p>
  
  <p>Also, I'm not sure if I should be creating the Entity AccountBalanceTransaction in the viewAction, Maybe I should create it in the prepareAction along with  PaymentDetails.
  But again, what if I did that and the payment failed should I delete it in the view action?</p>