Añadiendo los plugins base payments y cms_api

This commit is contained in:
Jordan
2026-03-05 18:31:21 +00:00
parent 83006dad83
commit 48fab839d0
1531 changed files with 163437 additions and 0 deletions

View File

@@ -0,0 +1,132 @@
<?php
/*
Pasos para integrar:
1. Integrar la carga del javascript principal con la key
<script c-if="tienda.pago_por_financiacion" src="https://cdn.aplazame.com/aplazame.js" data-aplazame="{{tienda.aplazame_public_key}}" data-sandbox="{{tienda.aplazame_modo ? 'true' : 'false'}}"></script>
2. Integrar el simulador para que el usuario sepa las cuotas o el propio widget
https://aplazame.com/docs/api/
3. Llama a $tpv = new AplazameMethod(); y $tpv->pay(); para generar el id de payment y haz la llamada a aplazame.checkout(data) desde javascript;
4. Apunta el confirm a la url https://tudominio.com/cms/lib/plugins/payments/ipn.php?ipn={{sha1(id de payment)}}
*/
// require_once __DIR__."/vendor/apiRedsys.php";
// require_once __DIR__."/PaymentMethod.class.php";
require_once __DIR__ . "/vendor/vendor-aplazame/autoload.php";
require_once __DIR__ . "/PaymentMethod.class.php";
use Aplazame\Api\Client as AplazameClient;
class AplazameMethod extends PaymentMethod {
function __construct() {
self::get_config();
$this->init(41, 'Aplazame', 'https://api.aplazame.com', [
'public_key' => self::$config['aplazame_public_key'],
'private_key' => self::$config['aplazame_private_key']
], 'https://api.aplazame.com', [
'public_key' => self::$config['aplazame_public_key'],
'private_key' => self::$config['aplazame_private_key']
]);
}
function can_be_used() {
$cred = $this->get_credentials();
return true;
}
function pay($quantity, $insertData = null, $payment = null, $curl = false) {
parent::pay($quantity, $insertData, $payment);
$privateKey = self::$config['aplazame_private_key'];
$environment = self::$isTest ? AplazameClient::ENVIRONMENT_SANDBOX : AplazameClient::ENVIRONMENT_PRODUCTION;
$apiBaseUri = 'https://api.aplazame.com';
$usuario_email = @CocoDB::get('usuarios', ['num' => $insertData["records"]["user"]], '', 1, ['ignoreSchema' => true])[0]['correo'];
$payload = (object) [
"merchant" => [
"notification_url" => $this->get_ipn_url($this->payment_record_id),
"success_url" => $this->get_success_url(),
"pending_url" => $this->get_success_url(),
"error_url" => $this->get_cancel_url($this->payment_record_id)
],
"order" => [
"id" => (string) $this->payment_record_id,
"articles" => array_map(function($rec) {
$producto_enlace = @CocoDB::get('productos', ['num' => $rec["num"]], '', 1, ['ignoreSchema' => true])[0]['enlace'];
$foto = (strpos($rec['photo'], 'https://') === 0) ? $rec['photo'] : 'https://' . $_SERVER['HTPT_HOST'] . $rec["photo"];
return [
"id" => $rec["num"],
"name" => $rec["referencia"],
"quantity" => $rec["quantity"],
"price" => ((float) floor(self::parse_number($rec["price"]) * 100)),
"url" => 'https://' . $_SERVER['HTPT_HOST'] . $producto_enlace,
"image_url" => $foto
];
}, $insertData["records"]["productos"]),
"currency" => "EUR",
"total_amount" => ((float) floor(self::parse_number($quantity) * 100)),
"tax_rate" => 0
],
"customer" => [
"email" => $usuario_email
],
"shipping" => [
"first_name" => @$insertData["records"]["direccion_envio"]["nombre"] ?: @$insertData["records"]["direccion_facturacion"]["nombre"],
"last_name" => @$insertData["records"]["direccion_envio"]["apellidos"] ?: @$insertData["records"]["direccion_facturacion"]["apellidos"],
"street" => @$insertData["records"]["direccion_envio"]["direccion"] ?: @$insertData["records"]["direccion_facturacion"]["direccion"],
"city" => trim(@explode(":",@$insertData["records"]["direccion_envio"]["poblacion_bd"][0] ? $insertData["records"]["direccion_envio"]["poblacion_bd"][0]["breadcrumb"] : @$insertData["records"]["direccion_facturacion"]["poblacion_bd"][0]["breadcrumb"])[1]),
"state" => trim(@explode(":",@$insertData["records"]["direccion_envio"]["poblacion_bd"][0] ? $insertData["records"]["direccion_envio"]["poblacion_bd"][0]["breadcrumb"] : @$insertData["records"]["direccion_facturacion"]["poblacion_bd"][0]["breadcrumb"])[0]),
"country" => "ES",
"postcode" => @$insertData["records"]["direccion_envio"]["codigo_postal"] ?: @$insertData["records"]["direccion_facturacion"]["codigo_postal"],
"price" => 0,
"name" => "Empresa habitual y/o recogida en tienda"
]
];
$aplazameApiClient = new Aplazame\Api\Client($apiBaseUri, $environment, $privateKey);
try{
$order_created = $aplazameApiClient->request('POST', '/checkout', $payload, 4)["id"];
} catch(Exception $e){
die(json_encode(["payload" => $payload,"error" => $e->getMessage()]));
}
return ["success" => true,"paymentId" => $order_created,"hashPaymendId" => sha1($this->payment_record_id)];
}
function autorizar_pedido($order_id){
self::get_config();
$is_production = self::$isTest ? false : true;
$private_key = self::$config['aplazame_private_key'];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.aplazame.com/orders/".$order_id."/authorize");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
$pro = (@$is_production) ? ".v1+json" : ".sandbox.v1+json";
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Accept: application/vnd.aplazame'.$pro,
'Authorization: Bearer '.$private_key,
'Host: api.aplazame.com'
));
$data = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$result = array(
'config' => curl_getinfo($ch),
'status' => $status,
'data' => json_decode($data),
'succeeded' => ( $status >= 200 && $status < 400 )
);
curl_close($ch);
return $result;
}
}

View File

@@ -0,0 +1,50 @@
<?php
require_once __DIR__."/PaymentMethod.class.php";
class Cetelem extends PaymentMethod {
function __construct() {
self::get_config();
$this->init(42, 'Cetelem', "https://test.cetelem.es/eCommerceLite/configuracion.htm", [
'COMANDO' => "INICIO",
'CodCentro' => self::$config['cetelem_codcentro']//,
// 'Modalidad' => "G"
], 'https://www.cetelem.es/eCommerceLite/configuracion.htm', [
'COMANDO' => "INICIO",
'CodCentro' => self::$config['cetelem_codcentro']//,
// 'Modalidad' => "G"
]);
}
function can_be_used() {
$cred = $this->get_credentials();
return @$cred['merchant'] && @$cred['currency'] && @$cred['terminal'] && @$cred['key'];
}
function pay($quantity, $insertData = null, $payment = null, $curl = false) {
parent::pay($quantity, $insertData, $payment);
$cred = $this->get_credentials();
$this->helper->add_field("COMANDO",$cred["COMANDO"]);
$this->helper->add_field("CodCentro",$cred["CodCentro"]);
$this->helper->add_field("IdTransaccion",str_pad($this->reference, 12, "0", STR_PAD_RIGHT));
if (@$cred["Modalidad"]) $this->helper->add_field("Modalidad",$cred["Modalidad"]);
$this->helper->add_field("CuotaPago","PC"); // Esta dani
// $this->helper->add_field("Material","330");
// $this->helper->add_field("Material","322"); // Esta dani
$this->helper->add_field("Importe", ((float) floor(self::parse_number($quantity) * 100)));
$this->helper->add_field("ReturnURL", $this->get_success_url());
$this->helper->add_field("ReturnOK", $this->get_ipn_url($this->payment_record_id));
if (@$insertData["userData"]){
foreach($insertData["userData"] as $key => $value){
$this->helper->add_field($key,$value);
}
}
$this->helper->paypal_url = $this->get_url();
if (self::$isTest) {
$this->helper->dump_fields();
}
$this->helper->submit_paypal_post($curl);
}
}

View File

@@ -0,0 +1,36 @@
<?php
abstract class IPNAction {
/**
* Ejecuta una acción al realizarse un pago correcto en el ipn
*
* @param array $payment
* @return void
*/
abstract function performAction($payment);
abstract function performCancel($payment, $message);
/**
* Devuelve el payment asignado al id
*
* @param string $id
* @return void
*/
static function get($id) {
$id = mysql_real_escape_string($id);
$record = mysql_fetch_assoc(mysql_query("SELECT * FROM `aux_plg_payments` WHERE SHA1(num)='$id'"));
if (!$record) {
throw new Exception('No se ha encontrado el pago');
}
return $record;
}
static function get_num_by_subscription($subscription) {
$subscription = mysql_real_escape_string($subscription);
$record = mysql_fetch_assoc(mysql_query("SELECT * FROM `aux_plg_payments` WHERE card_id='$subscription'"));
if (!$record) {
throw new Exception('No se ha encontrado el pago por suscripción');
}
return sha1($record['num']);
}
}

View File

@@ -0,0 +1,131 @@
<?php
require_once __DIR__ . "/vendor/iberent/Client.php";
require_once __DIR__ . "/PaymentMethod.class.php";
class Iberent extends PaymentMethod
{
public $order = [];
public $transaction = [];
public $token = "";
// Force updateState URL
// https://ibe.rent/order/test/<access_token>/<order_id>/<status>
// Example :
// https://ibe.rent/order/test/{{token}}/{{iberent_code}}/pending
// IPN Url event : /cms/lib/plugins/payments_banana/ipn.php?ipn={{payment_num}}
function __construct()
{
self::get_config();
$this->token = self::$config['iberent_token'];
$this->init(43, 'Iberent', 'https://api-test.iberent.es', [], 'https://api.iberent.es', []);
}
function can_be_used() {
$cred = $this->get_credentials();
return true;
}
function setTransaction($quantity, $insertData = null, $payment = null)
{
$this->transaction = array(
'lifetime' => 1800, //in seconds
'callback_urls' => json_encode(array(
'pending' => $this->get_success_url(),
'canceled' => $this->get_cancel_url($this->payment_record_id),
'completed' => $this->get_success_url(),
)),
'meta' => array(
/*'account_creation_date' => '2020-07-10', //YYYY-MM-DD
'pickup' => true, //boolean
'succeed_past_order' => 2,
'last_past_order' => '2020-09-21', //YYYY-MM-DD*/)
);
return $this->transaction;
}
function setOrder($quantity, $insertData = null, $payment = null)
{
$items = [];
$usuario_email = @CocoDB::get('usuarios', ['num' => $insertData["records"]["user"]], '', 1, ['ignoreSchema' => true])[0]['correo'];
foreach ($insertData["records"]["productos"] as $producto) {
$items[] = [
'product' => array(
'sku_number' => $producto["referencia"],
'part_number' => '',
'model' => '',
'description' => $producto["title"],
//'brand' => 'Apple',
//'category' => $this->getProductMainCategory($producto, "name")
),
'unit_price' => floatval($producto["price"]),
'quantity' => $producto["quantity"],
//'discount_percent' => round(@$producto["campanaPrice"] && @$producto["campanaPrice"] != $producto["originalPrice"] ? (100 - ((float) self::parse_number($producto["campanaPrice"]) * 100 / (float) self::parse_number($producto["originalPrice"]))) : 0, 2),
'total_price' => floatval(self::parse_number($producto["price"]) * $producto["quantity"])
];
}
$this->order = array(
'amount' => ((float) self::parse_number($quantity)),
'client' => array(
'company_name' => @$insertData["records"]["direccion_facturacion"]["razonSocial"] ?: @$insertData["records"]["direccion_facturacion"]["nombre"] . " " . @$insertData["records"]["direccion_facturacion"]["apellidos"],
'vat_id_number' => $insertData["records"]["direccion_facturacion"]["dni"],
'contact' => array(
'name' => @$insertData["records"]["direccion_facturacion"]["nombre"] . " " . @$insertData["records"]["direccion_facturacion"]["apellidos"],
'email' => @$usuario_email,
'phone' => @$insertData["records"]["direccion_facturacion"]["telefono"]
),
),
'events_url' => $this->get_ipn_url($this->payment_record_id),
'items' => $items,
'addresses' => array(
'billing' => array(
'street' => $insertData["records"]["direccion_facturacion"]["direccion"],
'postal_code' => $insertData["records"]["direccion_facturacion"]["codigo_postal"],
'city' => trim(@explode(":", $insertData["records"]["direccion_facturacion"]["poblacion_bd"][0]["breadcrumb"])[1] ?: $insertData["records"]["direccion_facturacion"]["poblacion_bd"][0]["breadcrumb"]),
'province' => trim(@explode(":", $insertData["records"]["direccion_facturacion"]["poblacion_bd"][0]["breadcrumb"])[0] ?: $insertData["records"]["direccion_facturacion"]["poblacion_bd"][0]["breadcrumb"]),
'country' => 'España',
),
'shipping' => array(
'street' => $insertData["records"]["direccion_envio"]["direccion"],
'postal_code' => $insertData["records"]["direccion_envio"]["codigo_postal"],
'city' => trim(@explode(":", $insertData["records"]["direccion_envio"]["poblacion_bd"][0]["breadcrumb"])[1] ?: $insertData["records"]["direccion_envio"]["poblacion_bd"][0]["breadcrumb"]),
'province' => trim(@explode(":", $insertData["records"]["direccion_envio"]["poblacion_bd"][0]["breadcrumb"])[0] ?: $insertData["records"]["direccion_envio"]["poblacion_bd"][0]["breadcrumb"]),
'country' => 'España',
)
)
);
return $this->order;
}
function pay($quantity, $insertData = null, $payment = null, $curl = false)
{
parent::pay($quantity, $insertData, $payment);
$client = new Iberent\Client($this->token, '', self::$isTest ? false : true);
$transaction = $this->setTransaction($quantity, $insertData, $payment);
$order = $this->setOrder($quantity, $insertData, $payment);
$response = $client->openTransaction($this->reference, $transaction, $order);
$checkout_url = @$response["result"]["checkout_url"];
if (!$checkout_url) {
if(self::$isTest) {
var_dump($checkout_url);
}
die("Error de petición. contacte con un administrador.");
}
$this->helper->paypal_url = $checkout_url;
if (self::$isTest) {
$this->helper->add_field("ORDER", json_encode($order,JSON_PRETTY_PRINT));
$this->helper->add_field("TRANSACTION", json_encode($transaction,JSON_PRETTY_PRINT));
$this->helper->dump_fields();
}
$this->helper->submit_paypal_post($curl);
}
}

View File

