Payum / PayumBundle

Payum offers everything you need to work with payments. From simplest use cases to very advanced ones.
https://payum.gitbook.io/payum
MIT License
569 stars 142 forks source link

One Time and Recurring Payment Items in same Payment #484

Open xeon826 opened 5 years ago

xeon826 commented 5 years ago

I have some items that will be one time payments and others that will recurring, I'm wanting to know how to have these in the same cart. One idea I had was that I could send them to the payment gateway to pay for the one time items and then redirect them back after the payment was successful or at least attempted and have them attempt to pay for the rest (The recurring payment items) But I don't know that this is the best way, is there a way I can specify both in the same cart?

     /**
     * @Route("/accounts/checkout", name="checkout")
     */
    public function checkoutAction(Request $request)
    {
        $orderForm = $this->createForm(OrderType::class, null, [
            'user' => $this->getUser(),
            'gateways' => $this->getParameter('app.gateways'),
        ]);
        $orderForm->handleRequest($request);
        $filter = $this->createForm(FilterHeroType::class, [], [
            'hierarchy_categories' => new Hierarchy($this->getDoctrine()->getRepository('AppBundle:Category'), 'category', 'categories'),
            'router' => $this->get('router'),
        ]);

        // Calculate total price
        $products = $request->getSession()->get('products');
        if (!$products) {
            $this->addFlash('danger', $this->get('translator')->trans('Cart is empty. Not able to proceed checkout.'));

            return $this->redirectToRoute('cart');
        }

        $price = 0;
        foreach ($products as $product) {
            $price += $product['price'];
        }

        // Save order
        if ($orderForm->isSubmitted() && $orderForm->isValid()) {
            $order = $orderForm->getData();
            $order->setStatus(Order::STATUS_NEW);
            $order->setUser($this->getUser());
            $order->setCurrency($this->getParameter('app.currency'));
            $order->setPrice($price);

            try {
                $em = $this->getDoctrine()->getManager();
                $em->persist($order);
                $em->flush();
            } catch (\Exception $e) {
                $this->addFlash('danger', $this->get('translator')->trans('An error occurred whe saving order.'));
            }

            // Save order items
            foreach ($products as $product) {
                $orderItem = new OrderItem();
                if ($product['type'] == 'pay_for_featured' || $product['type'] == 'pay_for_publish') {
                  $listing = $this->getDoctrine()->getRepository('AppBundle:Listing')->findOneBy(['id' => $product['listing_id']]);
                  $orderItem->setListing($listing);
                } else {
                  $county = $this->getDoctrine()->getRepository('AppBundle:County')->findOneBy(['id' => $product['county_id']]);
                  $orderItem->setCounty($county);
                }

                $orderItem->setOrder($order);
                $orderItem->setPrice($product['price']);
                $orderItem->setType($product['type']);
                $orderItem->setDuration($product['duration']);

                try {
                    $em = $this->getDoctrine()->getManager();
                    $em->persist($orderItem);
                    $em->flush();
                } catch (\Exception $e) {
                    $this->addFlash('danger', $this->get('translator')->trans('An error occurred whe saving order item.'));
                }
            }

            $request->getSession()->remove('products');
            $this->addFlash('success', $this->get('translator')->trans('Order has been successfully saved.'));

            if ($order->getGateway() == 'paypal') {
                $gatewayName = 'paypal_express_checkout';

                $storage = $this->get('payum')->getStorage(Payment::class);

                $payment = $storage->create();
                $payment->setNumber(uniqid());
                $payment->setCurrencyCode($this->getParameter('app.currency'));
                $payment->setTotalAmount($price * 100);
                $payment->setDescription('A description');
                $payment->setClientId($this->getUser()->getId());
                $payment->setClientEmail('user-email@host.com');
                $payment->setDescription('Payment for services rendered');
                $payment->setOrder($order);
                $payment->setDetails([
                  'PAYMENTREQUEST_0_AMT' => 0,
                  'L_BILLINGTYPE0' => Api::BILLINGTYPE_RECURRING_PAYMENTS,
                  'L_BILLINGAGREEMENTDESCRIPTION0' => "asdfasdf",
                  'NOSHIPPING' => 1
                ]);

                $storage->update($payment);

                $captureToken = $this->get('payum')->getTokenFactory()->createCaptureToken($gatewayName, $payment, 'order_paypal_completed');
                return $this->redirect($captureToken->getTargetUrl());
            }

            return $this->redirectToRoute('order');
        }

        return $this->render('FrontBundle::Order/checkout.html.twig', [
          'order' => $orderForm->createView(),
          'filter' => $filter->createView()
        ]);
    }

