Skip to content
Open
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
18 changes: 12 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ It targets Quickpay API v10. Composer and GitHub releases distribute the same
tracked standalone PHAR without installing Laravel Zero at runtime.

Use `php quickpay` when running from source. See `README.md` for the public
command reference and `skills/quickpay/SKILL.md` for the agent-facing usage
command reference and `skills/quickpay-cli/SKILL.md` for the agent-facing usage
guide.

## Structure

- `app/Commands` — thin command adapters grouped by API, authentication,
- `app/Commands` — thin command adapters grouped by API, merchants,
callbacks, and payments.
- `app/Callbacks` — callback replay/watch workflows and their delivery,
resolution, signing, and input capabilities.
Expand Down Expand Up @@ -65,7 +65,7 @@ validation, and the locked-dependency audit.
- Preserve raw API JSON in `--json` mode after sanitization. Keep
machine-readable output on stdout and diagnostics or prompts on stderr.
- Restore environment variables and other global state changed by a test.
- Update `README.md`, command help, and `skills/quickpay/SKILL.md` when a command
- Update `README.md`, command help, and `skills/quickpay-cli/SKILL.md` when a command
or safety behavior changes.
- Check current Quickpay documentation before changing API behavior; do not
infer the remote contract solely from existing code.
Expand All @@ -74,9 +74,15 @@ validation, and the locked-dependency audit.

- Never expose an API key in output, errors, logs, fixtures, or command
arguments. Preserve redaction of raw, Basic-auth, and encoded forms.
- Credentials come from a non-empty `QUICKPAY_API_KEY` before
`~/.config/quickpay/config.json`. Stored credentials use atomic writes, a
`0700` directory, and a `0600` file.
- Named credentials live in `~/.config/quickpay-cli/config.json`; never read or
migrate the old `~/.config/quickpay/config.json`. Stored credentials use locked
atomic writes, a `0700` directory, and a `0600` file.
- Stored selection is explicit `--merchant`, nearest `.quickpay-cli.json`, then
global default. Invalid or stale selection never falls back silently.
- A non-empty `QUICKPAY_API_KEY` bypasses saved storage when no explicit or
project selection exists; combining them fails before API requests.
- Resolve credentials once per command, including long-running watchers. Plain
`merchants:active` is local status; `--check` verifies merchant API access.
- The client owns the Quickpay host, authentication, API version, and JSON
headers. Raw API input must not override them or escape the
`api.quickpay.net` origin.
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ alignment and the main structural boundaries.
- Preserve original API JSON in `--json` mode unless credential sanitization is
required.
- Use integer minor units for amounts.
- Update the README, command help, and `skills/quickpay/SKILL.md` when the CLI
- Update the README, command help, and `skills/quickpay-cli/SKILL.md` when the CLI
contract or safety workflow changes.
- Check current Quickpay documentation before changing API behavior; do not
infer the remote contract solely from existing code.
Expand Down
72 changes: 68 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,30 @@ Quickpay CLI requires PHP 8.4 or newer.
composer global require tehwave/quickpay-cli
```

Install the bundled skill for coding agents:
Install the bundled [quickpay-cli skill](skills/quickpay-cli/SKILL.md) for coding agents:

```bash
skills add tehwave/quickpay-cli
```

Authenticate with a Quickpay merchant API key:
If you previously installed this project's skill as `quickpay`, remove that
installed copy and run the installation command again. Check its origin before
removal so an unrelated Quickpay skill stays intact.

Save access to each merchant once. The CLI privately prompts for its API key:

```bash
quickpay login
quickpay merchants:add shop-a
```

For automation, supply the API key through `QUICKPAY_API_KEY`.
Credentials are stored in `~/.config/quickpay-cli/config.json` with a `0700`
directory and `0600` file. Merchant names are local labels you choose, not verified
Quickpay account names or merchant IDs. Names contain 1-64 lowercase ASCII
letters, digits or single separating hyphens, starting with a letter.

For automation, supply the API key through `QUICKPAY_API_KEY`. It cannot be
combined with `--merchant` or a project binding; unset it before selecting a
saved merchant.

## Usage

Expand All @@ -55,6 +66,55 @@ Use `--help` to explore any command and its options:
quickpay callbacks:watch --help
```

### Select a merchant

```bash
quickpay merchants:add shop-b
quickpay merchants:list
quickpay merchants:select shop-a
quickpay merchants:select # Interactive picker
quickpay merchants:active # Local configuration, no API request
quickpay merchants:active --check --json # Verify API access for automation
quickpay payments:list --merchant=shop-b
```

The first saved merchant becomes the global default. Adding another merchant
keeps that default. Select a different default with `merchants:select`, or use
`--merchant` for one payment, callback or raw API command.

Bind the current project without changing the global default:

```bash
quickpay merchants:select shop-b --local
```

Run this from the project root. It writes `.quickpay-cli.json` containing only
`{"merchant":"shop-b"}`. Commands search the current directory and its parents
for the nearest binding. Ignore this file in Git when the selection is specific
to one developer. The selection order is an explicit `--merchant`, then the
nearest project binding, then the global default. Unknown names and invalid
bindings fail without silently switching to another merchant.

Each invocation resolves its merchant once. A running callback watcher stays
attached to that merchant when another command changes selection or removes the
saved credential. Mutation confirmations and watcher startup show the saved
label and credential source; payment confirmations also show the API's merchant
ID when available. These messages go to stderr in JSON mode.

`merchants:active` reports configuration. Add `--check` to verify the key against
Quickpay; a missing or rejected credential exits non-zero. `--json` keeps the
status machine-readable, with diagnostics on stderr.

Rotate a saved key with `quickpay merchants:add shop-a --replace`. The old key
remains intact if validation fails. `quickpay merchants:remove shop-a` removes
only local access, never the remote account. Removing the default leaves no
default; it does not select another saved merchant. Any binding to a removed
merchant will fail until you select a valid one.

This pre-v1 change replaces `login`, `logout` and `auth`. The old
`~/.config/quickpay/config.json` is not read or migrated. Add each merchant again
with `merchants:add`; after setup, you may remove the old file yourself.

### Watch for callbacks and relay them

```bash
Expand All @@ -69,6 +129,10 @@ payment changed after it becomes ready. Pass a payment ID or `--order-id` to
narrow the watch to one payment. Existing operations are not replayed. The
watcher runs in the foreground and has no JSON mode.

An explicit `QUICKPAY_PRIVATE_KEY` must belong to the selected merchant. Without
that override, the CLI fetches the signing key through the same merchant's API
access and keeps it only in memory.


```bash
quickpay callbacks:replay <payment-id> --to=<corrected-url>
Expand Down
19 changes: 18 additions & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@ Security-relevant areas include:
- callback private-key retrieval and in-memory-only handling;
- release artifact contents, checksums, and build provenance.

## Merchant credentials and selection

Saved merchant API keys live in `~/.config/quickpay-cli/config.json`, with a
`0700` directory and `0600` file. Credential updates use a persistent sibling
lock and atomic file replacement. `.quickpay-cli.json` selects an already saved
merchant by its local label; it cannot provide credentials, API headers or URLs.
Neither a binding nor selecting a merchant authorizes a payment mutation.

Explicit `--merchant` selection overrides a project binding and global default.
Malformed or stale bindings fail without selecting another merchant. A non-empty
`QUICKPAY_API_KEY` cannot be combined with an explicit or project selection.
Each command resolves its key once, so running watchers and inspect/confirm
workflows stay attached to the same credential even if local configuration
changes. Saved labels identify local configuration; actual merchant IDs come
from Quickpay resources, when available.

## Local callback forwarding

`callbacks:replay` and `callbacks:watch` send a signed payment resource to the
Expand All @@ -53,7 +69,8 @@ API key, Basic authorization, or Quickpay API request headers to the callback
destination. The account private key comes from a non-empty
`QUICKPAY_PRIVATE_KEY` or the authenticated `/account/private-key` endpoint and
is retained in process memory only. It is never written to the CLI config,
stdout, stderr, or local response summaries.
stdout, stderr, or local response summaries. A manually supplied
`QUICKPAY_PRIVATE_KEY` must belong to the selected merchant.

Because `--to` may name a public service, inspect it before running the command.
The explicit destination is the authorization to POST the payment payload; the
Expand Down
1 change: 1 addition & 0 deletions app/Commands/Api/ApiRequestCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public function handle(
);

if ($request->mutation) {
$this->writeMerchantContext($authenticated);
$this->writeSafetyLine("Quickpay API request: {$request->method}");

if (! $confirmation->approve(
Expand Down
94 changes: 0 additions & 94 deletions app/Commands/Authentication/AuthCommand.php

This file was deleted.

60 changes: 0 additions & 60 deletions app/Commands/Authentication/LoginCommand.php

This file was deleted.

36 changes: 0 additions & 36 deletions app/Commands/Authentication/LogoutCommand.php

This file was deleted.

1 change: 1 addition & 0 deletions app/Commands/Callbacks/WatchCallbacksCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ function (AuthenticatedQuickpay $authenticated) use ($watch): int {
$interval = $this->interval();
$deliveryAttempts = $this->deliveryAttempts();
$apiKey = $authenticated->apiKey->value();
$this->writeMerchantContext($authenticated);

if ($request->paymentId !== null || $request->orderId !== null) {
$this->info(ResponseBodySanitizer::terminalLine(
Expand Down
Loading
Loading