SDK de PHP
Usa el SDK oficial de PHP de OrcaRail para aceptar pagos cripto desde cualquier aplicación PHP. El SDK se encarga de la autenticación Basic, del formato JSON de peticiones y respuestas y de la verificación de las firmas de los webhooks.
El cliente sigue el estilo moderno de Stripe PHP: un único OrcaRailClient, propiedades de servicio creadas bajo demanda, arrays asociativos para los parámetros de las peticiones y objetos de respuesta compatibles con versiones futuras.
Requisitos
- PHP 8.1 o superior
ext-curlyext-json
Instalación
Instala el paquete con Composer:
composer require orcarail/orcarail-php
El paquete se llama orcarail/orcarail-php y carga automáticamente el namespace OrcaRail\ (PSR-4). Para instalarlo desde una copia del repositorio, añade un repositorio path o vcs a tu composer.json:
{
"repositories": [{ "type": "path", "url": "../orcarail/sdks-php" }],
"require": { "orcarail/orcarail-php": "*" }
}
Configuración
Crea un cliente con tu clave y tu secreto de API. Opcionalmente, define una URL base personalizada (por ejemplo, para el desarrollo local) y tiempos de espera.
<?php
require __DIR__ . '/vendor/autoload.php';
use OrcaRail\OrcaRailClient;
$orcarail = new OrcaRailClient([
'api_key' => getenv('ORCARAIL_API_KEY'), // e.g. "ak_live_xxx"
'api_secret' => getenv('ORCARAIL_API_SECRET'), // e.g. "sk_live_xxx"
'base_url' => 'https://api.orcarail.com/api/v1', // optional; default
'timeout' => 30000, // optional; milliseconds, default 30000
'connect_timeout' => 10000, // optional; milliseconds, default 10000
]);
Para el desarrollo local, apunta base_url a tu API:
$orcarail = new OrcaRailClient([
'api_key' => getenv('ORCARAIL_API_KEY'),
'api_secret' => getenv('ORCARAIL_API_SECRET'),
'base_url' => 'http://127.0.0.1:3000/api/v1',
'timeout' => 65000, // /complete can take up to ~65s
]);
Los servicios se crean en el primer acceso y se reutilizan: $orcarail->paymentIntents, $orcarail->subscriptions, $orcarail->pay, $orcarail->rates, $orcarail->products y $orcarail->prices.
Objetos de respuesta
Cada objeto JSON de una respuesta se convierte en un OrcaRail\OrcaRailObject. Los campos desconocidos se conservan, así que los campos nuevos de la API se pueden leer sin actualizar el SDK. Los objetos admiten acceso por propiedad, acceso como array, iteración, count(), toArray() y json_encode().
$intent = $orcarail->paymentIntents->retrieve($intentId);
echo $intent->status; // property access
echo $intent['client_secret']; // array access
echo $intent->payment_link->link; // nested object
$asArray = $intent->toArray(); // plain PHP array
Las claves que faltan devuelven null en lugar de generar un aviso, así que compruébalas antes de usarlas:
if ($intent->payment_link !== null) {
header('Location: ' . $intent->payment_link->link);
}
Live frente a sandbox
Las claves de una organización sandbox empiezan por ak_test_ (testnets, sin fondos reales). $orcarail->paymentIntents->simulate($id) completa un pago sandbox sin wallet. Los eventos de webhook llevan livemode ($event->livemode === false en los eventos sandbox).
Comisión (reparte y obtén tu parte)
Las claves de API pueden tener un porcentaje de comisión opcional (0–100). Defínelo en Panel → Claves de API o mediante PATCH /api/v1/api-keys/:id con commissionPercent. Los pagos creados con esa clave incluyen la comisión en la cotización (visible para el pagador), y el importe de la comisión se envía a la dirección de tu configuración de retiros cuando se retiran los fondos.
Payment Intents
Crear un Payment Intent
Crea un payment intent con un price_id del catálogo o con todos los parámetros de importe directo (amount, currency, tokenId, networkId). Las dos formas son excluyentes; el SDK rechaza una mezcla antes de enviar la petición.
$intent = $orcarail->paymentIntents->create([
'amount' => '100.00',
'currency' => 'usd',
'tokenId' => '550e8400-e29b-41d4-a716-446655440000', // token UUID
'networkId' => '3f1a0c1e-9d7b-4f8a-9d21-4a2f6b7c8d90', // network UUID
'return_url' => 'https://yourapp.com/success',
'cancel_url' => 'https://yourapp.com/cancel',
'description' => 'Order #12345',
'metadata' => ['order_id' => '12345'],
// optional: override where funds are withdrawn, keyed by chain family
'withdrawal_addresses' => ['evm' => '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb'],
]);
echo $intent->id; // e.g. "550e8400-e29b-41d4-a716-446655440000"
echo $intent->client_secret;
echo $intent->payment_link?->link;
payment_method_types vale ['crypto'] por defecto si lo omites. Usa Pagos: listar redes y Pagos: listar tokens por red para obtener los UUID de red y de token.
Con un precio del catálogo, pasa solo price_id:
$intent = $orcarail->paymentIntents->create([
'price_id' => $priceId,
'return_url' => 'https://yourapp.com/success',
]);
Confirmar y redirigir al pago
Confirma el intent para obtener la URL de pago alojada y redirige al cliente. La confirmación se autentica con tu clave de API y verifica el client secret.
$confirmed = $orcarail->paymentIntents->confirm($intent->id, [
'client_secret' => $intent->client_secret,
'return_url' => 'https://yourapp.com/success',
]);
$payUrl = $confirmed->pay_url ?? $confirmed->next_action?->redirect_to_url?->url;
header('Location: ' . $payUrl);
Obtener un Payment Intent
$intent = $orcarail->paymentIntents->retrieve('550e8400-e29b-41d4-a716-446655440000');
echo $intent->status;
Actualizar un Payment Intent
Actualiza el importe, los metadatos, las URLs o las direcciones de retiro antes de que pague el cliente:
$updated = $orcarail->paymentIntents->update('550e8400-e29b-41d4-a716-446655440000', [
'amount' => '150.00',
'metadata' => ['order_id' => '12345'],
'withdrawal_addresses' => ['evm' => '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb'],
]);
Cancelar un Payment Intent
$canceled = $orcarail->paymentIntents->cancel('550e8400-e29b-41d4-a716-446655440000');
Completar un Payment Intent
Cuando el cliente llegue a tu URL de éxito/retorno, marca el intent como en procesamiento (esto lanza el webhook payment_intent.processing):
$completed = $orcarail->paymentIntents->complete('550e8400-e29b-41d4-a716-446655440000');
Pago por slug
Si usas enlaces de pago con slug, lee los datos de pago y cancela por slug:
$pay = $orcarail->pay->retrieve('pl_abc123'); // get() is an alias
$orcarail->pay->cancel('pl_abc123');
Suscripciones
Las rutas de la colección de suscripciones deducen la organización de la clave de API autenticada. Crea con price_id o con todos los parámetros directos (amount, currency, token_id, network_id, interval); fíjate en que aquí los campos van en snake_case, como en el contrato REST.
$subscription = $orcarail->subscriptions->create([
'description' => 'Monthly Pro Plan',
'amount' => '10.00',
'currency' => 'usd',
'token_id' => $tokenId,
'network_id' => $networkId,
'interval' => 'month',
'interval_count' => 1,
'collection_method' => 'send_payment_link',
'total_cycles' => 12,
'payer_email' => '[email protected]',
'metadata' => ['plan_id' => 'pro_monthly'],
]);
echo $subscription->id;
echo $subscription->current_period_end;
echo $subscription->latest_payment_link?->link;
Listar, actualizar, cancelar y reanudar:
$page = $orcarail->subscriptions->all([
'status' => 'active',
'limit' => 20,
'current_period_end' => ['lte' => '2026-12-31T00:00:00.000Z'], // sent as current_period_end[lte]
]);
foreach ($page->data as $sub) {
echo $sub->id . ' ' . $sub->description . PHP_EOL;
}
echo $page->has_more ? 'more pages' : 'last page';
$orcarail->subscriptions->update($subscription->id, ['cancel_at_period_end' => true]);
$orcarail->subscriptions->cancel($subscription->id, [
'cancellation_details' => ['comment' => 'Switching to annual', 'feedback' => 'other'],
]);
$orcarail->subscriptions->resume($subscription->id);
$links = $orcarail->subscriptions->listPaymentLinks($subscription->id, ['limit' => 10]);
El método es all() en lugar de list() porque list es una palabra reservada de PHP.
Las llamadas de cobro automático de cara al pagador (approve-auto-charge, revoke-auto-charge, set-collection-method) no forman parte del SDK: llámalas por HTTP sin clave de API desde tu interfaz de pago. Consulta Cobro automático.
Catálogo: productos y precios
Las rutas del catálogo dependen de la organización, así que pasa el UUID de la organización como primer argumento.
$products = $orcarail->products->all($organizationId);
$product = $orcarail->products->create($organizationId, [
'name' => 'Pro Plan',
'description' => 'Monthly access',
'active' => true,
]);
$prices = $orcarail->prices->all($organizationId, ['active' => true]);
$recurring = $orcarail->prices->listActiveRecurring($organizationId);
// Find or create a one-time price for a fixed amount (idempotent by amount + currency + product name)
$price = $orcarail->prices->ensureOneTime($organizationId, [
'amount' => '25.00',
'currencyCode' => 'usd',
'tokenId' => $tokenId,
'networkId' => $networkId,
'productName' => 'One-time checkout',
]);
$intent = $orcarail->paymentIntents->create([
'price_id' => $price->id,
'return_url' => 'https://yourapp.com/success',
]);
products->all() y prices->all() desenvuelven el envoltorio de lista de la API y devuelven arrays de OrcaRailObject.
Tipos de cambio
$quote = $orcarail->rates->getFiatQuote(['amount' => '100.00', 'currency' => 'eur']);
echo $quote->amountUsd;
$currencies = $orcarail->rates->getCurrencies(['active' => true]);
foreach ($currencies as $currency) {
echo $currency->code . PHP_EOL;
}
Consulta la API de precios para los campos de respuesta.
Webhooks
Los helpers de webhooks son estáticos y no necesitan cliente, porque la verificación solo requiere el cuerpo en bruto, la cabecera x-webhook-signature y tu secreto de webhook.
Verificar una firma
use OrcaRail\WebhookSignature;
$payload = file_get_contents('php://input'); // raw body, never a re-encoded array
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
if (!WebhookSignature::verifyHeader($payload, $signature, $webhookSecret)) {
http_response_code(400);
echo 'Invalid signature';
exit;
}
Construir un evento (verificar y analizar)
use OrcaRail\Enum\WebhookEventType;
use OrcaRail\Exception\SignatureVerificationException;
use OrcaRail\Webhook;
try {
$event = Webhook::constructEvent(
file_get_contents('php://input'),
$_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '',
getenv('ORCARAIL_WEBHOOK_SECRET'),
);
} catch (SignatureVerificationException $exception) {
http_response_code(400);
exit;
}
$object = $event->data->object;
$type = WebhookEventType::tryFrom((string) $event->type);
match ($type) {
WebhookEventType::PaymentIntentCompleted => fulfilOrder($object->id),
WebhookEventType::PaymentIntentProcessing => markProcessing($object->id),
WebhookEventType::PaymentIntentCanceled => releaseOrder($object->id),
WebhookEventType::SubscriptionPaymentLinkPaid => creditCycle($object->id),
// tryFrom() returns null for event types this SDK version does not know
default => null,
};
http_response_code(200);
echo json_encode(['received' => true]);
constructEvent() lanza SignatureVerificationException si la firma no es válida, si el JSON está mal formado o si el cuerpo no es un objeto JSON. Responde siempre 200 solo después de haber aceptado el evento.
Ejemplo con Laravel
Lee el cuerpo de la petición en bruto: un array ya analizado no se puede verificar.
use Illuminate\Http\Request;
use OrcaRail\Exception\SignatureVerificationException;
use OrcaRail\Webhook;
Route::post('/webhooks/orcarail', function (Request $request) {
try {
$event = Webhook::constructEvent(
$request->getContent(),
$request->header('x-webhook-signature', ''),
config('services.orcarail.webhook_secret'),
);
} catch (SignatureVerificationException) {
return response('Invalid signature', 400);
}
// Dispatch a job keyed by $event->data->object->id, then acknowledge.
return response()->json(['received' => true]);
});
Excluye la ruta de la verificación CSRF y asegúrate de que ningún middleware reescriba el cuerpo antes de la verificación.
Ejemplo con Symfony
use OrcaRail\Exception\SignatureVerificationException;
use OrcaRail\Webhook;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class OrcaRailWebhookController
{
#[Route('/webhooks/orcarail', methods: ['POST'])]
public function __invoke(Request $request): Response
{
try {
$event = Webhook::constructEvent(
$request->getContent(),
$request->headers->get('x-webhook-signature', ''),
$_ENV['ORCARAIL_WEBHOOK_SECRET'],
);
} catch (SignatureVerificationException) {
return new Response('Invalid signature', Response::HTTP_BAD_REQUEST);
}
return new JsonResponse(['received' => true]);
}
}
Gestión de errores
El SDK lanza excepciones tipadas que extienden OrcaRail\Exception\OrcaRailException (que a su vez es una RuntimeException).
use OrcaRail\Exception\ApiException;
use OrcaRail\Exception\AuthenticationException;
use OrcaRail\Exception\OrcaRailException;
use OrcaRail\Exception\TransportException;
try {
$intent = $orcarail->paymentIntents->create([
'price_id' => $priceId,
'return_url' => 'https://yourapp.com/success',
]);
} catch (AuthenticationException $exception) {
// 401 — invalid API key or secret
error_log($exception->getMessage());
} catch (ApiException $exception) {
// Any other non-2xx response
error_log($exception->statusCode . ' ' . $exception->type . ' ' . $exception->getMessage());
error_log(json_encode($exception->request)); // method and URL of the failed call
} catch (TransportException $exception) {
// Network failure, timeout, or unparseable response
error_log($exception->getMessage());
} catch (OrcaRailException $exception) {
error_log($exception->getMessage());
}
Las combinaciones de parámetros no válidas (por ejemplo price_id junto con amount) lanzan InvalidArgumentException antes de hacer ninguna petición HTTP.
Pruebas sin red
Pasa tu propio transporte como http_client para registrar las peticiones en los tests. Sirve cualquier clase que implemente OrcaRail\HttpClient\ClientInterface, y no hace falta clave de API.
use OrcaRail\HttpClient\ClientInterface;
use OrcaRail\OrcaRailClient;
$recorder = new class implements ClientInterface {
/** @var list<array{string, string, ?array<string, mixed>, bool}> */
public array $calls = [];
public function request(string $method, string $path, ?array $body = null, bool $requireAuth = true): mixed
{
$this->calls[] = [$method, $path, $body, $requireAuth];
return ['id' => 'pi_test', 'status' => 'requires_confirmation'];
}
};
$orcarail = new OrcaRailClient(['http_client' => $recorder]);
$intent = $orcarail->paymentIntents->create([
'price_id' => 'price_test',
'return_url' => 'https://example.com/return',
]);
// $recorder->calls[0] === ['POST', 'payment_intents', [...], true]
Consulta también
- Referencia de la API del SDK de PHP: todas las clases, servicios y métodos
- SDK de Node.js: el SDK equivalente para JavaScript y TypeScript
- API de Payment Intents: referencia REST
- Webhooks: eventos y configuración
- Primeros pasos: cuenta y claves de API