In this, pay_for_county is the item type that would be a recurring payment and any other would be one time. Looking at this page at L_BILLINGTYPEn I'm lost on a couplethings, one of which, what does n or m indicate? Also, MerchantInitiatedBilling looks like the right option as I need to specify per-transaction billing agreements but how would I set this and then how would I specify which items were one-item and which were recurring?

    /**
     * @Route("/account/county/add/{id}", name="county_add", requirements={"id": "\d+"})
     * @ParamConverter("County", class="DirectoryPlatform\AppBundle\Entity\County")
     */
    public function countyAction(Request $request, County $county)
    {
        // if ($this->getUser() !== $listing->getUser()) {
        //     throw $this->createAccessDeniedException('You are not allowed to access this page.');
        // }

        $payments = $this->getParameter('app.payments');

        if ($payments['pay_for_county']['enabled']) {
            $session = $request->getSession();

            if ($session->get('products')) {
                foreach ($session->get('products') as $product) {
                    if ($product['type'] == 'pay_for_county' && $product['county_id'] == $county->getId()) {
                        $this->addFlash('danger', $this->get('translator')->trans('Product is already in cart.'));

                        return $this->redirectToRoute('user_counties', [
                          'stateId' => $county->getStateId()
                        ]);
                    }
                }
            }

            $product = [
                'type' => 'pay_for_county',
                'county_id' => $county->getId(),
                'price' => $payments['pay_for_county']['price'],
                'duration' => $payments['pay_for_county']['duration'],
            ];

            if ($session->has('products')) {
                $products = $session->get('products');

                array_push($products, $product);
                $session->set('products', $products);
            } else {
                $session->set('products', [$product]);
            }

            $this->addFlash('success', $this->get('translator')->trans('Request for advertising county has been added into cart.'));
        } else {

            $profile = $this->getUser()->getProfile();
            $county->setProfiles($profile);

            try {
                $em = $this->getDoctrine()->getManager();
                $em->persist($county);
                $em->flush();

                $this->addFlash('success', $this->get('translator')->trans('Listing has been successfully marked as featured.'));
            } catch (\Exception $e) {
                $this->addFlash('danger', $this->get('translator')->trans('An error occurred when saving listing object.'));
            }
        }

        return $this->redirectToRoute('user_counties', [
          'stateId' => $county->getStateId()
        ]);
    }

Here I'm adding the products to the final order, could I possibly do it here? And even though it's a thing I'll have to figure out AFTER I fix this, how would I go about pinging the payment gateway to ensure the recurring payment had been successful? Since there are several items that are recurring payments, I don't know that I could have a way of figuring out which had been successful.

xeon826 commented 5 years ago

So after looking around, I came up with this

                $payment->setDetails([
                  'METHOD' => 'SetExpressCheckout',
                  'VERSION' => '108',
                  'L_BILLINGTYPE0' => 'RecurringPayments',
                  'L_BILLINGAGREEMENTDESCRIPTION0' => "first item",
                  'L_BILLINGTYPE1' => 'RecurringPayments',
                  'L_BILLINGAGREEMENTDESCRIPTION1' => "second item",
                ]);

But only the first item shows up on Paypal after redirecting to the targetUrl, am I missing something?