n1coePayDocs Referencia del API

SDK de PHP

n1co/epay llama al API de ePay desde tu servidor PHP, con modelos tipados, excepciones por estado y verificación de webhooks.

  1. Instala n1co/epay con Composer (PHP 8.2+ con ext-curl y ext-json).
  2. Crea el cliente con tu llave secreta y el host de desarrollo.
  3. Crea intents con paymentIntents->create() y verifica webhooks con Webhook::constructEvent().
composer require n1co/epay

Crea el intent en tu endpoint de checkout:

checkout.php
<?php

require __DIR__ . '/vendor/autoload.php';

use N1co\EPay\N1co;

$n1co = new N1co((string) getenv('N1CO_SECRET_KEY'), [
    'base_url' => N1co::DEV_BASE_URL, // https://api-epay.dev.n1co.dev
]);

// El monto sale de tu pedido, nunca del navegador.
$intent = $n1co->paymentIntents->create([
    'amount' => 1250,
    'currency' => 'USD',
    'customer' => [
        'id' => 'cust-1001',
        'name' => 'Ana López',
        'email' => 'ana.lopez@example.com',
        'phone' => '+503 7000-1234',
    ],
    'reference' => 'order-8812',
]);

header('Content-Type: application/json');
echo json_encode(['client_secret' => $intent->client_secret]);

La página confirma el pago como en Cobrar.

Webhooks#

Verifica el cuerpo crudo con tu secreto whsec_…:

Laravel

routes/api.php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use N1co\EPay\Exception\WebhookSignatureException;
use N1co\EPay\Webhook;

Route::post('/webhooks/n1co', function (Request $request) {
    try {
        $event = Webhook::constructEvent(
            $request->getContent(),
            $request->headers->all(),
            (string) config('services.n1co.webhook_secret'),
        );
    } catch (WebhookSignatureException) {
        abort(400);
    }
    if ($event->type === 'payment_intent.succeeded') {
        // Entrega el pedido de $event->data->object; descarta los $event->id repetidos.
    }
    return response()->noContent();
});

Symfony

src/Controller/WebhookController.php
<?php

namespace App\Controller;

use N1co\EPay\Exception\WebhookSignatureException;
use N1co\EPay\Webhook;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

final class WebhookController
{
    public function __construct(
        #[Autowire(env: 'N1CO_WEBHOOK_SECRET')] private string $webhookSecret,
    ) {}

    #[Route('/webhooks/n1co', methods: ['POST'])]
    public function n1co(Request $request): Response
    {
        try {
            $event = Webhook::constructEvent($request->getContent(), $request->headers->all(), $this->webhookSecret);
        } catch (WebhookSignatureException) {
            return new Response('', Response::HTTP_BAD_REQUEST);
        }
        // Procesa $event->type…
        return new Response('', Response::HTTP_NO_CONTENT);
    }
}

PHP sin framework

webhook.php
<?php

require __DIR__ . '/vendor/autoload.php';

use N1co\EPay\Exception\WebhookSignatureException;
use N1co\EPay\Webhook;

try {
    $event = Webhook::constructEvent(
        file_get_contents('php://input'),
        getallheaders(),
        (string) getenv('N1CO_WEBHOOK_SECRET'),
    );
} catch (WebhookSignatureException) {
    http_response_code(400);
    exit;
}
// Procesa $event->type…
http_response_code(204);

Errores#

Cada estado HTTP tiene su excepción, y todas extienden N1coException con code(), status(), details() y requestId(). Decide con code(), nunca con el mensaje.

<?php

use N1co\EPay\Exception\InvalidRequestException;
use N1co\EPay\Exception\N1coException;

try {
    $n1co->paymentIntents->create($params);
} catch (InvalidRequestException $e) {
    error_log(json_encode($e->details())); // field y code
} catch (N1coException $e) {
    error_log($e->code() . ' ' . $e->status() . ' ' . $e->requestId());
}

getCode() es el estado HTTP, no el código del API. Los códigos están en Errores.

Lo que suele fallar#

  • Responder 400 a cualquier fallo. Responde 400 solo con WebhookSignatureException. Si falla tu propio código, responde 5xx y n1co reintenta.
  • El webhook detrás de CSRF. En Laravel, ponlo en routes/api.php o exclúyelo de validateCsrfTokens.
  • Reintentar un POST a ciegas. El SDK nunca reintenta un POST: el API no tiene claves de idempotencia. Tras una ConnectionException, lee el objeto o espera su webhook.
  • Omitir base_url. Sin él, el cliente usa el host de producción, que llega con el lanzamiento a producción.

Las opciones del cliente, los modelos y los reintentos de GET están en el README del paquete.