PHPackages                             dalpras/payment-nexi - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Payment Processing](/categories/payments)
4. /
5. dalpras/payment-nexi

ActiveLibrary[Payment Processing](/categories/payments)

dalpras/payment-nexi
====================

Nexi XPay connector for dalpras/payment-core.

v0.6.0(2mo ago)02MITPHPPHP ^8.2

Since Apr 21Pushed 1mo agoCompare

[ Source](https://github.com/dalpras/payment-nexi)[ Packagist](https://packagist.org/packages/dalpras/payment-nexi)[ RSS](/packages/dalpras-payment-nexi/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (5)Versions (7)Used By (0)

dalpras/payment-nexi
====================

[](#dalpraspayment-nexi)

Nexi XPay connector for `dalpras/payment-core`, focused on Hosted Payment Page checkout and operation-based post-payment actions.

Supported flow:

- create a Hosted Payment Page order with `POST /orders/hpp`
- redirect the buyer to Nexi `hostedPage`
- complete the browser return by querying `GET /orders/{orderId}`
- persist the Nexi `operationId` returned by order lookup
- capture, refund and cancel by calling Nexi operation endpoints
- sync order state
- parse basic notifications

Installation
------------

[](#installation)

```
composer require dalpras/payment-nexi
```

Dependencies
------------

[](#dependencies)

- `dalpras/payment-core`
- `psr/http-client`
- `psr/http-factory`
- `psr/http-message`

Bring your own PSR-18 client and PSR-17 factories.

Basic usage
-----------

[](#basic-usage)

```
use DalPraS\Payment\Nexi\Config\NexiConfig;
use DalPraS\Payment\Nexi\Http\NexiHttpClient;
use DalPraS\Payment\Nexi\Mapper\NexiOrderMapper;
use DalPraS\Payment\Nexi\Provider\NexiProvider;

$config = new NexiConfig(
    apiKey: 'sandbox-api-key',
    sandbox: true,
    defaultLanguage: 'ita',
    defaultCaptureType: 'IMPLICIT',
);

$httpClient = new NexiHttpClient(
    config: $config,
    httpClient: $psr18Client,
    requestFactory: $requestFactory,
    streamFactory: $streamFactory,
);

$provider = new NexiProvider(
    config: $config,
    httpClient: $httpClient,
    mapper: new NexiOrderMapper(),
);
```

Register the provider in `PaymentManager` through the core `ProviderRegistry`.

Checkout payload mapping
------------------------

[](#checkout-payload-mapping)

`CheckoutRequest` maps to Nexi HPP as:

```
[
    'order' => [
        'orderId' => $request->merchantReference,
        'amount' => (string) $request->amounts->grandTotal->minorAmount(),
        'currency' => $request->amounts->grandTotal->currency()->value,
        'customerInfo' => [...],
        'billingAddress' => [...],
    ],
    'paymentSession' => [
        'actionType' => 'PAY',
        'amount' => '5000',
        'language' => 'ita',
        'captureType' => 'IMPLICIT',
        'resultUrl' => $returnUrl,
        'cancelUrl' => $cancelUrl,
        'notificationUrl' => $webhookUrl,
    ],
]
```

Intent mapping:

Core intentNexi `actionType`Nexi `captureType``sale``PAY``IMPLICIT``authorize``PREAUTH``EXPLICIT``capture_later``PREAUTH``EXPLICIT`The `resultUrl` should include enough application context to reload the local payment, for example your payment UUID. When the provider is used through `PaymentManager`, the stored payment metadata is also used to enrich completion, refund, cancel, capture and sync requests.

Provider options
----------------

[](#provider-options)

Recommended Nexi options for a sale flow:

```
providerOptions: [
    'language' => 'ita',
    'capture_type' => 'IMPLICIT',
]
```

### Force capture after authorization

[](#force-capture-after-authorization)

Some Nexi accounts/terminals can return an `AUTHORIZATION` operation even when the checkout was created as a sale with `captureType = IMPLICIT`. Do not map that authorization to `Captured` manually. If you want the library to perform a real capture immediately after completion, enable the core force-capture policy:

```
providerOptions: [
    'language' => 'ita',
    'capture_type' => 'IMPLICIT',
    'force_capture_after_authorization' => true,
    'capture_description' => 'Forced capture after Nexi authorization',
]
```

Also provide amount and currency in checkout metadata so the forced capture can build the Nexi capture payload:

```
metadata: [
    'amount_minor' => (string) $total->minorAmount(),
    'currency' => $currency->value,
]
```

When completion returns `Authorized` and `force_capture_after_authorization` is true, `payment-core` calls `capture()` with the extracted Nexi `operationId`. The final completion result becomes the capture result when capture succeeds.

Idempotency keys and correlation ids
------------------------------------

[](#idempotency-keys-and-correlation-ids)

Nexi operation APIs require UUID-formatted headers for provider requests, especially:

- `Idempotency-Key`
- `Correlation-Id`

This provider automatically generates a valid UUID `Correlation-Id` when none is provided or when the candidate value is not UUID-formatted. You may safely pass `correlationId: null` in `CheckoutRequest` and `CompletionRequest`.

Idempotency is different: the idempotency key is a business retry key and must be stable for the same logical operation. For Nexi, it must also be UUID-formatted because it is forwarded as the `Idempotency-Key` header on POST operation endpoints.

Do not use keys such as:

```
$paymentReference . ':capture';
$paymentReference . ':cancel';
$paymentReference . ':refund';
```

Those are stable, but they are not UUID-formatted and Nexi will reject them.

Use `ramsey/uuid` UUID v5 when you need a deterministic key:

```
use Ramsey\Uuid\Uuid;

$checkoutIdempotencyKey = Uuid::uuid5(
    Uuid::NAMESPACE_URL,
    $paymentReference . ':checkout'
)->toString();

$completionIdempotencyKey = Uuid::uuid5(
    Uuid::NAMESPACE_URL,
    $paymentReference . ':complete'
)->toString();

$captureIdempotencyKey = Uuid::uuid5(
    Uuid::NAMESPACE_URL,
    $paymentReference . ':capture'
)->toString();

$cancelIdempotencyKey = Uuid::uuid5(
    Uuid::NAMESPACE_URL,
    $paymentReference . ':cancel'
)->toString();

$refundIdempotencyKey = Uuid::uuid5(
    Uuid::NAMESPACE_URL,
    $paymentReference . ':refund:' . $refundId
)->toString();
```

For a one-time checkout key, UUID v4 is also valid:

```
use Ramsey\Uuid\Uuid;

$idempotencyKey = Uuid::uuid4()->toString();
```

If you enable `force_capture_after_authorization`, make sure your `payment-core` version generates a UUID-formatted idempotency key for the internal forced capture. A deterministic UUID v5 based on `$paymentReference . ':capture'` is recommended.

Example checkout request
------------------------

[](#example-checkout-request)

```
use Ramsey\Uuid\Uuid;

$providerOptions = [];

if ($providerCode === 'nexi') {
    $providerOptions = [
        'language' => 'ita',
        'capture_type' => 'IMPLICIT',
        'force_capture_after_authorization' => true,
        'capture_description' => sprintf('Forced capture for order %s', $order->getId()),
    ];
}

$request = new CheckoutRequest(
    providerCode: $providerCode,
    paymentReference: $paymentReference,
    merchantReference: 'order-' . $order->getId(),
    customer: $customer,
    items: $items,
    amounts: $amounts,
    returnUrl: $returnUrl,
    cancelUrl: $cancelUrl,
    webhookUrl: $webhookUrl,
    intent: PaymentIntent::Sale,
    locale: 'it-IT',
    idempotencyKey: Uuid::uuid5(Uuid::NAMESPACE_URL, $paymentReference . ':checkout')->toString(),
    correlationId: null,
    metadata: [
        'orderId' => $order->getId(),
        'amount_minor' => (string) $order->getAmountMoney()->minorAmount(),
        'currency' => $order->getCurrency()->value,
    ],
    providerOptions: $providerOptions,
);
```

Metadata returned by this provider
----------------------------------

[](#metadata-returned-by-this-provider)

### Checkout creation

[](#checkout-creation)

At checkout creation time, the actionable provider reference is the Nexi HPP order id.

```
[
    'provider' => 'nexi',
    'provider_payment_id' => $merchantReference,
    'order_id' => $merchantReference,
    'nexi_order_id' => $merchantReference,
    'nexi_security_token' => $securityToken,
    'amount_minor' => '5000',
    'currency' => 'EUR',
]
```

### Completion / sync

[](#completion--sync)

After `GET /orders/{orderId}`, the provider extracts operation ids from the order payload. At this point the actionable provider reference becomes the Nexi `operationId`, because capture/refund/cancel endpoints use operation ids, not the HPP order id.

```
[
    'provider' => 'nexi',
    'provider_payment_id' => $mainOperationId,
    'order_id' => $orderId,
    'nexi_order_id' => $orderId,
    'operation_id' => $mainOperationId,
    'nexi_operation_id' => $mainOperationId,
    'nexi_transaction_ids' => [$operationId1, $operationId2],
]
```

`CompletionResult::$providerPaymentId` and `SyncResult::$providerPaymentId` are also set to `$mainOperationId` when available. The original HPP order id remains available as `metadata['nexi_order_id']`.

### Capture

[](#capture)

```
[
    'provider' => 'nexi',
    'operation_id' => $newOperationIdOrSourceOperationId,
    'nexi_operation_id' => $newOperationIdOrSourceOperationId,
    'nexi_capture_operation_id' => $newOperationId,
    'nexi_source_operation_id' => $sourceOperationId,
]
```

### Refund

[](#refund)

```
[
    'provider' => 'nexi',
    'operation_id' => $sourceOperationId,
    'nexi_operation_id' => $sourceOperationId,
    'nexi_refund_operation_id' => $refundOperationId,
]
```

### Cancel

[](#cancel)

```
[
    'provider' => 'nexi',
    'operation_id' => $sourceOperationId,
    'nexi_operation_id' => $sourceOperationId,
    'nexi_cancel_operation_id' => $cancelOperationId,
]
```

Completion
----------

[](#completion)

`completeCheckout()` resolves the Nexi order id from, in order:

1. `queryParams['orderId']`
2. `queryParams['order_id']`
3. `queryParams['codTrans']`
4. `bodyParams['orderId']`
5. `bodyParams['order_id']`
6. `bodyParams['codTrans']`
7. `expectedProviderPaymentId`
8. `metadata['nexi_order_id']`
9. `metadata['order_id']`

When used through `PaymentManager`, `expectedProviderPaymentId` and metadata are normally filled automatically from the stored `Payment`.

Capture, refund and cancel
--------------------------

[](#capture-refund-and-cancel)

Nexi post-payment actions use operation ids, not the HPP order id.

The provider resolves the operation id from:

1. `metadata['operation_id']`
2. `metadata['nexi_operation_id']`
3. `metadata['nexi_capture_operation_id']`
4. `metadata['nexi_authorization_operation_id']`
5. `providerPaymentId`

When used through `PaymentManager`, this metadata is normally persisted after completion/sync and re-injected automatically.

### Capture request metadata

[](#capture-request-metadata)

```
$result = $paymentManager->capture(new CaptureRequest(
    providerCode: 'nexi',
    paymentReference: $paymentReference,
    providerPaymentId: null,
    idempotencyKey: Uuid::uuid5(Uuid::NAMESPACE_URL, $paymentReference . ':capture')->toString(),
    metadata: [
        'amount_minor' => '5000',
        'currency' => 'EUR',
        'description' => 'Capture authorized Nexi payment',
    ],
));
```

### Refund request metadata

[](#refund-request-metadata)

```
$result = $paymentManager->refund(new RefundRequest(
    providerCode: 'nexi',
    paymentReference: $paymentReference,
    providerPaymentId: null,
    idempotencyKey: Uuid::uuid5(Uuid::NAMESPACE_URL, $paymentReference . ':refund:' . $refundId)->toString(),
    metadata: [
        'amount_minor' => '5000',
        'currency' => 'EUR',
        'description' => 'Customer refund',
    ],
));
```

### Cancel request metadata

[](#cancel-request-metadata)

```
$result = $paymentManager->cancel(new CancelRequest(
    providerCode: 'nexi',
    paymentReference: $paymentReference,
    providerPaymentId: null,
    idempotencyKey: Uuid::uuid5(Uuid::NAMESPACE_URL, $paymentReference . ':cancel')->toString(),
    metadata: [
        'description' => 'Cancel accounting operation',
    ],
));
```

The cancel payload intentionally sends only `description`; the operation id is part of the URL.

Persistent state
----------------

[](#persistent-state)

For redirect-based payments, use a persistent or cache-backed `PaymentRepositoryInterface` implementation, such as a Redis/Symfony Cache repository. Do not use an in-memory repository for production redirects because the payment metadata would be lost between checkout creation and completion.

Applications should also persist the final provider metadata in their own order/payment table, for example:

```
$order
    ->setPaymentStatus($completionResult->status)
    ->setProviderPaymentId($completionResult->providerPaymentId)
    ->mergePaymentMetadata($completionResult->metadata);
```

For Nexi, after completion this normally means:

```
$order->getProviderPaymentId(); // Nexi operationId
$order->getPaymentMetadata()['nexi_order_id']; // Original HPP order id
$order->getPaymentMetadata()['operation_id']; // Nexi operation id
```

Notification verification
-------------------------

[](#notification-verification)

The current implementation provides a basic shared-secret/security-token comparison. Production systems should confirm the exact Nexi notification signing/verification strategy used by the merchant account and add reconciliation through `sync()`.

Testing
-------

[](#testing)

```
composer install
vendor/bin/phpunit
```

Syntax check:

```
find src tests -name '*.php' -print0 | xargs -0 -n1 php -l
```

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance90

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity41

Maturing project, gaining track record

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~6 days

Recently: every ~0 days

Total

6

Last Release

64d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/f4ad7c52ee1f0a8936e8bb6bcc95f15d4f55ce63233a0fa629da25bba024bd97?d=identicon)[dalpras](/maintainers/dalpras)

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/dalpras-payment-nexi/health.svg)

```
[![Health](https://phpackages.com/badges/dalpras-payment-nexi/health.svg)](https://phpackages.com/packages/dalpras-payment-nexi)
```

###  Alternatives

[tempest/framework

The PHP framework that gets out of your way.

2.2k34.4k16](/packages/tempest-framework)[flow-php/flow

PHP ETL - Extract Transform Load - Data processing framework

85036.3k](/packages/flow-php-flow)[cakephp/cakephp

The CakePHP framework

8.8k19.5M1.8k](/packages/cakephp-cakephp)[telnyx/telnyx-php

Official Telnyx PHP SDK — APIs for Voice, SMS, MMS, WhatsApp, Fax, SIP Trunking, Wireless IoT, Call Control, and more. Build global communications on Telnyx's private carrier-grade network.

36789.4k2](/packages/telnyx-telnyx-php)[typo3/cms

TYPO3 CMS is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.

1.2k1.9M122](/packages/typo3-cms)[chargebee/chargebee-php

ChargeBee API client implementation for PHP

758.5M9](/packages/chargebee-chargebee-php)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
