JMSPaymentCoreBundle¶
A unified API for processing payments with Symfony
Introduction¶
This bundle provides the foundation for different payment backends. It abstracts away the differences between payment protocols, and offers a simple, and unified API for performing financial transactions.
Features:
- Simple, unified API (integrate once and use any payment provider)
- Persistence of financial entities (such as payments, transactions, etc.)
- Transaction management including retry logic
- Encryption of sensitive data
- Support for many payment backends out of the box
- Easily support other payment backends
Getting started¶
Once you followed the Setup instructions, if you have no prior experience with this bundle or payment processing in general, you should follow the Accepting payments guide.
Once you grasp how this bundle works, take a look at the Payment form chapter to learn how to customize the form.
License¶
- Code: Apache2
- Docs: CC BY-NC-ND 3.0
Setup¶
Configuration¶
The configuration is as simple as setting an encryption key which will be used for encrypting data. You can generate a random key with the following command:
bin/console jms_payment_core:generate-key
And then use it in your configuration:
# config/packages/payment.yaml
jms_payment_core:
encryption:
secret: output_of_above_command
Warning
If you change the secret
or the crypto
provider, all encrypted data will become unreadable.
Create database tables¶
This bundle requires a few database tables, which you can create as follows.
If you’re not using database migrations:
bin/console doctrine:schema:update
Or, if you’re using migrations:
bin/console doctrine:migrations:diff
bin/console doctrine:migrations:migrate
Note
It’s assumed you have entity auto mapping enabled, which is usually the case. If you don’t, you need to either enable it:
# config/packages/doctrine.yaml
doctrine:
orm:
auto_mapping: true
Or explicitly register the configuration from this bundle:
# config/packages/doctrine.yaml
doctrine:
orm:
mappings:
JMSPaymentCoreBundle: ~
Configure a payment backend¶
In addition to setting up this bundle, you will also need to install a plugin for each payment backend you intend to support. Plugins are simply bundles you add to your application, as you would with any other Symfony bundle.
Tip
See Available payment backends for the list of existing plugins.
Using the Paypal plugin as an example, you would install it with composer:
composer require jms/payment-paypal-bundle
And configure it:
# config/packages/payment.yaml
jms_payment_paypal:
username: your api username
password: your api password
signature: your api signature
Note
Other plugins will require different configuration. Take a look at their documentation for complete instructions.
Next steps¶
If you have no prior experience with this bundle or payment processing in general, you should follow the Accepting payments guide. Otherwise, proceed to the Payment form chapter.
Payment form¶
This bundle ships with a form type that automatically renders a choice
(radio button, select) so that the user can choose their preferred payment method.
Additionally, each payment plugin you have installed, includes a specific form that is also rendered. This form is dependent on the payment method itself, different methods will have different forms.
As an example, if you have both the PayPal and Paymill plugins installed, both their forms will be rendered. In PayPal’s case, the form is empty (since the user does not enter any information on your site) but for Paymill a Credit Card form is rendered.
Tip
See the Accepting payments guide for detailed instructions on how to integrate the form in your application, namely how to handle form submission.
Creating the form¶
When creating the form you need to specify at least the amount
and currency
options. See below for all the available options.
// src/App/Controller/OrdersController.php
use JMS\Payment\CoreBundle\Form\ChoosePaymentMethodType;
$form = $this->createForm(ChoosePaymentMethodType::class, null, [
'amount' => '10.42',
'currency' => 'EUR',
]);
Note
If your Symfony version is earlier than 3.0
, you must refer to the form by its alias instead of using the class directly:
// src/App/Controller/OrdersController.php
$form = $this->createForm('jms_choose_payment_method', null, [
'amount' => '10.42',
'currency' => 'EUR',
]);
Changing how the form looks¶
If you need to change how the form looks, you can use form theming, which allows you to customize how each element of the form is rendered. Our theme will be implemented in a separate Twig file, which we will then reference from the template where the form is rendered.
Tip
See the form component’s documentation for more information about form theming
Start by creating an empty theme file:
{# templates/Orders/theme.html.twig #}
{% extends 'form_div_layout.html.twig' %}
Note
We’re extending Symfony’s default form_div_layout.html.twig
theme. If your application is setup to use another theme, you probably want to extend that one instead.
And then reference it from the template where the form is rendered:
{# templates/Orders/show.html.twig #}
{% form_theme form 'Orders\theme.html.twig' %}
{{ form_start(form) }}
{{ form_widget(form) }}
<input type="submit" value="Pay € {{ order.amount }}" />
{{ form_end(form) }}
Hiding the payment method radio button¶
When the form only has one available payment method (either because only one payment plugin is installed or because you used the allowed_methods
option) you likely want to hide the payment method radio button completely. You can do so as follows:
{# templates/Orders/theme.html.twig #}
{# Don't render the radio button's label #}
{% block _jms_choose_payment_method_method_label %}
{% endblock %}
{# Hide each entry in the radio button #}
{% block _jms_choose_payment_method_method_widget %}
<div style="display: none;">
{{ parent() }}
</div>
{% endblock %}
Tip
If you hide the radio button, you will want to use the default_method option to automatically select the payment method.
Available options¶
amount
¶
Mandatory
The amount (i.e. total price) of the payment.
// src/App/Controller/OrdersController.php
use JMS\Payment\CoreBundle\Form\ChoosePaymentMethodType;
$form = $this->createForm(ChoosePaymentMethodType::class, null, [
'amount' => '10.42',
'currency' => 'EUR',
]);
You might want to add extra costs for a specific payment method. You can implement this by passing a closure instead of a static value:
// src/App/Controller/OrdersController.php
use JMS\Payment\CoreBundle\Entity\ExtendedData;
use JMS\Payment\CoreBundle\Form\ChoosePaymentMethodType;
$amount = '10.42';
$amountClosure = function ($currency, $paymentSystemName, ExtendedData $data) use ($amount) {
if ($paymentSystemName === 'paypal_express_checkout') {
return $amount * 1.05;
}
return $amount;
};
$form = $this->createForm(ChoosePaymentMethodType::class, null, [
'amount' => $amountClosure,
'currency' => 'EUR',
]);
currency
¶
Mandatory
The three-letter currency code, i.e. EUR
or USD
.
// src/App/Controller/OrdersController.php
use JMS\Payment\CoreBundle\Form\ChoosePaymentMethodType;
$form = $this->createForm(ChoosePaymentMethodType::class, null, [
'amount' => '10.42',
'currency' => 'EUR',
]);
predefined_data
¶
Optional
Default: []
The payment plugins likely require you to provide additional configuration in order to create a payment. You can do this by passing an array to the predefined_data
option of the form.
As an example, if we would be using the Stripe plugin, we would need to provide a description
, which would look like the following:
// src/App/Controller/OrdersController.php
use JMS\Payment\CoreBundle\Form\ChoosePaymentMethodType;
$predefinedData = [
'stripe_checkout' => [
'description' => 'My product',
],
];
$form = $this->createForm(ChoosePaymentMethodType::class, null, [
'amount' => '10.42',
'currency' => 'EUR',
'predefined_data' => $predefinedData,
]);
If you would be using multiple payment backends, the $predefinedData
array would have an entry for each of the methods:
// src/App/Controller/OrdersController.php
$predefinedData = [
'paypal_express_checkout' => [...],
'stripe_checkout' => [...],
];
allowed_methods
¶
Optional
Default: []
In case you wish to constrain the methods presented to the user, use the allowed_methods
option:
// src/App/Controller/OrdersController.php
use JMS\Payment\CoreBundle\Form\ChoosePaymentMethodType;
$form = $this->createForm(ChoosePaymentMethodType::class, null, [
'amount' => '10.42',
'currency' => 'EUR',
'allowed_methods' => ['paypal_express_checkout']
]);
default_method
¶
Optional
Default: null
By default, no payment method is selected in the radio button, which means users must select one themselves. This is the case even if you only have one payment method available.
If you wish to set a default payment method, you can use the default_method
option:
// src/App/Controller/OrdersController.php
use JMS\Payment\CoreBundle\Form\ChoosePaymentMethodType;
$form = $this->createForm(ChoosePaymentMethodType::class, null, [
'amount' => '10.42',
'currency' => 'EUR',
'default_method' => 'paypal_express_checkout',
]);
choice_options
¶
Optional
Default: []
Pass options to the payment method choice
type. See the ChoiceType refererence for all available options.
For example, to display a select instead of a radio button, set the expanded
option to false
:
// src/App/Controller/OrdersController.php
use JMS\Payment\CoreBundle\Form\ChoosePaymentMethodType;
$form = $this->createForm(ChoosePaymentMethodType::class, null, [
'amount' => '10.42',
'currency' => 'EUR',
'choice_options' => [
'expanded' => false,
],
]);
method_options
¶
Optional
Default: []
Pass options to each payment method’s form type. For example, to hide the main label of the PayPal Express Checkout form, set the label
option to false
:
// src/App/Controller/OrdersController.php
use JMS\Payment\CoreBundle\Form\ChoosePaymentMethodType;
$form = $this->createForm(ChoosePaymentMethodType::class, null, [
'amount' => '10.42',
'currency' => 'EUR',
'method_options' => [
'paypal_express_checkout' => [
'label' => false,
],
],
]);
Events¶
The PluginController dispatches events for certain payment changes. This can be used by your application to perform certain actions, for example, when a payment is successful.
Take a look at Symfony’s documentation for information on how to listen to events.
PaymentInstruction State Change Event¶
Name: payment_instruction.state_change
Class: JMS\Payment\CoreBundle\PluginController\Event\PaymentInstructionStateChangeEvent
This event is dispatched after the state of a payment instruction changes.
You have access to the PaymentInstruction
, the new state and the old state of the payment instruction.
Payment State Change Event¶
Name: payment.state_change
Class: JMS\Payment\CoreBundle\PluginController\Event\PaymentStateChangeEvent
This event is dispatched directly after the state of a payment changed. All related entities have already been updated.
You have access to the Payment
, the PaymentInstruction
, the new state and the old state of the payment.
Plugins¶
A plugin is a flexible way of providing access to a specific payment back end, payment processor, or payment service provider. Plugins are used to execute a FinancialTransaction against a payment service provider, such as Paypal.
Implementing a custom plugin¶
The easiest way is to simply extend the provided AbstractPlugin
class, and override the remaining abstract methods:
use JMS\Payment\CoreBundle\Plugin\AbstractPlugin;
class PaypalPlugin extends AbstractPlugin
{
public function processes($name)
{
return 'paypal' === $name;
}
}
Now, you only need to setup your plugin as a service, and it will be added to the plugin controller automatically:
- YAML
services: payment.plugin.paypal: class: PaypalPlugin tags: [{name: payment.plugin}]
- XML
<service id="payment.plugin.paypal" class="PaypalPlugin"> <tag name="payment.plugin" /> </service>
That’s it! You just created your first plugin :) Right now, it does not do anything useful, but we will get to the specific transactions that you can perform in the next section.
Available transaction types¶
Each plugin may implement a variety of available transaction types. Depending on the used payment method and the capabilities of the backend, you rarely need all of them.
Following is a list of all available transactions, and two exemplary payment method plugins. A “x” indicates that the method is implement, “-” that it is not:
Financial Transaction | CreditCardPlugin | ElectronicCheckPlugin |
---|---|---|
checkPaymentInstruction | x | x |
validatePaymentInstruction | x | x |
approveAndDeposit | x | x |
approve | x | - |
reverseApproval | x | - |
deposit | x | x |
reverseDeposit | x | - |
credit | x | - |
reverseCredit | x | - |
If you are unsure which transactions to implement, have a look at the PluginInterface
which contains detailed descriptions for each of them.
Tip
In cases, where a certain method does not make sense for your payment backend, you should throw a FunctionNotSupportedException
. If you extend the AbstractPlugin
base class, this is already done for you.
Available exceptions¶
Exceptions play an important part in the communication between the different payment plugin, and the PluginController
which manages them.
Following is a list with available exceptions, and how they are treated by the PluginController
. Of course, you can also add your own exceptions, but it is recommend that you sub-class an existing exception when doing so.
Tip
All exceptions which are relevant for plugins are located in the namespace JMS\Payment\CoreBundle\Plugin\Exception
.
Class | Description | Payment Plugin Controller Interpretation |
---|---|---|
Exception | Base exception used by all exceptions thrown from plugins. | Causes any transaction to be rolled back. Exception will be re-thrown. |
FunctionNotSupportedException | This exception is thrown whenever a method on the interface is not supported by the plugin. | In most cases, this causes any transactions to be rolled back. Notable exceptions to this rule: checkPaymentInstruction, validatePaymentInstruction |
InvalidDataException | This exception is thrown whenever the plugin realizes that the data associated with the transaction is invalid. | Causes any transaction to be rolled back. Exception will be re-thrown. |
InvalidPaymentInstructionException | This exception is typically thrown from within either checkPaymentInstruction, or validatePaymentInstruction. | Causes PaymentInstruction to be set to STATE_INVALID. |
BlockedException | This exception is thrown whenever a transaction cannot be processed. The exception must only be used when the situation is temporary, and there is a chance that the transaction can be performed at a later time successfully. |
Sets the transaction to STATE_PENDING, and converts the exception to a Result object. |
TimeoutException (sub-class of BlockedException) | This exception is thrown when there is an enduring communication problem with the payment backend system. | Sets the transaction to STATE_PENDING, and converts the exception to a Result object. |
ActionRequiredException (sub-class of BlockedException) | This exception is thrown whenever an action is required before the transaction can be completed successfully. A typical action would be for the user to visit an URL in order to authorize the payment. |
Sets the transaction to STATE_PENDING, and converts the exception to a Result object. |
Model¶
PaymentInstruction¶
A PaymentInstruction
is the first object that you need to create. It contains information such as the total amount, the payment method, the currency, and any data that is necessary for the payment method, for example credit card information.
Tip
Any payment related data may be automatically encrypted if you request this.
Below you find the different states that a PaymentInstruction
can go through:

Payment¶
Each PaymentInstruction
may be split up into several payments. A Payment
always holds an amount, and the current state of the workflow, such as initiated, approved, deposited, etc.
This allows, for example, to request a fraction of the total amount to be deposited before an order ships, and the rest afterwards.
Below, you find the different states that a Payment
can go through:

FinancialTransaction¶
Each Payment
may have several transactions. Each FinancialTransaction
represents a specific interaction with the payment backend. In the case of a credit card payment, this could for example be an authorization transaction.
Note
There may only ever be one open transaction for each PaymentInstruction
at a time. This is enforced, and guaranteed.
Below, you find the different states that a FinancialTransaction
can go through:

Available payment backends¶
This is the list of currently supported payment backends, through community-created plugins.
Note
If a backend you intend to use is not on the list, you can create a custom plugin.
Tip
If you have implemented a payment backend, please add it to this list by editing this file on GitHub and submitting a Pull Request.
Accepting payments¶
In this guide, we explore how to accept payments using this bundle, by building a simplified Checkout system from scratch.
Tip
In no way are you forced to use the presented system in your application, this is merely the simplest way to show this bundle in action. We recomend you follow the steps below and, once you grasp how this bundle works, think about the best way to integrate it into your application.
Warning
We have completely left out any security considerations. In a real-world scenario, you must make sure a user is not able to access other users’ data.
The Order entity¶
The Order
entity represents what is being purchased and usually contains:
$id
: The unique id of the order$amount
: The total price$paymentInstruction
: ThePaymentInstruction
instance
Tip
If you’re wondering what a PaymentInstruction
is, take a look at The Model, though you don’t strictly need to understand it to follow the instructions below.
Here’s the full code for a minimal Order
entity:
// src/App/Entity/Order.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use JMS\Payment\CoreBundle\Entity\PaymentInstruction;
/**
* @ORM\Table(name="orders")
* @ORM\Entity
*/
class Order
{
/**
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\OneToOne(targetEntity="JMS\Payment\CoreBundle\Entity\PaymentInstruction")
*/
private $paymentInstruction;
/**
* @ORM\Column(type="decimal", precision=10, scale=5)
*/
private $amount;
public function __construct($amount)
{
$this->amount = $amount;
}
public function getId()
{
return $this->id;
}
public function getAmount()
{
return $this->amount;
}
public function getPaymentInstruction()
{
return $this->paymentInstruction;
}
public function setPaymentInstruction(PaymentInstruction $instruction)
{
$this->paymentInstruction = $instruction;
}
}
Warning
Note that the precision
and scale
in the $amount
column definition are set to 10
and 5
, respectively. This is consistent with the mapping this bundle uses internally and means that the greatest amount you will be able to accept is 99999.99999
.
See the Overriding entity mapping guide for instructions on how to override this limit.
Before proceeding, make sure you update your database schema, in order to create the orders
table:
bin/console doctrine:schema:update
Or, if using migrations:
bin/console doctrine:migrations:diff
bin/console doctrine:migrations:migrate
The Controller¶
Each step of our Checkout process will be implemented as an action in an OrdersController
. All routes will be namespaced under /orders
.
Go ahead and create the controller:
// src/App/Controller/OrdersController.php
namespace App\Controller;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
/**
* @Route("/orders")
*/
class OrdersController extends AbstractController
{
}
Creating an Order¶
The first step in our Checkout process is to create an Order
, which we will do in a newAction
. This action acts as the bridge between the Checkout process and the rest of your application.
To simplify, we will only be passing an amount
(the total price of the items being purchased) as a parameter to the action. In a real world application you would probably pass the $id
of a Shopping Cart, or a similar entity that holds information about the items being purchased.
Create the newAction
in the OrdersController
:
// src/App/Controller/OrdersController.php
use AppBundle\Entity\Order;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/new/{amount}")
*/
public function newAction($amount)
{
$em = $this->getDoctrine()->getManager();
$order = new Order($amount);
$em->persist($order);
$em->flush();
return $this->redirectToRoute('app_orders_show', [
'orderId' => $order->getId(),
]);
}
If you navigate to /orders/new/42.24
, a new Order
will be inserted in the database with 42.24
as the amount
and you will be redirected to the showAction
, which we will create next.
Creating the payment form¶
Once the Order
has been created, the next step in our Checkout process is to display it, along with the payment form. We will be doing this in a showAction
:
// src/App/Controller/OrdersController.php
use App\Entity\Order;
use JMS\Payment\CoreBundle\Form\ChoosePaymentMethodType;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
/**
* @Route("/{orderId}/show")
*/
public function showAction($orderId, Request $request, PluginController $ppc)
{
$order = $this->getDoctrine()->getManager()->getRepository(Order::class)->find($orderId);
$form = $this->createForm(ChoosePaymentMethodType::class, null, [
'amount' => $order->getAmount(),
'currency' => 'EUR',
]);
return $this->render('Orders/show.html.twig', [
'order' => $order,
'form' => $form->createView(),
]);
}
Note
If your Symfony version is earlier than 3.0
, you must refer to the form by its alias instead of using the class directly:
// src/AppBundle/Controller/OrdersController.php
$form = $this->createForm('jms_choose_payment_method', null, [
'amount' => $order->getAmount(),
'currency' => 'EUR',
]);
And the corresponding template:
{# templates/Orders/show.html.twig #}
Total price: € {{ order.amount }}
{{ form_start(form) }}
{{ form_widget(form) }}
<input type="submit" value="Pay € {{ order.amount }}" />
{{ form_end(form) }}
If you now refresh the page in your browser, you should see the template rendered, with all the payment methods you have installed. The form includes a radio button so the user can select the payment method they wish to use.
Tip
If you get a There is no payment method available
exception, you haven’t configured any payment backends yet. Please see Configure a payment backend for information on how to do this.
Tip
See Payment form for information on all the available options you can pass to the form.
Handling form submission¶
We’ll handle form submission in the same action which renders the form. Upon binding, the form type will validate the data for the chosen payment method and, on success, give us back a valid PaymentInstruction
instance.
We’ll attach this PaymentInstruction
to the Order
and then redirect to the paymentCreateAction
. In case the form is not valid, we don’t redirect and the template is re-rendered with form errors displayed.
Note that no remote calls to the payment backend are made in this action, we’re simply manipulating data in the local database.
// src/App/Controller/OrdersController.php
use App\Entity\Order;
use JMS\Payment\CoreBundle\Form\ChoosePaymentMethodType;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
/**
* @Route("/{orderId}/show")
*/
public function showAction($orderId, Request $request, PluginController $ppc)
{
$form = $this->createForm(ChoosePaymentMethodType::class, null, [
'amount' => $order->getAmount(),
'currency' => 'EUR',
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$ppc->createPaymentInstruction($instruction = $form->getData());
$order->setPaymentInstruction($instruction);
$em = $this->getDoctrine()->getManager();
$em->persist($order);
$em->flush($order);
return $this->redirectToRoute('app_orders_paymentcreate', [
'orderId' => $order->getId(),
]);
}
return $this->render('Orders/show.html.twig', [
'order' => $order,
'form' => $form->createView(),
]);
}
Depositing money¶
In the previous section, we created our PaymentInstruction
and redirected to the paymentCreateAction
. In this section we will be implementing that action.
Creating a Payment
instance¶
Let’s start by creating a private method in our controller, which will aid us in creating the Payment
instance. No remote calls will be made yet.
// src/App/Controller/OrdersController.php
use App\Entity\Order;
use JMS\Payment\CoreBundle\PluginController\PluginController;
private function createPayment(Order $order, PluginController $ppc)
{
$instruction = $order->getPaymentInstruction();
$pendingTransaction = $instruction->getPendingTransaction();
if ($pendingTransaction !== null) {
return $pendingTransaction->getPayment();
}
$amount = $instruction->getAmount() - $instruction->getDepositedAmount();
return $ppc->createPayment($instruction->getId(), $amount);
}
Issuing the payment¶
Now we’ll call the createPayment
method we implemented in the previous section in a new createPaymentAction
, where we will actually create a payment through the payment backend and, if successful, redirect the user to a paymentCompleteAction
:
// src/App/Controller/OrdersController.php
use App\Entity\Order;
use Symfony\Component\Routing\Annotation\Route;
use JMS\Payment\CoreBundle\PluginController\PluginController;
use JMS\Payment\CoreBundle\PluginController\Result;
/**
* @Route("/{orderId}/payment/create")
*/
public function paymentCreateAction($orderId, PluginController $ppc)
{
$order = $this->getDoctrine()->getManager()->getRepository(Order::class)->find($orderId);
$payment = $this->createPayment($order, $ppc);
$result = $ppc->approveAndDeposit($payment->getId(), $payment->getTargetAmount());
if ($result->getStatus() === Result::STATUS_SUCCESS) {
return $this->redirectToRoute('app_orders_paymentcomplete', [
'orderId' => $order->getId(),
]);
}
throw $result->getPluginException();
// In a real-world application you wouldn't throw the exception. You would,
// for example, redirect to the showAction with a flash message informing
// the user that the payment was not successful.
}
Tip
If you get an Unable to generate a URL
exception, the transaction was successful. We just haven’t created that action yet, we will be doing so later.
If you get an ActionRequiredException
, you are using a payment backend which requires offsite operations. In the next section we explain what this means and how to support it.
Performing the payment offsite¶
Certain payment backends (e.g. Paypal) require the user to go their site to actually perform the payment. In that case, $result
will have status Pending
and we need to redirect the user to a given URL.
We would add the following to our action:
// src/App/Controller/OrdersController.php
use JMS\Payment\CoreBundle\Plugin\Exception\Action\VisitUrl;
use JMS\Payment\CoreBundle\Plugin\Exception\ActionRequiredException;
use JMS\Payment\CoreBundle\PluginController\Result;
if ($result->getStatus() === Result::STATUS_PENDING) {
$ex = $result->getPluginException();
if ($ex instanceof ActionRequiredException) {
$action = $ex->getAction();
if ($action instanceof VisitUrl) {
return $this->redirect($action->getUrl());
}
}
}
throw $result->getPluginException();
Tip
If you get an exception, you probably didn’t configure the payment plugin correctly. Take a look at the respective plugin’s documentation and make sure you followed the instructions.
Displaying a Payment complete page¶
The last step in out Checkout process is to tell the user the payment was successful. We wil be doing so in a paymentCompleteAction
, to which we have been redirected from the paymentCreateAction
:
// src/App/Controller/OrdersController.php
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/{orderId}/payment/complete")
*/
public function paymentCompleteAction($orderId)
{
return new Response('Payment complete');
}
Migrating from Mcrypt¶
Coming soon
Overriding entity mapping¶
By default, this bundle sets the type of database columns which store amounts to decimal
with the precision
set to 10
and scale
set to 5
. This means that the greatest amount you are able to process is 99999.99999
.
In case you need to accept payments of greater value, it’s possible to override the entity mapping supplied by this bundle and use a custom one. Keep reading for instructions on how to do this.
Note
In a future major release, amounts will be stored as strings, thus removing this limitation.
Copying the mapping files¶
Start by copying the mapping files from this bundle to your application:
cd my-app
mkdir -p config/packages/JMSPaymentCoreBundle
cp vendor/jms/payment-core-bundle/JMS/Payment/CoreBundle/Resources/config/doctrine/* config/packages/JMSPaymentCoreBundle/
You now have a copy of the following mapping files under config/packages/JMSPaymentCoreBundle
:
Credit.orm.xml
FinancialTransaction.orm.xml
Payment.orm.xml
PaymentInstruction.orm.xml
Configuring custom mapping¶
The next step is to tell Symfony to use your copy of the files instead of the ones supplied by this bundle:
# config/packages/doctrine.yml
doctrine:
orm:
# ...
mappings:
JMSPaymentCoreBundle:
type: xml
dir: '%kernel.root_dir%/config/packages/JMSPaymentCoreBundle'
prefix: JMS\Payment\CoreBundle\Entity
alias: JMSPaymentCoreBundle
Overriding decimal columns¶
Symfony is now using your custom mapping. Taking PaymentInstruction.orm.xml
as an example, we can increase the maximum value of the amount
column as follows:
<!-- config/packages/JMSPaymentCoreBundle/PaymentInstruction.orm.xml -->
<!-- Set maximum value to 9999999999.99999 -->
<field name="amount" type="decimal" precision="15" scale="5" />
Warning
Make sure you change the definition of all the decimal
columns in all the mapping files.
Updating the database¶
Now that you changed the mapping, you need to update your database schema.
If you’re not using database migrations:
bin/console doctrine:schema:update
Or, if you’re using migrations:
bin/console doctrine:migrations:diff
bin/console doctrine:migrations:migrate