From 8bfcbfe4c54b82b1fcf79ec4eac5e236fdfbdf17 Mon Sep 17 00:00:00 2001 From: Andrei Ghinea Date: Wed, 6 May 2026 17:43:22 +0300 Subject: [PATCH] Update documentation --- README.md | 4 +- {examples => docs}/casefile-e2e-demo.php | 2 +- docs/cli-examples.md | 136 +++++++++++++++++++++++ docs/interactive_oauth_example.php | 119 ++++++++++++++++---- docs/programmatic_oauth_example.php | 2 + examples/README.md | 74 ------------ 6 files changed, 237 insertions(+), 100 deletions(-) rename {examples => docs}/casefile-e2e-demo.php (99%) create mode 100644 docs/cli-examples.md delete mode 100644 examples/README.md diff --git a/README.md b/README.md index 388aa25..e4885d9 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,8 @@ dependencies at [getcomposer.org](http://getcomposer.org). This section documents the different objects available through the SDK and how to use them. +For **runnable CLI demos** (case file E2E script and OAuth sample scripts), see [docs/cli-examples.md](docs/cli-examples.md). + ### Authentication The SDK supports three different methods of authentication: @@ -153,7 +155,7 @@ connector using the already authorized `$oAuth` instance: Penneo\SDK\ApiConnector::initializeOAuth($oAuth); ``` -> :point_right: see a full, functional example in [docs/interactive_oauth_example.php](docs/interactive_oauth_example.php). +> :point_right: see a full, configurable example in [docs/interactive_oauth_example.php](docs/interactive_oauth_example.php) and **localhost setup** in [docs/cli-examples.md](docs/cli-examples.md#interactive-oauth-on-localhost-step-by-step). ##### OAuth Token Storage diff --git a/examples/casefile-e2e-demo.php b/docs/casefile-e2e-demo.php similarity index 99% rename from examples/casefile-e2e-demo.php rename to docs/casefile-e2e-demo.php index d4bb8c9..fef48c1 100644 --- a/examples/casefile-e2e-demo.php +++ b/docs/casefile-e2e-demo.php @@ -4,7 +4,7 @@ * End-to-end demo: build a case file with the Penneo SDK (multiple entity types). * * Run from the repository root after `composer install`: - * php examples/casefile-e2e-demo.php + * php docs/casefile-e2e-demo.php * * Authentication (pick one): * WSSE (default API v1 sandbox): diff --git a/docs/cli-examples.md b/docs/cli-examples.md new file mode 100644 index 0000000..6d7ca8e --- /dev/null +++ b/docs/cli-examples.md @@ -0,0 +1,136 @@ +# CLI examples (Penneo SDK for PHP) + +Runnable PHP scripts in this folder: + +| Script | Purpose | +|--------|---------| +| [casefile-e2e-demo.php](casefile-e2e-demo.php) | Full flow: folder, case file, documents, signer, signing request, copy recipient, activate (see below). | +| [programmatic_oauth_example.php](programmatic_oauth_example.php) | OAuth without browser (server-side / no redirect). | +| [interactive_oauth_example.php](interactive_oauth_example.php) | OAuth with PKCE redirect flow (`?code=` callback). | + +The root [README](../README.md) links to the OAuth examples from the authentication section. + +### How to try the OAuth scripts + +From the repository root (after `composer install`), replace the placeholder `clientId` / `clientSecret` / `apiKey` / `apiSecret` (and for interactive, `redirectUri` registered at Penneo), then: + +```bash +# Programmatic — needs valid OAuth client + API key/secret; reaches API on CaseFile::persist() +php docs/programmatic_oauth_example.php +``` + +```bash +# Interactive — from repo or from docs/, `php interactive_oauth_example.php` prints the Penneo authorize URL +# (header() alone shows nothing in CLI). To complete the flow use a browser: +php -S 127.0.0.1:8080 -t docs +# Open http://127.0.0.1:8080/interactive_oauth_example.php — login — callback with ?code= runs persist() +``` + +Without real credentials, programmatic fails at token exchange (e.g. HTTP 401) and interactive still performs the redirect to Penneo’s authorize URL (verify in browser). + +### Interactive OAuth on localhost (step by step) + +Penneo sends the user back to an URL you register on the OAuth client. **That URL, the address bar in the browser, and `PENNEO_OAUTH_REDIRECT_URI` must be identical** (including `http` vs `https`, `localhost` vs `127.0.0.1`, port, path, and **`/interactive_oauth_example.php`** — the file extension is required). + +1. In Penneo (sandbox), add a redirect URI such as: + `http://127.0.0.1:8080/interactive_oauth_example.php` + +2. From the **repository root** (where `vendor/` lives): + + ```bash + composer install + export PENNEO_OAUTH_CLIENT_ID="your_client_id" + export PENNEO_OAUTH_CLIENT_SECRET="your_client_secret" + # Optional if you use localhost instead of 127.0.0.1 everywhere: + # export PENNEO_OAUTH_REDIRECT_URI="http://localhost:8080/interactive_oauth_example.php" + + php -S 127.0.0.1:8080 -t docs + ``` + +3. Open **exactly**: + `http://127.0.0.1:8080/interactive_oauth_example.php` + (not `…/interactive_oauth_example` without `.php` unless your server maps it) + +4. Log in at Penneo; after redirect you should see plain text like `OK — Case file created. id=…` + +**Typical failures** + +| What you see | Cause | +|--------------|--------| +| `redirect_uri_mismatch` | Redirect URI in Penneo ≠ URL in browser or ≠ env var. | +| `Missing PKCE code_verifier` | Started flow in one browser/session, callback in another; use one tab, avoid clearing cookies mid-flow. | +| Blank or 404 | Wrong path: add `.php`; ensure `-t docs` and script is under `docs/`. | +| Prompt for env vars | Export `PENNEO_OAUTH_CLIENT_ID` / `PENNEO_OAUTH_CLIENT_SECRET` in the **same terminal** where you run `php -S` (child inherits env). | + +## `casefile-e2e-demo.php` + +Demo script: creates a **folder**, a **case file** with **annex document + signable document**, **signer**, **signature line**, **signing request**, **copy recipient**, then **activates** the case file (no `send()` — no automated outbound e-mail). It prints the **signing link** at the end. + +### Prerequisites + +From the repository root: + +```bash +composer install +``` + +### Minimal run (WSSE, default sandbox) + +The SDK defaults to `https://sandbox.penneo.com/api/v1/` unless you set a different `PENNEO_API_BASE`. + +```bash +export PENNEO_WSSE_KEY="your_key" +export PENNEO_WSSE_SECRET="your_secret" + +php docs/casefile-e2e-demo.php +``` + +Optional (reseller account, acting on behalf of a customer): + +```bash +export PENNEO_WSSE_USER="12345" # Penneo customer id +``` + +### Programmatic OAuth (API v3) + +Same kind of setup as in the main README (`client_id`, `client_secret`, API key + API secret). + +```bash +export PENNEO_AUTH=oauth +export PENNEO_OAUTH_ENV=sandbox # or production +export PENNEO_CLIENT_ID="..." +export PENNEO_CLIENT_SECRET="..." +export PENNEO_API_KEY="..." +export PENNEO_API_SECRET="..." + +php docs/casefile-e2e-demo.php +``` + +### Optional environment variables (demo) + +| Variable | Purpose | +|----------|---------| +| `PENNEO_DEMO_PDF` | Path to your PDF; if unset, a minimal temporary PDF is generated. | +| `PENNEO_DEMO_SIGNER_EMAIL` | E-mail on the `SigningRequest` (default: demo placeholder). | +| `PENNEO_DEMO_COPY_EMAIL` | E-mail for the **CopyRecipient** (default: demo placeholder). | +| `PENNEO_API_BASE` | API base URL for WSSE (see Production below). | + +### Sandbox → production + +**WSSE:** use **production** credentials and the live API base, for example: + +```bash +export PENNEO_API_BASE="https://app.penneo.com/api/v1/" +export PENNEO_WSSE_KEY="..." +export PENNEO_WSSE_SECRET="..." +``` + +Confirm the exact API path (`v1` or other) with Penneo for your account. + +**OAuth:** set `PENNEO_OAUTH_ENV=production` and production credentials (client + keys). The SDK will use the production signing API host (`https://app.penneo.com` / `api/v3/` as configured). + +Do not reuse sandbox keys or secrets in production. + +### Troubleshooting + +For clearer HTTP errors you can use `ApiConnector::throwExceptions(true)` in your code (the demo script already does). For production, `ApiConnector::setLogger(...)` helps capture request ids for support. diff --git a/docs/interactive_oauth_example.php b/docs/interactive_oauth_example.php index 9117afb..96188fd 100644 --- a/docs/interactive_oauth_example.php +++ b/docs/interactive_oauth_example.php @@ -1,5 +1,27 @@ setEnvironment(Environment::SANDBOX) - ->setClientId('clientId') // <- the credentials provided by Penneo - ->setClientSecret('clientSecret') // <- - ->setRedirectUri('http://dev.php.local') // the exact URL you provided to Penneo + ->setEnvironment($environment) + ->setClientId($clientId) + ->setClientSecret($clientSecret) + ->setRedirectUri($redirectUri) ->setTokenStorage($tokenStorage) ->build(); if (isset($_GET['error'])) { - // something went wrong - handle the error - print_r($_GET['error']); - exit; -} elseif (isset($_GET['code'])) { - // we are returning with a code after authorization + $detail = $_GET['error_description'] ?? ''; + header('Content-Type: text/plain; charset=utf-8'); + echo 'OAuth error: ' . $_GET['error'] . ($detail !== '' ? "\n" . $detail : ''); + exit(1); +} + +if (isset($_GET['code'])) { + if (empty($_SESSION['code_verifier'])) { + interactiveOauthFail( + "Missing PKCE code_verifier in session. Open this URL first in the same browser (no private window switch):\n" + . $redirectUri + ); + } try { $penneoOAuth->exchangeAuthCode($_GET['code'], $_SESSION['code_verifier']); } catch (PenneoSdkRuntimeException $e) { - /// something went wrong - handle the error - print_r($e); - exit; + header('Content-Type: text/plain; charset=utf-8'); + echo 'Token exchange failed: ' . $e->getMessage(); + exit(1); } - - // optionally, handle the returned state - print_r($_GET['state']); } elseif (!$penneoOAuth->isAuthorized()) { - // set up the code challenge $pkce = new PKCE(); $codeVerifier = $pkce->getCodeVerifier(); $_SESSION['code_verifier'] = $codeVerifier; try { - // build the redirect URL for authorization $url = $penneoOAuth->buildRedirectUrl( ['full_access'], $pkce->getCodeChallenge($codeVerifier) ); + if (PHP_SAPI === 'cli') { + fwrite( + STDOUT, + "Open in a browser (after: php -S 127.0.0.1:8080 -t docs):\n" + . $redirectUri . "\n\n" + . "Or paste this authorize URL:\n" . $url . "\n" + ); + exit(0); + } + header('Location: ' . $url); exit; } catch (PenneoSdkRuntimeException $e) { - // something went wrong - handle the error - var_dump($e); + if (PHP_SAPI === 'cli') { + var_dump($e); + exit(1); + } + header('Content-Type: text/plain; charset=utf-8'); + echo 'Could not build authorize URL: ' . $e->getMessage(); + exit(1); } } -// the OAuth flow has finished, so we can start using the API ApiConnector::initializeOAuth($penneoOAuth); $casefile = new CaseFile(); $casefile->setTitle('new test casefile from PHP'); CaseFile::persist($casefile); + +header('Content-Type: text/plain; charset=utf-8'); +echo 'OK — Case file created. id=' . (string) $casefile->getId() . PHP_EOL; diff --git a/docs/programmatic_oauth_example.php b/docs/programmatic_oauth_example.php index 8727447..5cd0958 100644 --- a/docs/programmatic_oauth_example.php +++ b/docs/programmatic_oauth_example.php @@ -28,3 +28,5 @@ $casefile = new CaseFile(); $casefile->setTitle('new test casefile from PHP'); CaseFile::persist($casefile); + +var_dump($casefile); diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index d79e586..0000000 --- a/examples/README.md +++ /dev/null @@ -1,74 +0,0 @@ -# CLI examples (Penneo SDK PHP) - -## `casefile-e2e-demo.php` - -Demo script: creates a **folder**, a **case file** with **annex document + signable document**, **signer**, **signature line**, **signing request**, **copy recipient**, then **activates** the case file (no `send()` — no automated outbound e-mail). It prints the **signing link** at the end. - -### Prerequisites - -From the repository root: - -```bash -composer install -``` - -### Minimal run (WSSE, default sandbox) - -The SDK defaults to `https://sandbox.penneo.com/api/v1/` unless you set a different `PENNEO_API_BASE`. - -```bash -export PENNEO_WSSE_KEY="your_key" -export PENNEO_WSSE_SECRET="your_secret" - -php examples/casefile-e2e-demo.php -``` - -Optional (reseller account, acting on behalf of a customer): - -```bash -export PENNEO_WSSE_USER="12345" # Penneo customer id -``` - -### Programmatic OAuth (API v3) - -Same kind of setup as in the main README (`client_id`, `client_secret`, API key + API secret). - -```bash -export PENNEO_AUTH=oauth -export PENNEO_OAUTH_ENV=sandbox # or production -export PENNEO_CLIENT_ID="..." -export PENNEO_CLIENT_SECRET="..." -export PENNEO_API_KEY="..." -export PENNEO_API_SECRET="..." - -php examples/casefile-e2e-demo.php -``` - -### Optional environment variables (demo) - -| Variable | Purpose | -|----------|---------| -| `PENNEO_DEMO_PDF` | Path to your PDF; if unset, a minimal temporary PDF is generated. | -| `PENNEO_DEMO_SIGNER_EMAIL` | E-mail on the `SigningRequest` (default: demo placeholder). | -| `PENNEO_DEMO_COPY_EMAIL` | E-mail for the **CopyRecipient** (default: demo placeholder). | -| `PENNEO_API_BASE` | API base URL for WSSE (see Production below). | - -### Sandbox → production - -**WSSE:** use **production** credentials and the live API base, for example: - -```bash -export PENNEO_API_BASE="https://app.penneo.com/api/v1/" -export PENNEO_WSSE_KEY="..." -export PENNEO_WSSE_SECRET="..." -``` - -Confirm the exact API path (`v1` or other) with Penneo for your account. - -**OAuth:** set `PENNEO_OAUTH_ENV=production` and production credentials (client + keys). The SDK will use the production signing API host (`https://app.penneo.com` / `api/v3/` as configured). - -Do not reuse sandbox keys or secrets in production. - -### Troubleshooting - -For clearer HTTP errors you can use `ApiConnector::throwExceptions(true)` in your code (the demo script already does). For production, `ApiConnector::setLogger(...)` helps capture request ids for support.