dalpras / payment-core
Provider-agnostic payment core with adapters for PayPal, Nexi and future connectors.
Requires
- php: ^8.2
- psr/http-message: ^1.1 || ^2.0
Requires (Dev)
- phpunit/phpunit: ^11.0
This package is auto-updated.
Last update: 2026-04-20 09:36:22 UTC
README
A provider-agnostic PHP payment library skeleton designed to replace legacy Omnipay-style integrations with a modern, explicit, extensible architecture.
It is intentionally split into:
- a core package with provider-neutral contracts and value objects
- provider connectors such as PayPal and Nexi implemented separately
- optional framework bridges for Laminas or other frameworks
Goals
- support PayPal, Nexi, and future providers through native connectors
- keep business entities decoupled from payment SDKs
- model money, items, totals, checkout, completion, capture, refund, and sync explicitly
- make retries, callbacks, and reconciliation idempotent
- allow different purchasable objects to be adapted into a common payment model
Package status
This package is a skeleton intended as a strong starting point. It already contains:
- contracts
- DTOs
- enums
- value objects
- repository interfaces and in-memory implementations
- an idempotency contract and in-memory store
- a payment manager
- a provider registry
- a normalized payment aggregate
- a basic state machine
It does not yet contain concrete connector implementations for PayPal or Nexi. Those should live in:
dalpras/payment-paypaldalpras/payment-nexi
Installation
composer require dalpras/payment-core
Namespace
DalPraS\Payment\
High-level architecture
Core concepts
Money: immutable monetary value in minor unitsLineItem: normalized item to buyAmountBreakdown: subtotal, tax, discount, shipping, totalCustomer,Address: snapshots used for checkoutPayment: normalized payment aggregatePaymentOperation: normalized provider interaction logPaymentProviderInterface: provider-neutral connector contractPaymentManager: orchestration servicePurchasableAdapterInterface: adapts domain objects into payment inputsPaymentCalculatorInterface: computes totals from adapted itemsPaymentRepositoryInterface: persists payments and operationsIdempotencyStoreInterface: protects external operations from duplication
Suggested split into packages
dalpras/payment-coredalpras/payment-paypaldalpras/payment-nexidalpras/payment-laminas
Example flow
use DalPraS\Payment\Manager\PaymentManager; use DalPraS\Payment\Registry\ProviderRegistry; use DalPraS\Payment\Repository\InMemoryPaymentRepository; use DalPraS\Payment\Idempotency\InMemoryIdempotencyStore; $manager = new PaymentManager( new ProviderRegistry(), new InMemoryPaymentRepository(), new InMemoryIdempotencyStore(), );
A real application would:
- adapt a domain object through
PurchasableAdapterInterface - calculate totals through
PaymentCalculatorInterface - create a
CheckoutRequest - call
PaymentManager::createCheckout() - store the returned redirect URL and provider payment ID
- handle browser return through
completeCheckout() - handle webhooks separately through connector-specific parsing and verification
Core interfaces
PaymentProviderInterface
interface PaymentProviderInterface { public function code(): string; public function createCheckout(CheckoutRequest $request): CheckoutResponse; public function completeCheckout(CompletionRequest $request): CompletionResult; public function authorize(AuthorizeRequest $request): AuthorizationResult; public function capture(CaptureRequest $request): CaptureResult; public function cancel(CancelRequest $request): CancelResult; public function refund(RefundRequest $request): RefundResult; public function sync(SyncRequest $request): SyncResult; public function parseWebhook(ServerRequestInterface $request): WebhookEvent; public function verifyWebhook(WebhookEvent $event): VerificationResult; }
Adapting business objects
Keep domain entities clean. Prefer adapters instead of making domain models implement payment interfaces directly.
Example idea:
final class VatCrmOrderAdapter implements PurchasableAdapterInterface { public function supports(object $subject): bool { return $subject instanceof VatCrmOrderEntity; } public function toPaymentDraft(object $subject): PaymentDraft { // map domain order to normalized customer, items, totals, metadata } }
State model
Normalized payment statuses:
draftpending_redirectpending_customer_actionauthorizedcapturedpartially_capturedfailedcancelledrefundedpartially_refundedexpiredunknown
What to build next
dalpras/payment-paypal
Implement PaymentProviderInterface using PayPal Orders v2.
dalpras/payment-nexi
Implement PaymentProviderInterface using Nexi official APIs / SDK.
dalpras/payment-laminas
Add controller helpers, URL builders, dependency configuration, and HTTP entrypoints.
Testing
This skeleton includes in-memory implementations so you can start writing tests before choosing persistence and provider SDKs.
License
MIT