Download the PHP package davit-vardanyan/ameriabank-vpos-php without Composer

On this page you can find all versions of the php package davit-vardanyan/ameriabank-vpos-php. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package ameriabank-vpos-php

This is an unofficial client. It is not affiliated with, endorsed by, or supported by Ameriabank.

ameriabank-vpos-php

A framework-agnostic PHP client for the Ameriabank vPOS 3.1 payment gateway.

CI Static analysis Latest version

Status: pre-release. Nothing has been tagged yet. The enums, the Amount and ResponseCode value objects, the request and response DTOs, the PSR-18 transport, the client surface that ties them together — Vpos::payments() and its siblings — and the BackURL callback type with the verification round trip it forces are all in place, so the REST payment lifecycle is usable end to end. Because nothing is tagged, the public API is not yet stable. SOAP reporting and SSNCheck are not implemented, and this README does not document methods that do not exist.

Requirements

Installation

You must install a PSR-18 HTTP client separately

This package is PSR-18 abstract: it depends on the PSR-18, PSR-17, PSR-7, and PSR-3 interfaces, and never on a concrete HTTP implementation. No HTTP client is pulled in for you, so that the package does not force a second copy of one into an application that already has its own.

You supply the client. Either install one that php-http/discovery can find automatically:

…or hand the client your application already has to Vpos's httpClient argument, which accepts any PSR-18 implementation. Leave it null and discovery runs instead; if discovery finds nothing, you get a ConfigurationException rather than a bare php-http/discovery error.

guzzlehttp/guzzle and symfony/http-client appear under suggest in composer.json. They are never in require.

Timeouts belong to the client you inject

PSR-18 has no timeout API. Its whole surface is one method, sendRequest(), and there is nowhere in it to say how long the caller is willing to wait. This package therefore cannot bound a request portably, and deliberately does not try: there is no timeout option on any class here, and adding one would mean depending on a concrete HTTP implementation, which this package does not do.

The client is yours, so the timeout is yours. Set it when you build the client, before you hand it over.

With Guzzle:

With Symfony's HTTP client:

Both of those are configuration for software you chose. Neither package is a dependency of this one — they are suggest entries and nothing more — and any other PSR-18 implementation works the same way, with whatever option names it happens to use.

Timeout in InitPaymentRequest is a different thing entirely

InitPaymentRequest takes a timeout. It is not an HTTP timeout. It is a gateway-side session length in seconds — how long the payment page stays valid for the customer after the payment is registered — and it bounds nothing about the HTTP request that registers it:

Setting one does not substitute for the other, in either direction. A timeout: 900 on an injected client with no timeout of its own is a request that can hang indefinitely while offering the customer a quarter of an hour to pay.

This library validates the value to 1..1200 seconds and throws ValidationException outside that range. The gateway does not validate it at all — 1201, 0 and −1 were each accepted silently on the sandbox — so without the client-side check a typo would become a payment page that expires at the wrong moment rather than an error.

Why the timeout you choose is a real decision

Four operations move money and are never retried: ConfirmPayment, RefundPayment, CancelPayment and MakeBindingPayment. A timeout is a transport failure, and when one of these fails in transport this library cannot know whether the gateway acted, so it throws IndeterminateStateException after exactly one attempt. That exception tells the caller to reconcile through GetPaymentDetails and not to retry, and it is deliberately not a subtype of TransportException, so a catch written to retry transport failures cannot swallow it.

A timeout set too short therefore does not merely fail a request. It can turn a capture or a refund the gateway may well have completed into work a human has to reconcile. The read-only operations and InitPayment are retried automatically instead — InitPayment only ever with the byte-identical body it first sent — so the cost of a short timeout is not the same across operations.

The one completed sandbox payment behind this package puts numbers on that, and they do not fall the way "writes are slower than reads" would suggest. A rejected write answers at read speed: both refusals on record — an over-refund and a cancel that the payment's state forbade — came back in about 102 ms, faster than every GetPaymentDetails call in the same run, which took 145–174 ms. InitPayment took 61 ms. The two refunds that actually moved money took 518 ms and 910 ms. The cost is in the settlement, not in the verb, so the slowest operations are exactly the ones this package will never retry: a timeout short enough to cut one of them off buys you an IndeterminateStateException and a manual reconciliation, not a second attempt. Those are one sandbox and one payment — read them as orders of magnitude rather than as a budget (CONVENTIONS.md §4.21).

