Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a transaction id generation by date #7

Open
wants to merge 2 commits into
base: 1.6
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 26 additions & 4 deletions src/Action/Api/ApiRequestAction.php
Original file line number Diff line number Diff line change
@@ -2,7 +2,9 @@

namespace Ekyna\Component\Payum\Payzen\Action\Api;

use Ekyna\Component\Payum\Payzen\Api\TransactionIdInterface;
use Ekyna\Component\Payum\Payzen\Request\Request;
use Exception;
use Payum\Core\Bridge\Spl\ArrayObject;
use Payum\Core\Exception\RequestNotSupportedException;
use Payum\Core\Reply\HttpRedirect;
@@ -14,10 +16,21 @@
*/
class ApiRequestAction extends AbstractApiAction
{
/**
* @var TransactionIdInterface
*/
private $transactionId;

public function __construct(
TransactionIdInterface $transactionId
) {
$this->transactionId = $transactionId;
}

/**
* @inheritdoc
*
* @throws HttpRedirect
* @throws HttpRedirect|Exception
*/
public function execute($request): void
{
@@ -30,9 +43,18 @@ public function execute($request): void
return;
}

$model['vads_trans_id'] = $this->api->getTransactionId();
// Current UTC date time
$model['vads_trans_date'] = (new \DateTime('now', new \DateTimeZone('UTC')))->format('YmdHis');
// If your application needs to generate a trans_id before this request
if (isset($model['generated_vads_trans_id']) && isset($model['generated_vads_trans_date'])) {
$model['vads_trans_id'] = $model['generated_vads_trans_id'];
$model['vads_trans_date'] = $model['generated_vads_trans_date'];
unset($model['generated_vads_trans_id']);
unset($model['generated_vads_trans_date']);
} else {
//You can generate a trans_id by a file method or a date method, you need to have the same date used to generate the trans_id
$transactionId = $this->transactionId->getTransactionId();
$model['vads_trans_id'] = $transactionId['vads_trans_id'];
$model['vads_trans_date'] = $transactionId['vads_trans_date'];
}

$data = $model->getArrayCopy();

14 changes: 8 additions & 6 deletions src/Action/ConvertPaymentAction.php
Original file line number Diff line number Diff line change
@@ -36,24 +36,26 @@ public function execute($request)

$model = ArrayObject::ensureArrayObject($payment->getDetails());

if (false == $model['vads_amount']) {
if (!$model['vads_amount']) {
$this->gateway->execute($currency = new GetCurrency($payment->getCurrencyCode()));
if (2 < $currency->exp) {
throw new RuntimeException('Unexpected currency exp.');
}
$divisor = pow(10, 2 - $currency->exp);
// $currecy->exp is the number of decimal required with this currency
$multiplier = pow(10, $currency->exp);

$model['vads_currency'] = (string)$currency->numeric;
$model['vads_amount'] = (string)abs($payment->getTotalAmount() / $divisor);
// used to send a non-decimal value to the platform, it can be reverted with currency->exp who be known by Payzen
$model['vads_amount'] = (string)abs($payment->getTotalAmount() * $multiplier);
}

if (false == $model['vads_order_id']) {
if (!$model['vads_order_id']) {
$model['vads_order_id'] = $payment->getNumber();
}
if (false == $model['vads_cust_id']) {
if (!$model['vads_cust_id']) {
$model['vads_cust_id'] = $payment->getClientId();
}
if (false == $model['vads_cust_email']) {
if (!$model['vads_cust_email']) {
$model['vads_cust_email'] = $payment->getClientEmail();
}

71 changes: 2 additions & 69 deletions src/Api/Api.php
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@
namespace Ekyna\Component\Payum\Payzen\Api;

use Payum\Core\Exception\LogicException;
use Payum\Core\Exception\RuntimeException;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;

@@ -54,55 +53,9 @@ public function setConfig(array $config)
->resolve($config);
}

/**
* Returns the next transaction id.
*
* @return string
*/
public function getTransactionId(): string
public function getConfig(): array
{
$path = $this->getDirectoryPath() . 'transaction_id';

// Create file if not exists
if (!file_exists($path)) {
touch($path);
chmod($path, 0600);
}

$date = (new \DateTime())->format('Ymd');
$fileDate = date('Ymd', filemtime($path));
$isDailyFirstAccess = ($date != $fileDate);

// Open file
$handle = fopen($path, 'r+');
if (false === $handle) {
throw new RuntimeException('Failed to open the transaction ID file.');
}
// Lock File
if (!flock($handle, LOCK_EX)) {
throw new RuntimeException('Failed to lock the transaction ID file.');
}

$id = 1;
// If not daily first access, read and increment the id
if (!$isDailyFirstAccess) {
$id = (int)fread($handle, 6);
$id++;
}

// Truncate, write, unlock and close.
fseek($handle, 0);
ftruncate($handle, 0);
fwrite($handle, (string)$id);
fflush($handle);
flock($handle, LOCK_UN);
fclose($handle);

if ($this->config['debug']) {
$id += 89000;
}

return str_pad($id, 6, '0', STR_PAD_LEFT);
return $this->config;
}

/**
@@ -198,26 +151,6 @@ public function generateSignature(array $data, $hashed = true): string
return $content;
}

/**
* Returns the directory path and creates it if not exists.
*
* @return string
*/
private function getDirectoryPath(): string
{
$path = $this->config['directory'];


// Create directory if not exists
if (!is_dir($path)) {
if (!mkdir($path, 0755, true)) {
throw new RuntimeException('Failed to create cache directory');
}
}

return $path . DIRECTORY_SEPARATOR;
}

/**
* Check that the API has been configured.
*
21 changes: 21 additions & 0 deletions src/Api/IdGeneratedByDate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Ekyna\Component\Payum\Payzen\Api;

use Exception;

class IdGeneratedByDate implements TransactionIdInterface
{
/**
* @throws Exception
*/
public function getTransactionId(): array
{
$diff = (new \DateTimeImmutable('midnight', new \DateTimeZone('UTC')))
->diff(new \DateTimeImmutable('now', new \DateTimeZone('UTC')));
return [
'vads_trans_date' => (new \DateTime('now', new \DateTimeZone('UTC')))->format('YmdHis'),
'vads_trans_id' => sprintf('%06d', random_int(0, 9) + (($diff->h * 3600 + $diff->i * 60 + $diff->s) * 10))
];
}
}
87 changes: 87 additions & 0 deletions src/Api/IdGeneratedByFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

namespace Ekyna\Component\Payum\Payzen\Api;

use Exception;
use Payum\Core\Exception\RuntimeException;

class IdGeneratedByFile implements TransactionIdInterface
{
private $path;
private $debug;

public function __construct(
string $path,
bool $debug = false
) {
$this->path = $path;
$this->debug = $debug;
}

/**
* @throws Exception
*/
public function getTransactionId(): array
{
$this->createDirectoryPath();
$this->path = rtrim($this->path,DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
$path = $this->path . 'transaction_id';

// Create file if not exists
if (!file_exists($path)) {
touch($path);
chmod($path, 0600);
}

$date = (new \DateTime())->format('Ymd');
$fileDate = date('Ymd', filemtime($path));
$isDailyFirstAccess = ($date != $fileDate);

// Open file
$handle = fopen($path, 'r+');
if (false === $handle) {
throw new RuntimeException('Failed to open the transaction ID file.');
}
// Lock File
if (!flock($handle, LOCK_EX)) {
throw new RuntimeException('Failed to lock the transaction ID file.');
}

$id = 1;
// If not daily first access, read and increment the id
if (!$isDailyFirstAccess) {
$id = (int)fread($handle, 6);
$id++;
}

// Truncate, write, unlock and close.
fseek($handle, 0);
ftruncate($handle, 0);
fwrite($handle, (string)$id);
fflush($handle);
flock($handle, LOCK_UN);
fclose($handle);

if ($this->debug) {
$id += 89000;
}

return [
'vads_trans_date' => (new \DateTime('now', new \DateTimeZone('UTC')))->format('YmdHis'),
'vads_trans_id' => str_pad($id, 6, '0', STR_PAD_LEFT)
];
}

/**
* Returns the directory path and creates it if not exists.
*/
public function createDirectoryPath(): void
{
// Create directory if not exists
if (!is_dir($this->path)) {
if (!mkdir($this->path, 0755, true)) {
throw new RuntimeException('Failed to create cache directory');
}
}
}
}
9 changes: 9 additions & 0 deletions src/Api/TransactionIdInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Ekyna\Component\Payum\Payzen\Api;

interface TransactionIdInterface
{
/** @return array{vads_trans_date: string, vads_trans_id: string} */
public function getTransactionId(): array;
}
23 changes: 18 additions & 5 deletions src/PayzenGatewayFactory.php
Original file line number Diff line number Diff line change
@@ -2,6 +2,8 @@

namespace Ekyna\Component\Payum\Payzen;

use Ekyna\Component\Payum\Payzen\Api\IdGeneratedByFile;
use Ekyna\Component\Payum\Payzen\Api\TransactionIdInterface;
use Payum\Core\Bridge\Spl\ArrayObject;
use Payum\Core\GatewayFactory;
use Payum\Core\GatewayFactoryInterface;
@@ -13,17 +15,28 @@
*/
class PayzenGatewayFactory extends GatewayFactory
{
private $transactionId;

public function __construct(
array $defaultConfig = array(),
GatewayFactoryInterface $coreGatewayFactory = null,
TransactionIdInterface $transactionId = null
) {
parent::__construct($defaultConfig, $coreGatewayFactory);
$this->transactionId = $transactionId ?? new IdGeneratedByFile($defaultConfig['directory'] ?? sys_get_temp_dir());
}

/**
* Builds a new factory.
*
* @param array $defaultConfig
* @param array $defaultConfig
* @param GatewayFactoryInterface|null $coreGatewayFactory
*
* @param TransactionIdInterface|null $transactionId
* @return PayzenGatewayFactory
*/
public static function build(array $defaultConfig, GatewayFactoryInterface $coreGatewayFactory = null): PayzenGatewayFactory
public static function build(array $defaultConfig, GatewayFactoryInterface $coreGatewayFactory = null, TransactionIdInterface $transactionId = null): PayzenGatewayFactory
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not compatible with Payum/Core:
PayumBuilder::buildAddedGatewayFactories() won't give a value to $transationId argument.
I think default translation id generator should be defined in the default config.

{
return new static($defaultConfig, $coreGatewayFactory);
return new static($defaultConfig, $coreGatewayFactory, $transactionId);
}

/**
@@ -42,7 +55,7 @@ protected function populateConfig(ArrayObject $config)
'payum.action.refund' => new Action\RefundAction(),
'payum.action.status' => new Action\StatusAction(),
'payum.action.notify' => new Action\NotifyAction(),
'payum.action.api.request' => new Action\Api\ApiRequestAction(),
'payum.action.api.request' => new Action\Api\ApiRequestAction($this->transactionId),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inject transaction id generator into Api instance (see next comment) instead of ApiRequestAction .
ApiRequestAction is aware of Api (just give access to generator).

'payum.action.api.response' => new Action\Api\ApiResponseAction(),
Copy link
Owner

@ekyna ekyna Sep 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be possible to configure transaction id generator here. Something like:

            'payum.action.api.directory'      => sys_get_temp_dir(),
            'payum.action.api.id_generator'   => function(array $config) { 
                return new IdGeneratedByFile($config['payum.action.api.directory']) 
            },

then, when configuring api (around line 92), maybe you can do something like this:

            $config['payum.api'] = function (ArrayObject $config) {
                $api = new Api\Api();
                $api->setConfig($payzenConfig);
                $api->setIdGenerator($config['payum.action.api.id_generator']);
                return $api;
            };

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree too, when you are using Payum, it's better to inject what you need on the Action or on the Api services. Here it's better to just inject it into payum.action.api.request as @ekyna said.

]);

Loading
Oops, something went wrong.