diff --git a/composer.json b/composer.json index 686f19f9..7d1bd970 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,8 @@ ], "require": { "nesbot/carbon": "*", - "paypal/rest-api-sdk-php": "*" + "paypal/rest-api-sdk-php": "*", + "ext-soap": "*" }, "autoload": { "psr-4": { diff --git a/config/gateway.php b/config/gateway.php index fffe3d7e..d4f4eda7 100644 --- a/config/gateway.php +++ b/config/gateway.php @@ -2,6 +2,18 @@ return [ + /* + |-------------------------------------------------------------------------- + | Default Gateway Configuration Source + |-------------------------------------------------------------------------- + | + | Here you may specify the default source + | to obtain configuration from (file,db,...) + | + */ + + 'default' => env('GATEWAY_CONFIG_SOURCE', 'file'), + //------------------------------- // Timezone for insert dates in database // If you want Gateway not set timezone, just leave it empty @@ -126,5 +138,6 @@ //------------------------------- // Tables names //-------------------------------- + 'config_table' => 'gateway_configurations', 'table' => 'gateway_transactions', ]; diff --git a/migrations/2023_12_15_224213_create_gateway_configurations_table.php b/migrations/2023_12_15_224213_create_gateway_configurations_table.php new file mode 100644 index 00000000..eebb97ac --- /dev/null +++ b/migrations/2023_12_15_224213_create_gateway_configurations_table.php @@ -0,0 +1,50 @@ +getTable(), function (Blueprint $table) { + $table->increments('id'); + + //TODO Laravel 4 does not support enums + // We must use following line in that case + // DB::statement("ALTER TABLE gateway_configurations ADD port ENUM(".join(",",(array)Enum::getIPGs())."); + $table->enum('port', (array)Enum::getIPGs()); + + // Since Multiple settings can be applied to port other than main configuration + // there must be a key-value pair system in db. + //the main configuration key to load the gateway connectiong settings is : "main" + $table->string('key'); + $table->text('value'); + + $table->integer('user_id')->unsigned()->nullable(); + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade')->onUpdate("cascade"); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop($this->getTable()); + } +} diff --git a/src/GatewayResolver.php b/src/GatewayResolver.php index 53071630..771e0493 100644 --- a/src/GatewayResolver.php +++ b/src/GatewayResolver.php @@ -2,165 +2,196 @@ namespace Larabookir\Gateway; -use Larabookir\Gateway\Irankish\Irankish; +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\DB; +use Larabookir\Gateway\Exceptions\InvalidRequestException; +use Larabookir\Gateway\Exceptions\NotFoundTransactionException; +use Larabookir\Gateway\Exceptions\PortNotFoundException; +use Larabookir\Gateway\Exceptions\RetryException; +use Larabookir\Gateway\Mellat\Mellat; use Larabookir\Gateway\Parsian\Parsian; -use Larabookir\Gateway\Paypal\Paypal; +use Larabookir\Gateway\Payir\Payir; use Larabookir\Gateway\Sadad\Sadad; -use Larabookir\Gateway\Mellat\Mellat; -use Larabookir\Gateway\Pasargad\Pasargad; use Larabookir\Gateway\Saman\Saman; -use Larabookir\Gateway\Asanpardakht\Asanpardakht; use Larabookir\Gateway\Zarinpal\Zarinpal; -use Larabookir\Gateway\Payir\Payir; -use Larabookir\Gateway\Exceptions\RetryException; -use Larabookir\Gateway\Exceptions\PortNotFoundException; -use Larabookir\Gateway\Exceptions\InvalidRequestException; -use Larabookir\Gateway\Exceptions\NotFoundTransactionException; -use Illuminate\Support\Facades\DB; class GatewayResolver { - protected $request; - - /** - * @var Config - */ - public $config; - - /** - * Keep current port driver - * - * @var Mellat|Saman|Sadad|Zarinpal|Payir|Parsian - */ - protected $port; - - /** - * Gateway constructor. - * @param null $config - * @param null $port - */ - public function __construct($config = null, $port = null) - { - $this->config = app('config'); - $this->request = app('request'); - - if ($this->config->has('gateway.timezone')) - date_default_timezone_set($this->config->get('gateway.timezone')); - - if (!is_null($port)) $this->make($port); - } - - /** - * Get supported ports - * - * @return array - */ - public function getSupportedPorts() - { - return (array) Enum::getIPGs(); - } - - /** - * Call methods of current driver - * - * @return mixed - */ - public function __call($name, $arguments) - { - - // calling by this way ( Gateway::mellat()->.. , Gateway::parsian()->.. ) - if(in_array(strtoupper($name),$this->getSupportedPorts())){ - return $this->make($name); - } - - return call_user_func_array([$this->port, $name], $arguments); - } - - /** - * Gets query builder from you transactions table - * @return mixed - */ - function getTable() - { - return DB::table($this->config->get('gateway.table')); - } - - /** - * Callback - * - * @return $this->port - * - * @throws InvalidRequestException - * @throws NotFoundTransactionException - * @throws PortNotFoundException - * @throws RetryException - */ - public function verify() - { - if (!$this->request->has('transaction_id') && !$this->request->has('iN')) - throw new InvalidRequestException; - if ($this->request->has('transaction_id')) { - $id = $this->request->get('transaction_id'); - }else { - $id = $this->request->get('iN'); - } - - $transaction = $this->getTable()->whereId($id)->first(); - - if (!$transaction) - throw new NotFoundTransactionException; - - if (in_array($transaction->status, [Enum::TRANSACTION_SUCCEED, Enum::TRANSACTION_FAILED])) - throw new RetryException; - - $this->make($transaction->port); - - return $this->port->verify($transaction); - } - - - /** - * Create new object from port class - * - * @param int $port - * @throws PortNotFoundException - */ - function make($port) + /** + * @var Config + */ + public $config; + protected $request; + /** + * Keep current port driver + * + * @var Mellat|Saman|Sadad|Zarinpal|Payir|Parsian + */ + protected $port; + + /** + * Gateway constructor. + * @param null $config + * @param null $port + */ + public function __construct($config = null, $port = null) + { + $this->config = app('config'); + $this->request = app('request'); + + if ($this->config->has('gateway.timezone')) + date_default_timezone_set($this->config->get('gateway.timezone')); + + if (!is_null($port)) $this->make($port); + } + + /** + * Create new object from port class + * + * @param int $port + * @throws PortNotFoundException + */ + function make($port) { - if ($port InstanceOf Mellat) { - $name = Enum::MELLAT; - } elseif ($port InstanceOf Parsian) { - $name = Enum::PARSIAN; - } elseif ($port InstanceOf Saman) { - $name = Enum::SAMAN; - } elseif ($port InstanceOf Zarinpal) { - $name = Enum::ZARINPAL; - } elseif ($port InstanceOf Sadad) { - $name = Enum::SADAD; - } elseif ($port InstanceOf Asanpardakht) { - $name = Enum::ASANPARDAKHT; - } elseif ($port InstanceOf Paypal) { - $name = Enum::PAYPAL; - } elseif ($port InstanceOf Payir) { - $name = Enum::PAYIR; - } elseif ($port InstanceOf Pasargad) { - $name = Enum::PASARGAD; - } elseif ($port InstanceOf Irankish) { - $name = Enum::IRANKISH; - } elseif (in_array(strtoupper($port), $this->getSupportedPorts())) { - $port = ucfirst(strtolower($port)); - $name = strtoupper($port); - $class = __NAMESPACE__ . '\\' . $port . '\\' . $port; - $port = new $class; - } else - throw new PortNotFoundException; - - $this->port = $port; - $this->port->setConfig($this->config); // injects config - $this->port->setPortName($name); // injects config - $this->port->boot(); + // Check if $port is an instance of PortAbstract or a string + if ($port instanceof PortAbstract) { + // $port is an instance, get the port name from the object + $portName = $port->getPortName(); + $portKey = strtoupper($portName); + } else if (is_string($port)) { + // $port is a string, use it directly + $portKey = strtoupper($port); + } else { + throw new PortNotFoundException("Invalid port type provided."); + } + + // Check if the port is supported + $supportedPorts = $this->getSupportedPorts(); + if (!in_array($portKey, $supportedPorts)) { + throw new PortNotFoundException("Gateway '$portKey' is not supported."); + } + + // Get the current user from the request + $user = $this->request->user(); + $userId = $user ? $user->id : null; + + // Define a unique cache key for the gateway configuration + $cacheKey = "gateway_config_{$portKey}_user_{$userId}"; + + // Try to get the configuration from the cache + $config = Cache::get($cacheKey); + + if (!$config) { + // Configuration not in cache, fetch from source + $configSource = $this->config->get('gateway.default', 'file'); + + if ($configSource == 'db') { + // Fetch from database using DB facade, user-specific if user is authenticated + $query = DB::table('gateway_configurations') + ->where(['port' => $portKey, 'key' => 'main']); + + if ($userId) { + $query->where('user_id', $userId); + } + + $gatewayConfig = $query->first(); + $config = $gatewayConfig ? json_decode($gatewayConfig->value, true) : null; + } else { + // Fetch from file + $configKey = "gateway.".strtolower($portKey); + $config = $this->config->get($configKey); + } + + if (!$config) { + throw new PortNotFoundException("Configuration for {$portKey} not found."); + } + + // Store the configuration in the cache + Cache::put($cacheKey, $config, 60); // Cache for 60 minutes + } + + // Create and configure the gateway + $class = __NAMESPACE__ . '\\' . ucfirst(strtolower($port)) . '\\' . ucfirst(strtolower($port)); + if (class_exists($class)) { + $this->port = new $class($config); + $this->port->setPortName($portKey); + $this->port->setConfig($config); + $this->port->boot(); + } else { + throw new PortNotFoundException("Gateway class '{$class}' not found."); + } return $this; } + + /** + * Get supported ports + * + * @return array + */ + public function getSupportedPorts() + { + return (array)Enum::getIPGs(); + } + + /** + * Call methods of current driver + * + * @return mixed + */ + public function __call($name, $arguments) + { + + // calling by this way ( Gateway::mellat()->.. , Gateway::parsian()->.. ) + if (in_array(strtoupper($name), $this->getSupportedPorts())) { + return $this->make($name); + } + + return call_user_func_array([$this->port, $name], $arguments); + } + + /** + * Callback + * + * @return $this->port + * + * @throws InvalidRequestException + * @throws NotFoundTransactionException + * @throws PortNotFoundException + * @throws RetryException + */ + public function verify() + { + if (!$this->request->has('transaction_id') && !$this->request->has('iN')) + throw new InvalidRequestException; + if ($this->request->has('transaction_id')) { + $id = $this->request->get('transaction_id'); + } else { + $id = $this->request->get('iN'); + } + + $transaction = $this->getTable()->whereId($id)->first(); + + if (!$transaction) + throw new NotFoundTransactionException; + + if (in_array($transaction->status, [Enum::TRANSACTION_SUCCEED, Enum::TRANSACTION_FAILED])) + throw new RetryException; + + $this->make($transaction->port); + + return $this->port->verify($transaction); + } + + /** + * Gets query builder from you transactions table + * @return mixed + */ + function getTable() + { + return DB::table($this->config->get('gateway.table')); + } + + } diff --git a/src/PortAbstract.php b/src/PortAbstract.php index bccbf060..4b932dc4 100644 --- a/src/PortAbstract.php +++ b/src/PortAbstract.php @@ -99,7 +99,12 @@ function boot(){ function setConfig($config) { - $this->config = $config; + // If $config is an array, simply use it. Otherwise, assume it's a Laravel config object. + if (is_array($config)) { + $this->config = new \Illuminate\Config\Repository($config); + } else { + $this->config = $config; + } } /**