PHPackages                             ylynfatt/powertranz-php-sdk - 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. ylynfatt/powertranz-php-sdk

ActiveLibrary[Payment Processing](/categories/payments)

ylynfatt/powertranz-php-sdk
===========================

Unofficial PHP SDK for the PowerTranz payment gateway — 3DS, tokenisation, HPP, recurring billing

v0.1.1(yesterday)00MITPHPPHP &gt;=8.1

Since Aug 16Pushed yesterdayCompare

[ Source](https://github.com/ylynfatt/powertranz-php-sdk)[ Packagist](https://packagist.org/packages/ylynfatt/powertranz-php-sdk)[ RSS](/packages/ylynfatt-powertranz-php-sdk/feed)WikiDiscussions main Synced today

READMEChangelog (2)Dependencies (12)Versions (3)Used By (0)

PowerTranz PHP SDK
==================

[](#powertranz-php-sdk)

Unofficial PHP SDK for the [PowerTranz](https://powertranz.com/) payment gateway — SPI transactions, 3-D Secure 2.x, tokenisation, and Hosted Payment Pages.

[![PHP](https://camo.githubusercontent.com/69ada8118f91b7cbf415af2f0d9f7a21ad3fe896d8dad3109d8b3d7c6c275941/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d253345253344382e312d373737626234)](composer.json)[![License](https://camo.githubusercontent.com/b8cadaa967891081f8f165695470689986c028821dd8a040132f6e661795dc0d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c7565)](LICENSE)

> This is an independent, community-maintained SDK. It is not affiliated with, sanctioned by, or supported by PowerTranz. For official support, contact PowerTranz directly.

Requirements
------------

[](#requirements)

- PHP 8.1 or higher
- `ext-curl`

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

[](#installation)

```
composer require ylynfatt/powertranz-php-sdk
```

The SDK ships with a built-in cURL client, so no additional HTTP dependency is required. To use your own PSR-18 client instead, see [Custom HTTP client](#custom-http-client).

While the SDK is on a `0.x` line the public API may still change without a major version bump, so Composer's default caret constraint resolves to `0.1.*` only. Pin an exact version if you need it held still.

Quick start
-----------

[](#quick-start)

```
use Brick\Money\Money;
use PowerTranz\PowerTranzClient;
use PowerTranz\Model\Request\SaleRequest;
use PowerTranz\Model\Request\Parts\CardSource;
use PowerTranz\Model\Response\ThreeDSecureChallenge;

$client = new PowerTranzClient('merchant-id', 'password');

$result = $client->spi->sale(new SaleRequest(
    totalAmount:     Money::of('29.99', 'USD'),
    orderIdentifier: 'order-1234',
    source:          new CardSource(
        cardPan:        '4111111111111111',
        cardExpiration: '2512',   // YYMM
        cardCvv:        '123',
        cardholderName: 'Jane Doe',
    ),
));

if ($result instanceof ThreeDSecureChallenge) {
    // A redirect is pending — render it in an iframe. See 3-D Secure below.
    echo $result->iframe();
    exit;
}

echo $result->approved
    ? "Approved: {$result->transactionIdentifier}"
    : "Declined: {$result->responseMessage}";
```

The client defaults to the **sandbox** environment. Pass `Environment::PRODUCTION` to go live.

Configuration
-------------

[](#configuration)

```
use PowerTranz\Config\Environment;

$client = new PowerTranzClient('merchant-id', 'password', [
    'environment'     => Environment::PRODUCTION,
    'timeout'         => 30,   // seconds; must be >= 1
    'connect_timeout' => 10,
    'max_retries'     => 3,    // 0 disables retries
    'logger'          => $psrLogger,
]);
```

OptionDefaultNotes`environment``Environment::SANDBOX``SANDBOX` → `staging.ptranz.com`, `PRODUCTION` → `api.ptranz.com``timeout``30`Total request timeout in seconds`connect_timeout``10`Connection timeout in seconds`max_retries``3`Retries with exponential backoff`logger``NullLogger`Any PSR-3 loggerFor DI containers, build a `Configuration` and hand it over:

```
use PowerTranz\Config\ConfigurationBuilder;

$config = ConfigurationBuilder::create()
    ->withCredentials('merchant-id', 'password')
    ->withProductionEnvironment()
    ->withMaxRetries(5)
    ->build();

$client = PowerTranzClient::fromConfiguration($config);
```

> **Note:** `fromConfiguration()` currently forwards only the environment, timeouts, and retry count. A logger or custom retry delay set on the `Configuration` is not carried over — pass `logger` through the constructor options instead.

### Custom HTTP client

[](#custom-http-client)

Supply a PSR-18 client along with PSR-17 request and stream factories. All three are required together — passing `http_client` without the factories throws `InvalidArgumentException`.

```
$psr17  = new \Nyholm\Psr7\Factory\Psr17Factory();

$client = new PowerTranzClient('merchant-id', 'password', [
    'http_client'     => new \GuzzleHttp\Client(),
    'request_factory' => $psr17,
    'stream_factory'  => $psr17,
]);
```

Services
--------

[](#services)

The client exposes three services as readonly properties.

### `$client->spi` — charges and authentication

[](#client-spi--charges-and-authentication)

MethodReturnsPurpose`sale(SaleRequest)``SaleResponse|ThreeDSecureChallenge`Authorise and capture in one step`authorize(AuthRequest)``AuthResponse|ThreeDSecureChallenge`Reserve funds without capturing`riskManagement(RiskManagementRequest)``RiskManagementResponse|ThreeDSecureChallenge`Non-financial: 3DS authentication, fraud check, tokenisation`payment(PaymentRequest)``PaymentResponse`Complete the payment once authentication has finishedThe union return types are deliberate: static analysis forces you to handle the redirect branch, so a pending challenge can't be silently treated as an approval.

### `$client->transactions` — post-authorisation

[](#client-transactions--post-authorisation)

MethodReturnsPurpose`capture(CaptureRequest)``CaptureResponse`Capture authorised funds (supports partial)`refund(RefundRequest)``RefundResponse`Refund a settled transaction (supports partial)`void(VoidRequest)``VoidResponse`Cancel before settlement### `$client->hostedPage` — HPP

[](#client-hostedpage--hpp)

MethodReturnsPurpose`sale(...)``SaleResponse|ThreeDSecureChallenge`Hosted-page sale`authorize(...)``AuthResponse|ThreeDSecureChallenge`Hosted-page authorisationSee [Hosted Payment Pages](#hosted-payment-pages) for the full flow.

Money and currency
------------------

[](#money-and-currency)

All monetary values are [`Brick\Money\Money`](https://github.com/brick/money) objects — arbitrary-precision decimals, no float rounding. Currency travels with the amount, so no separate currency argument is ever needed.

```
use Brick\Money\Money;
use PowerTranz\Enum\CurrencyCode;

Money::of('29.99', 'USD');
CurrencyCode::TTD->money('150.00');   // convenience helper
```

Amounts must be strictly greater than zero; this is enforced by the `PositiveMoney` constraint on every request that carries an amount.

On responses, `$response->totalAmount` is a `Money` — or `null` if the gateway returns a currency code the SDK doesn't recognise. The raw value is always available via `$response->getRaw('TotalAmount')`.

3-D Secure 2.x
--------------

[](#3-d-secure-2x)

3DS is a **two-call flow**. The first call returns a `ThreeDSecureChallenge` carrying an HTML document to render in an iframe; the second completes the payment once authentication has finished.

You need a **`merchantResponseUrl`**: a publicly reachable URL on your site. PowerTranz POSTs the authentication result there and returns the cardholder to it. It is required — without it the cardholder finishes in the iframe and control never comes back.

**Step 1 — initiate.** Enable the `threeDSecure` flag and supply the parameters in `ExtendedData`:

```
use PowerTranz\Model\Request\Parts\{ExtendedData, ThreeDSecure};

$result = $client->spi->sale(new SaleRequest(
    totalAmount:     Money::of('75.00', 'USD'),
    orderIdentifier: 'order-1234',
    source:          $cardSource,
    threeDSecure:    true,
    extendedData:    ExtendedData::forThreeDSecure(
        merchantResponseUrl: 'https://example.com/3ds/callback',
        threeDSecure:        new ThreeDSecure(
            challengeWindowSize: ThreeDSecure::WINDOW_FULLPAGE,
            challengeIndicator:  ThreeDSecure::CHALLENGE_NO_PREFERENCE,
        ),
    ),
));

if ($result instanceof ThreeDSecureChallenge) {
    $_SESSION['spi_token'] = $result->spiToken;   // expires in 5 minutes
    echo $result->iframe();                        // renders the gateway's document
    exit;
}
```

`iframe()` emits the wrapper PowerTranz documents, with the payload escaped for the `srcdoc` attribute. Use `render()` if you need the raw document to wrap yourself.

> **Both halves are required.** The `threeDSecure` flag is what switches authentication on; `ExtendedData.ThreeDSecure` alone is inert. The gateway accepts parameters sent with a false flag and quietly processes a plain e-commerce sale — no challenge, no liability shift — so the SDK rejects the mismatch in either direction before the request leaves.

**Step 2 — complete, in your `merchantResponseUrl` handler.** Parse the POST with `ThreeDSecureResult::fromCallback()`, then complete the payment within five minutes:

```
use PowerTranz\Model\Response\ThreeDSecureResult;

$result = ThreeDSecureResult::fromCallback($_POST);

if ($result->canCompletePayment()) {
    $payment = $client->spi->payment(new PaymentRequest($result->spiToken));

    echo $payment->approved
        ? "Approved: {$payment->transactionIdentifier}"
        : "Failed: {$payment->responseMessage}";
}
```

The result posted to your callback is the **authentication** outcome, not a financial one — no funds move until step 2.

> **The callback body is not JSON.** The integration guide shows the result as a JSON document, but it arrives `application/x-www-form-urlencoded` with three fields — `Response`, `TransactionIdentifier` and `SpiToken` — and the whole authentication document is nested inside `Response` as a JSON *string*. Reading `$_POST['IsoResponseCode']` finds nothing. `fromCallback()` unwraps it; it also accepts a JSON body or an already-decoded `Response`.

`ThreeDSecureResult` exposes `spiToken`, `isoResponseCode`, `responseMessage`, `authenticationStatus`, `eci`, `cavv`, `protocolVersion`, `dsTransId`, `panToken`, `cardBrand`, `cardholderInfo`, plus `getRaw()` for anything unmodelled. The helpers are `isAuthenticated()` (`Y` or `A`), `isThreeDsUnsupported()` (`3D1`), and `canCompletePayment()`.

If `cardholderInfo` is present, the issuer wants that message shown to the cardholder.

### Reading the response codes

[](#reading-the-response-codes)

`IsoResponseCode` carries two different families of code depending on the stage:

CodeMeaningWhere you see it`SP4`SPI preprocessing completeStep 1 — a redirect is pending`HP0`HPP preprocessing completeStep 1, hosted page`3D0`3DS completePOSTed to your `merchantResponseUrl``3D1`3DS not supported by the cardSame; proceed as standard e-commerce`00`Issuer approvedStep 2, after payment completionPrefer the helpers over comparing codes by hand: `requiresRedirect()`, `isApproved()`, `isNonFinancialSuccess()`, `isDeclined()`, `isRetryable()`, `requiresCardRetention()`.

All 94 documented ISO 8583 codes are modelled, alongside the nine gateway status codes above. `isoResponseCode` is **nullable** — card networks add codes, so it is null for anything unrecognised, and `isoResponseCodeValue` always holds the raw string the gateway sent. Nothing is ever substituted:

```
if ($payment->isoResponseCode?->isRetryable()) {
    // 91 issuer inoperative, 96 system malfunction, 98 host unreachable…
    // Transient. Retrying is reasonable.
}

// Always safe to log, even for a code the SDK does not know:
error_log("gateway returned {$payment->isoResponseCodeValue}");
```

`isRetryable()` matters operationally: `91` (issuer unreachable) may succeed on a retry, while `05` (do not honour) will not, and retrying it risks tripping issuer velocity rules.

3DS requires `CardholderName` on the source, plus an **email address and/or phone number** on the billing address. Omitting both fails the authentication.

Hosted Payment Pages
--------------------

[](#hosted-payment-pages)

HPP keeps card entry off your servers, reducing your PCI DSS scope. There is no separate HPP endpoint — it is an ordinary `spi/sale` or `spi/auth` carrying hosted-page parameters and **no card source**. The cardholder types their card into the gateway's iframe.

```
use PowerTranz\Model\Request\Parts\HostedPage;

$result = $client->hostedPage->sale(
    totalAmount:         Money::of('50.00', 'USD'),
    orderIdentifier:     'order-1234',
    page:                HostedPage::fromPortal('MyPageSet', 'MyPageName'),
    merchantResponseUrl: 'https://example.com/payment/return',
);

if ($result instanceof ThreeDSecureChallenge) {
    $_SESSION['spi_token'] = $result->spiToken;
    echo $result->iframe();
    exit;
}
```

From there the flow is identical to 3DS: the cardholder pays in the iframe, PowerTranz posts to your `merchantResponseUrl`, and you complete with `payment()`.

> Page sets created in the Merchant Portal **must** carry a `PTZ/` prefix. Without it the page silently fails to load and the transaction fails with no clear reason. `HostedPage::fromPortal()` adds it for you; use the constructor directly only if your page set genuinely has no prefix.

Tokenisation
------------

[](#tokenisation)

Tokenising happens on **`riskManagement()`** — a non-financial request that moves no money. `Tokenize` is not accepted on `sale` or `authorize`.

```
$result = $client->spi->riskManagement(new RiskManagementRequest(
    totalAmount:     Money::of('1.00', 'USD'),   // nominal; nothing is charged
    orderIdentifier: 'tokenise-1234',
    source:          $cardSource,
    tokenize:        true,
));

$token = $result->panToken;   // store this, never the PAN
```

`$result->approved` is `false` here — nothing was approved because nothing was charged. Check `panToken` instead, or `isoResponseCode` for `TK0`.

Charge the token later with a `TokenSource`:

```
use PowerTranz\Model\Request\Parts\TokenSource;

$client->spi->sale(new SaleRequest(
    totalAmount:     Money::of('19.99', 'USD'),
    orderIdentifier: 'order-5678',
    source:          new TokenSource($token),
));
```

Note the field names differ by direction: the gateway **returns** the token as `PanToken` but **expects** it back as `Source.Token`. `TokenSource` handles that. For First Atlantic Commerce tokens use `TokenSource::fac($token)`, which tags them `PG2`.

Error handling
--------------

[](#error-handling)

All exceptions extend `PowerTranzException` (itself a `RuntimeException`).

ExceptionRaised when`ValidationException`A request fails local validation — never reaches the network`AuthenticationException`Credentials rejected (extends `ApiException`)`ApiException`Gateway returned an error; `getHttpStatus()`, `getResponseBody()``TokenExpiredException`SpiToken used after its 5-minute window`NetworkException`Connection failure, timeout, retries exhausted`ValidationException::getErrors()` returns a map keyed by field name, with **all** violations collected in a single pass:

```
use PowerTranz\Exception\ValidationException;

try {
    $client->spi->sale($sale);
} catch (ValidationException $e) {
    foreach ($e->getErrors() as $field => $message) {
        echo "{$field}: {$message}\n";
    }
    // totalAmount: TotalAmount must be greater than zero.
    // orderIdentifier: OrderIdentifier must not be empty.
}
```

A **declined** transaction is not an exception. Check `$response->approved` and inspect `$response->responseMessage` / `$response->isoResponseCode`.

Responses
---------

[](#responses)

Every response extends `SpiResponse` and exposes:

`isoResponseCode`, `responseCode`, `responseMessage`, `transactionIdentifier`, `orderIdentifier`, `referenceNumber`, `authorizationCode`, `panToken`, `spiToken`, `cardBrand`, `transactionType`, `totalAmount`, `approved`, `requiresRedirect`

Every one of those is a **projection** of the gateway response, not a copy of it. Some keys are renamed (`RRN` becomes `referenceNumber`), some values are converted (`totalAmount` becomes a `Money`, normalised to 2dp), and some are computed by the SDK rather than sent at all (`approved`, `requiresRedirect`, `isoResponseCode`'s enum case and `label()`).

When you need the gateway's own words — audit logs, support tickets, debugging an unmodelled field — use the raw accessors, available on `SpiResponse`, `ThreeDSecureChallenge` and `ThreeDSecureResult` alike:

```
$response->getRaw('FieldName');   // one field, untouched
$response->raw();                 // the whole decoded payload, untouched
```

`raw()` is the safest thing to log: it never renames, coerces, or infers.

Examples
--------

[](#examples)

Runnable scripts in [`examples/`](examples):

```
POWERTRANZ_ID=your-id POWERTRANZ_PASSWORD=your-password php examples/simple_sale.php
```

- [`simple_sale.php`](examples/simple_sale.php) — one-step sale
- [`authorize_and_capture.php`](examples/authorize_and_capture.php) — two-step flow
- [`three_ds_flow.php`](examples/three_ds_flow.php) — full 3DS 2.x flow
- [`tokenized_payment.php`](examples/tokenized_payment.php) — tokenise, then charge the token

Security
--------

[](#security)

- **Never log or persist a PAN or CVV.** Store the `panToken` instead.
- Use HPP (`$client->hostedPage`) to keep card entry off your servers and reduce PCI DSS scope.
- Keep credentials in environment variables or a secrets manager — never in version control.
- Sandbox and production credentials are not interchangeable.

Development
-----------

[](#development)

```
composer install
vendor/bin/phpunit --testsuite Unit
vendor/bin/phpstan analyse
vendor/bin/php-cs-fixer fix
```

License
-------

[](#license)

This SDK is open-sourced software licensed under the [MIT license](LICENSE).

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity33

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

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 ~0 days

Total

2

Last Release

1d ago

### Community

Maintainers

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

---

Top Contributors

[![ylynfatt](https://avatars.githubusercontent.com/u/19831?v=4)](https://github.com/ylynfatt "ylynfatt (29 commits)")

---

Tags

paymentgatewaytokenisationpowertranz3dscaribbean

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/ylynfatt-powertranz-php-sdk/health.svg)

```
[![Health](https://phpackages.com/badges/ylynfatt-powertranz-php-sdk/health.svg)](https://phpackages.com/packages/ylynfatt-powertranz-php-sdk)
```

###  Alternatives

[tempest/framework

The PHP framework that gets out of your way.

2.3k37.6k21](/packages/tempest-framework)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

595.8M672](/packages/shopware-core)[sylius/sylius

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

8.5k6.0M777](/packages/sylius-sylius)[shopware/platform

The Shopware e-commerce core

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

PHP ETL - Extract Transform Load - Data processing framework

86337.5k](/packages/flow-php-flow)[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)

PHPackages © 2026

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