PHPackages                             dalpras/payment-core - 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-core

ActiveLibrary[Payment Processing](/categories/payments)

dalpras/payment-core
====================

Provider-agnostic payment core with adapters for PayPal, Nexi and future connectors.

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

Since Apr 14Pushed 1mo agoCompare

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

READMEChangelogDependencies (3)Versions (8)Used By (3)

DalPraS Payment Core
====================

[](#dalpras-payment-core)

Provider-agnostic PHP payment orchestration for checkout, completion, capture, refund, cancel and reconciliation.

The package is designed to keep application code independent from provider-specific identifiers while still supporting real payment lifecycles such as:

- Nexi Hosted Payment Page: `orderId` for checkout/completion, `operationId` for capture/refund/cancel
- PayPal Orders v2: order id for checkout/completion, capture id for refund, authorization id for delayed capture/void

Goals
-----

[](#goals)

- Keep business entities decoupled from payment SDKs and provider payloads.
- Persist a normalized `Payment` aggregate and immutable `PaymentOperation` audit log.
- Make browser returns, retries, provider API calls and reconciliation idempotent.
- Let provider packages return normalized metadata once, then let core reuse it automatically.
- Allow application adapters to provide business metadata without knowing provider lifecycle IDs.

Package status
--------------

[](#package-status)

This package contains:

- DTOs and value objects for checkout and provider operations
- `PaymentProviderInterface`
- `PaymentManager`
- provider registry
- payment repository contract with in-memory and Redis implementations
- idempotency contract and in-memory/cache/Redis implementations
- normalized `Payment` aggregate
- immutable `PaymentOperation` audit entries
- basic state machine

Concrete providers live in separate packages:

- `dalpras/payment-paypal`
- `dalpras/payment-nexi`

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

[](#installation)

```
composer require dalpras/payment-core
```

Namespace
---------

[](#namespace)

```
DalPraS\Payment\
```

Core lifecycle
--------------

[](#core-lifecycle)

```
Application adapter
    -> CheckoutRequest with business data and business metadata
PaymentManager::createCheckout()
    -> provider creates order/session
    -> provider returns providerPaymentId, providerToken and normalized metadata
    -> core stores Payment and PaymentOperation
Browser return / webhook / admin action
    -> application calls completeCheckout(), sync(), capture(), refund(), or cancel()
    -> core enriches the request with stored Payment metadata and latest operation metadata
    -> provider resolves the correct provider ID and performs the operation
    -> core stores the new result metadata for future operations

```

Metadata model
--------------

[](#metadata-model)

`Payment::$metadata` is intentionally part of the orchestration model. It contains:

1. **Application metadata** supplied by your adapter.
2. **Provider checkout metadata** returned by providers.
3. **Provider operation metadata** returned by completion/capture/refund/cancel/sync.

Common normalized keys:

KeyMeaning`provider`Provider code such as `nexi` or `paypal``provider_payment_id`Main provider order/payment identifier`order_id`Generic provider order id`operation_id`Generic operation id for providers such as Nexi`capture_id`Generic capture id, used by PayPal refunds`authorization_id`Generic authorization id, used by delayed capture/void`nexi_order_id`Nexi HPP order id`nexi_operation_id`Nexi operation id`paypal_order_id`PayPal order id`paypal_capture_id`PayPal capture id`paypal_authorization_id`PayPal authorization idRequest-level metadata always wins over stored metadata, so applications can still override the selected provider identifier when needed.

Important DTOs
--------------

[](#important-dtos)

### `CheckoutResponse`

[](#checkoutresponse)

Providers return `metadata` here to persist identifiers discovered at checkout creation.

```
new CheckoutResponse(
    status: PaymentStatus::PendingCustomerAction,
    redirectRequired: true,
    redirectUrl: $redirectUrl,
    providerPaymentId: $providerOrderId,
    providerToken: $token,
    raw: $rawProviderResponse,
    metadata: [
        'provider_payment_id' => $providerOrderId,
        'order_id' => $providerOrderId,
    ],
);
```

### `OperationResult`

[](#operationresult)

All operation results carry `transactionIds` and `metadata`. Provider packages should normalize useful IDs here instead of making the application parse `raw`.

```
new RefundResult(
    status: PaymentStatus::Refunded,
    providerPaymentId: $captureOrOperationId,
    transactionIds: [$refundOperationId],
    raw: $rawProviderResponse,
    metadata: [
        'capture_id' => $captureId,
        'refund_id' => $refundOperationId,
    ],
);
```

PaymentManager enrichment
-------------------------

[](#paymentmanager-enrichment)

`PaymentManager` automatically enriches requests before invoking providers:

- `completeCheckout()` receives stored provider order/payment id when the browser return does not include it.
- `refund()` receives the latest stored `capture_id`, `operation_id`, or provider-specific equivalent.
- `cancel()` receives the latest stored `operation_id` or `authorization_id`.
- `capture()` receives the latest stored `authorization_id` or provider-specific equivalent.
- `sync()` receives the stored order/payment id.

This means application code can usually call:

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

Core will merge the stored Nexi `operation_id` before calling the Nexi provider.

Application adapter guidance
----------------------------

[](#application-adapter-guidance)

Application adapters should provide business data, not provider-generated IDs.

Good adapter metadata:

```
metadata: [
    'application' => 'my-shop',
    'local_order_id' => (string) $order->id(),
    'order_number' => $order->number(),
    'payment_uuid' => $paymentReference,
    'description' => 'Order ' . $order->number(),
    'amount_minor' => (string) $order->grandTotalMinor(),
    'amount_decimal' => $order->grandTotalDecimal(),
    'currency' => $order->currencyCode(),
]
```

Provider packages then add provider metadata after API calls.

Redis payment repository
------------------------

[](#redis-payment-repository)

`RedisPaymentRepository` is the recommended production replacement for `InMemoryPaymentRepository` when you need a lightweight cross-request repository for redirect-based providers.

It stores the `Payment` aggregate and related `PaymentOperation` entries in Redis using PHP serialization and a configurable TTL. This lets `PaymentManager` recover provider metadata after the customer returns from Nexi/PayPal:

```
createCheckout() -> RedisPaymentRepository::save() -> provider redirect -> completeCheckout() -> RedisPaymentRepository::get()

```

Example Symfony wiring:

```
DalPraS\Payment\Repository\RedisPaymentRepository:
  class: DalPraS\Payment\Repository\RedisPaymentRepository
  arguments:
    - '@redis'
    - 'payment:repository:'
    - 86400
  public: false

DalPraS\Payment\Contract\PaymentRepositoryInterface:
  alias: DalPraS\Payment\Repository\RedisPaymentRepository
  public: false
```

Use a TTL long enough for abandoned browser sessions and delayed redirects. `86400` seconds, or 24 hours, is a practical default.

Redis should still be treated as active-flow storage. For final accounting, support, reporting and later refunds/cancels, persist important provider metadata in your own durable entity/table, for example `OrderEntity.paymentMetadata`.

Persistence notes
-----------------

[](#persistence-notes)

If you replace `InMemoryPaymentRepository` with a durable database repository, persist at least:

### Payment

[](#payment)

- reference
- merchant reference
- provider code
- intent
- status
- customer snapshot
- line items snapshot
- amount breakdown
- provider payment id
- provider token
- idempotency key
- correlation id
- metadata JSON
- created/updated timestamps

### PaymentOperation

[](#paymentoperation)

- payment reference
- operation type
- status
- provider payment id
- transaction ids JSON
- metadata JSON
- raw payload JSON
- message
- created timestamp

The new metadata and transaction id fields are important. Without them, automatic refund/cancel/capture enrichment will lose provider lifecycle identifiers.

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

[](#basic-usage)

```
use DalPraS\Payment\Idempotency\InMemoryIdempotencyStore;
use DalPraS\Payment\Manager\PaymentManager;
use DalPraS\Payment\Registry\ProviderRegistry;
use DalPraS\Payment\Repository\InMemoryPaymentRepository;
use DalPraS\Payment\Repository\RedisPaymentRepository;

$registry = new ProviderRegistry();
$registry->register($paypalProvider);
$registry->register($nexiProvider);

$manager = new PaymentManager(
    providers: $registry,
    payments: new InMemoryPaymentRepository(), // Replace with RedisPaymentRepository or a DB repository in production.
    idempotency: new InMemoryIdempotencyStore(),
);
```

Production-style repository example:

```
$manager = new PaymentManager(
    providers: $registry,
    payments: new RedisPaymentRepository($redis, 'payment:repository:', 86400),
    idempotency: new InMemoryIdempotencyStore(),
);
```

Testing
-------

[](#testing)

Run syntax checks:

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

Run PHPUnit after installing development dependencies:

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

License
-------

[](#license)

MIT

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance91

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community7

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

Total

7

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-core/health.svg)

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

###  Alternatives

[symfony/symfony

The Symfony PHP framework

31.4k87.2M2.2k](/packages/symfony-symfony)[tempest/framework

The PHP framework that gets out of your way.

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

AWS SDK for PHP - Use Amazon Web Services in your PHP project

6.2k543.5M2.7k](/packages/aws-aws-sdk-php)[google/auth

Google Auth Library for PHP

1.4k294.2M224](/packages/google-auth)[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.5k5.9M754](/packages/sylius-sylius)[shopware/platform

The Shopware e-commerce core

3.4k1.5M3](/packages/shopware-platform)

PHPackages © 2026

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