Usage

Vpos is the entry point and the only class you construct. It owns one transport and exposes three operation clients — payments(), bindings() and reports() — plus paymentPageUrl(), which performs no API call.

Never hard-code credentials in source. Read ClientID, username and password from your environment or secret store, as above. A blank one throws ConfigurationException at construction, rather than reaching the gateway and coming back as something that looks like a wrong password.

environment is required and has no default, deliberately. Defaulting to Test means a misconfigured deployment silently takes no money and finds out at reconciliation weeks later; defaulting to Production means a developer who forgets it hits live infrastructure. Naming it has neither failure mode.

The payment lifecycle

Four steps: register the order, send the customer to the payment page, ask the gateway what happened, and — if you have to — give the money back.

1. Register the order. InitPayment returns the PaymentID the payment page is keyed on.

Set Description on the request, and read trxnDescription back off the response. The symmetry you expect is not there: GetPaymentDetails returns your submitted text in TrxnDescription and overwrites the response's own Description with the processor's wording — Approved. - Payment post authorized on a deposited payment, Approved. - Refunded payment back to client card once a refund has run. $details->description is the gateway talking, not you (CONVENTIONS.md §4.15).

That echo is not byte-exact, so never use a non-ASCII Description as a reconciliation key. An Armenian Description came back from TrxnDescription with every Armenian codepoint replaced by ¿ (U+00BF); the codepoint count and the ASCII prefix survived, the letters did not. Reconcile on OrderID, or on an ASCII value you put in Opaque, and treat trxnDescription as text for a human to read rather than a value to compare. Scope this exactly as it was observed: Armenian only, Description → TrxnDescription only, on the test environment. Russian and other scripts are untested, and the request itself is accepted — the package sends your text as raw UTF-8 and the gateway takes it. The question of why the value comes back altered is with the bank (CONVENTIONS.md §4.15, §13).

Amount holds an integer minor-unit count and a Currency; there is no constructor taking a float, and none will be added. Build one with Amount::fromMinorUnits(1000, Currency::AMD) or Amount::fromDecimalString('10.00', Currency::AMD). Only Currency::AMD has ever been accepted by the gateway — see Unverified behaviour.

2. Send the customer to the payment page.

paymentPageUrl() refuses a blank PaymentID rather than building a broken page. That matters because a failed InitPayment answers with "PaymentID": "" — an empty string, never null — so a caller who reached this line holding nothing gets an exception instead of a customer staring at an error page.

3. Read the outcome — from the gateway, never from the callback.

The customer comes back to your backUrl with a query string like:

Those parameters are unsigned. Never trust them. There is no HMAC, no signature and no shared secret anywhere in this callback, so anyone who can type a URL can send you resposneCode=00 for an order nobody paid for. The library gives you no way to read a status out of them, because there is no safe way to read one.

Treat the callback as a notification that something happened, take its identifiers as lookup keys and nothing more, and ask the gateway:

Every amount arrives twice: a raw string exactly as the gateway sent it, and a typed Amount beside it. The raw one is the field you can always read — the typed one is null unless the same response also carried a currency and the value is non-zero, so on a payment that has not been refunded refundedAmount is null while refundedAmountRaw is "0.0".

rrn and mdOrderId came back byte-identical on every body observed, so reconciling on both is reconciling on the same value twice (CONVENTIONS.md §4.19).

VposCallback::fromServerRequest($request) does the same from a PSR-7 ServerRequestInterface, so no framework is assumed.

Either constructor throws ValidationException when paymentID or orderID is missing or blank, because a callback with no identifiers has nothing to verify. Both match the five parameter names exactly and case-sensitively — resposneCode misspelling included, since that is the wire spelling — so a spelling or case variant is not quietly absorbed.

