Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -225,4 +225,9 @@ jobs:
dependency-versions: "${{ matrix.dependencies }}"

- name: "Run infection/infection"
env:
# Uploads the mutation score to the Stryker dashboard for the README badge.
# infection.json.dist gates the upload to the 2.x branch, so pull request runs
# compute the score but publish nothing.
STRYKER_DASHBOARD_API_KEY: "${{ secrets.STRYKER_DASHBOARD_API_KEY }}"
run: "composer infection"
38 changes: 26 additions & 12 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ binaries live in `vendor/bin`, and the user's shell aliases (`ca`, `cf`, etc.) m
- `composer fix-style` — ECS auto-fix
- `composer rector` — Rector dry/apply (config in `rector.php`; not run in CI)
- `composer infection` — Infection mutation testing (`infection.json.dist`: source `src`, gates
`minMsi 65` / `minCoveredMsi 70`; needs a coverage driver — CI runs it on PHP 8.3 with pcov)
`minMsi 65` / `minCoveredMsi 70`; needs a coverage driver — CI runs it on PHP 8.3 with pcov). The
Stryker dashboard upload behind the README badge is **branch-gated** in `infection.json.dist`
(`logs.stryker.badge`, currently `2.x`) and needs the `STRYKER_DASHBOARD_API_KEY` repository secret,
which the CI job passes through — pull request runs compute the score but publish nothing. Update the
gated branch when the working branch changes, or the badge silently stops refreshing.
- `composer checks` — style check + static analysis
- `composer all` — `checks` + `test` (the full local gate)
- `composer normalize` — normalize `composer.json` (CI enforces `--dry-run`)
Expand Down Expand Up @@ -67,12 +71,14 @@ tagged; the README and `docs/UPGRADE-2.0.md` say so.

