We start off by creating a new module called FoggylinePaymentBundle
. We do so with the help of the console by running the following command:
php bin/console generate:bundle --namespace=Foggyline/PaymentBundle
The command triggers an interactive process which asks us several questions along the way, shown as follows:
Once done, files app/AppKernel.php
and app/config/routing.yml
are modified automatically. The registerBundles
method of an AppKernel
class has been added to the following line under the $bundles
array:
new FoggylinePaymentBundleFoggylinePaymentBundle(),
The routing.yml
has been updated with the following entry:
foggyline_payment: resource: "@FoggylinePaymentBundle/Resources/config/routing.xml" prefix: /
In order to avoid colliding with the core application code, we need to change the prefix: /
to prefix: /payment/
.
Even though we won't be storing any credit cards in our database as part of this chapter, we want to reuse the Symfony auto-generate CRUD feature in order for it to provide us with a credit card model and form. Let's go ahead and create a Card
entity. We will do so by using the console, shown as follows:
php bin/console generate:doctrine:entity
The command triggers the interactive generator, providing it with FoggylinePaymentBundle:Card
for an entity shortcut, where we also need to provide entity properties. We want to model our Card
entity with the following fields:
card_type
: stringcard_number
: stringexpiry_date
: datesecurity_code
: stringOnce done, the generator creates Entity/Card.php
and Repository/CardRepository.php
within the src/Foggyline/PaymentBundle/
directory. We can now update the database so it pulls in the Card
entity, shown as follows:
php bin/console doctrine:schema:update --force
With the entity in place, we are ready to generate its CRUD. We will do so by using the following command:
php bin/console generate:doctrine:crud
This results in a src/Foggyline/PaymentBundle/Controller/CardController.php
file being created. It also adds an entry to our app/config/routing.yml file
, as follows:
foggyline_payment_card: resource: "@FoggylinePaymentBundle/Controller/CardController.php" type: annotation
Again, the view files were created under the app/Resources/views/card/
directory. Since we won't actually be doing any CRUD related actions around cards as such, we can go ahead and delete all of the generated view files, as well as the entire body of the CardController
class. At this point, we should have our Card
entity, CardType
form, and empty CardController
class.
The card payment service is going to provide the relevant information our future sales module will need for its checkout process. Its role is to provide the payment method label, code, and processing URLs of an order, such as authorize
, capture
, and cancel
.
We will start by defining the following service under the services element of the src/Foggyline/PaymentBundle/Resources/config/services.xml
file:
<service id="foggyline_payment.card_payment"class="FoggylinePaymentBundleServiceCardPayment"> <argument type="service" id="form.factory"/> <argument type="service" id="router"/> <tag name="payment_method"/> </service>
This service accepts two arguments: one being form.factory
and the other being router
. form.factory
that will be used within service to create a form view for the CardType
form. The tag is a crucial element here, as our SalesBundle
module will be looking for payment methods based on the payment_method
tag assigned to the service.
We now need to create the actual service class within the src/Foggyline/PaymentBundle/Service/CardPayment.php
file as follows:
namespace FoggylinePaymentBundleService; use FoggylinePaymentBundleEntityCard; class CardPayment { private $formFactory; private $router; public function __construct( $formFactory, SymfonyBundleFrameworkBundleRoutingRouter $router ) { $this->formFactory = $formFactory; $this->router = $router; } public function getInfo() { $card = new Card(); $form = $this->formFactory->create('FoggylinePaymentBundleFormCardType', $card); return array( 'payment' => array( 'title' =>'Foggyline Card Payment', 'code' =>'card_payment', 'url_authorize' => $this->router->generate('foggyline_payment_card_authorize'), 'url_capture' => $this->router->generate('foggyline_payment_card_capture'), 'url_cancel' => $this->router->generate('foggyline_payment_card_cancel'), 'form' => $form->createView() ) ); } }
The getInfo
method is what's going to provide the necessary information to our future SalesBundle
module in order for it to construct the payment step of the checkout process. We are passing on three different types of URLs here: authorize
, capture
, and cancel
. These routes do not exist just yet, as we will create them soon. The idea is that we will shift the payment actions and process to the actual payment
method. Our future SalesBundle
module will merely be doing an AJAX POST
to these payment URLs, and will expect either a success or error JSON response. A success response should yield some sort of transaction ID and an error response should yield a label message to show to the user.
We will edit the src/Foggyline/PaymentBundle/Resources/config/routing.xml
file by adding the following route definitions to it:
<route id="foggyline_payment_card_authorize" path="/card/authorize"> <default key="_controller">FoggylinePaymentBundle:Card:authorize</default> </route> <route id="foggyline_payment_card_capture" path="/card/capture"> <default key="_controller">FoggylinePaymentBundle:Card:capture</default> </route> <route id="foggyline_payment_card_cancel" path="/card/cancel"> <default key="_controller">FoggylinePaymentBundle:Card:cancel</default> </route>
We will then edit the body of the CardController
class by adding the following to it:
public function authorizeAction(Request $request) { $transaction = md5(time() . uniqid()); // Just a dummy string, simulating some transaction id, if any if ($transaction) { return new JsonResponse(array( 'success' => $transaction )); } return new JsonResponse(array( 'error' =>'Error occurred while processing Card payment.' )); } public function captureAction(Request $request) { $transaction = md5(time() . uniqid()); // Just a dummy string, simulating some transaction id, if any if ($transaction) { return new JsonResponse(array( 'success' => $transaction )); } return new JsonResponse(array( 'error' =>'Error occurred while processing Card payment.' )); } public function cancelAction(Request $request) { $transaction = md5(time() . uniqid()); // Just a dummy string, simulating some transaction id, if any if ($transaction) { return new JsonResponse(array( 'success' => $transaction )); } return new JsonResponse(array( 'error' =>'Error occurred while processing Card payment.' )); }
We should now be able to access URLs like /app_dev.php/payment/card/authorize
and see the output of authorizeAction
. Implementations given here are dummy ones. For the purpose of this chapter ,we are not going to connect to a real payment processing API. What is important for us to know is that the sales
module will, during its checkout process, render any possible form view pushed through the ['payment']['form']
key of the getInfo
method of a payment_method
tagged service. Meaning, the checkout process should show a credit card form under card payment. The behavior of checking out will be coded such that if payment with a form is selected and the Place Order button is clicked, that payment form will prevent the checkout process from proceeding until the payment form is submitted to either authorize or capture the URL defined in the payment itself. We will touch upon this some more when we get to the SalesBundle
module.
Aside from the credit card payment method, let's go ahead and define one more static payment, called Check Money.
We will start by defining the following service under the services element of the src/Foggyline/PaymentBundle/Resources/config/services.xml
file:
<service id="foggyline_payment.check_money"class="FoggylinePaymentBundleServiceCheckMoneyPayment"> <argument type="service" id="router"/> <tag name="payment_method"/> </service>
The service
defined here accepts only one router
argument. The tag name
is the same as with the card payment service.
We will then create the src/Foggyline/PaymentBundle/Service/CheckMoneyPayment.php
file, with content as follows:
namespace FoggylinePaymentBundleService; class CheckMoneyPayment { private $router; public function __construct( SymfonyBundleFrameworkBundleRoutingRouter $router ) { $this->router = $router; } public function getInfo() { return array( 'payment' => array( 'title' =>'Foggyline Check Money Payment', 'code' =>'check_money', 'url_authorize' => $this->router->generate('foggyline_payment_check_money_authorize'), 'url_capture' => $this->router->generate('foggyline_payment_check_money_capture'), 'url_cancel' => $this->router->generate('foggyline_payment_check_money_cancel'), //'form' =>'' ) ); } }
Unlike a card payment, the check money payment has no form key defined under the getInfo
method. This is because there are no credit card entries for it to define. It is just going to be a static payment method. However, we still need to define the authorize
, capture
, and cancel
URLs, even though their implementation might be nothing more than just a simple JSON response with success or error keys.
Once the check money payment service is in place, we can go ahead and create the necessary routes for it. We will start by adding the following route definitions to the src/Foggyline/PaymentBundle/Resources/config/routing.xml
file:
<route id="foggyline_payment_check_money_authorize"path="/check_money/authorize"> <default key="_controller">FoggylinePaymentBundle:CheckMoney:authorize</default> </route> <route id="foggyline_payment_check_money_capture"path="/check_money/capture"> <default key="_controller">FoggylinePaymentBundle:CheckMoney:capture</default> </route> <route id="foggyline_payment_check_money_cancel"path="/check_money/cancel"> <default key="_controller">FoggylinePaymentBundle:CheckMoney:cancel</default> </route>
We will then create the src/Foggyline/PaymentBundle/Controller/CheckMoneyController.php
file, with content as follows:
namespace FoggylinePaymentBundleController; use SymfonyComponentHttpFoundationJsonResponse; use SymfonyComponentHttpFoundationRequest; use SymfonyBundleFrameworkBundleControllerController; class CheckMoneyController extends Controller { public function authorizeAction(Request $request) { $transaction = md5(time() . uniqid()); return new JsonResponse(array( 'success' => $transaction )); } public function captureAction(Request $request) { $transaction = md5(time() . uniqid()); return new JsonResponse(array( 'success' => $transaction )); } public function cancelAction(Request $request) { $transaction = md5(time() . uniqid()); return new JsonResponse(array( 'success' => $transaction )); } }
Similar to a card payment, here we added a simple dummy implementation of the authorize
, capture
, and cancel
methods. The method responses will feed into the SalesBundle
module later on. We can easily implement more robust functionality from within these methods, but that is out of the scope of this chapter.
3.18.220.243