The case of a value is pinned for the same reason, and it will surprise you once. The lowercase paymentID in the query string above is not a typo: InitPayment returns that identifier in uppercase and the callback echoes the very same identifier in lowercase. This package normalises neither, because the case a channel sends is part of that channel's wire format. Compare the two case-insensitively, or compare orderID instead; a case-sensitive === between them reports a mismatch that is not one (CONVENTIONS.md §4.12). The callback's description needs the same care for a different reason: both successful callbacks on record delivered Operation Approved with a trailing space, handed back verbatim rather than trimmed. The second arrived byte-identical to the first, trailing space included, on a different payment and a different order — so the space is not a one-off oddity of a single redirect that a future call might tidy up. Two observations is still two, but they agree. Log the value; never compare against it.

verify() is details() plus one check you would otherwise have to write yourself, and it is worth knowing which. The paymentID in that query string is attacker-controlled like everything else in it, and a PaymentID is not a secret — it was handed to the customer's browser — so somebody can put a genuine paymentID from a stranger's paid order on your backUrl beside their own orderID, and a bare details() call will answer, truthfully, that a payment succeeded. verify() closes that: when the response names an OrderID, it must be identical to the callback's or this throws ValidationException; and a response naming a blank OrderID — empty or whitespace-only — is refused too, for a distinct reason and with a distinct message, since nothing disagreed there and there was simply no order identity to check against. The comparison itself is exact — no trimming, no case folding, no numeric coercion, the one trim being the blank test — because '1001' and '01001' are different orders.

That is one check of three, and it is the only one verify() makes. A call that returns proves that the gateway answered, not that money moved — and on the unhappy path it will often not return at all but throw, since every GetPaymentDetails observation on record against a payment that never went through is a failure (see A failed operation throws). So handle the throw, and before you treat anything as paid, all three of these must hold:

This library will not make that last check for you, because it cannot. One completed sandbox payment carried OrderStatus "2" beside PaymentState payment_deposited, and "4" beside payment_refunded after a refund — but "4" appeared on a payment that was only partially refunded, with a balance still outstanding, so the refunded status does not mean fully refunded and a merchant who reads the status and skips the amounts above will get it wrong (CONVENTIONS.md §4.18). One payment on one sandbox client is not the status table for your account. Recognise the statuses you have watched settle in your own account, and treat every other status as unpaid.

VposCallback exposes no success accessor, no status and no response code — there is nothing there that could implement one honestly. It does keep the two diagnostics, reachable only as $callback->untrustedDiagnostics(), an array carrying resposneCode and description as they arrived. Log them on a failed callback, where the gateway's error text is often the only clue you get. Do not branch on them: both are attacker-controlled, and reading ['resposneCode'] to decide anything is exactly the mistake the array shape exists to make visible at the call site.

Every enum-typed field arrives in pairs — a raw value and a nullable enum. The enum is null when the gateway sent something this SDK does not yet recognise, which is a value the bank added without notice, not an error. Read the raw when the enum is null; never assume the enum being null means the field was absent.

4. Refund, in full or in part.

Partial refunds accumulate, and the field that tells you what is left is depositedAmountRaw on a fresh details() read — a payment of 10 refunded by 4 and then by 3 reported DepositedAmount 10, then 6, then 3, while RefundedAmount went 0, then 4, then 7 and ApprovedAmount stayed at 10 throughout. Ask for more than the balance and the gateway refuses rather than clamping: ResponseCode "07", Refund amount exceeds deposited amount, raised here as ApiException.

Do not treat "07" as a single condition. A cancel() that the payment's state forbids answers "07" as well, with Reversal is impossible for current transaction state. The two are told apart only by responseMessage(), which is why no response code is mapped to a dedicated exception subclass and why this package ships no code-to-description table: the vendor PDF calls 07 "System Error", and a transcribed table would have printed that over the top of two accurate messages the gateway had already sent (CONVENTIONS.md §4.17). The success message varies by endpoint too — InitPayment answers "OK" and RefundPayment answers "Success" — so read responseMessage() rather than assuming a word.

confirm() captures an authorised payment and cancel() voids one before capture; both take the same shape. None of the three is ever retried — see below.

A failed operation throws; it does not return a code