@@ -0,0 +1,63 @@
<?php
require_once __DIR__."/PaymentMethod.class.php";
class PayPal extends PaymentMethod {
function __construct() {
self::get_config();
$this->init(2, 'PayPal', 'https://www.sandbox.paypal.com/cgi-bin/webscr', [
'paypal_account' => self::$config['test_paypal_account'],
], 'https://www.paypal.com/cgi-bin/webscr', [
'paypal_account' => self::$config['paypal_account']
]);
}
function can_be_used() {
$cred = $this->get_credentials();
return @$cred['paypal_account'] ? true : false;
}
function pay($quantity, $insertData = null, $payment = null, $curl = false) {
parent::pay($quantity, $insertData);
$url = @$insertData['urlok'] ?: protocol()."://".$_SERVER['HTTP_HOST'];
$cred = $this->get_credentials();
$this->helper->paypal_url = $this->get_url();
$this->helper->admin_mail = $cred['paypal_account'];
$this->helper->add_field('business', $cred['paypal_account']);
$this->helper->add_field('return', $url);
$this->helper->add_field('cancel_return', $this->get_cancel_url($this->payment_record_id));
$this->helper->add_field('notify_url', $this->get_ipn_url($this->payment_record_id));
$this->helper->add_field('item_name', "REF.".$this->reference);
$this->helper->add_field('currency_code', "EUR");
$this->helper->add_field('cmd', '_xclick');
$this->helper->add_field('amount', (float) $quantity);
$this->helper->add_field('custom', $this->payment_record_id);
$this->helper->add_field('rm', '2');
if (self::$isTest) {
$this->helper->dump_fields();
}
$this->helper->submit_paypal_post($curl);
}
function subscribe($quantity, $insertData = null) {
die("No implementado");
parent::subscribe($quantity, $insertData);
$url = @$insertData['urlok'] ?: protocol()."://".$_SERVER['HTTP_HOST'];
$cred = $this->get_credentials();
$this->helper->paypal_url = $this->get_url();
$this->helper->add_field('return', $url);
$this->helper->add_field('cancel_return', $this->get_cancel_url($this->payment_record_id));
$this->helper->add_field('notify_url', $this->get_ipn_url($this->payment_record_id));
$this->helper->add_field('item_name', "REF.".$this->reference);
$this->helper->add_field('currency_code', "EUR");
$this->helper->add_field('cmd', '_xclick');
$this->helper->add_field('amount', (float) $quantity);
$this->helper->add_field('custom', $this->payment_record_id);
$this->helper->add_field('rm', '2');
if (self::$isTest) {
$this->helper->dump_fields();
}
$this->helper->submit_paypal_post();
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,445 @@
<?php
require_once __DIR__."/PaymentHelper.class.php";
abstract class PaymentMethod {
private static $css = [];
private static $js = [];
protected static $config = null;
// Define si estamos en modo test o no
protected static $isTest = false;
// ID del tipo de pago
private $payment_id = null;
// Record ID para pagos aplazados
public $payment_record_id = null;
// Tipo de pago
private $payment_name = null;
// URL Test
public $test_url = null;
// Array con las credenciales test
public $test_credentials = null;
// URL Producción
public $prod_url = null;
// Array con las credenciales producción
public $prod_credentials = null;
// Cantidad a pagar
private $quantity = 0;
// Clase paypal que realiza el envío de formulario
public $helper = null;
// Referencia del pedido
protected $reference = null;
// Descripción del pedido
protected $description = '';
public $url_ok = null;
public $url_ko = null;
/**
* Añade un archivo CSS a la lista para su importación
*
* @param string path
* @return void
*/
static function add_css($path) {
$filename = basename($path);
self::$css[$filename] = $path;
}
/**
* Devuelve la lista de links a importar
*
* @return array
*/
static function get_links() {
$links = '';
foreach (self::$css as $css) {
$links .= '<link href="'.h($css).'" rel="stylesheet"';
}
return $links;
}
/**
* Añade un archivo JS a la lista para su importación
*
* @param string path
* @return void
*/
static function add_js($path) {
$filename = basename($path);
self::$js[$filename] = $path;
}
/**
* Devuelve la lista de links a importar
*
* @return array
*/
static function get_scripts() {
$links = '';
foreach (self::$css as $css) {
$links .= '<link href="'.h($css).'" rel="stylesheet"';
}
return $links;
}
/**
* Define si los pagos están en modo test o producción
*
* @param bool test
* @return void
*/
static function set_test($test = true) {
self::$isTest = $test ? true : false;
}
// Dani (2025-02-06): Añadida función.
public function isTest() {
return self::$isTest;
}
/**
* Devuelve la configuración del plugin
*
* @return array
*/
static function get_config() {
if (self::$config) return self::$config;
// Dani (2024-10-04): En clementime, la configuración de los métodos de
// pagos están en la tabla del cliente.
if (
isset($_SERVER['SERVER_NAME'])
&& $_SERVER['SERVER_NAME'] === 'clementime.cocosolution.com'
&& isset($_GET['client'])
) {
$num = mysql_real_escape_string($_GET['client']);
$cliente = mysql_fetch_assoc(mysql_query("
SELECT *
FROM `cms_clientes`
WHERE `num` = '{$num}'
"));
self::$config = [
'test' => strpos($cliente['entorno_tpv'] ?? '', 'sis-t') !== false,
'test_stripe_pk' => $cliente['clave_publica_stripe'],
'test_stripe_sk' => $cliente['clave_privada_stripe'],
'test_webhook_sk' => '',
'stripe_pk' => $cliente['clave_publica_stripe'],
'stripe_sk' => $cliente['clave_privada_stripe'],
'webhook_sk' => '',
'test_paypal_account' => $cliente['cuenta_paypal'],
'paypal_account' => $cliente['cuenta_paypal'],
'test_tpv_merchant' => $cliente['merchant_code'],
'test_tpv_currency' => $cliente['merchant_currency'],
'test_tpv_terminal' => $cliente['terminal'],
'test_tpv_key' => $cliente['clave_tpv'],
'tpv_merchant' => $cliente['merchant_code'],
'tpv_currency' => $cliente['merchant_currency'],
'tpv_terminal' => $cliente['terminal'],
'tpv_key' => $cliente['clave_tpv'],
'aplazame_public_key' => '',
'aplazame_private_key' => '',
'clave_debug' => 'Coco$olut10n',
];
return self::$config;
}
self::$config = loadINI(__DIR__."/../custom-schema.ini.php")['config'];
$result = mysql_query("SHOW TABLES LIKE 'aux_plg_config'");
if($result->num_rows == 1) {
$result = mysql_query_fetch_all_assoc("select * from aux_plg_config where plugin='payments'");
if (@$result){
foreach($result as $record){
if (@$record["config"]){
foreach(json_decode($record["config"],true) as $confBD){
if (@$confBD["padre"] == "config") {
self::$config[$confBD["campo"]] = $confBD["valor"];
}
}
}
}
}
}
if (self::$config['test']) {
self::set_test(true);
}
return self::$config;
}
/**
* Parsea un número quitándole las comas
*
* @param string $price
* @return float
*/
static function parse_number($price) {
return floatval(str_replace(',', '.', $price));
}
/**
* Devuelve las credenciales oportunas según estemos en modo test o en producción
*
* @return array
*/
function get_credentials() {
return self::$isTest ? $this->test_credentials : $this->prod_credentials;
}
/**
* Devuelve el ID de pago
*
* @return int
*/
function get_payment_id() {
return $this->payment_id;
}
/**
* Devuelve la descripción del producto
*
* @return string
*/
function get_product_description() {
return $this->description;
}
/**
* Define la descripción del producto
*
* @param string $description
* @return void
*/
function set_product_description($description) {
$this->description = $description;
}
/**
* Devuelve la URL de test o producción
*
* @return string
*/
protected function get_url() {
return self::$isTest ? $this->test_url : $this->prod_url;
}
/**
* Inicializa el método de pago para ser usado
*
* @param int $payment_id
* @param string $payment_name
* @param string $test_url
* @param array $test_credentials
* @param string $prod_url
* @param array $prod_credentials
* @return void
*/
protected function init($payment_id, $payment_name, $test_url, $test_credentials, $prod_url, $prod_credentials) {
$this->payment_id = $payment_id;
$this->payment_name = $payment_name;
$this->test_url = $test_url;
$this->test_credentials = $test_credentials;
$this->prod_url = $prod_url;
$this->prod_credentials = $prod_credentials;
$this->reference = time();
$this->helper = new PaymentHelper;
}
/**
* Devuelve True si el tipo de pago puede ser utilizado (tiene todos los datos rellenos)
*
* @return void
*/
abstract function can_be_used();
// Dani (2025-02-06): Añadida función.
public function setQuantity($quantity) {
$this->quantity = self::parse($quantity);
}
public function setReference($reference) {
$this->reference = $reference;
}
/**
* Realiza el pago de una cuantía X.
* El segundo parámetro es un array con las claves `records`: lista de registros, `ipn`: Nombre de la clase IPNAction, `callback`, `urlok`
*
* @param float $quantity
* @param array $insertData
* @return void
*/
function pay($quantity, $insertData = null, $payment = null, $curl = false) {
$this->quantity = self::parse($quantity);
if ($this->quantity < 0) {
// Anael me dijo que puedo quitar el igual en esta condicion el 11 de julio de 2025
throw new Exception('Cantidad no válida');
}
if ($insertData && !is_array($insertData)) {
throw new Exception('Datos no válidos. Deben ser un array');
}
if(isset($insertData['urlok'])) $this->url_ok = $insertData['urlok'];
if(isset($insertData['urlko'])) $this->url_ko = $insertData['urlko'];
if(isset($insertData['reference'])) $this->reference = $insertData['reference'];
if(isset($insertData['paymentId'])){
$this->payment_record_id = $insertData['paymentId'];
return $this->payment_record_id;
}else{
$this->insert(@$insertData['records'], @$insertData['email'], @$insertData['ipn'], @$insertData['callback']);
return mysql_insert_id();
}
}
/**
* Crea una suscripción de cuantía X.
* El segundo parámetro es un array con las claves `records`: lista de registros, `ipn`: Nombre de la clase IPNAction, `callback`, `urlok`
*
* @param float $quantity
* @param array $insertData
* @return void
*/
function subscribe($quantity, $insertData = null) {
$this->quantity = self::parse($quantity);
if ($this->quantity <= 0) {
throw new Exception('Cantidad no válida');
}
if ($insertData && !is_array($insertData)) {
throw new Exception('Datos no válidos. Deben ser un array');
}
if(isset($insertData['urlok'])) $this->url_ok = $insertData['urlok'];
if(isset($insertData['urlko'])) $this->url_ko = $insertData['urlko'];
$this->insert(@$insertData['records'], @$insertData['email'], @$insertData['ipn'], @$insertData['callback'], 1);
}
/**
* Inserta en la tabla de pagos del plugin.
* @param array $records: Lista de registros
* @param string $email: Objeto CocoEmail
* @param string $ipn: Nombre de la clase IPNAction
* @param callback $callback
* @param int $tipo: 0 si es pago y 1 suscripción
*/
// Dani (2025-02-06): Modificada la función de privada a pública.
public function insert($records = null, $email = null, $ipn = null, $callback = null, $tipo = 0) {
self::_install();
$tipo = $tipo === 0 ? 0 : 1;
# Inserción en tablas de funcionamiento interno
$d = date('Y-m-d H:i:s');
$ip = $_SERVER['REMOTE_ADDR'];
if (is_array($records)) {
$recordsJson = json_encode($records,JSON_UNESCAPED_UNICODE);
}
else if ($records) {
$recordsJson = json_encode([$records],JSON_UNESCAPED_UNICODE);
}
else {
$recordsJson = json_encode([],JSON_UNESCAPED_UNICODE);
}
$recordsJson = mysql_real_escape_string($recordsJson);
$ipn = mysql_real_escape_string($ipn) ?: 'NULL';
$request = mysql_real_escape_string(json_encode($_REQUEST ?: [],JSON_UNESCAPED_UNICODE));
// Dani (2024-10-04): Comentada línea porque no permitía introducir emojis.
// mysql_set_charset('utf8');
switch($this->payment_id){
case 4: $status = "Financiacion Solicitada";break;
default: $status = "Esperando";
}
mysql_query("INSERT INTO `aux_plg_payments` SET num=NULL, createdDate='$d', ip='$ip', tipo=$tipo, price=".$this->quantity.", records='$recordsJson', status='".$status."', ipn_action='$ipn', `error`='', `request`='$request', method='".$this->payment_id."'") or die(mysql_error());
$this->payment_record_id = mysql_insert_id();
# Callback tras inserción
if ($callback && is_callable($callback)) {
call_user_func_array($callback, array(&$records, $_REQUEST, $this->payment_record_id));
if (isset($records["referencia"])) $this->reference = $records["referencia"];
}
}
/**
* Parsea un precio de string a float
*
* @param string $price
* @return float
*/
private static function parse($price) {
return floatval(str_replace(",", ".", $price));
}
/**
* Devuelve la URL del ipn asociada a dicho registro
*
* @param int $num
* @return string
*/
// Dani (2024-10-04): Añadido parámetro $extraParams para obtener más
// información en la petición que hace el banco a nuestro ipn.php
function get_ipn_url($num, $extraParams = []) {
$params = [
"ipn" => sha1($num),
"idioma" => @$_REQUEST["idioma"],
];
$params = array_merge($params, $extraParams);
$params = http_build_query($params);
return protocol()."://".$_SERVER["HTTP_HOST"]."/cms/lib/plugins/payments/ipn.php?".$params;
}
/**
* Devuelve la URL de cancelación asociada a dicho registro
*
* @param int $num
* @return string
*/
function get_cancel_url($num) {
$params = ['cancel' => sha1($num)];
if ($this->url_ko) $params['url'] = base64_encode($this->url_ko);
$params["idioma"] = @$_REQUEST["idioma"];
$params = http_build_query($params);
return protocol()."://".$_SERVER["HTTP_HOST"]."/cms/lib/plugins/payments/cancel.php?".$params;
}
/**
* Devuelve la URL de pago realizado
*
* @param int $num
* @return string
*/
function get_success_url() {
$params = [];
if ($this->url_ok) $params['url'] = base64_encode($this->url_ok);
$params["idioma"] = @$_REQUEST["idioma"];
$params = http_build_query($params);
return protocol()."://".$_SERVER["HTTP_HOST"]."/cms/lib/plugins/payments/ok.php?".$params;
}
private static function _install() {
mysql_query("CREATE TABLE IF NOT EXISTS `aux_plg_payments` (
`num` INT NOT NULL AUTO_INCREMENT,
`createdDate` DATETIME NOT NULL,
`ip` VARCHAR(255) NOT NULL,
`records` MEDIUMTEXT,
`request` MEDIUMTEXT,
`price` FLOAT NOT NULL,
`status` ENUM('Esperando', 'Cancelado', 'Pagado', 'Error','Financiacion Solicitada') NOT NULL,
`tipo` TINYINT DEFAULT 0,
`error` MEDIUMTEXT,
`ipn_action` VARCHAR(255),
`method` INT NOT NULL,
`card_id` VARCHAR(255),
`card_caduc` VARCHAR(255),
`ipn_response` MEDIUMTEXT,
PRIMARY KEY (`num`),
INDEX (`tipo`),
INDEX (`method`),
INDEX (`status`)
)") or die(mysql_error());
}
}

View File

@@ -0,0 +1,815 @@
<?php
require_once __DIR__.'/../Stripe/vendor/autoload.php';
require_once __DIR__."/PaymentMethod.class.php";
class Stripe extends PaymentMethod {
function __construct() {
self::get_config();
$this->init(5, 'Stripe', '', [
'stripe_pk' => self::$config['test_stripe_pk'],
'stripe_sk' => self::$config['test_stripe_sk'],
'webhook_sk' => self::$config['test_webhook_sk']
],
'',
[
'stripe_pk' => self::$config['stripe_pk'],
'stripe_sk' => self::$config['stripe_sk'],
'webhook_sk' => self::$config['webhook_sk']
]);
}
function can_be_used() {
$cred = $this->get_credentials();
return @$cred['stripe_sk'] ? true : false;
}
function pay($quantity, $insertData = null, $payment = null, $curl = false, $return_result = false) {
$inserted_payment = parent::pay($quantity, $insertData);
$cred = $this->get_credentials();
\Stripe\Stripe::setApiKey($cred["stripe_sk"]);
$paymentData = [
'metadata' => [
'payment_num' => sha1($inserted_payment),
],
'line_items' => [[
'price_data' => [
'currency' => 'eur',
'product_data' => [
'name' => "Cesta de productos",
],
'unit_amount' => round($quantity * 100),
],
'quantity' => 1,
]],
'mode' => 'payment',
'success_url' =>"https://".$_SERVER["HTTP_HOST"].$insertData['urlok'],
'cancel_url' => "https://".$_SERVER["HTTP_HOST"].$insertData['urlko'],
];
$pay = \Stripe\Checkout\Session::create($paymentData);
if(!$return_result){
header("location: ".$pay->url);
}else{
return [
'url' => $pay->url,
'session_id' => $pay->id,
'customer_id' => null,
'type' => 'payment',
'total_pago' => round($quantity * 100)
];
}
}
function payProduct($priceId, $isSubscription, $quantity, $insertData = null, $payment = null, $curl = false, $return_result = false, $taxStripeId = null) {
$inserted_payment = parent::pay($quantity, $insertData);
$cred = $this->get_credentials();
\Stripe\Stripe::setApiKey($cred["stripe_sk"]);
// Crear una sesión en stripe dado un identificador de producto de Stripe y una cantidad
// Preparar metadata base
$metadata = [
'payment_num' => sha1($inserted_payment),
];
// Si hay metadata personalizada, añadirla
if (isset($insertData['metadata']) && is_array($insertData['metadata'])) {
$metadata = array_merge($metadata, $insertData['metadata']);
}
$paymentData = [
'metadata' => $metadata,
'line_items' => [[
'price' => $priceId,
'quantity' => 1,
]],
'mode' => $isSubscription ? 'subscription' : 'payment',
'success_url' => strpos($insertData['urlok'], "http") === 0 ? $insertData['urlok'] : "https://".$_SERVER["HTTP_HOST"].$insertData['urlok'],
'cancel_url' => strpos($insertData['urlko'], "http") === 0 ? $insertData['urlko'] : "https://".$_SERVER["HTTP_HOST"].$insertData['urlko'],
'allow_promotion_codes' => true
];
// Si hay tax y es suscripción, añade así:
if ($taxStripeId && $isSubscription) {
$paymentData['subscription_data'] = [
'default_tax_rates' => [$taxStripeId]
];
} elseif ($taxStripeId) {
foreach($paymentData["line_items"] as &$item) {
$item['tax_rates'] = [$taxStripeId];
}
}
if (@$insertData['customer_email']) {
$paymentData['customer_email'] = $insertData['customer_email'];
}
if (@$insertData['customer_id']) {
$paymentData['customer'] = $insertData['customer_id'];
}
try{
$pay = \Stripe\Checkout\Session::create($paymentData);
if(!$return_result){
header("location: ".$pay->url);
}else{
return [
'url' => $pay->url,
'session_id' => $pay->id,
'customer_id' => null,
'type' => $isSubscription ? 'subscription' : 'payment',
'total_pago' => round($quantity * 100)
];
}
}catch(\Stripe\Exception\ApiErrorException $e){
return [
'error' => $e->getMessage(),
'type' => $isSubscription ? 'subscription' : 'payment',
'total_pago' => round($quantity * 100)
];
}
}
function updateInvoiceFooter($invoiceId, $footerText) {
$cred = $this->get_credentials();
\Stripe\Stripe::setApiKey($cred["stripe_sk"]);
// Actualizar el pie de página de una factura en stripe
try {
$invoice = \Stripe\Invoice::update($invoiceId, [
'footer' => $footerText
]);
return ["success" => true, "invoice" => $invoice];
} catch (\Stripe\Exception\ApiErrorException $e) {
return [
'error' => $e->getMessage(),
];
}
}
function createInvoice($invoiceItems,$customer_id,$taxStripeId, $payment_intent = null){
$cred = $this->get_credentials();
\Stripe\Stripe::setApiKey($cred["stripe_sk"]);
// Crear una factura en stripe
try {
$invoice = \Stripe\Invoice::create([
'customer' => $customer_id,
'auto_advance' => true, // Auto-advance the invoice
'collection_method' => 'send_invoice',
'days_until_due' => 30,
'metadata' => [
'payment_intent' => $payment_intent,
'created_by' => 'Quantum Asis'
]
]);
foreach ($invoiceItems as $item) {
$invoiceItem = \Stripe\InvoiceItem::create([
'customer' => $customer_id,
'amount' => round($item['amount'] * 100),
'currency' => 'eur',
'description' => $item['description'],
'invoice' => $invoice->id,
'tax_rates' => [$taxStripeId]
]);
}
try{
$invoice = \Stripe\Invoice::finalizeInvoice($invoice->id, []);
}catch(\Stripe\Exception\ApiErrorException $e){
return [
'error' => $e->getMessage(),
];
}
//$invoice = \Stripe\Invoice::retrieve($invoice->id);
$link = $invoice->hosted_invoice_url;
return ["success" => true, "invoice" => $invoice,"url" => $link];
} catch (\Stripe\Exception\ApiErrorException $e) {
return [
'error' => $e->getMessage(),
];
}
}
function checkCustomer($customerId) {
$cred = $this->get_credentials();
\Stripe\Stripe::setApiKey($cred["stripe_sk"]);
// Comprobar si un cliente existe en stripe dado un identificador de cliente de Stripe
try {
$customer = \Stripe\Customer::retrieve($customerId);
return ["success" => true, "customer" => $customer];
} catch (\Stripe\Exception\ApiErrorException $e) {
return [
'error' => $e->getMessage(),
];
}
}
function insertCustomer($invoiceData){
$cred = $this->get_credentials();
\Stripe\Stripe::setApiKey($cred["stripe_sk"]);
// Insertar un cliente en stripe
try {
$customer = \Stripe\Customer::create([
'name' => $invoiceData['nombre'],
'email' => $invoiceData['email'],
'phone' => '',
'address' => [
'line1' => $invoiceData['direccionFiscal'],
'postal_code' => $invoiceData['codigoPostal'],
'city' => $invoiceData['ciudad'],
'state' => $invoiceData['provincia'],
'country' => $invoiceData['pais']
],
'metadata' => [
'nif' => $invoiceData['nif']
]
]);
return ["success" => true, "customer" => $customer];
} catch (\Stripe\Exception\ApiErrorException $e) {
return [
'error' => $e->getMessage(),
];
}
}
function updateCustomerAddress($customer_id,$invoiceData){
$cred = $this->get_credentials();
\Stripe\Stripe::setApiKey($cred["stripe_sk"]);
// Actualizar la dirección de un cliente en stripe
try {
$customer = \Stripe\Customer::update($customer_id, [
'address' => [
'line1' => $invoiceData['direccionFiscal'],
'postal_code' => $invoiceData['codigoPostal'],
'city' => $invoiceData['ciudad'],
'state' => $invoiceData['provincia'],
'country' => $invoiceData['pais']
],
'metadata' => [
'nif' => $invoiceData['nif']
]
]);
return ["success" => true, "customer" => $customer];
} catch (\Stripe\Exception\ApiErrorException $e) {
return [
'error' => $e->getMessage(),
];
}
}
function cancelSubscriptionAtEnd($subscriptionID, $reactivate = false) {
$cred = $this->get_credentials();
\Stripe\Stripe::setApiKey($cred["stripe_sk"]);
try {
$subscription = \Stripe\Subscription::update($subscriptionID, [
'cancel_at_period_end' => $reactivate ? false : true, // 👈 Esta es la clave
]);
return [
"success" => true,
"cancel_at" => date('Y-m-d H:i:s', $subscription->cancel_at),
];
} catch (\Stripe\Exception\ApiErrorException $e) {
return [
'error' => $e->getMessage(),
];
}
}
function cancelSubscription($subscriptionID) {
$cred = $this->get_credentials();
\Stripe\Stripe::setApiKey($cred["stripe_sk"]);
// Cancelar una suscripción en stripe dado un identificador de suscripción de Stripe
try{
$subscription = \Stripe\Subscription::retrieve($subscriptionID);
$subscription->cancel();
return ["success" => true];
}catch(\Stripe\Exception\ApiErrorException $e){
return [
'error' => $e->getMessage(),
];
}
}
function createTaxRate($name, $description, $percentage, $inclusive = false, $country = "ES", $state = "CN") {
$cred = $this->get_credentials();
\Stripe\Stripe::setApiKey($cred["stripe_sk"]);
// Crear una tasa de impuesto en stripe
try {
$tax_rate = \Stripe\TaxRate::create([
'display_name' => $name,
'percentage' => $percentage,
'inclusive' => $inclusive,
'country' => $country ?? 'ES', // Default to Spain
'state' => $state ?? 'CN', // Default to Canarias
'description' => $description ?? 'Impuesto',
]);
return ["success" => true, "tax_rate" => $tax_rate];
} catch (\Stripe\Exception\ApiErrorException $e) {
return [
'error' => $e->getMessage(),
];
}
}
function getCustomerPortal($customerId, $returnUrl) {
$cred = $this->get_credentials();
\Stripe\Stripe::setApiKey($cred["stripe_sk"]);
// Crear una sesión de portal de cliente
try{
$session = \Stripe\BillingPortal\Session::create([
'customer' => $customerId,
'return_url' => $returnUrl,
]);
}catch(\Stripe\Exception\ApiErrorException $e){
return [
'error' => $e->getMessage(),
];
}
return ["success" => true,"url" => $session->url,"session" => $session];
}
function updateSubscription($subscriptionID, $allPricesID, $insertData = null, $payment = null, $curl = false, $return_result = false) {
$cred = $this->get_credentials();
\Stripe\Stripe::setApiKey($cred["stripe_sk"]);
// Actualizar una suscripción en stripe dado un identificador de suscripción de Stripe y una lista de precios
$subscription = \Stripe\Subscription::retrieve($subscriptionID);
// Obtener los items actuales de la suscripción
$currentItems = $subscription->items->data;
// Preparar los items para la actualización
$itemsToUpdate = [];
// Marcar items existentes para eliminar
foreach ($currentItems as $item) {
$itemsToUpdate[] = [
'id' => $item->id,
'deleted' => true
];
}
// Agregar los nuevos precios
foreach ($allPricesID as $priceId) {
$itemsToUpdate[] = [
'price' => $priceId,
'quantity' => 1
];
}
$itemsToUpdateWithQuantityRepeated = [];
foreach ($itemsToUpdate as $item) {
if (isset($item['price'])) {
$priceId = $item['price'];
if (!isset($itemsToUpdateWithQuantityRepeated[$priceId])) {
$itemsToUpdateWithQuantityRepeated[$priceId] = [
'price' => $priceId,
'quantity' => 0
];
}
$itemsToUpdateWithQuantityRepeated[$priceId]['quantity'] += $item['quantity'] ?? 1;
} else {
$itemsToUpdateWithQuantityRepeated[] = $item;
}
}
$itemsToUpdate = array_values($itemsToUpdateWithQuantityRepeated);
$subscription = \Stripe\Subscription::update(
$subscriptionID,
[
'items' => $itemsToUpdate,
'proration_behavior' => 'always_invoice'
]
);
if(!$return_result){
header("location: ".$insertData['urlok']);
}else{
return [
'url' => $insertData['urlok'],
'session_id' => null,
'customer_id' => null,
'type' => 'subscription',
'total_pago' => 0
];
}
}
function getClientSubscriptionInfo($customerId) {
$cred = $this->get_credentials();
\Stripe\Stripe::setApiKey($cred["stripe_sk"]);
try {
$subscriptions = \Stripe\Subscription::all(['customer' => $customerId]);
} catch (\Stripe\Exception\ApiErrorException $e) {
return [
'error' => $e->getMessage(),
];
}
return ["success" => true, "subscriptions" => $subscriptions];
}
function pay_embed($quantity, $insertData = null, $payment = null, $curl = false) {
$inserted_payment = parent::pay($quantity, $insertData);
$cred = $this->get_credentials();
\Stripe\Stripe::setApiKey($cred["stripe_sk"]);
$paymentData = [
'metadata' => [
'payment_num' => sha1($inserted_payment),
],
'line_items' => [[
'price_data' => [
'currency' => 'eur',
'product_data' => [
'name' => "Cesta de productos",
],
'unit_amount' => $quantity * 100,
],
'quantity' => 1,
]],
'mode' => 'payment',
'ui_mode' => 'embedded',
'return_url' =>"https://".$_SERVER["HTTP_HOST"].$insertData['urlok']
];
$pay = \Stripe\Checkout\Session::create($paymentData);
echo json_encode(array('clientSecret' => $pay->client_secret));
die();
}
function subscribe($quantity, $insertData = null, $payment = null) {
die("a");
$cred = $this->get_credentials();
\Stripe\Stripe::setApiKey($cred["stripe_sk"]);
$records = $insertData['records'];
// Analizar productos correctamente
$recurring_items = [];
$onetime_items = [];
$inserted_payment = parent::pay($quantity, $insertData);
// 1. PLAN BASE (siempre recurrente si existe)
if (isset($records['plan_seleccionado']) && !empty($records['plan_seleccionado'])) {
$plan = $records['plan_seleccionado'];
// Usar el precio correcto del plan según el período
$precio_plan = $records['periodo_facturacion'] === 'year'
? floatval($plan['price_year'])
: floatval($plan['price']);
if ($precio_plan > 0) {
$recurring_items[] = [
'name' => $plan['title'],
'amount' => $precio_plan,
'interval' => $records['periodo_facturacion'] === 'year' ? 'year' : 'month',
'quantity' => 1
];
}
}
// 2. PACKS ADICIONALES
if (isset($records['packs_seleccionados']) && is_array($records['packs_seleccionados'])) {
foreach ($records['packs_seleccionados'] as $pack) {
if (isset($pack['cantidad']) && $pack['cantidad'] > 0) {
$pack_item = [
'name' => $pack['title'],
'amount' => floatval($pack['price']),
'quantity' => intval($pack['cantidad'])
];
if (strpos($pack['id_plan'], 'PRODUCTO_TOKENS') !== false) {
// Es un producto de tokens (pago único)
$onetime_items[] = $pack_item;
} else {
// Es un pack recurrente
$pack_item['interval'] = 'month';
$recurring_items[] = $pack_item;
}
}
}
}
if (!empty($recurring_items) && !empty($onetime_items)) {
return $this->handleMixedPayment($recurring_items, $onetime_items, $insertData, $inserted_payment, $records);
}
if (!empty($recurring_items)) {
return $this->handleSubscriptionOnly($recurring_items, $insertData, $inserted_payment, $records);
}
}
function getProduct($productId) {
$cred = $this->get_credentials();
\Stripe\Stripe::setApiKey($cred["stripe_sk"]);
return \Stripe\Product::retrieve($productId);
}
function getPrice($priceId) {
$cred = $this->get_credentials();
\Stripe\Stripe::setApiKey($cred["stripe_sk"]);
return \Stripe\Price::retrieve($priceId);
}
function createProduct($name, $description = '', $amount = 0, $recurring = false, $interval = 'month', $currency = 'eur') {
try{
$cred = $this->get_credentials();
\Stripe\Stripe::setApiKey($cred["stripe_sk"]);
// Crear un producto en Stripe
$product = \Stripe\Product::create([
'name' => $name,
'description' => $description
]);
// Crear un precio para el producto
$productData = [
'product' => $product->id,
'currency' => $currency,
'unit_amount' => round($amount * 100)
];
if ($recurring) {
$productData['recurring'] = ['interval' => $interval];
}
$price = \Stripe\Price::create($productData);
return [
'product_id' => $product->id,
'price_id' => $price->id
];
} catch (\Stripe\Exception\ApiErrorException $e) {
return [
'error' => $e->getMessage(),
];
}
}
function createPrice($productId, $amount, $recurring = false, $interval = 'month', $currency = 'eur') {
$cred = $this->get_credentials();
\Stripe\Stripe::setApiKey($cred["stripe_sk"]);
// Crear un precio para el producto
$productData = [
'product' => $productId,
'currency' => $currency,
'unit_amount' => round($amount * 100)
];
if ($recurring) {
$productData['recurring'] = ['interval' => $interval];
}
return \Stripe\Price::create($productData);
}
function updatePrice($productId, $priceId, $amount, $recurring = false, $interval = 'month', $currency = 'eur') {
$cred = $this->get_credentials();
\Stripe\Stripe::setApiKey($cred["stripe_sk"]);
// Actualizar el precio del producto
$productData = [
'active' => false
];
$result = \Stripe\Price::update($priceId, $productData);
$resultCreate = $this->createPrice($productId, $amount, $recurring, $interval, $currency);
return [
'product_id' => $productId,
'price_id' => $resultCreate->id
];
}
private function handleSubscriptionOnly($recurring_items, $insertData, $inserted_payment, $records) {
$customer = $this->createCustomer($records, $inserted_payment, [
'payment_type' => 'subscription'
]); $total_pago = 0;
$line_items = [];
foreach ($recurring_items as $item) {
$product = \Stripe\Product::create(['name' => $item['name']]);
$product_price = round(($item['amount'] * 1.21) * 100);
$price = \Stripe\Price::create([
'product' => $product->id,
'currency' => 'eur',
'unit_amount' => $product_price,
'recurring' => ['interval' => $item['interval']]
]);
$total_pago += $product_price;
$line_items[] = [
'price' => $price->id,
'quantity' => $item['quantity'] ?? 1
];
}
$session_data = [
'customer' => $customer->id,
'payment_method_types' => ['card'],
'line_items' => $line_items,
'mode' => 'subscription',
'success_url' => "https://".$_SERVER["HTTP_HOST"].$insertData['urlok'],
'cancel_url' => "https://".$_SERVER["HTTP_HOST"].$insertData['urlko'],
'metadata' => [
'payment_num' => sha1($inserted_payment),
'user_id' => $records['user'],
'payment_type' => 'subscription'
]
];
if ($this->shouldAddTrial()) {
$session_data['subscription_data'] = ['trial_period_days' => 30];
}
$session = \Stripe\Checkout\Session::create($session_data);
return [
'url' => $session->url,
'session_id' => $session->id,
'customer_id' => $customer->id,
'type' => 'subscription',
'total_pago' => $total_pago
];
}
private function handleMixedPayment($recurring_items, $onetime_items, $insertData, $inserted_payment, $records) {
$total_recurring = 0;
$total_onetime = 0;
foreach ($recurring_items as $item) {
$total_recurring += $item['amount'] * ($item['quantity'] ?? 1);
}
foreach ($onetime_items as $item) {
$total_onetime += $item['amount'] * ($item['quantity'] ?? 1);
}
$total_recurring_con_iva = $total_recurring * 1.21;
$total_onetime_con_iva = $total_onetime * 1.21;
$precio_primer_mes = $total_recurring_con_iva + $total_onetime_con_iva;
$customer = $this->createCustomer($records, $inserted_payment, [
'recurring_amount' => $total_recurring_con_iva,
'onetime_amount' => $total_onetime_con_iva,
'payment_type' => 'mixed_simple'
]);
// Crear descripción detallada
$description = sprintf(
"Productos mensuales: %.2f€ (Base: %.2f€ + IVA: %.2f€) Productos de pago único: %.2f€ (Base: %.2f€ + IVA: %.2f€) A partir del próximo mes solo: %.2f€",
$total_recurring_con_iva,
$total_recurring,
$total_recurring * 0.21,
$total_onetime_con_iva,
$total_onetime,
$total_onetime * 0.21,
$total_recurring_con_iva
);
$product = \Stripe\Product::create([
'name' => 'Plan LIME Completo (Primer mes + Setup)',
'description' => $description
]);
$price = \Stripe\Price::create([
'product' => $product->id,
'currency' => 'eur',
'unit_amount' => round($precio_primer_mes * 100),
'recurring' => ['interval' => 'month']
]);
$session = \Stripe\Checkout\Session::create([
'customer' => $customer->id,
'payment_method_types' => ['card'],
'line_items' => [[
'price' => $price->id,
'quantity' => 1
]],
'mode' => 'subscription',
'success_url' => "https://".$_SERVER["HTTP_HOST"].$insertData['urlok'],
'cancel_url' => "https://".$_SERVER["HTTP_HOST"].$insertData['urlko'],
'metadata' => [
'payment_num' => sha1($inserted_payment),
'user_id' => $records['user'],
'payment_type' => 'mixed_simple',
'recurring_amount' => $total_recurring_con_iva,
'onetime_amount' => $total_onetime_con_iva,
'total_first_month' => $precio_primer_mes
]
]);
return [
'url' => $session->url,
'session_id' => $session->id,
'customer_id' => $customer->id,
'type' => 'mixed_simple',
'total_pago' => $precio_primer_mes
];
}
private function buildDetailedLineItems($items, $type = 'recurring') {
$line_items = [];
foreach ($items as $item) {
$precio_con_iva = $item['amount'] * 1.21;
$description = $this->buildItemDescription($item, $type);
if ($type === 'recurring') {
$product = \Stripe\Product::create([
'name' => $item['name'],
'description' => $description
]);
$price = \Stripe\Price::create([
'product' => $product->id,
'currency' => 'eur',
'unit_amount' => round($precio_con_iva * 100),
'recurring' => ['interval' => $item['interval'] ?? 'month']
]);
$line_items[] = [
'price' => $price->id,
'quantity' => $item['quantity'] ?? 1
];
} else {
// Para pagos únicos, crear con más detalle
$line_items[] = [
'price_data' => [
'currency' => 'eur',
'product_data' => [
'name' => $item['name'],
'description' => $description
],
'unit_amount' => round($precio_con_iva * 100),
],
'quantity' => $item['quantity'] ?? 1
];
}
}
return $line_items;
}
private function buildItemDescription($item, $type) {
$precio_sin_iva = $item['amount'];
$iva = $precio_sin_iva * 0.21;
$precio_con_iva = $precio_sin_iva + $iva;
$description = sprintf(
"Base: %.2f€ + IVA (21%%): %.2f€ = Total: %.2f€",
$precio_sin_iva,
$iva,
$precio_con_iva
);
if ($type === 'recurring') {
$interval = $item['interval'] ?? 'month';
$description .= " - Cobro " . ($interval === 'month' ? 'mensual' : 'anual');
} else {
$description .= " - Pago único";
}
return $description;
}
private function shouldAddTrial() {
return false;
}
private function createCustomer($records, $inserted_payment, $extra_metadata = []) {
$metadata = [
'payment_num' => sha1($inserted_payment),
'user_id' => $records['user']
];
// Añadir metadatos extra si existen
if (!empty($extra_metadata)) {
$metadata = array_merge($metadata, $extra_metadata);
}
return \Stripe\Customer::create([
'email' => $records['email'] ?? '',
'name' => $records['datos_facturacion']['razonSocial'] ?? '',
'metadata' => $metadata
]);
}
}

View File

@@ -0,0 +1,176 @@
<?php
require_once __DIR__ . "/vendor/apiRedsys.php";
require_once __DIR__ . "/PaymentMethod.class.php";
class TPV extends PaymentMethod
{
function __construct()
{
self::get_config();
$this->init(1, 'TPV', 'https://sis-t.redsys.es:25443/sis/realizarPago', [
'merchant' => self::$config['test_tpv_merchant'],
'currency' => self::$config['test_tpv_currency'],
'terminal' => self::$config['test_tpv_terminal'],
'key' => self::$config['test_tpv_key']
], 'https://sis.redsys.es/sis/realizarPago', [
'merchant' => self::$config['tpv_merchant'],
'currency' => self::$config['tpv_currency'],
'terminal' => self::$config['tpv_terminal'],
'key' => self::$config['tpv_key'],
]);
}
function can_be_used()
{
$cred = $this->get_credentials();
return @$cred['merchant'] && @$cred['currency'] && @$cred['terminal'] && @$cred['key'];
}
function insert_payments($records = null, $email = null, $ipn = null, $callback = null, $tipo = 0, $price = 0, $payment_id = null)
{
// Mat (2024-08-06): Creamos esta función para poder generar un payment sin el pay previo al generar compra con stripe
parent::insert($records, $email, $ipn, $callback, $tipo, $price, $payment_id);
return mysql_insert_id();
}
function pay($quantity, $insertData = null, $payment = null, $curl = false)
{
parent::pay($quantity, $insertData, $payment);
$cred = $this->get_credentials();
$isBizum = $this->is_bizum();
$miObj = new RedsysAPI;
$miObj->setParameter("DS_MERCHANT_AMOUNT", ((float) floor(self::parse_number($quantity) * 100)));
$miObj->setParameter("DS_MERCHANT_ORDER", $this->reference);
$miObj->setParameter("DS_MERCHANT_MERCHANTCODE", $cred['merchant']);
$miObj->setParameter("DS_MERCHANT_CURRENCY", $cred['currency']);
$miObj->setParameter("DS_MERCHANT_TRANSACTIONTYPE", 0);
$miObj->setParameter("DS_MERCHANT_TERMINAL", $cred['terminal']);
$miObj->setParameter("DS_MERCHANT_PRODUCTDESCRIPTION", $this->get_product_description());
// Dani (2024-07-24): Si realizamos el pago por CURL, modificamos los
// endpoints de Redsys y no enviamos los campos `URLOK` y `URLKO`.
if ($curl) {
$this->test_url = 'https://sis-t.redsys.es:25443/sis/rest/trataPeticionREST';
$this->prod_url = 'https://sis.redsys.es/sis/rest/trataPeticionREST';
} else {
// Dani (2025-02-06): Mostramos la pasarela de pago en el idioma del
// usuario, siendo español por defecto.
// https://github.com/creagia/redsys-php/blob/main/src/Enums/ConsumerLanguage.php
$idiomas = [
'es' => 1,
'en' => 2,
'fr' => 4,
'de' => 5,
];
$idioma = $_REQUEST['idioma'] ?? 'es';
$consumerLanguage = array_key_exists($idioma, $idiomas)
? $idiomas[$idioma]
: $idiomas['es'];
$miObj->setParameter("DS_MERCHANT_CONSUMERLANGUAGE", $consumerLanguage);
$miObj->setParameter("DS_MERCHANT_URLOK", $this->get_success_url());
$miObj->setParameter("DS_MERCHANT_URLKO", $this->get_cancel_url($this->payment_record_id));
}
// Dani (2024-10-04): Añadido parámetro $extraParams para obtener más
// información en la petición que hace el banco a nuestro ipn.php
$miObj->setParameter("DS_MERCHANT_MERCHANTURL", $this->get_ipn_url($this->payment_record_id, $insertData['ipnExtraParams'] ?? []));
if ($isBizum)
$miObj->setParameter("DS_MERCHANT_PAYMETHODS", "z");
if ($payment && $payment['card_id'] && $payment['card_caduc']) {
$caducidad = strtotime(substr($payment['card_caduc'], 0, 2) . "-" . substr($payment['card_caduc'], 2) . "-28 23:59:59");
if ($caducidad >= time()) { // Aún no ha caducado
$miObj->setParameter("DS_MERCHANT_IDENTIFIER", $payment['card_id']);
$miObj->setParameter("DS_MERCHANT_DIRECTPAYMENT", true); // Pago sin pantallas adicionales
// Dani (2024-07-04): `DS_MERCHANT_EXCEP_SCA` es necesario para
// indicar que la transacción ha sido iniciada por nosotros sin
// intervención del titular de la tarjeta (e.j pagos domiciliados)
// https://pagosonline.redsys.es/desarrolladores-inicio/documentacion-funcionalidades-avanzadas/tokenizacion/
$miObj->setParameter("DS_MERCHANT_EXCEP_SCA", "MIT");
}
}
$params = $miObj->createMerchantParameters();
$signature = $miObj->createMerchantSignature($cred['key']);
$this->helper->paypal_url = $this->get_url();
$this->helper->add_field('Ds_SignatureVersion', 'HMAC_SHA256_V1');
$this->helper->add_field('Ds_MerchantParameters', $params);
$this->helper->add_field('Ds_Signature', $signature);
if (self::$isTest) {
$this->helper->dump_fields();
}
// Dani (2024-07-24): Añadido `return` para devolver la respuesta del
// CURL si se ha realizado el pago por CURL.
return $this->helper->submit_paypal_post($curl);
}
function refundAmount($referencia, $amount)
{
// Mat (2024-08-06): Creamos esta función para poder generar una devolución mediante el identificador y la cantidad de un pago ya realizado.
$cred = $this->get_credentials();
$miObj = new RedsysAPI;
$miObj->setParameter("DS_MERCHANT_AMOUNT", ((float) floor(self::parse_number($amount) * 100)));
$miObj->setParameter("DS_MERCHANT_ORDER", $referencia);
$miObj->setParameter("DS_MERCHANT_MERCHANTCODE", $cred['merchant']);
$miObj->setParameter("DS_MERCHANT_CURRENCY", $cred['currency']);
$miObj->setParameter("DS_MERCHANT_TRANSACTIONTYPE", 3);
$miObj->setParameter("DS_MERCHANT_TERMINAL", $cred['terminal']);
$params = $miObj->createMerchantParameters();
$signature = $miObj->createMerchantSignature($cred['key']);
if (self::$isTest) {
$this->helper->paypal_url = 'https://sis-t.redsys.es:25443/sis/rest/trataPeticionREST';
} else {
$this->helper->paypal_url = 'https://sis.redsys.es/sis/rest/trataPeticionREST';
}
$this->helper->add_field('Ds_SignatureVersion', 'HMAC_SHA256_V1');
$this->helper->add_field('Ds_MerchantParameters', $params);
$this->helper->add_field('Ds_Signature', $signature);
$res = $this->helper->submit_paypal_post(true, false);
$res = json_decode($res, true);
return $res;
}
function is_bizum()
{
return @$this->bizum ? true : false;
}
function subscribe($quantity, $insertData = null, $subscription_args = [])
{
parent::subscribe($quantity, $insertData);
$cof_ini = @$subscription_args["cof_ini"] ? $subscription_args["cof_ini"] : "S";
$cof_type = @$subscription_args["cof_type"] ? $subscription_args["cof_type"] : "I";
$cred = $this->get_credentials();
$miObj = new RedsysAPI;
$miObj->setParameter("DS_MERCHANT_AMOUNT", ((float) floor(self::parse_number($quantity) * 100)));
$miObj->setParameter("DS_MERCHANT_ORDER", $this->reference);
$miObj->setParameter("DS_MERCHANT_MERCHANTCODE", $cred['merchant']);
$miObj->setParameter("DS_MERCHANT_CURRENCY", $cred['currency']);
$miObj->setParameter("DS_MERCHANT_TRANSACTIONTYPE", 0);
$miObj->setParameter("DS_MERCHANT_IDENTIFIER", 'REQUIRED');
$miObj->setParameter("DS_MERCHANT_TERMINAL", $cred['terminal']);
// Datos de suscripcion
$miObj->setParameter("DS_MERCHANT_IDENTIFIER", 'REQUIRED'); //Pedir que banco guarde tarjeta
$miObj->setParameter("DS_MERCHANT_COF_INI", $cof_ini); // Es una petición inicial de tokenizacion
// $miObj->setParameter("DS_MERCHANT_COF_TYPE", 'I'); // Pago aplazado, un solo pago
// $miObj->setParameter("DS_MERCHANT_COF_TYPE", 'R'); // Pago aplazado, un solo pago
$miObj->setParameter("DS_MERCHANT_COF_TYPE", $cof_type); // Pago aplazado, pago recurrente
// Dani (2024-10-04): Añadido parámetro $extraParams para obtener más
// información en la petición que hace el banco a nuestro ipn.php
$miObj->setParameter("DS_MERCHANT_MERCHANTURL", $this->get_ipn_url($this->payment_record_id, $insertData['ipnExtraParams'] ?? []));
$miObj->setParameter("DS_MERCHANT_PRODUCTDESCRIPTION", $this->get_product_description());
$miObj->setParameter("DS_MERCHANT_URLOK", $this->get_success_url());
$miObj->setParameter("DS_MERCHANT_URLKO", $this->get_cancel_url($this->payment_record_id));
$params = $miObj->createMerchantParameters();
$signature = $miObj->createMerchantSignature($cred['key']);
$this->helper->paypal_url = $this->get_url();
$this->helper->add_field('Ds_SignatureVersion', 'HMAC_SHA256_V1');
$this->helper->add_field('Ds_MerchantParameters', $params);
$this->helper->add_field('Ds_Signature', $signature);
if (self::$isTest) {
$this->helper->dump_fields();
}
$this->helper->submit_paypal_post();
}
}

View File

@@ -0,0 +1,51 @@
<?php
require_once __DIR__."/vendor/apiRedsys.php";
require_once __DIR__."/PaymentMethod.class.php";
class Transfer extends PaymentMethod {
function __construct() {
self::get_config();
$this->init(3, 'Transfer', "", ['method' => 'transfer'], "", ['method' => 'transfer']);
}
function can_be_used() {
$cred = $this->get_credentials();
return true;
}
function pay($quantity, $insertData = null, $payment = null, $curl = false) {
parent::pay($quantity, $insertData, $payment);
self::process_ipn(sha1($this->payment_record_id));
$cred = $this->get_credentials();
$this->helper->paypal_url = $this->get_success_url();
$this->helper->add_field('ReferenceOrder', $this->reference);
if (self::$isTest) {
$this->helper->dump_fields();
}
$this->helper->submit_paypal_post($curl,false);
}
function process_ipn($paymentId,$datos = []){
$payment = IPNAction::get($paymentId);
$response = mysql_real_escape_string(is_array($datos) ? json_encode($datos) : $datos);
if (!@$_REQUEST['clave']) {
mysql_query("UPDATE `aux_plg_payments` SET ipn_response='$response' WHERE num=$payment[num]");
}
if ($payment['ipn_action']) {
try {
$class = new ReflectionClass($payment['ipn_action']);
$class = $class->newInstance();
$class->performAction($payment);
}catch(Exception $e) {
$error = mysql_real_escape_string($e->getMessage());
mysql_query("UPDATE `aux_plg_payments` SET error='$error' WHERE num=$payment[num]");
if (!file_exists(__DIR__."/../ipn_log.txt")) {
touch(__DIR__."/../ipn_log.txt");
}
file_put_contents(__DIR__."/../ipn_log.txt", "------\n".date('Y-m-d H:i:s')." - ".$e->getMessage()."\n\n", FILE_APPEND);
}
}
}
}

View File

@@ -0,0 +1,207 @@
<?php
/**
* NOTA SOBRE LA LICENCIA DE USO DEL SOFTWARE
*
* El uso de este software está sujeto a las Condiciones de uso de software que
* se incluyen en el paquete en el documento "Aviso Legal.pdf". También puede
* obtener una copia en la siguiente url:
* http://www.redsys.es/wps/portal/redsys/publica/areadeserviciosweb/descargaDeDocumentacionYEjecutables
*
* Redsys es titular de todos los derechos de propiedad intelectual e industrial
* del software.
*
* Quedan expresamente prohibidas la reproducción, la distribución y la
* comunicación pública, incluida su modalidad de puesta a disposición con fines
* distintos a los descritos en las Condiciones de uso.
*
* Redsys se reserva la posibilidad de ejercer las acciones legales que le
* correspondan para hacer valer sus derechos frente a cualquier infracción de
* los derechos de propiedad intelectual y/o industrial.
*
* Redsys Servicios de Procesamiento, S.L., CIF B85955367
*/
class RedsysAPI{
/****** Array de DatosEntrada ******/
var $vars_pay = array();
/****** Set parameter ******/
function setParameter($key,$value){
$this->vars_pay[$key]=$value;
}
/****** Get parameter ******/
function getParameter($key){
return $this->vars_pay[$key];
}
//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
//////////// FUNCIONES AUXILIARES: ////////////
//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
/****** 3DES Function ******/
function encrypt_3DES($message, $key){
// Se cifra
$l = ceil(strlen($message) / 8) * 8;
return substr(openssl_encrypt($message . str_repeat("\0", $l - strlen($message)), 'des-ede3-cbc', $key, OPENSSL_RAW_DATA, "\0\0\0\0\0\0\0\0"), 0, $l);
}
/****** Base64 Functions ******/
function base64_url_encode($input){
return strtr(base64_encode($input), '+/', '-_');
}
function encodeBase64($data){
$data = base64_encode($data);
return $data;
}
function base64_url_decode($input){
return base64_decode(strtr($input, '-_', '+/'));
}
function decodeBase64($data){
$data = base64_decode($data);
return $data;
}
/****** MAC Function ******/
function mac256($ent,$key){
$res = hash_hmac('sha256', $ent, $key, true);//(PHP 5 >= 5.1.2)
return $res;
}
//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
//////////// FUNCIONES PARA LA GENERACIÓN DEL FORMULARIO DE PAGO: ////////////
//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
/****** Obtener Número de pedido ******/
function getOrder(){
$numPedido = "";
if(empty($this->vars_pay['DS_MERCHANT_ORDER'])){
$numPedido = $this->vars_pay['Ds_Merchant_Order'];
} else {
$numPedido = $this->vars_pay['DS_MERCHANT_ORDER'];
}
return $numPedido;
}
/****** Convertir Array en Objeto JSON ******/
function arrayToJson(){
$json = json_encode($this->vars_pay); //(PHP 5 >= 5.2.0)
return $json;
}
function createMerchantParameters(){
// Se transforma el array de datos en un objeto Json
$json = $this->arrayToJson();
// Se codifican los datos Base64
return $this->encodeBase64($json);
}
function createMerchantSignature($key){
// Se decodifica la clave Base64
$key = $this->decodeBase64($key);
// Se genera el parámetro Ds_MerchantParameters
$ent = $this->createMerchantParameters();
// Se diversifica la clave con el Número de Pedido
$key = $this->encrypt_3DES($this->getOrder(), $key);
// MAC256 del parámetro Ds_MerchantParameters
$res = $this->mac256($ent, $key);
// Se codifican los datos Base64
return $this->encodeBase64($res);
}
//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
//////////// FUNCIONES PARA LA RECEPCIÓN DE DATOS DE PAGO (Notif, URLOK y URLKO): ////////////
//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
/****** Obtener Número de pedido ******/
function getOrderNotif(){
$numPedido = "";
if(empty($this->vars_pay['Ds_Order'])){
$numPedido = $this->vars_pay['DS_ORDER'];
} else {
$numPedido = $this->vars_pay['Ds_Order'];
}
return $numPedido;
}
function getOrderNotifSOAP($datos){
$posPedidoIni = strrpos($datos, "<Ds_Order>");
$tamPedidoIni = strlen("<Ds_Order>");
$posPedidoFin = strrpos($datos, "</Ds_Order>");
return substr($datos,$posPedidoIni + $tamPedidoIni,$posPedidoFin - ($posPedidoIni + $tamPedidoIni));
}
function getRequestNotifSOAP($datos){
$posReqIni = strrpos($datos, "<Request");
$posReqFin = strrpos($datos, "</Request>");
$tamReqFin = strlen("</Request>");
return substr($datos,$posReqIni,($posReqFin + $tamReqFin) - $posReqIni);
}
function getResponseNotifSOAP($datos){
$posReqIni = strrpos($datos, "<Response");
$posReqFin = strrpos($datos, "</Response>");
$tamReqFin = strlen("</Response>");
return substr($datos,$posReqIni,($posReqFin + $tamReqFin) - $posReqIni);
}
/****** Convertir String en Array ******/
function stringToArray($datosDecod){
$this->vars_pay = json_decode($datosDecod, true); //(PHP 5 >= 5.2.0)
}
function decodeMerchantParameters($datos){
// Se decodifican los datos Base64
$decodec = $this->base64_url_decode($datos);
// Los datos decodificados se pasan al array de datos
$this->stringToArray($decodec);
return $decodec;
}
function createMerchantSignatureNotif($key, $datos){
// Se decodifica la clave Base64
$key = $this->decodeBase64($key);
// Se decodifican los datos Base64
$decodec = $this->base64_url_decode($datos);
// Los datos decodificados se pasan al array de datos
$this->stringToArray($decodec);
// Se diversifica la clave con el Número de Pedido
$key = $this->encrypt_3DES($this->getOrderNotif(), $key);
// MAC256 del parámetro Ds_Parameters que envía Redsys
$res = $this->mac256($datos, $key);
// Se codifican los datos Base64
return $this->base64_url_encode($res);
}
/****** Notificaciones SOAP ENTRADA ******/
function createMerchantSignatureNotifSOAPRequest($key, $datos){
// Se decodifica la clave Base64
$key = $this->decodeBase64($key);
// Se obtienen los datos del Request
$datos = $this->getRequestNotifSOAP($datos);
// Se diversifica la clave con el Número de Pedido
$key = $this->encrypt_3DES($this->getOrderNotifSOAP($datos), $key);
// MAC256 del parámetro Ds_Parameters que envía Redsys
$res = $this->mac256($datos, $key);
// Se codifican los datos Base64
return $this->encodeBase64($res);
}
/****** Notificaciones SOAP SALIDA ******/
function createMerchantSignatureNotifSOAPResponse($key, $datos, $numPedido){
// Se decodifica la clave Base64
$key = $this->decodeBase64($key);
// Se obtienen los datos del Request
$datos = $this->getResponseNotifSOAP($datos);
// Se diversifica la clave con el Número de Pedido
$key = $this->encrypt_3DES($numPedido, $key);
// MAC256 del parámetro Ds_Parameters que envía Redsys
$res = $this->mac256($datos, $key);
// Se codifican los datos Base64
return $this->encodeBase64($res);
}
}
?>

View File

@@ -0,0 +1,529 @@
<?php
namespace Iberent;
class Client {
/*
* Iberent's production API URL
*/
const PRODUCTION_BASE_URL = 'https://api.iberent.es';
/*
* Iberent's test API URL
*/
const TEST_BASE_URL = 'https://api-test.iberent.es';
/**
* HTTP client object
*/
private $client;
/*
* url to request
*/
protected $url;
/**
* @param string $accessToken
* @param string $origin
* @param bool $production
*/
public function __construct($access_token, $origin = '', $production = false)
{
$this->client = new \Http\Client(
[
'Authorization' => "$access_token",
'Origin' => "$origin",
'User-Agent' => 'iberent-php-api_client 0.3'
]
);
$this->url = $production ? self::PRODUCTION_BASE_URL : self::TEST_BASE_URL;
}
/**
* @param array $conditions
*
* @return array
*/
public function countPayments($conditions = [])
{
//$params = $this->extractQueryParameters($conditions);
return $this->request('GET', 'v0/payment/count.json', $conditions);
}
/**
* @param array $conditions
*
* @return array
*/
public function openTransaction($reference = '', $transaction = [], $order = [])
{
$params = array_merge(
$transaction,
array(
'callback_urls' => json_encode($transaction['callback_urls'])
),
array(
'reference' => $reference,
'order' => array_merge(
$order,
array(
'reference' => $reference
)
)
)
);
return $this->request('POST', 'v0/transaction/open.json', $params);
}
/**
* @param string $transaction_id
*
* @return array
*/
public function getTransaction($transaction_id)
{
return $this->request('GET', "v0/transaction/$transaction_id.json");
}
/**
* @param array $conditions
*
* @return array
*/
public function getTransactions($conditions)
{
return $this->request('GET', "v0/transaction/list.json", $conditions);
}
/**
* @param string $type
* @param array $options
* @param string $ticket
*
* @return array
*/
public function createWidget($type, $options = [], $ticket = null)
{
$params = array_merge(
$options,
array(
'type' => $type,
'ticket' => $ticket,
)
);
return $this->request('POST', "v0/widget/create.json", $params);
}
/**
* @param string $order_id
*
* @return array
*/
public function getOrder($order_id)
{
return $this->request('GET', "v0/order/$order_id.json");
}
/**
* @param array $conditions
*
* @return array
*/
public function getOrders($conditions)
{
return $this->request('GET', "v0/order/list.json", $conditions);
}
/**
* @param string $user_id
*
* @return array
*/
public function getUser($user_id)
{
return $this->request('GET', "v0/user/$user_id.json");
}
/**
* @param string $account_id
*
* @return array
*/
public function getAccount($account_id)
{
return $this->request('GET', "v0/account/$account_id.json");
}
/**
* @param string $method
* @param string $path
* @param array $params
* @param boolean $json
*
* @return array
*/
private function request($method, $path, $params = [], $json = true){
try {
$response = $this->client->request($method, "$this->url/$path", $params);
if ($json) {
$response = json_decode($response, true);
}
} catch (\Exception $exception) {
$response = $exception->getMessage();
}
return $response;
}
}
namespace Http;
class Client {
/**
* HTTP headers
*/
private $headers;
/**
* @param array $headers
*/
public function __construct($headers = [])
{
$this->headers = [];
foreach ($headers as $key => $value) {
$this->headers[] = "$key: $value";
}
}
/**
* @param string $method
* @param string $url
* @param array $params
*
* @return string
*/
public function request($method, $url = '', $params = [])
{
array_walk_recursive($params, function (&$value) { $value = \Http\Encoding::fixUTF8($value);});
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_ENCODING => 'UTF-8',
CURLOPT_HTTPHEADER => $this->headers,
CURLOPT_POSTFIELDS => json_encode($params),
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
}
class Encoding {
/*
* Active transliteration characters that cannot be represented in the target charset
*/
const ICONV_TRANSLIT = "TRANSLIT";
/*
* Active ignore characters that cannot be represented in the target charset
*/
const ICONV_IGNORE = "IGNORE";
/*
* Performs string conversion without iconv function
*/
const WITHOUT_ICONV = "";
/**
* Converting table Windows-1252 to UTF-8
*/
protected static $win1252_to_utf8 = array(
128 => "\xe2\x82\xac",
130 => "\xe2\x80\x9a",
131 => "\xc6\x92",
132 => "\xe2\x80\x9e",
133 => "\xe2\x80\xa6",
134 => "\xe2\x80\xa0",
135 => "\xe2\x80\xa1",
136 => "\xcb\x86",
137 => "\xe2\x80\xb0",
138 => "\xc5\xa0",
139 => "\xe2\x80\xb9",
140 => "\xc5\x92",
142 => "\xc5\xbd",
145 => "\xe2\x80\x98",
146 => "\xe2\x80\x99",
147 => "\xe2\x80\x9c",
148 => "\xe2\x80\x9d",
149 => "\xe2\x80\xa2",
150 => "\xe2\x80\x93",
151 => "\xe2\x80\x94",
152 => "\xcb\x9c",
153 => "\xe2\x84\xa2",
154 => "\xc5\xa1",
155 => "\xe2\x80\xba",
156 => "\xc5\x93",
158 => "\xc5\xbe",
159 => "\xc5\xb8"
);
/**
* Converting table broken UTF-8 to UTF-8
*/
protected static $broken_utf8_to_utf8 = array(
"\xc2\x80" => "\xe2\x82\xac",
"\xc2\x82" => "\xe2\x80\x9a",
"\xc2\x83" => "\xc6\x92",
"\xc2\x84" => "\xe2\x80\x9e",
"\xc2\x85" => "\xe2\x80\xa6",
"\xc2\x86" => "\xe2\x80\xa0",
"\xc2\x87" => "\xe2\x80\xa1",
"\xc2\x88" => "\xcb\x86",
"\xc2\x89" => "\xe2\x80\xb0",
"\xc2\x8a" => "\xc5\xa0",
"\xc2\x8b" => "\xe2\x80\xb9",
"\xc2\x8c" => "\xc5\x92",
"\xc2\x8e" => "\xc5\xbd",
"\xc2\x91" => "\xe2\x80\x98",
"\xc2\x92" => "\xe2\x80\x99",
"\xc2\x93" => "\xe2\x80\x9c",
"\xc2\x94" => "\xe2\x80\x9d",
"\xc2\x95" => "\xe2\x80\xa2",
"\xc2\x96" => "\xe2\x80\x93",
"\xc2\x97" => "\xe2\x80\x94",
"\xc2\x98" => "\xcb\x9c",
"\xc2\x99" => "\xe2\x84\xa2",
"\xc2\x9a" => "\xc5\xa1",
"\xc2\x9b" => "\xe2\x80\xba",
"\xc2\x9c" => "\xc5\x93",
"\xc2\x9e" => "\xc5\xbe",
"\xc2\x9f" => "\xc5\xb8"
);
/**
* Converting table UTF-8 to Windows-1252
*/
protected static $utf8_to_win1252 = array(
"\xe2\x82\xac" => "\x80",
"\xe2\x80\x9a" => "\x82",
"\xc6\x92" => "\x83",
"\xe2\x80\x9e" => "\x84",
"\xe2\x80\xa6" => "\x85",
"\xe2\x80\xa0" => "\x86",
"\xe2\x80\xa1" => "\x87",
"\xcb\x86" => "\x88",
"\xe2\x80\xb0" => "\x89",
"\xc5\xa0" => "\x8a",
"\xe2\x80\xb9" => "\x8b",
"\xc5\x92" => "\x8c",
"\xc5\xbd" => "\x8e",
"\xe2\x80\x98" => "\x91",
"\xe2\x80\x99" => "\x92",
"\xe2\x80\x9c" => "\x93",
"\xe2\x80\x9d" => "\x94",
"\xe2\x80\xa2" => "\x95",
"\xe2\x80\x93" => "\x96",
"\xe2\x80\x94" => "\x97",
"\xcb\x9c" => "\x98",
"\xe2\x84\xa2" => "\x99",
"\xc5\xa1" => "\x9a",
"\xe2\x80\xba" => "\x9b",
"\xc5\x93" => "\x9c",
"\xc5\xbe" => "\x9e",
"\xc5\xb8" => "\x9f"
);
/**
* Converting table UTF-8 to Windows-1252
*/
static function toUTF8($data)
{
return mb_convert_encoding($data, 'Windows-1252', 'UTF-8');
if(is_array($data)) {
foreach($data as $key => $value) {
$data[$key] = self::toUTF8($value);
}
return $data;
}
if(!is_string($data)) {
return $data;
}
$max = self::strlen($data);
$buf = "";
for($i = 0; $i < $max; $i++){
$c1 = $data[$i];
if($c1>="\xc0"){ //Should be converted to UTF8, if it's not UTF8 already
$c2 = $i+1 >= $max? "\x00" : $data[$i+1];
$c3 = $i+2 >= $max? "\x00" : $data[$i+2];
$c4 = $i+3 >= $max? "\x00" : $data[$i+3];
if($c1 >= "\xc0" & $c1 <= "\xdf"){ //looks like 2 bytes UTF8
if($c2 >= "\x80" && $c2 <= "\xbf"){ //yeah, almost sure it's UTF8 already
$buf .= $c1 . $c2;
$i++;
} else { //not valid UTF8. Convert it.
$cc1 = (chr(ord($c1) / 64) | "\xc0");
$cc2 = ($c1 & "\x3f") | "\x80";
$buf .= $cc1 . $cc2;
}
} elseif($c1 >= "\xe0" & $c1 <= "\xef"){ //looks like 3 bytes UTF8
if($c2 >= "\x80" && $c2 <= "\xbf" && $c3 >= "\x80" && $c3 <= "\xbf"){ //yeah, almost sure it's UTF8 already
$buf .= $c1 . $c2 . $c3;
$i = $i + 2;
} else { //not valid UTF8. Convert it.
$cc1 = (chr(ord($c1) / 64) | "\xc0");
$cc2 = ($c1 & "\x3f") | "\x80";
$buf .= $cc1 . $cc2;
}
} elseif($c1 >= "\xf0" & $c1 <= "\xf7"){ //looks like 4 bytes UTF8
if($c2 >= "\x80" && $c2 <= "\xbf" && $c3 >= "\x80" && $c3 <= "\xbf" && $c4 >= "\x80" && $c4 <= "\xbf"){ //yeah, almost sure it's UTF8 already
$buf .= $c1 . $c2 . $c3 . $c4;
$i = $i + 3;
} else { //not valid UTF8. Convert it.
$cc1 = (chr(ord($c1) / 64) | "\xc0");
$cc2 = ($c1 & "\x3f") | "\x80";
$buf .= $cc1 . $cc2;
}
} else { //doesn't look like UTF8, but should be converted
$cc1 = (chr(ord($c1) / 64) | "\xc0");
$cc2 = (($c1 & "\x3f") | "\x80");
$buf .= $cc1 . $cc2;
}
} elseif(($c1 & "\xc0") === "\x80"){ // needs conversion
if(isset(self::$win1252_to_utf8[ord($c1)])) { //found in Windows-1252 special cases
$buf .= self::$win1252_to_utf8[ord($c1)];
} else {
$cc1 = (chr(ord($c1) / 64) | "\xc0");
$cc2 = (($c1 & "\x3f") | "\x80");
$buf .= $cc1 . $cc2;
}
} else { // it doesn't need conversion
$buf .= $c1;
}
}
return $buf;
}
static function toWin1252($data, $option = self::WITHOUT_ICONV)
{
if(is_array($data)) {
foreach($data as $key => $value) {
$data[$key] = self::toWin1252($value, $option);
}
return $data;
} elseif(is_string($data)) {
return static::utf8_decode($data, $option);
} else {
return $data;
}
}
static function toISO8859($data, $option = self::WITHOUT_ICONV)
{
return self::toWin1252($data, $option);
}
static function toLatin1($data, $option = self::WITHOUT_ICONV)
{
return self::toWin1252($data, $option);
}
static function fixUTF8($text, $option = self::WITHOUT_ICONV)
{
if(is_array($text)) {
foreach($text as $k => $v) {
$text[$k] = self::fixUTF8($v, $option);
}
return $text;
}
if(!is_string($text)) {
return $text;
}
$last = "";
while($last <> $text){
$last = $text;
$text = self::toUTF8(static::utf8_decode($text, $option));
}
$text = self::toUTF8(static::utf8_decode($text, $option));
return $text;
}
static function UTF8FixWin1252Chars($text)
{
return str_replace(array_keys(self::$broken_utf8_to_utf8), array_values(self::$broken_utf8_to_utf8), $text);
}
/**
* @param string $string
*
* @return string
*/
static function remove_bom($string = "")
{
if (substr($string, 0,3) === pack("CCC", 0xef, 0xbb, 0xbf)) {
$string = substr($string, 3);
}
return $string;
}
/**
* @param string $string
*
* @return int
*/
protected static function strlen($string)
{
return (function_exists('mb_strlen') && ((int) ini_get('mbstring.func_overload')) & 2) ? mb_strlen($string, '8bit') : strlen($string);
}
public static function normalizeEncoding($charset)
{
$charset = preg_replace('/[^a-zA-Z0-9\s]/', '', strtoupper($charset));
$equivalences = array(
'ISO88591' => 'ISO-8859-1',
'ISO8859' => 'ISO-8859-1',
'ISO' => 'ISO-8859-1',
'LATIN1' => 'ISO-8859-1',
'LATIN' => 'ISO-8859-1',
'UTF8' => 'UTF-8',
'UTF' => 'UTF-8',
'WIN1252' => 'ISO-8859-1',
'WINDOWS1252' => 'ISO-8859-1'
);
if(empty($equivalences[$charset])){
return 'UTF-8';
}
return $equivalences[$charset];
}
public static function encode($charset, $data)
{
if (self::normalizeEncoding($charset) === 'ISO-8859-1') return self::toLatin1($data);
return self::toUTF8($data);
}
protected static function utf8_decode($data, $option = self::WITHOUT_ICONV)
{
if ($option == self::WITHOUT_ICONV || !function_exists('iconv')) {
$o = utf8_decode(
str_replace(array_keys(self::$utf8_to_win1252), array_values(self::$utf8_to_win1252), self::toUTF8($data))
);
} else {
$o = iconv("UTF-8", "Windows-1252" . ($option === self::ICONV_TRANSLIT ? '//TRANSLIT' : ($option === self::ICONV_IGNORE ? '//IGNORE' : '')), $data);
}
return $o;
}
}

View File

@@ -0,0 +1,19 @@
version: 2
updates:
- package-ecosystem: composer
directory: "/"
schedule:
interval: daily
time: "09:00"
timezone: Europe/Madrid
open-pull-requests-limit: 10
commit-message:
prefix: "[Dep][compose] "
#ignore:
#- dependency-name: "django"
# For django, ignore all updates for version 3.2.x and 4.x
#versions: [ "3.2", "4" ]
labels:
- dependabot
reviewers:
- "aplazame/squad_plugins-reviewers"

View File

@@ -0,0 +1,162 @@
# Change Log
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
### Added
### Changed
### Deprecated
### Removed
### Fixed
### Security
## [0.3.6] - 2023-11-28
### Added
- PHP 8.1 compatibility.
### Changed
### Deprecated
### Removed
### Fixed
## [0.3.5] - 2023-10-11
### Added
### Changed
- API version at checkout header.
### Deprecated
### Removed
### Fixed
## [0.3.4] - 2021-06-22
### Added
- English API documentation links.
### Changed
### Deprecated
### Removed
### Fixed
## [0.3.3] - 2021-06-22
### Added
### Changed
- URL redirects.
### Deprecated
### Removed
### Fixed
- Documentation links.
## [0.3.2] - 2020-10-16
### Added
### Changed
- [Examples] Checkout creation real examples changed for non-real ones
### Deprecated
### Removed
### Fixed
## [0.3.1] - 2019-10-01
### Added
### Changed
### Deprecated
### Removed
### Fixed
- [Api] Request now allow to provide "objects" as payload
## [0.3.0] - 2019-08-29
### Added
- Added examples (/examples) for the server to server checkout integration
### Changed
- Use native PHP 5.5 JsonSerializable on Date and Decimal serializer
### Deprecated
### Removed
- Drop support for PHP version lower than 5.6
- Remove custom JsonSerializer in favor on builtin JsonSerializable interface
### Fixed
## [0.2.2] - 2017-11-22
### Added
### Changed
### Deprecated
### Removed
### Fixed
- [Serializer] Fix serialization of empty objects.
## [0.2.1] - 2016-11-23
### Added
### Changed
### Deprecated
### Removed
### Fixed
- [Api] path is appended twice to API base URI.
### Security
## [0.2.0] - 2016-11-23
### Added
- [Api] Add `getHttpStatusCode` to `ApiClientException` and `ApiServerException`. This method returns the response HTTP status code.
- [Serializer] New component focused only in assist the conversion of PHP types to JSON compatible types.
### Changed
- [BusinessModel] This component has been replaced by [Serializer]
- [Api] Change in `ApiClientException` and `ApiServerException` constructor signature.
### Deprecated
### Removed
- [BusinessModel] This component has been replaced by [Serializer]
### Fixed
### Security
## [0.1.0] - 2016-07-07
### Added
- API Client
- Business models
[Unreleased]: https://github.com/aplazame/php-sdk/compare/v0.3.0...HEAD
[0.3.0]: https://github.com/aplazame/php-sdk/compare/v0.2.2...v0.3.0
[0.2.2]: https://github.com/aplazame/php-sdk/compare/v0.2.1...v0.2.2
[0.2.1]: https://github.com/aplazame/php-sdk/compare/v0.2.0...v0.2.1
[0.2.0]: https://github.com/aplazame/php-sdk/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/aplazame/php-sdk/commit/cd32febb1dfb0afd3a4916204a2efd07a60a4b5f

View File

@@ -0,0 +1,19 @@
# This is a comment.
# Each line is a file pattern followed by one or more owners.
# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence,
# review when someone opens a pull request.
# owner(s) will be requested for a review.
Jenkinsfile @aplazame/squad_devops-reviewers
DockerFile @aplazame/squad_devops-reviewers
CODEOWNERS @aplazame/squad_devops-reviewers
jenkins/ @aplazame/squad_devops-reviewers
docker/DockerFile @aplazame/squad_devops-reviewers
Makefile @aplazame/squad_devops-reviewers
makefile @aplazame/squad_devops-reviewers
node.yaml @aplazame/squad_devops-reviewers
deploy.yaml @aplazame/squad_devops-reviewers
build_dependencies.yaml @aplazame/squad_devops-reviewers
build.yaml @aplazame/squad_devops-reviewers
Dockerfile @aplazame/squad_devops-reviewers

View File

@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2023, Aplazame
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,31 @@
# Aplazame PHP SDK
[![Aplazame](docs/banner-728-white-php.png)](https://aplazame.com)
[Aplazame](https://aplazame.com), a consumer credit company, offers a payment system that can be
used by online buyers to receive funding for their purchases.
## Installation
You can use [Composer](https://getcomposer.org):
```bash
composer require aplazame/aplazame-api-sdk
```
## Usage
This SDK assists you to complete the steps from the integration guide https://aplazame.com/en/docs/api/
Explore the examples folder for to learn how to:
1. Create an Aplazame's checkout [examples/step1_checkout_create.php](examples/step1_checkout_create.php)
2. Start the checkout browser flow [examples/step2_checkout_start.php](examples/step2_checkout_start.php)
3. Receive and confirm the payment statuses [examples/step3_checkout_confirm.php](examples/step3_checkout_confirm.php)
## Documentation
Documentation is available at https://aplazame.com/en/docs/api/ or https://aplazame.com/integraciones/api/

View File

@@ -0,0 +1,26 @@
{
"name": "aplazame/aplazame-api-sdk",
"description": "Aplazame API SDK",
"keywords": [
"aplazame"
],
"license": "GPL-3.0",
"require": {
"php": ">= 5.6.0",
"ext-json": "*"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "1.11.*",
"phpunit/phpunit": "*"
},
"autoload": {
"psr-4": {
"Aplazame\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Aplazame\\": "test/"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,213 @@
<?php
/*
* This file provides an example about how to complete the step 01 of Aplazame's
* integration as described in:
* - https://aplazame.com/en/docs/api/
* - https://aplazame.com/integraciones/api/
*
* It contains three steps:
*
* 1. Create an Aplazame\Api\Client with your Aplazame API credentials
* 2. Create the checkout payload.
* 3. Send the payload to Aplazame and retrieve the generated ID by Aplazame.
*/
/*
* Aplazame's private key.
* You can find your keys on your Aplazame's control panel (https://vendors.aplazame.com/)
*/
$privateKey = '<aplazame private API key>';
/*
* Enable test mode (`Aplazame\Api\Client::ENVIRONMENT_SANDBOX`) or
* production mode (`Aplazame\Api\Client::ENVIRONMENT_PRODUCTION`)
*/
$environment = Aplazame\Api\Client::ENVIRONMENT_SANDBOX;
/*
* Aplazame Client setup
*/
$apiBaseUri = 'https://api.aplazame.com';
$aplazameApiClient = new Aplazame\Api\Client($apiBaseUri, $environment, $privateKey);
/**
* Create checkout payload as described in https://aplazame.com/en/docs/api/checkout-creation/ or https://aplazame.com/integraciones/api/checkout-creation/
*
* @return object
*/
function createCheckoutPayload() {
/*
* Merchant model
*/
$merchant = new stdClass();
$merchant->notification_url = 'https://merchant.com/order/step3_checkout_confirm.php'; // url where you will receive Aplazame webhook events as described in https://aplazame.com/en/docs/api/checkout-confirmation/ or https://aplazame.com/integraciones/api/checkout-confirmation/
$merchant->success_url = "/success"; // url that the customer is sent to after confirming their order.
$merchant->pending_url = "/pending"; // url that the customer is sent to if the order status is pending.
$merchant->error_url = "/error"; // url that the customer is sent to if there is an error in the checkout.
$merchant->dismiss_url = "/checkout"; // url that the customer is sent to if the customer chooses to back to the e-commerce, by default is /.
$merchant->ko_url = "/ko"; // url that the customer is sent to if Aplazame refuses the order.
/*
* Article model
*/
$article = new stdClass();
$article->id = '89793238462643383279'; // The article ID.
$article->name = 'Reloj en oro blanco de 18 quilates y diamantes'; // Article name.
$article->url = 'http://shop.example.com/product.html'; // Article url.
$article->image_url = 'http://shop.example.com/product_image.png'; // Article image url.
$article->quantity = 2; // Article quantity.
$article->price = Aplazame\Serializer\Decimal::fromFloat(4020.00); // Article price (tax is not included). (4,020.00 €)
$article->description = 'Movimiento de cuarzo de alta precisión'; // Article description.
$article->tax_rate = Aplazame\Serializer\Decimal::fromFloat(21.00); // Article tax rate. (21.00%)
$article->discount = Aplazame\Serializer\Decimal::fromFloat(5.00); // The discount amount of the article. (5.00 €)
// ... rest of articles in the shopping cart.
/*
* Articles collection
*/
$articles = array( $article, );
/*
* Order model
*/
$order = new stdClass();
$order->id = '28475648233786783165'; // Your order ID.
$order->currency = 'EUR'; // Currency code of the order.
$order->tax_rate = Aplazame\Serializer\Decimal::fromFloat(21.00); // Order tax rate. (21.00%)
$order->total_amount = Aplazame\Serializer\Decimal::fromFloat(4620.00); // Order total amount. (4,620.00 €)
$order->articles = $articles; // Articles in cart.
$order->discount = Aplazame\Serializer\Decimal::fromFloat(160.00); // The discount amount of the order. (160.00 €)
$order->cart_discount = Aplazame\Serializer\Decimal::fromFloat(0.50); // The discount amount of the cart. (0.50 €)
/*
* Customer address model
*/
$customerAddress = new stdClass();
$customerAddress->first_name = 'John'; // Address first name.
$customerAddress->last_name = 'Coltrane'; // Address last name.
$customerAddress->street = 'Plaza del Valle Boreal nº10'; // Address street.
$customerAddress->city = 'Madrid'; // Address city.
$customerAddress->state = 'Madrid'; // Address state.
$customerAddress->country = 'ES'; // Address country code.
$customerAddress->postcode = '28080'; // Address postcode.
$customerAddress->phone = '601234567'; // Address phone number.
$customerAddress->address_addition = 'Cerca de la plaza Pontífice Sulyvahn'; // Address addition.
/*
* Customer model
*/
$customer = new stdClass();
$customer->id = '1618'; // Customer ID.
$customer->email = 'customer@address.com'; // The customer email.
$customer->type = 'e'; // Customer type, the choices are g:guest, n:new, e:existing.
$customer->gender = 0; // Customer gender, the choices are 0: not known, 1: male, 2:female, 3: not applicable.
$customer->first_name = 'John'; // Customer first name.
$customer->last_name = 'Coltrane'; // Customer last name.
$customer->birthday = Aplazame\Serializer\Date::fromDateTime(new DateTime('1990-08-21 13:56:45')); // Customer birthday.
$customer->language = 'es'; // Customer language preferences.
$customer->date_joined = Aplazame\Serializer\Date::fromDateTime(new DateTime('2014-08-21 13:56:45')); // A datetime designating when the customer account was created.
$customer->last_login = Aplazame\Serializer\Date::fromDateTime(new DateTime('2020-08-27 19:57:56')); // A datetime of the customer last login.
$customer->address = $customerAddress; // Customer address.
/*
* Billing address model
*/
$billingAddress = new stdClass();
$billingAddress->first_name = 'Bill'; // Billing first name.
$billingAddress->last_name = 'Evans'; // Billing last name.
$billingAddress->street = 'Calle Central Yharnam 92'; // Billing street.
$billingAddress->city = 'Madrid'; // Billing city.
$billingAddress->state = 'Madrid'; // Billing state.
$billingAddress->country = 'ES'; // Billing country code.
$billingAddress->postcode = '28080'; // Billing postcode.
$billingAddress->phone = '601765432'; // Billing phone number.
$billingAddress->address_addition = 'Cerca del Gran Puente'; // Billing address addition.
/*
* Shipping info model
*/
$shippingInfo = new stdClass();
$shippingInfo->first_name = 'Django'; // Shipping first name.
$shippingInfo->last_name = 'Reinhard'; // Shipping last name.
$shippingInfo->street = 'Plaza del Valle Boreal nº10'; // Shipping street.
$shippingInfo->city = 'Madrid'; // Shipping city.
$shippingInfo->state = 'Madrid'; // Shipping state.
$shippingInfo->country = 'ES'; // Shipping country code.
$shippingInfo->postcode = '28080'; // Shipping postcode.
$shippingInfo->name = 'Planet Express'; // Shipping name.
$shippingInfo->price = Aplazame\Serializer\Decimal::fromFloat(5.00); // Shipping price (tax is not included). (5.00 €)
$shippingInfo->phone = '601234567'; // Shipping phone number.
$shippingInfo->address_addition = 'Cerca de la plaza Pontífice Sulyvahn'; // Shipping address addition.
$shippingInfo->tax_rate = Aplazame\Serializer\Decimal::fromFloat(21.00); // Shipping tax rate. (21.00%)
$shippingInfo->discount = Aplazame\Serializer\Decimal::fromFloat(1.00); // The discount amount of the shipping. (1.00 €)
/*
* Checkout model
*/
$checkout = new stdClass();
$checkout->toc = true;
$checkout->merchant = $merchant;
$checkout->order = $order;
$checkout->customer = $customer;
$checkout->billing = $billingAddress;
$checkout->shipping = $shippingInfo;
return $checkout;
}
/**
* Send the checkout payload generated by createCheckoutPayload to Aplazame and returns the Aplazame Checkout ID
*
* @param \Aplazame\Api\Client $aplazameApiClient A configured client with the api key and environment
* @param array|object $payload Checkout Parameters as described in https://aplazame.com/en/docs/api/checkout-creation/ or https://aplazame.com/integraciones/api/checkout-creation/
*
* @return string Aplazame checkout ID
*
* @throws \Aplazame\Api\ApiClientException Payload contains invalid data
* @throws \Aplazame\Api\ApiServerException An error has occur on Aplazame's servers
* @throws \Aplazame\Api\ApiCommunicationException A network error has occurred while sending the request or receiving the response.
* @throws \Aplazame\Api\DeserializeException Deserialization exception (unusual to happen but it could)
*/
function createAplazameCheckout(Aplazame\Api\Client $aplazameApiClient, $payload)
{
try {
return $aplazameApiClient->request('POST','/checkout', $payload, 3)['id'];
} catch (Aplazame\Api\ApiCommunicationException $apiCommunicationException) {
// A network error has occurred while sending the request or receiving the response.
// Retry
throw $apiCommunicationException;
} catch (Aplazame\Api\DeserializeException $deserializationException) {
// Nobody knows when this happen, may an HTTP Proxy on our side or on your side started to return HTML responses with errors.
// Retry
throw $deserializationException;
} catch (Aplazame\Api\ApiServerException $apiServerException) {
// Our server has crashed. We promise to fix it ASAP.
echo 'HTTP Status code', $apiServerException->getHttpStatusCode(), PHP_EOL;
echo 'Error type', $apiServerException->getType(), PHP_EOL;
echo 'Error message', $apiServerException->getMessage(), PHP_EOL;
$rawErrorWithErrorDetails = $apiServerException->getError();
throw $apiServerException;
} catch (Aplazame\Api\ApiClientException $apiClientException) {
// Your client has sent an invalid request. Please check your code.
echo 'HTTP Status code', $apiClientException->getHttpStatusCode(), PHP_EOL;
echo 'Error type', $apiClientException->getType(), PHP_EOL;
echo 'Error message', $apiClientException->getMessage(), PHP_EOL;
$rawErrorWithErrorDetails = $apiClientException->getError();
throw $apiClientException;
}
}
$aplazameCheckoutId = createAplazameCheckout($aplazameApiClient, createCheckoutPayload());
echo 'Aplazame Checkout ID: ', $aplazameCheckoutId;

View File

@@ -0,0 +1,59 @@
<?php
/*
* This file provides an example about how to start Aplazame checkout
*
* NOTE: Contents of this file are displayed on customer browser. USE THE PUBLIC KEY (not the private key)
*/
/*
* Aplazame's PUBLIC key.
* You can find your keys on your Aplazame's control panel (https://vendors.aplazame.com/)
*/
$publicKey = '<aplazame PUBLIC API key>';
/*
* Enable test mode (`Aplazame\Api\Client::ENVIRONMENT_SANDBOX`) or
* production mode (`Aplazame\Api\Client::ENVIRONMENT_PRODUCTION`)
*/
$environment = Aplazame\Api\Client::ENVIRONMENT_SANDBOX;
/*
* Use the ID generated by Aplazame at step1
*/
$aplazameCheckoutId = '<checkout id from step1>'
?>
<html>
<head>
<script
src="https://aplazame.com/static/aplazame.js"
data-aplazame="<?php echo $publicKey; ?>"
data-sandbox="<?php echo ($environment === Aplazame\Api\Client::ENVIRONMENT_SANDBOX) ? 'true' : 'false'; ?>"
></script>
</head>
<body>
<h1>
Aplazame checkout
</h1>
<!-- This fragment is for to assist you about to debug issues. Don't deploy to production! -->
<script>
var display_error = function (event) {
var error_container = document.getElementById("error_container");
console.error(event);
error_container.innerHTML = event.message
};
window.addEventListener('error', display_error);
</script>
<div id="error_container" style="color: red;"></div>
<!-- End fragment -->
<script>
aplazame.checkout("<?php echo $aplazameCheckoutId; ?>")
</script>
</body>
</html>

View File

@@ -0,0 +1,162 @@
<?php
/*
* This file provides an example about how to complete the step 03 of Aplazame's integration as described in:
* - https://aplazame.com/en/docs/api/
* - https://aplazame.com/integraciones/api/
*
* This file is split in two main sections:
*
* A. Basic settings:
*
* - Aplazame's private key
* - Test mode
* - Failed callback
* - Accepted callback
* - Pending callback. Please read the notes of this callback and check if some scenario may apply to your integration.
*
* B. Aplazame's checkout checks and confirmation logic.
*/
/*
* Aplazame's private key.
* You can find your keys on your Aplazame's control panel (https://vendors.aplazame.com/)
*/
$privateKey = "<aplazame private API key>";
/*
* Enable test mode (`Aplazame\Api\Client::ENVIRONMENT_SANDBOX`) or
* production mode (`Aplazame\Api\Client::ENVIRONMENT_PRODUCTION`)
*/
$environment = Aplazame\Api\Client::ENVIRONMENT_SANDBOX;
/**
* Aplazame's payment has been REJECTED by Aplazame.
*
* Apply here your own logic to reject the order and return cart items to the inventory stock.
*
* @param string $order_id This is the same value as you filled in `order.id` when the checkout was created.
*
* @return bool `true` is the regular value. You can return `false` if something strange has happen, anyway for Aplazame
* the order is already declined.
*/
function setOrderPaymentStatusFailed($order_id)
{
throw new BadMethodCallException(
'You have to customize this function with your own logic when the payment is declined'
);
return true;
}
/**
* Aplazame's payment has been ACCEPTED by Aplazame but we need your system to confirm the order.
*
* Apply here your own logic to set the order as paid and then proceed with the delivery of the order items.
*
* @param string $order_id This is the same value as you filled in `order.id` when the checkout was created.
*
* @return bool `true` is the regular value. You can return `false` if you don't want to proceed with this order.
*/
function setOrderPaymentStatusAsPaid($order_id)
{
throw new BadMethodCallException(
'You have to customize this function with your own logic when the payment is accepted'
);
/*
* Most shops always return "true" here but some scenarios may apply to your checkout process:
*
* - Cart item has been sold to a different customer.
* - Any other undefined reason to reject this order.
*
* If any of these scenarios apply to your checkout process, then you should to return `false` here.
*/
return true;
}
/**
* Aplazame's payment is PENDING.
*
* Apply here your own logic for to set the order as pending payment.
*
* This callback is called only for specific orders (not for every order). Regular callbacks (Failed and Accepted) will be
* called once the pending state has been resolved.
*
* @param string $order_id This is the same value as you filled in `order.id` when the checkout was created.
*
* @return bool `true` is the default value. You can return `false` if you don't want to proceed with this order.
*/
function setOrderPaymentStatusAsPending($order_id)
{
/*
* Most shops does not need to do nothing here but there are some scenarios may affect you:
*
* - Items in the order are saved from the stock (i.e. another customer could purchase the same item)
* - You don't want to place on hold specific orders
*
* If any of these scenarios apply to your checkout process, then you have to return `false` here.
*/
return true;
}
/*
* This section is a more specific Aplazame flow as described in https://aplazame.com/en/docs/api/checkout-confirmation/ or https://aplazame.com/integraciones/api/checkout-confirmation/
*
* Feel free to adjust to the specific requirements of your checkout process. The following snippet is provided as a quick start.
*/
function response($status)
{
header('Content-Type: application/json');
echo json_encode(array('status' => $status));
return null;
}
function confirm($payload, $sandbox)
{
if (!$payload) {
return response('Payload is malformed');
}
if (!isset($payload['sandbox']) || $payload['sandbox'] !== $sandbox) {
return response('"sandbox" not provided');
}
if (!isset($payload['mid'])) {
return response('"mid" not provided');
}
$mid = $payload['mid'];
switch ($payload['status']) {
case 'pending':
switch ($payload['status_reason']) {
case 'challenge_required':
if (!setOrderPaymentStatusAsPending($mid)) {
return response('ko');
}
break;
case 'confirmation_required':
if (!setOrderPaymentStatusAsPaid($mid)) {
return response('ko');
}
break;
}
break;
case 'ko':
setOrderPaymentStatusFailed($mid);
break;
}
return response('ok');
}
if ($_GET['access_token'] !== $privateKey) {
return response(403);
}
confirm(
json_decode(file_get_contents('php://input'), true),
$environment === Aplazame\Api\Client::ENVIRONMENT_SANDBOX
);

View File

@@ -0,0 +1,79 @@
<?php
namespace Aplazame\Api;
use Aplazame\Http\ResponseInterface;
use LogicException;
/**
* Exception thrown for HTTP 4xx client errors.
*/
class ApiClientException extends LogicException implements AplazameExceptionInterface
{
/**
* @param ResponseInterface $response
*
* @return self
*/
public static function fromResponse(ResponseInterface $response)
{
$responseBody = (string) $response->getBody();
if (empty($responseBody)) {
return new self($response->getStatusCode(), $response->getReasonPhrase());
}
$decodedBody = json_decode($responseBody, true);
if (!isset($decodedBody['error'])) {
return new self($response->getStatusCode(), $response->getReasonPhrase());
}
$error = $decodedBody['error'];
return new self($response->getStatusCode(), $error['message'], $error['type'], $error);
}
/**
* @var string
*/
private $type;
/**
* @var array
*/
private $error;
/**
* @param string $statusCode
* @param string $message
* @param string $type
* @param array $error
*/
public function __construct($statusCode, $message, $type = '', array $error = array())
{
parent::__construct($message, $statusCode);
$this->type = $type;
$this->error = $error;
}
public function getHttpStatusCode()
{
return $this->getCode();
}
/**
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* @return array
*/
public function getError()
{
return $this->error;
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Aplazame\Api;
use Exception;
use RuntimeException;
/**
* Exception thrown when there is communication possible with the API.
*/
class ApiCommunicationException extends RuntimeException implements AplazameExceptionInterface
{
/**
* @param Exception $exception
*
* @return self
*/
public static function fromException(Exception $exception)
{
return new self($exception->getMessage(), $exception->getCode(), $exception);
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace Aplazame\Api;
use Aplazame\Http\Request;
use DomainException;
class ApiRequest extends Request
{
const SDK_VERSION = '0.3.6';
const FORMAT_JSON = 'json';
/**
* @param string $accessToken
*
* @return string
*/
public static function createAuthorizationHeader($accessToken)
{
return 'Bearer ' . $accessToken;
}
/**
* @param bool $useSandbox
* @param int $apiVersion
* @param string $format
*
* @return string
*/
public static function createAcceptHeader($useSandbox, $apiVersion, $format)
{
$header = 'application/vnd.aplazame';
if ($useSandbox) {
$header .= '.sandbox';
}
$header .= sprintf('.v%d+%s', $apiVersion, $format);
return $header;
}
/**
* @param bool $useSandbox
* @param int $apiVersion The API version of the request.
* @param string $accessToken The Access Token of the request (Public API key or Private API key)
* @param string $method The HTTP method of the request.
* @param string $uri The URI of the request.
* @param mixed $data The data of the request.
*/
public function __construct(
$useSandbox,
$apiVersion,
$accessToken,
$method,
$uri,
$data = null
) {
$headers = array(
'Accept' => array(self::createAcceptHeader($useSandbox, $apiVersion, self::FORMAT_JSON)),
'Authorization' => array(self::createAuthorizationHeader($accessToken)),
'User-Agent' => array(
'Aplazame/' . self::SDK_VERSION,
'PHP/' . PHP_VERSION,
),
);
if ($data && !is_string($data)) {
$data = json_encode($data);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new DomainException(json_last_error_msg(), json_last_error());
}
$headers['Content-Type'] = array('application/json');
}
parent::__construct($method, $uri, $headers, $data);
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace Aplazame\Api;
use Aplazame\Http\ResponseInterface;
use RuntimeException;
/**
* Exception thrown for HTTP 5xx client errors.
*/
class ApiServerException extends RuntimeException implements AplazameExceptionInterface
{
/**
* @param ResponseInterface $response
*
* @return self
*/
public static function fromResponse(ResponseInterface $response)
{
$responseBody = (string) $response->getBody();
if (empty($responseBody)) {
return new self($response->getStatusCode(), $response->getReasonPhrase());
}
$decodedBody = json_decode($responseBody, true);
if (!isset($decodedBody['error'])) {
return new self($response->getStatusCode(), $response->getReasonPhrase());
}
$error = $decodedBody['error'];
return new self($response->getStatusCode(), $error['message'], $error['type'], $error);
}
/**
* @var string
*/
private $type;
/**
* @var array
*/
private $error;
/**
* @param string $statusCode
* @param string $message
* @param string $type
* @param array $error
*/
public function __construct($statusCode, $message, $type = '', array $error = array())
{
parent::__construct($message, $statusCode);
$this->type = $type;
$this->error = $error;
}
public function getHttpStatusCode()
{
return $this->getCode();
}
/**
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* @return array
*/
public function getError()
{
return $this->error;
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Aplazame\Api;
/**
* Common interface for custom exceptions.
*/
interface AplazameExceptionInterface
{
}

View File

@@ -0,0 +1,204 @@
<?php
namespace Aplazame\Api;
use Aplazame\Http\ClientInterface;
use Aplazame\Http\CurlClient;
use RuntimeException;
class Client
{
const ENVIRONMENT_PRODUCTION = 'production';
const ENVIRONMENT_SANDBOX = 'sandbox';
/**
* @var string
*/
private $apiBaseUri;
/**
* @var bool
*/
private $useSandbox;
/**
* @var string
*/
private $accessToken;
/**
* @var ClientInterface
*/
private $httpClient;
/**
* @param string $apiBaseUri The API base URI.
* @param string $environment Destination of the request.
* @param string $accessToken The Access Token of the request (Public API key or Private API key)
* @param ClientInterface|null $httpClient
*/
public function __construct(
$apiBaseUri,
$environment,
$accessToken,
ClientInterface $httpClient = null
) {
$this->apiBaseUri = $apiBaseUri;
$this->useSandbox = ($environment === self::ENVIRONMENT_SANDBOX) ? true : false;
$this->accessToken = $accessToken;
$this->httpClient = $httpClient ? $httpClient : new CurlClient();
}
/**
* Performs a DELETE request.
*
* @param string $path The path of the request.
*
* @return array The data of the response.
*
* @throws ApiCommunicationException if an I/O error occurs.
* @throws DeserializeException if response cannot be deserialized.
* @throws ApiServerException if server was not able to respond.
* @throws ApiClientException if request is invalid.
*/
public function delete($path)
{
return $this->request('DELETE', $path);
}
/**
* Performs a GET request.
*
* @param string $path The path of the request.
* @param array $query The filters of the request.
*
* @return array The data of the response.
*
* @throws ApiCommunicationException if an I/O error occurs.
* @throws DeserializeException if response cannot be deserialized.
* @throws ApiServerException if server was not able to respond.
* @throws ApiClientException if request is invalid.
*/
public function get($path, array $query = array())
{
if (!empty($query)) {
$query = http_build_query($query);
$path .= '?' . $query;
}
return $this->request('GET', $path);
}
/**
* Performs a POST request.
*
* @param string $path The path of the request.
* @param array|object $data The data of the request.
*
* @return array The data of the response.
*
* @throws ApiCommunicationException if an I/O error occurs.
* @throws DeserializeException if response cannot be deserialized.
* @throws ApiServerException if server was not able to respond.
* @throws ApiClientException if request is invalid.
*/
public function patch($path, $data)
{
return $this->request('PATCH', $path, $data);
}
/**
* Performs a POST request.
*
* @param string $path The path of the request.
* @param array|object $data The data of the request.
*
* @return array The data of the response.
*
* @throws ApiCommunicationException if an I/O error occurs.
* @throws DeserializeException if response cannot be deserialized.
* @throws ApiServerException if an I/O error occurs.
* @throws ApiClientException if request is invalid.
*/
public function post($path, $data)
{
return $this->request('POST', $path, $data);
}
/**
* Performs a PUT request.
*
* @param string $path The path of the request.
* @param array|object $data The data of the request.
*
* @return array The data of the response.
*
* @throws ApiCommunicationException if an I/O error occurs.
* @throws DeserializeException if response cannot be deserialized.
* @throws ApiServerException if server was not able to respond.
* @throws ApiClientException if request is invalid.
*/
public function put($path, $data)
{
return $this->request('PUT', $path, $data);
}
/**
* @param string $method The HTTP method of the request.
* @param string $path The path of the request.
* @param array|object|null $data The data of the request.
* @param int $apiVersion The API version of the request.
*
* @return array The data of the response.
*
* @throws ApiCommunicationException if an I/O error occurs.
* @throws DeserializeException if response cannot be deserialized.
* @throws ApiServerException if server was not able to respond.
* @throws ApiClientException if request is invalid.
*/
public function request($method, $path, $data = null, $apiVersion = 1)
{
$uri = $this->apiBaseUri . '/' . ltrim($path, '/');
$request = new ApiRequest($this->useSandbox, $apiVersion, $this->accessToken, $method, $uri, $data);
try {
$response = $this->httpClient->send($request);
} catch (RuntimeException $e) {
throw ApiCommunicationException::fromException($e);
}
if ($response->getStatusCode() >= 500) {
throw ApiServerException::fromResponse($response);
}
if ($response->getStatusCode() >= 400) {
throw ApiClientException::fromResponse($response);
}
$payload = $this->decodeResponseBody((string) $response->getBody());
return $payload;
}
/**
* @param string $responseBody The HTTP response body.
*
* @return array Decoded payload.
*
* @throws DeserializeException if response cannot be deserialized.
*/
protected function decodeResponseBody($responseBody)
{
// Response body is empty for HTTP 204 and 304 status code.
if (empty($responseBody)) {
return array();
}
$responseBody = json_decode($responseBody, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new DeserializeException('Unable to deserialize JSON data: ' . json_last_error_msg(), json_last_error());
}
return $responseBody;
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace Aplazame\Api;
use UnexpectedValueException;
/**
* This exception is thrown when the data cannot be deserialized/unmarshalled.
*/
class DeserializeException extends UnexpectedValueException implements AplazameExceptionInterface
{
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Aplazame\Http;
use RuntimeException;
interface ClientInterface
{
/**
* @param RequestInterface $request
*
* @return ResponseInterface
*
* @throws RuntimeException If requests cannot be performed due network issues.
*/
public function send(RequestInterface $request);
}

View File

@@ -0,0 +1,54 @@
<?php
namespace Aplazame\Http;
use RuntimeException;
class CurlClient implements ClientInterface
{
public function __construct()
{
if (!function_exists('curl_init')) {
throw new \LogicException('cURL extension is not loaded');
}
}
public function send(RequestInterface $request)
{
$rawHeaders = array();
foreach ($request->getHeaders() as $header => $value) {
$rawHeaders[] = sprintf('%s:%s', $header, implode(', ', $value));
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $request->getUri());
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $request->getMethod());
curl_setopt($ch, CURLOPT_HTTPHEADER, $rawHeaders);
$body = $request->getBody();
if (!empty($body)) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
$responseBody = curl_exec($ch);
if (false === $responseBody) {
$message = curl_error($ch);
$code = curl_errno($ch);
curl_close($ch);
throw new RuntimeException($message, $code);
}
$response = new Response(
curl_getinfo($ch, CURLINFO_HTTP_CODE),
$responseBody
);
curl_close($ch);
return $response;
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace Aplazame\Http;
class Request implements RequestInterface
{
/**
* @var string
*/
private $method;
/**
* @var string
*/
private $uri;
/**
* @var array
*/
private $headers;
/**
* @var string
*/
private $body;
/**
* @param string $method The HTTP method of the request.
* @param string $uri The URI of the request.
* @param array $headers The headers of the request.
* @param string $body The body of the message.
*/
public function __construct($method, $uri, array $headers = array(), $body = '')
{
$this->method = strtoupper($method);
$this->uri = $uri;
$this->headers = $headers;
$this->body = $body;
}
public function getMethod()
{
return $this->method;
}
public function getHeaders()
{
return $this->headers;
}
public function getUri()
{
return $this->uri;
}
public function getBody()
{
return $this->body;
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace Aplazame\Http;
interface RequestInterface
{
/**
* Retrieves the HTTP method of the request.
*
* @return string Returns the request method. The return value must use uppercase letters.
*/
public function getMethod();
/**
* Retrieves all message header values.
*
* The keys represent the header name as it will be sent over the wire, and
* each value is an array of strings associated with the header.
*
* // Represent the headers as a string
* foreach ($message->getHeaders() as $name => $values) {
* echo $name . ": " . implode(", ", $values);
* }
*
* // Emit headers iteratively:
* foreach ($message->getHeaders() as $name => $values) {
* foreach ($values as $value) {
* header(sprintf('%s: %s', $name, $value), false);
* }
* }
*
* While header names are not case-sensitive, getHeaders() will preserve the
* exact case in which headers were originally specified.
*
* @return array Returns an associative array of the message's headers. Each
* key MUST be a header name, and each value MUST be an array of strings
* for that header.
*/
public function getHeaders();
/**
* Retrieves the URI instance.
*
* @link http://tools.ietf.org/html/rfc3986#section-4.3
*
* @return string Returns the URI of the request.
*/
public function getUri();
/**
* Gets the body of the message.
*
* @return string Returns the body of the request.
*/
public function getBody();
}

View File

@@ -0,0 +1,114 @@
<?php
namespace Aplazame\Http;
class Response implements ResponseInterface
{
/** @var array Map of standard HTTP status code/reason phrases */
private static $phrases = array(
100 => 'Continue',
101 => 'Switching Protocols',
102 => 'Processing',
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
207 => 'Multi-status',
208 => 'Already Reported',
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
306 => 'Switch Proxy',
307 => 'Temporary Redirect',
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Time-out',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Large',
415 => 'Unsupported Media Type',
416 => 'Requested range not satisfiable',
417 => 'Expectation Failed',
418 => "'I'm a teapot'",
422 => 'Unprocessable Entity',
423 => 'Locked',
424 => 'Failed Dependency',
425 => 'Unordered Collection',
426 => 'Upgrade Required',
428 => 'Precondition Required',
429 => 'Too Many Requests',
431 => 'Request Header Fields Too Large',
451 => 'Unavailable For Legal Reasons',
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Time-out',
505 => 'HTTP Version not supported',
506 => 'Variant Also Negotiates',
507 => 'Insufficient Storage',
508 => 'Loop Detected',
511 => 'Network Authentication Required',
);
/**
* @var int
*/
private $statusCode;
/**
* @var string
*/
private $reasonPhrase = '';
/**
* @var string
*/
private $body;
/**
* @param int $statusCode
* @param string $body
*/
public function __construct($statusCode, $body)
{
$this->statusCode = $statusCode;
$this->body = $body;
if (isset(self::$phrases[$this->statusCode])) {
$this->reasonPhrase = self::$phrases[$statusCode];
} else {
$this->reasonPhrase = '';
}
}
public function getStatusCode()
{
return $this->statusCode;
}
public function getReasonPhrase()
{
return $this->reasonPhrase;
}
public function getBody()
{
return $this->body;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Aplazame\Http;
/**
* Representation of an outgoing, server-side response.
*/
interface ResponseInterface
{
/**
* Gets the response status code.
*
* The status code is a 3-digit integer result code of the server's attempt
* to understand and satisfy the request.
*
* @return int Status code.
*/
public function getStatusCode();
/**
* Gets the response reason phrase associated with the status code.
*
* Because a reason phrase is not a required element in a response
* status line, the reason phrase value MAY be null. Implementations MAY
* choose to return the default RFC 7231 recommended reason phrase (or those
* listed in the IANA HTTP Status Code Registry) for the response's
* status code.
*
* @link http://tools.ietf.org/html/rfc7231#section-6
* @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
*
* @return string Reason phrase; must return an empty string if none present.
*/
public function getReasonPhrase();
/**
* Gets the body of the message.
*
* @return string
*/
public function getBody();
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Aplazame\Serializer;
use DateTime;
/**
* DateTime Type.
*/
class Date implements \JsonSerializable
{
/**
* @param DateTime $value
*
* @return self
*/
public static function fromDateTime($value)
{
return new self($value->format(DateTime::ISO8601));
}
/**
* @var null|string
*/
public $value;
/**
* @param string $value
*/
public function __construct($value)
{
$this->value = $value;
}
/**
* @return DateTime
*/
public function asDateTime()
{
$dateTime = DateTime::createFromFormat(DateTime::ISO8601, $this->value);
return $dateTime;
}
public function jsonSerialize():mixed
{
return $this->value;
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Aplazame\Serializer;
/**
* Decimal Type.
*/
class Decimal implements \JsonSerializable
{
public static function fromFloat($value)
{
return new self((int) number_format($value, 2, '', ''));
}
/**
* @var null|int
*/
public $value;
/**
* @param int $value
*/
public function __construct($value)
{
$this->value = $value;
}
/**
* @return float
*/
public function asFloat()
{
return $this->value / 100;
}
public function jsonSerialize():mixed
{
return $this->value;
}
}

View File

@@ -0,0 +1,25 @@
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
trigger_error(
$err,
E_USER_ERROR
);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInitd7596b5697fd1b453367d94ecd0f0b70::getLoader();

View File

@@ -0,0 +1,581 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var \Closure(string):void */
private static $includeFile;
/** @var ?string */
private $vendorDir;
// PSR-4
/**
* @var array[]
* @psalm-var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, array<int, string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* @var array[]
* @psalm-var array<string, array<string, string[]>>
*/
private $prefixesPsr0 = array();
/**
* @var array[]
* @psalm-var array<string, string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var string[]
* @psalm-var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var bool[]
* @psalm-var array<string, bool>
*/
private $missingClasses = array();
/** @var ?string */
private $apcuPrefix;
/**
* @var self[]
*/
private static $registeredLoaders = array();
/**
* @param ?string $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
}
/**
* @return string[]
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array[]
* @psalm-return array<string, array<int, string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return array[]
* @psalm-return array<string, string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return array[]
* @psalm-return array<string, string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return string[] Array of classname => path
* @psalm-return array<string, string>
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param string[] $classMap Class to filename map
* @psalm-param array<string, string> $classMap
*
* @return void
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param string[]|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param string[]|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param string[]|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param string[]|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
(self::$includeFile)($file);
return true;
}
return null;
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders indexed by their corresponding vendor directories.
*
* @return self[]
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
private static function initializeIncludeClosure(): void
{
if (self::$includeFile !== null) {
return;
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
*/
self::$includeFile = static function($file) {
include $file;
};
}
}

View File

@@ -0,0 +1,352 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/
class InstalledVersions
{
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints($constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
}
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = require __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
$installed[] = self::$installed;
return $installed;
}
}

View File

@@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,10 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
);

View File

@@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
);

View File

@@ -0,0 +1,10 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Aplazame\\' => array($vendorDir . '/aplazame/aplazame-api-sdk/src'),
);

View File

@@ -0,0 +1,38 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInitd7596b5697fd1b453367d94ecd0f0b70
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInitd7596b5697fd1b453367d94ecd0f0b70', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInitd7596b5697fd1b453367d94ecd0f0b70', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInitd7596b5697fd1b453367d94ecd0f0b70::getInitializer($loader));
$loader->register(true);
return $loader;
}
}

View File

@@ -0,0 +1,36 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInitd7596b5697fd1b453367d94ecd0f0b70
{
public static $prefixLengthsPsr4 = array (
'A' =>
array (
'Aplazame\\' => 9,
),
);
public static $prefixDirsPsr4 = array (
'Aplazame\\' =>
array (
0 => __DIR__ . '/..' . '/aplazame/aplazame-api-sdk/src',
),
);
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInitd7596b5697fd1b453367d94ecd0f0b70::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInitd7596b5697fd1b453367d94ecd0f0b70::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInitd7596b5697fd1b453367d94ecd0f0b70::$classMap;
}, null, ClassLoader::class);
}
}

View File

@@ -0,0 +1,51 @@
{
"packages": [
{
"name": "aplazame/aplazame-api-sdk",
"version": "v0.3.6",
"version_normalized": "0.3.6.0",
"source": {
"type": "git",
"url": "https://github.com/aplazame/php-sdk.git",
"reference": "a8da64706478b1ebb3f6ccfa6677710121846771"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aplazame/php-sdk/zipball/a8da64706478b1ebb3f6ccfa6677710121846771",
"reference": "a8da64706478b1ebb3f6ccfa6677710121846771",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": ">= 5.6.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "1.11.*",
"phpunit/phpunit": "*"
},
"time": "2023-11-28T15:06:00+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Aplazame\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-3.0"
],
"description": "Aplazame API SDK",
"keywords": [
"aplazame"
],
"support": {
"issues": "https://github.com/aplazame/php-sdk/issues",
"source": "https://github.com/aplazame/php-sdk/tree/v0.3.6"
},
"install-path": "../aplazame/aplazame-api-sdk"
}
],
"dev": true,
"dev-package-names": []
}

View File

@@ -0,0 +1,32 @@
<?php return array(
'root' => array(
'name' => '__root__',
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'reference' => NULL,
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev' => true,
),
'versions' => array(
'__root__' => array(
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'reference' => NULL,
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev_requirement' => false,
),
'aplazame/aplazame-api-sdk' => array(
'pretty_version' => 'v0.3.6',
'version' => '0.3.6.0',
'reference' => 'a8da64706478b1ebb3f6ccfa6677710121846771',
'type' => 'library',
'install_path' => __DIR__ . '/../aplazame/aplazame-api-sdk',
'aliases' => array(),
'dev_requirement' => false,
),
),
);

View File

@@ -0,0 +1,26 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 50600)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 5.6.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}