PHPackages                             remp/crm-payments-module - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Payment Processing](/categories/payments)
4. /
5. remp/crm-payments-module

ActiveLibrary[Payment Processing](/categories/payments)

remp/crm-payments-module
========================

CRM Payments Module

4.4.1(3mo ago)051.9k—9.5%6[1 issues](https://github.com/remp2020/crm-payments-module/issues)[1 PRs](https://github.com/remp2020/crm-payments-module/pulls)1MITPHPPHP ^8.1

Since Apr 18Pushed 3mo ago6 watchersCompare

[ Source](https://github.com/remp2020/crm-payments-module)[ Packagist](https://packagist.org/packages/remp/crm-payments-module)[ Docs](https://remp2030.com)[ RSS](/packages/remp-crm-payments-module/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (10)Dependencies (8)Versions (89)Used By (1)

CRM Payments Module
===================

[](#crm-payments-module)

[![Translation status @ Weblate](https://camo.githubusercontent.com/a2a2200e3e0e8854444eec0844e176af7780e6e3c0ecc78389c553316f45e52c/68747470733a2f2f686f737465642e7765626c6174652e6f72672f776964676574732f72656d702d63726d2f2d2f7061796d656e74732d6d6f64756c652f7376672d62616467652e737667)](https://hosted.weblate.org/projects/remp-crm/payments-module/)

Installing module
-----------------

[](#installing-module)

We recommend using Composer for installation and update management.

```
composer require remp/crm-payments-module
```

### Enabling module

[](#enabling-module)

Add installed extension to your `app/config/config.neon` file.

```
extensions:
	- Crm\PaymentsModule\DI\PaymentsModuleExtension
```

Add following commands to your scheduler (e.g. *crontab*) and change the path to match your deploy path:

```
# charge available recurrent payments
*/15 * * * * flock /tmp/payments_charge.lock /usr/bin/php /var/www/html/bin/command.php payments:charge

# pre-calculate payment-related metadata
04 04 * * * /usr/bin/php /var/www/html/bin/command.php payments:calculate_averages
```

### Configuration

[](#configuration)

#### Data retention configuration

[](#data-retention-configuration)

You can configure time before which `application:cleanup` deletes old repository data and column which it uses by using (in your project configuration file):

```
services:
    paymentLogsRepository:
        setup:
            - setRetentionThreshold('-2 months', 'created_at')
```

#### Fast charge check configuration

[](#fast-charge-check-configuration)

You can configure fast charge threshold check by adding this to your configuration:

```
payments:
    fastcharge_threshold: 24 # default: 24; number of hours (if set to 0 fast charge check is disabled)
```

Fast charge check is done by `RecurrentPaymentsChargeCommand::validateRecurrentPayment` and it prevents system from repeated charging if error occurs while charging.

### Scheduled commands

[](#scheduled-commands)

For payment module to work correctly, please add execution of following commands to your scheduler. Example displays crontab usage for execution (alter paths to your deploy paths):

```
# calculate payment related averages; expensive calculations that should be done nightly
04 04 * * * php /var/www/html/bin/command.php payments:calculate_averages

# recurrent payment charges; using flock to allow only single instance running at once
*/15 * * * * flock /tmp/payments_charge.lock /usr/bin/php /var/www/html/bin/command.php payments:charge

### OPTIONAL

# failcheck to prevent payments not working without anyone noticing (see command options)
*/10 * * * * php /var/www/html/bin/command.php payments:last_payments_check --notify=admin@example.com

# try to acquire debit card expiration dates for cards that don't have it
*/10 * * * * php /var/www/html/bin/command.php payments:update_recurrent_payments_expires

# stop recurrent payments with expired cards
7 2 1 * * php /var/www/html/bin/command.php payments:stop_expired_recurrent_payments

# if you use Cardpay/Comfortpay gateways and bank sends you email notifications, you can confirm payments based
# on those emails
*/3 * * * * php /var/www/html/bin/command.php payments:tatra_banka_mail_confirmation

```

### Service commands

[](#service-commands)

Module might provide service commands to be run in the deployed environment. Mostly to handle internal changes and to prevent direct manipulation with the database. You can display required and optional arguments by using `--help` switch when running the command.

Payments module doesn't provide service commands.

Payment gateways
----------------

[](#payment-gateways)

Module has a default set of supported payment gateways developed and used by us:

- `free`. Developed for development purposes, to be used for testing payment-related flows.
- `bank_transfer`. Gateway generates unfinished payment and displays user bank account, amount and transaction identification so the payment can be paired and confirmed later.
- `cardpay` (tatrabanka.sk). One-time card payment provided by Slovak bank.
- `comfortpay` (tatrabanka.sk). Recurrent card payment provided by Slovak bank (CRM is handling charging)
- `csob` (csob.cz). One-time card payment provided by Czech bank.
- `csob_one_click` (csob.cz). Recurrent card payment provided by Czech bank (CRM ish handling charging)
- `paypal` (paypal.com). One-time payment provided by major global provider.
- `paypal_reference` (paypal.com). Recurrent payment provided by major global provider (CRM is handling charging)
- `tatrapay` (tatrabanka.sk). One-time payment linked to Slovak bank's internet banking.

By default, only `bank_transfer` as a default payment gateway is enabled by PaymentsModule. You can enable gateways you wish to use by adding following snippet to your `app/config/config.neon`:

```
services:
	# ...
	gatewayFactory:
		setup:
			- registerGateway(free, Crm\PaymentsModule\Gateways\Free)
```

At this moment, there are several gateway implementations you can add to your CRM installation as a separate module:

- [`stripe`](https://github.com/remp2020/crm-stripe-module) (stripe.com). One-time payment provided by major global provider.
- [`stripe_recurrent`](https://github.com/remp2020/crm-stripe-module) (stripe.com). Recurrent payment provided by major global provider (CRM is handling charging)
- [`slsp_sporopay`](https://github.com/remp2020/crm-slsp-sporopay-module) (slsp.sk). One-time payment linked to Slovak bank's internet banking.
- [`vub_eplatby`](https://github.com/remp2020/crm-vub-eplatby-module) (vub.sk). One-time payment linked to Slovak bank's internet banking.

### Standard (one-time) payments

[](#standard-one-time-payments)

Standard and initial recurrent payment have common beginning of process. Once the system generates instance of new payment, user can be redirected to payment gateway for processing. Each gateway requires different set of parameters to be provided, therefore gateway is responsible for generating the redirect URL with all required parameters.

As `remp/crm-payments-module` is responsible only for actual payment processing, frontend flow can be managed by our [`remp/crm-salesfunnel-module`](https://github.com/remp2020/crm-salesfunnel-module) which provides a way to create sales funnels (payment windows), aggregates statistics and displays user success page after the payment with possibility to extend it with widgets.

After the payment, user is directed back to the CRM. Each gateway provides its own URL where user is directed for payment completion processing.

If the payment is successful, payments module uses [`PaymentCompleteRedirectManager`](extensions/payments-module/src/Models/SuccessPageResolver/PaymentCompleteRedirectManager.php)to determine what kind of success page the user should see. If `crm-salesfunnel-module` is used, user is directed to the success page registered by the module.

The flow of payment processing can be described with following diagram:

[![Payment processing diagram](./docs/payment_processing.svg)](./docs/payment_processing.svg)

### Recurrent payments

[](#recurrent-payments)

#### Initiating payment

[](#initiating-payment)

If the payment uses gateway that supports recurrent payment, the initial flow is usually the same as with the regular payments. The difference comes in during processing of successful initial payment.

PaymentsModule creates new instance of *recurrent payment* - a profile defining when the system should charge user again, and what *subscription type* will the user get when charged.

Each *recurrent payment* instance represent a single payment that will be charged in the future. That means, that if the charge fails, system creates new *recurrent payment* with charge date calculated based on retry rules and stores failing information to the original recurrent payment. Similarly, if the charge was successful, new subscription is created and new *recurrent payment* is defined to be charged in the next period. Thanks to that the system is able to provide information about each charge attempt for whole history of user charging including the bank approval/failure code.

This is all done on backend without system requiring any kind of user interaction. This block merely explains the flow and describes the terms so when displayed in CRM admin, the reader understands the displayed data.

#### Automatic charges

[](#automatic-charges)

To charge the user, add `payments:charge` command to your scheduler. Command doesn't handle concurrent runs - that means that it's responsibility of your scheduler to prevent multiple overlapping instances of command running at the same time. Otherwise a user could be charged twice during the same period.

We recommend using `flock` or some other locking tool which will prevent command to be execute while the previous instance is still running. Following is an example snippet for *crontab* to run the charging every 15 minutes:

```
*/15 * * * * flock /tmp/payments_charge.lock /usr/bin/php /var/www/html/bin/command.php payments:charge
```

#### Notifying expired cards

[](#notifying-expired-cards)

If gateway supports it, CRM fetches expiration date for each `cid` (effectively credit card) used to execute recurring charges. Optionally, you can add command to your scheduler that automatically stops expired recurrent payments:

```
# stop recurrent payments with expired cards
7 2 1 * * php /var/www/html/bin/command.php payments:stop_expired_recurrent_payments

```

When recurrent payment is stopped, [`Crm\PaymentsModule\Events\RecurrentPaymentCardExpiredEvent`](./src/Events/RecurrentPaymentCardExpiredEvent.php) is emitted. By default, `PaymentsModule` checks if there's another active recurring payment for user. If there isn't, it sends `NotificationEvent` with `card_expires_this_month` template code.

If you're not satisfied with the default implementation, you can remove the default handler by unregistering it in your module definition:

```
class FooModule extends Crm\ApplicationModule\CrmModule
{
    // ...
    public function registerEventHandlers(League\Event\Emitter $emitter)
    {
        $emitter->removeListener(
            \Crm\PaymentsModule\Events\RecurrentPaymentCardExpiredEvent::class,
            $this->getInstance(\Crm\PaymentsModule\Events\RecurrentPaymentCardExpiredEventHandler::class)
        );
        // ...
    }
}
```

### Storing additional payment card information

[](#storing-additional-payment-card-information)

You can store additional payment card information using `FetchCardInformationCommand`. For this to work, you need to implement `FetchCardInformationCommandProviderInterface` for your specific payment gateway. By default, we do not provide any `FetchCardInformationCommandProviderInterface` implementations.

example configuration:

```
services:
	fetchCardInformationCommand:
	    setup:
	        - registerProvider(Crm\YourPaymentModule\Commands\FetchCardInformationCommandProvider())
```

example `FetchCardInformationCommandProviderInterface` implementation:

```
class FetchCardInformationCommandProvider implements FetchCardInformationCommandProviderInterface
{
    public function fetch(InputInterface $input, OutputInterface $output): void
    {
        // fetch payment methods for specific payment gateway
        // ...

        // add `from` and `user_id` options filter (input options are defined in `FetchCardInformationCommand`
        if ($from = $input->getOption('from')) {
            $paymentMethods->where('created_at > ?', new DateTime($from));
        }
        if ($userId = $input->getOption('user_id')) {
            $paymentMethods->where('user_id = ?', intval($userId));
        }

        // loop over payment methods fetch card information and store it using `PaymentCardsRepository`
        // ...
            try {
                $this->paymentCardsRepository->upsert(
                  $paymentMethod,
                  $expiration,
                  $maskedCardNumber,
                  $description,
                );
            } catch (\Exception $exception) {
                Debugger::log($exception, Debugger::EXCEPTION);
                continue;
            }
        // ...
    }
}
```

### Implementing new gateway

[](#implementing-new-gateway)

You can implement and integrate new gateways to the CRM if necessary. Based on whether you're implementing standard or recurrent gateway, you implementation should implement just [`Crm\PaymentsModule\Gateways\PaymentInterface`](./extensions/payments-module/src/Models/Gateway/PaymentInterface.php)or the former and [`Crm\PaymentsModule\Gateways\RecurrentPaymentInterface`](./extensions/payments-module/src/Models/Gateway/RecurrentPaymentInterface.php).

When implementing a gateway, we recommend extending [`Crm\PaymentsModule\Gateways\GatewayAbstract`](./extensions/payments-module/src/Models/Gateway/GatewayAbstract.php)to avoid implementing parts which are always similar and would cause code duplication.

Once you have your implementation ready, you need to seed it into the database from within seeder in your own module (see [PaymentGatewaysSeeder](extensions/payments-module/src/Seeders/PaymentGatewaysSeeder.php) as an example) and register it into the application's configuration:

```
services:
	# ...
	- Crm\FooModule\Gateways\Foo
	# ...
	gatewayFactory:
		setup:
			- registerGateway(foo, Crm\FooModule\Gateways\Foo)
```

Then, add *seeder* that will insert the gateway to database. See [`Crm\PaymentsModule\Seeders\PaymentGatewaysSeeder`](./extensions/payments-module/src/Seeders/PaymentGatewaysSeeder.php)as an example *seeder* implementation and [register seeders](https://github.com/remp2020/crm-skeleton#registerseeders) section of CRM skeleton documentation too see how the seeders should be registered in your module.

### Adding PaymentCompletedRedirectResolver

[](#adding-paymentcompletedredirectresolver)

If you want to change the success page displayed to user after the payment based on any arbitrary rule - for example your gateway might want user to see some special offering or require him to enter some additional data - you can register *redirect resolver* to process this request. When the payment is confirmed, redirect resolver will decide (based on priority of registered resolvers) whether to redirect user to a special success page or whether a default success page (if `remp/crm-salesfunnel-module` is used) is sufficient.

The implementation of redirect resolver can look like this:

```