Nothing above checks a response code, and that is deliberate. HTTP status carries no business meaning at this gateway — an authentication failure arrives as HTTP 200, and a well-formed request can come back as HTTP 500 — so this library decides success from the body and raises on anything else. A method that returns has succeeded; the response object is there to tell you the details, not whether it worked.

That is a claim about the call, not about the payment. On a read such as details() or verify(), a return means the gateway answered the question you asked — nothing more. The payment's own outcome is in the body, and reading it is step 3's three checks, not this method's return.

Do not read the converse into it either: a query about a payment that never completed does not reliably come back at all. Three response shapes have been seen from GetPaymentDetails, and only one of them returns. Every lookup made with valid credentials against a payment that had actually been paid answered HTTP 200 with ResponseCode "00" and a fully populated body. Every other observation is a failure, and so throws, in one of two shapes: HTTP 500 with the ASP.NET envelope {"Message":"An error has occurred."}, which surfaces as GatewayFaultException, or HTTP 200 carrying ResponseCode "550", which surfaces as ApiException. What separates those two failure shapes from each other has never been explained (see Unverified behaviour).

"550" in particular tells you less than it looks like it does, and that is worth knowing before you debug one. As recorded in CONVENTIONS.md §4.25, it has arrived for an order that was registered and never attempted, asked about with correct credentials; and it has arrived for a payment that had completed, asked about with a wrong password (case L5.3). Neither of those is the meaning of the code — it is overloaded, exactly as "07" is (§4.17). Nor does the message narrow it down, because there is none: this endpoint sends no ResponseMessage at all, so the exception reads … failed with response code 550: (no message), and the only diagnostic text on the body is Description "System Error", which a failure code leaves no response object to read off. Suspect the credentials you sent as readily as the order you asked about.

So expect the unhappy path to throw rather than to hand you a body to inspect, and catch both — a thrown GatewayFaultException means the gateway would not answer, which is evidence neither that the payment happened nor that it did not, and on this endpoint it is not evidence about your credentials either: a wrong password against an order that produces the fault produces the fault, not a "550" (CONVENTIONS.md §4.26).

catch (VposExceptionInterface) catches everything from this package and nothing else. IndeterminateStateException is deliberately not a TransportException, so a catch written to retry transport failures cannot swallow the one case where retrying may refund twice.

Bindings and reporting

bindings()->pay() is not a silent server-to-server charge: its response carries AcsUrl, PaReq and TermUrl, a 3-D Secure challenge you must still put in front of the cardholder.

Only PaymentType::MainRest and PaymentType::BindingMainRest are accepted by the binding endpoints; anything else is rejected here, before dispatch, because the gateway answers an invalid one with an unparseable HTTP 500.

Not implemented

Three gateway operations have no method on this client, and the omissions are deliberate rather than pending. Read this section as being about absence and the next one as being about doubt: what is listed here does not exist, so there is nothing to call; what is listed under Unverified behaviour is implemented and shipped, and it is only the confirmation that is missing.

None of the three is scheduled. If you need one, say so in an issue rather than reading this section as a roadmap.

Unverified behaviour

Some of this package is built against a specification the sandbox has never confirmed. Everything below is implemented — unlike the section above, what is missing here is a confirmed observation, not a method. Those places are marked @todo unverified in the source and catalogued in full under What the sandbox never confirmed in docs/DEVELOPMENT-HISTORY.md, which is export-ignored and does not ship in the installed package — read it in the repository. The ones most likely to matter to a merchant:

API reference

The gateway surface this client targets is documented in docs/api-reference/reference.md, generated from the live vPOS Help pages. That reference — and the api-surface.json manifest beside it — is the specification of record for this package.

The reference is committed for versioning, but it is export-ignored and does not ship in the distributed package. Read it in the repository.

Security

Vulnerabilities must be reported privately, never through a public issue. See SECURITY.md.

