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:
Config.yml:
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
My PaymentDetails class:
/**
* @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
}
AccountBalanceTransaction class:
/**
* @ORM\Entity
* @ORM\Table(name="account_balance_transaction")
*/
class AccountBalanceTransaction
{
public function __construct()
{
$this->setDate(new \DateTime());
$this->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;
}
And finally, this is my PaymentController class:
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->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->createPurchaseForm();
$form->handleRequest($request);
if ($form->isValid()) {
$data = $form->getData();
$storage = $this->get('payum')->getStorage(PaymentDetails::class);
/** @var $payment PaymentDetails */
$payment = $storage->create();
$amount = $data['amount'];
$payment['PAYMENTREQUEST_0_CURRENCYCODE'] = 'USD';
$payment['PAYMENTREQUEST_0_AMT'] = $amount;
$storage->update($payment);
$captureToken = $this->get('payum')->getTokenFactory()->createCaptureToken(
$gatewayName,
$payment,
'paypal_express_checkout_done_add_funds'
);
$payment['INVNUM'] = $payment->getId();
$payment->setUser($user);
$storage->update($payment);
return $this->redirect($captureToken->getTargetUrl());
}
return array(
'form' => $form->createView(),
'gatewayName' => $gatewayName
);
}
/**
* @Extra\Route(
* "/payment/details",
* name="paypal_express_checkout_done_add_funds"
* )
*/
public function viewAction(Request $request)
{
$user = $this->getUser();
if (!is_object($user) || !$user instanceof UserInterface) {
throw new AccessDeniedException('This user does not have access to this section.');
}
$token = $this->get('payum')->getHttpRequestVerifier()->verify($request);
$gateway = $this->get('payum')->getGateway($token->getGatewayName());
try {
$gateway->execute(new Sync($token));
} catch (RequestNotSupportedException $e) {}
$gateway->execute($status = new GetHumanStatus($token));
$payment = $status->getFirstModel();
if ($user->getId() != || $payment->getUser()->getId()) {
throw new AccessDeniedException('This user does not have access to this section.');
}
if ($status->isCaptured()) {
$entityManager = $this->getDoctrine()->getManager();
$amount = $details['AMT'];
// check if this paymentDetails already has an AccountBalanceTransaction
$accountBalanceTransaction = $this->getDoctrine()
->getRepository(AccountBalanceTransaction::class)
->findOneBy(['paymentDetails' => $payment->getId()]);
// if not then create a new one and increase the user's balance
if($accountBalanceTransaction === null){
$accountBalanceTransaction = new AccountBalanceTransaction();
$accountBalanceTransaction->setUser($user);
$accountBalanceTransaction->setPaymentDetails($payment);
$accountBalanceTransaction->setCredit($amount);
$accountBalanceTransaction->setDescription('User added funds with Paypal.');
$accountBalanceTransaction->setBalance($user->getBalance() + $amount);
$entityManager->persist($accountBalanceTransaction);
$entityManager->flush();
$user->setBalance($user->getBalance() + $amount);
$entityManager->persist($user);
$entityManager->flush();
}
$refundToken = $this->get('payum')->getTokenFactory()->createRefundToken(
$token->getGatewayName(),
$status->getFirstModel(),
$request->getUri()
);
}
return $this->render('payment/view.html.twig', array(
'status' => $status->getValue(),
'refundToken' => $refundToken
));
}
/**
* @return \Symfony\Component\Form\FormInterface
*/
protected function createPurchaseForm()
{
return $this->createFormBuilder()
->add('amount', null, array(
'data' => 100,
'constraints' => array(new Range(array('max' => 10000)))
))
->getForm()
;
}
}
I'm using FosUserBundle for users management.
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.
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?