`QuickpayGatewayFactory::populateConfig()` is the composition root. It registers every action under a
`payum.action.*` key and defines `payum.api` — a factory closure that builds the `Api` value object.
Required options are just `api_key` and `private_key`. The 1.x spellings `apikey`/`privatekey` remain as
deprecated aliases, mapped by `aliasDeprecatedOptions()` **before** the defaults are applied — after
that, `api_key` exists as `''` and there is no way to tell the consumer only supplied the old name.
Unlike the misspelled `syncronized` that 2.0 dropped outright, these two are required and therefore set
by every consumer, and Sylius stores the gateway config keyed by them, so a hard rename would break
every existing shop. Other options (`payment_methods`, `auto_capture`,
Required options are just `api_key` and `private_key`. Three 1.x names — `apikey`, `privatekey` and
`agreement` — remain as deprecated aliases, mapped by `aliasDeprecatedOptions()` **before** the defaults
are applied; after that, `api_key` exists as `''` and there is no way to tell the consumer only supplied
the old name. They are aliased rather than dropped (unlike the misspelled `syncronized`, which nobody had
meaningfully set) because Sylius stores the gateway config keyed by these names, so a hard rename would
break every existing shop. `agreement` is the one to be most careful with: it is optional, so a name that
stops being read fails **silently** — the link is created without an agreement id and Quickpay falls back
to the account default. Other options (`payment_methods`, `auto_capture`,
`order_prefix`, `language`, `synchronized`, `agreement_id` → link `agreementId`, `branding_id`) are
defaulted. The closure constructs the SDK `Client` from the api key **and the `synchronized` flag** (or
accepts a prebuilt `Setono\Quickpay\Client\ClientInterface` via the optional `quickpay.client` option —
Expand All @@ -87,9 +93,12 @@ never reaches `Api`, and `null` is what keeps the parameter off the request enti

### Actions (`src/Action/`)

Actions are the unit of behavior. Each implements `ActionInterface` plus the aware-interfaces it needs
(`ApiAwareInterface` via `Action/Api/ApiAwareTrait`, `GatewayAwareInterface`,
`GenericTokenFactoryAwareInterface`). `supports()` gates on the Payum request type **and** the model
Actions are the unit of behavior. Each implements `ActionInterface` plus the aware-interfaces it needs —
`ApiAwareInterface` (via `Action/Api/ApiAwareTrait`, which types `$api` as `Api` so PHPStan can see
through it, unlike payum/core's `mixed` version) and `GatewayAwareInterface`. Only `AuthorizeAction`
implements `GenericTokenFactoryAwareInterface`, because only it mints a token (the notify token for
`callback_url`); keep it that way, since that interface is the package's one remaining contact with
payum/core's deprecated `GenericTokenFactoryInterface` (issue #3). `supports()` gates on the Payum request type **and** the model
being an `ArrayAccess`. The model is normalized with `ArrayObject::ensureArrayObject($request->getModel())`;
the `quickpayPaymentId` (int) it carries is the single source of truth — actions re-fetch the payment via
`Api::payments()->getById()` rather than passing the model through as API params.
Expand Down Expand Up @@ -204,8 +213,13 @@ Two things it encodes that are easy to get wrong:
- `HeaderAwareGetHttpRequestAction` — payum/core's plain-PHP bridge does **not** populate
`GetHttpRequest::$headers`, so outside Symfony every callback would be rejected as unsigned. It is
registered via `addCoreGatewayFactoryConfig(['payum.action.get_http_request' => ...])`.
- `RefundAction` refunds `details['amount']`, so a partial refund means pointing that at the amount for
the duration of the call — the gateway has no partial-refund parameter of its own.
- `listen.php` handles **both** callback shapes: the payment-window one carries a `payum_token`, while a
callback sent to the account-wide url cannot, so it resolves the payment from the body's `order_id`
instead. The log tags each `via=token` or `via=order_id`, which is how the two-url routing above was
established in the first place.

`e2e:operate` takes an optional amount on `capture` and `refund`, setting the `capture_amount` /
`refund_amount` details keys.

Credentials come from a gitignored `.env.local` (see `.env.local.example`); `examples/e2e/var/` is
gitignored too. Quickpay has no sandbox — you use the production API key and a test **card**.
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
[![Software License][ico-license]](LICENSE)
[![Build Status][ico-github-actions]][link-github-actions]
[![Code Coverage][ico-code-coverage]][link-code-coverage]
[![Mutation testing badge][ico-infection]][link-infection]

This component enables the use of Quickpay with Payum. Under the hood it uses the
[`setono/quickpay-php-sdk`](https://github.com/Setono/quickpay-php-sdk) client.
Expand Down Expand Up @@ -110,7 +111,7 @@ uses. `quickpayPaymentId` is the single source of truth — everything else is a
| `order_id` | `Convert` | `order_prefix` + the Payum payment number. |
| `continue_url`, `cancel_url` | `Convert` | From the token's after-URL. |
| `callback_url` | `Authorize` | The notify token url given to Quickpay. |
| `balance` | `GetStatus`, `Sync`, `Notify` | **What is still captured** — captured minus refunded. |
| `balance` | `GetStatus`, `Sync`, `Notify`, `Refund` | **What is still captured** — captured minus refunded. |
| `state` | `Sync` | Quickpay's own payment state. |
| `capture_amount`, `refund_amount` | *you* | Optional partial-operation amounts; see below. |

Expand Down Expand Up @@ -181,7 +182,9 @@ the `balance` details key for the actual figure rather than inferring it from th
[ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square
[ico-github-actions]: https://github.com/Setono/payum-quickpay/actions/workflows/build.yaml/badge.svg?branch=2.x
[ico-code-coverage]: https://codecov.io/gh/Setono/payum-quickpay/branch/2.x/graph/badge.svg
[ico-infection]: https://img.shields.io/endpoint?style=flat-square&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2FSetono%2Fpayum-quickpay%2F2.x

[link-packagist]: https://packagist.org/packages/setono/payum-quickpay
[link-github-actions]: https://github.com/Setono/payum-quickpay/actions/workflows/build.yaml?query=branch%3A2.x
[link-code-coverage]: https://codecov.io/gh/Setono/payum-quickpay/branch/2.x
[link-infection]: https://dashboard.stryker-mutator.io/reports/github.com/Setono/payum-quickpay/2.x
5 changes: 4 additions & 1 deletion infection.json.dist
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
]
},
"logs": {
"text": "php://stderr"
"text": "php://stderr",
"stryker": {
"badge": "2.x"
}
},
"minMsi": 65,
"minCoveredMsi": 70
Expand Down
Loading