The library redacts credentials, card data and personal data from its logs, and never accepts a switch to disable TLS verification. Know one detail before you read a log file: a card number is truncated, not removed. A value under a card-number key survives as first-6 and last-4 with the middle masked whenever it is shaped like a card number, and is replaced outright when it is not, because a masked PAN is what reconciliation needs and the gateway already sends that field pre-masked in exactly that shape. Every other sensitive value is replaced outright — passwords, the approval code, the expiry date, and the two fields that identify a person rather than a card: ClientName holds the cardholder's own name, not the merchant's, and ProcessingIP holds the address the cardholder paid from, not your server's (CONVENTIONS.md §4.20).

Exceptions serialize with their sensitive state scrubbed

Every exception this package throws defines __serialize(), so one can be put in a queue payload, a cache entry or a failed-job record without taking anything with it that does not belong there. Serializing never fails, including on a routine decline.

What survives a round trip:

A response code keeps the type it arrived with. The gateway answers int from InitPayment and string everywhere else, so failure code 20 is 20 from one endpoint and "20" from another; a restored exception reports whichever it was. Compare with === and it will still be right.

What does not survive is the previous chain. The cause of a transport failure is the PSR-18 client's own exception, and a PSR-18 exception hands back the request it was sent — body, credentials and all — so it is dropped rather than carried into a store this package cannot see. That is recorded rather than hidden:

chainDropped() is declared on VposExceptionInterface, so a worker that catches the one interface can ask without narrowing first. A restored exception is still throwable and still caught by its own class.

Contributing

All checks must pass before a pull request is merged:

Command Checks
composer test PHPUnit
composer stan PHPStan level 10
composer cs:check PHP-CS-Fixer, PER-CS 2.0
composer rector Rector, dry run
composer validate --strict Composer manifest
composer normalize --dry-run Manifest formatting
composer coverage:check Line coverage 100% on src/ — PHP 8.3 / highest dependencies only
composer infection Infection, MSI 100 on src/ — PHP 8.3 / highest dependencies only

Verify against the supported floor, not your default interpreter. Composer resolves script binaries through PATH, so prepend the PHP 8.3 bindir:

PATH="/path/to/[email protected]/bin:$PATH" composer test

Mutation testing requires a coverage driver. php-code-coverage 12 supports only pcov and Xdebug — the phpdbg driver was removed upstream — and pcov is considerably faster:

pecl install pcov

On Homebrew PHP for macOS that bare command fails to compile with fatal error: 'pcre2.h' file not found, because Homebrew's php-config --includes omits the pcre2 headers its own php_pcre.h includes. Point the compiler at them:

CPPFLAGS="-I$(brew --prefix pcre2)/include" pecl install pcov

Verify with php -m | grep pcov. composer infection enforces a mutation score of 100 on src/ — the floor is what the suite actually holds, because a floor below the tree's real score is a silent regression budget. It runs in CI on the PHP 8.3 / highest-dependencies job only.

composer coverage:check and composer infection therefore gate a pull request, but on that single matrix job; the other five run the test suite with no coverage driver. The two answer different questions and neither substitutes for the other: Infection generates no mutants for uncovered code, so a mutation score alone cannot see an untested class at all. composer bc is not part of the gate at all — the backward-compatibility check needs a prior release tag to diff against, and nothing has been tagged yet.

Bootstrap with composer install. composer cs applies the fixes that composer cs:check reports.

CI splits the table across two workflows: the static gates — validate, normalize, stan, cs:check and rector — run once on PHP 8.3, in an order of their own; the table is a checklist, not a sequence. composer test runs separately across the matrix PHP 8.3 / 8.4 / 8.5 against lowest and highest resolvable dependencies, and composer infection runs on the 8.3 / highest cell alone. normalize is a plugin command rather than a composer.json script, so it is easy to miss after hand-editing composer.json — run it before opening a pull request.

Changelog

See CHANGELOG.md.

License

MIT. See LICENSE.


All versions of ameriabank-vpos-php with dependencies

PHP Build Version
Package Version
Requires php Version ^8.3
ext-json Version *
php-http/discovery Version ^1.20
psr/http-client Version ^1.0
psr/http-factory Version ^1.1
psr/http-message Version ^2.0
psr/log Version ^3.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package davit-vardanyan/ameriabank-vpos-php contains the following files

Loading the files please wait ...