Skip to content

Commit dc3177a

Browse files
authored
Update documentation (#102)
1 parent d2ea649 commit dc3177a

6 files changed

Lines changed: 237 additions & 100 deletions

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ dependencies at [getcomposer.org](http://getcomposer.org).
5151

5252
This section documents the different objects available through the SDK and how to use them.
5353

54+
For **runnable CLI demos** (case file E2E script and OAuth sample scripts), see [docs/cli-examples.md](docs/cli-examples.md).
55+
5456
### Authentication
5557

5658
The SDK supports three different methods of authentication:
@@ -153,7 +155,7 @@ connector using the already authorized `$oAuth` instance:
153155
Penneo\SDK\ApiConnector::initializeOAuth($oAuth);
154156
```
155157

156-
> :point_right: see a full, functional example in [docs/interactive_oauth_example.php](docs/interactive_oauth_example.php).
158+
> :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).
157159
158160
##### OAuth Token Storage
159161

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* End-to-end demo: build a case file with the Penneo SDK (multiple entity types).
55
*
66
* Run from the repository root after `composer install`:
7-
* php examples/casefile-e2e-demo.php
7+
* php docs/casefile-e2e-demo.php
88
*
99
* Authentication (pick one):
1010
* WSSE (default API v1 sandbox):

docs/cli-examples.md

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# CLI examples (Penneo SDK for PHP)
2+
3+
Runnable PHP scripts in this folder:
4+
5+
| Script | Purpose |
6+
|--------|---------|
7+
| [casefile-e2e-demo.php](casefile-e2e-demo.php) | Full flow: folder, case file, documents, signer, signing request, copy recipient, activate (see below). |
8+
| [programmatic_oauth_example.php](programmatic_oauth_example.php) | OAuth without browser (server-side / no redirect). |
9+
| [interactive_oauth_example.php](interactive_oauth_example.php) | OAuth with PKCE redirect flow (`?code=` callback). |
10+
11+
The root [README](../README.md) links to the OAuth examples from the authentication section.
12+
13+
### How to try the OAuth scripts
14+
15+
From the repository root (after `composer install`), replace the placeholder `clientId` / `clientSecret` / `apiKey` / `apiSecret` (and for interactive, `redirectUri` registered at Penneo), then:
16+
17+
```bash
18+
# Programmatic — needs valid OAuth client + API key/secret; reaches API on CaseFile::persist()
19+
php docs/programmatic_oauth_example.php
20+
```
21+
22+
```bash
23+
# Interactive — from repo or from docs/, `php interactive_oauth_example.php` prints the Penneo authorize URL
24+
# (header() alone shows nothing in CLI). To complete the flow use a browser:
25+
php -S 127.0.0.1:8080 -t docs
26+
# Open http://127.0.0.1:8080/interactive_oauth_example.php — login — callback with ?code= runs persist()
27+
```
28+
29+
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).
30+
31+
### Interactive OAuth on localhost (step by step)
32+
33+
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).
34+
35+
1. In Penneo (sandbox), add a redirect URI such as:
36+
`http://127.0.0.1:8080/interactive_oauth_example.php`
37+
38+
2. From the **repository root** (where `vendor/` lives):
39+
40+
```bash
41+
composer install
42+
export PENNEO_OAUTH_CLIENT_ID="your_client_id"
43+
export PENNEO_OAUTH_CLIENT_SECRET="your_client_secret"
44+
# Optional if you use localhost instead of 127.0.0.1 everywhere:
45+
# export PENNEO_OAUTH_REDIRECT_URI="http://localhost:8080/interactive_oauth_example.php"
46+
47+
php -S 127.0.0.1:8080 -t docs
48+
```
49+
50+
3. Open **exactly**:
51+
`http://127.0.0.1:8080/interactive_oauth_example.php`
52+
(not `…/interactive_oauth_example` without `.php` unless your server maps it)
53+
54+
4. Log in at Penneo; after redirect you should see plain text like `OK — Case file created. id=…`
55+
56+
**Typical failures**
57+
58+
| What you see | Cause |
59+
|--------------|--------|
60+
| `redirect_uri_mismatch` | Redirect URI in Penneo ≠ URL in browser or ≠ env var. |
61+
| `Missing PKCE code_verifier` | Started flow in one browser/session, callback in another; use one tab, avoid clearing cookies mid-flow. |
62+
| Blank or 404 | Wrong path: add `.php`; ensure `-t docs` and script is under `docs/`. |
63+
| 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). |
64+
65+
## `casefile-e2e-demo.php`
66+
67+
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.
68+
69+
### Prerequisites
70+
71+
From the repository root:
72+
73+
```bash
74+
composer install
75+
```
76+
77+
### Minimal run (WSSE, default sandbox)
78+
79+
The SDK defaults to `https://sandbox.penneo.com/api/v1/` unless you set a different `PENNEO_API_BASE`.
80+
81+
```bash
82+
export PENNEO_WSSE_KEY="your_key"
83+
export PENNEO_WSSE_SECRET="your_secret"
84+
85+
php docs/casefile-e2e-demo.php
86+
```
87+
88+
Optional (reseller account, acting on behalf of a customer):
89+
90+
```bash
91+
export PENNEO_WSSE_USER="12345" # Penneo customer id
92+
```
93+
94+
### Programmatic OAuth (API v3)
95+
96+
Same kind of setup as in the main README (`client_id`, `client_secret`, API key + API secret).
97+
98+
```bash
99+
export PENNEO_AUTH=oauth
100+
export PENNEO_OAUTH_ENV=sandbox # or production
101+
export PENNEO_CLIENT_ID="..."
102+
export PENNEO_CLIENT_SECRET="..."
103+
export PENNEO_API_KEY="..."
104+
export PENNEO_API_SECRET="..."
105+
106+
php docs/casefile-e2e-demo.php
107+
```
108+
109+
### Optional environment variables (demo)
110+
111+
| Variable | Purpose |
112+
|----------|---------|
113+
| `PENNEO_DEMO_PDF` | Path to your PDF; if unset, a minimal temporary PDF is generated. |
114+
| `PENNEO_DEMO_SIGNER_EMAIL` | E-mail on the `SigningRequest` (default: demo placeholder). |
115+
| `PENNEO_DEMO_COPY_EMAIL` | E-mail for the **CopyRecipient** (default: demo placeholder). |
116+
| `PENNEO_API_BASE` | API base URL for WSSE (see Production below). |
117+
118+
### Sandbox → production
119+
120+
**WSSE:** use **production** credentials and the live API base, for example:
121+
122+
```bash
123+
export PENNEO_API_BASE="https://app.penneo.com/api/v1/"
124+
export PENNEO_WSSE_KEY="..."
125+
export PENNEO_WSSE_SECRET="..."
126+
```
127+
128+
Confirm the exact API path (`v1` or other) with Penneo for your account.
129+
130+
**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).
131+
132+
Do not reuse sandbox keys or secrets in production.
133+
134+
### Troubleshooting
135+
136+
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.

docs/interactive_oauth_example.php

Lines changed: 95 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
<?php
22

3+
/**
4+
* Interactive OAuth (PKCE): open this script in a browser after starting a local server.
5+
*
6+
* 1) Register this exact Redirect URI in Penneo (OAuth client config):
7+
* http://127.0.0.1:8080/interactive_oauth_example.php
8+
* (or http://localhost:8080/... — must match character-for-character what you open in the browser)
9+
*
10+
* 2) From repository root:
11+
* export PENNEO_OAUTH_CLIENT_ID="..."
12+
* export PENNEO_OAUTH_CLIENT_SECRET="..."
13+
* php -S 127.0.0.1:8080 -t docs
14+
*
15+
* 3) Open in browser: http://127.0.0.1:8080/interactive_oauth_example.php
16+
*
17+
* Optional: PENNEO_OAUTH_REDIRECT_URI — if unset, defaults to 127.0.0.1 URL above.
18+
* Optional: PENNEO_OAUTH_ENV=sandbox|production (default sandbox).
19+
*/
20+
21+
declare(strict_types=1);
22+
23+
require_once dirname(__DIR__) . '/vendor/autoload.php';
24+
325
use Penneo\SDK\ApiConnector;
426
use Penneo\SDK\CaseFile;
527
use Penneo\SDK\OAuth\Config\Environment;
@@ -10,60 +32,109 @@
1032

1133
session_start();
1234

13-
// set up where to store the tokens - either use the provided session storage
14-
$tokenStorage = new SessionTokenStorage('optionalKeyToPlaceTokensInto');
35+
function interactiveOauthFail(string $message): void
36+
{
37+
if (PHP_SAPI === 'cli') {
38+
fwrite(STDERR, $message . PHP_EOL);
39+
} else {
40+
header('Content-Type: text/plain; charset=utf-8');
41+
echo $message;
42+
}
43+
exit(1);
44+
}
45+
46+
$clientId = getenv('PENNEO_OAUTH_CLIENT_ID') ?: '';
47+
$clientSecret = getenv('PENNEO_OAUTH_CLIENT_SECRET') ?: '';
48+
$redirectUri = getenv('PENNEO_OAUTH_REDIRECT_URI') ?: 'http://127.0.0.1:8080/interactive_oauth_example.php';
49+
$environment = getenv('PENNEO_OAUTH_ENV') ?: Environment::SANDBOX;
50+
51+
if ($clientId === '' || $clientSecret === '') {
52+
interactiveOauthFail(
53+
"Set environment variables before running:\n"
54+
. " export PENNEO_OAUTH_CLIENT_ID='...'\n"
55+
. " export PENNEO_OAUTH_CLIENT_SECRET='...'\n"
56+
. "Optional:\n"
57+
. " export PENNEO_OAUTH_REDIRECT_URI='http://127.0.0.1:8080/interactive_oauth_example.php'\n"
58+
. " export PENNEO_OAUTH_ENV=sandbox\n"
59+
. "\n"
60+
. 'The redirect URI must be registered identically in your Penneo OAuth client.'
61+
);
62+
}
63+
64+
if (!Environment::isSupported($environment)) {
65+
interactiveOauthFail("PENNEO_OAUTH_ENV must be 'sandbox' or 'production'. Got: {$environment}");
66+
}
1567

16-
// or build a custom one by implementing the interface
17-
// $tokenStorage = new class implements \Penneo\SDK\OAuth\Tokens\TokenStorage {};
68+
$tokenStorage = new SessionTokenStorage('optionalKeyToPlaceTokensInto');
1869

1970
$penneoOAuth = OAuthBuilder::start()
20-
->setEnvironment(Environment::SANDBOX)
21-
->setClientId('clientId') // <- the credentials provided by Penneo
22-
->setClientSecret('clientSecret') // <-
23-
->setRedirectUri('http://dev.php.local') // the exact URL you provided to Penneo
71+
->setEnvironment($environment)
72+
->setClientId($clientId)
73+
->setClientSecret($clientSecret)
74+
->setRedirectUri($redirectUri)
2475
->setTokenStorage($tokenStorage)
2576
->build();
2677

2778
if (isset($_GET['error'])) {
28-
// something went wrong - handle the error
29-
print_r($_GET['error']);
30-
exit;
31-
} elseif (isset($_GET['code'])) {
32-
// we are returning with a code after authorization
79+
$detail = $_GET['error_description'] ?? '';
80+
header('Content-Type: text/plain; charset=utf-8');
81+
echo 'OAuth error: ' . $_GET['error'] . ($detail !== '' ? "\n" . $detail : '');
82+
exit(1);
83+
}
84+
85+
if (isset($_GET['code'])) {
86+
if (empty($_SESSION['code_verifier'])) {
87+
interactiveOauthFail(
88+
"Missing PKCE code_verifier in session. Open this URL first in the same browser (no private window switch):\n"
89+
. $redirectUri
90+
);
91+
}
3392
try {
3493
$penneoOAuth->exchangeAuthCode($_GET['code'], $_SESSION['code_verifier']);
3594
} catch (PenneoSdkRuntimeException $e) {
36-
/// something went wrong - handle the error
37-
print_r($e);
38-
exit;
95+
header('Content-Type: text/plain; charset=utf-8');
96+
echo 'Token exchange failed: ' . $e->getMessage();
97+
exit(1);
3998
}
40-
41-
// optionally, handle the returned state
42-
print_r($_GET['state']);
4399
} elseif (!$penneoOAuth->isAuthorized()) {
44-
// set up the code challenge
45100
$pkce = new PKCE();
46101
$codeVerifier = $pkce->getCodeVerifier();
47102
$_SESSION['code_verifier'] = $codeVerifier;
48103

49104
try {
50-
// build the redirect URL for authorization
51105
$url = $penneoOAuth->buildRedirectUrl(
52106
['full_access'],
53107
$pkce->getCodeChallenge($codeVerifier)
54108
);
55109

110+
if (PHP_SAPI === 'cli') {
111+
fwrite(
112+
STDOUT,
113+
"Open in a browser (after: php -S 127.0.0.1:8080 -t docs):\n"
114+
. $redirectUri . "\n\n"
115+
. "Or paste this authorize URL:\n" . $url . "\n"
116+
);
117+
exit(0);
118+
}
119+
56120
header('Location: ' . $url);
57121
exit;
58122
} catch (PenneoSdkRuntimeException $e) {
59-
// something went wrong - handle the error
60-
var_dump($e);
123+
if (PHP_SAPI === 'cli') {
124+
var_dump($e);
125+
exit(1);
126+
}
127+
header('Content-Type: text/plain; charset=utf-8');
128+
echo 'Could not build authorize URL: ' . $e->getMessage();
129+
exit(1);
61130
}
62131
}
63132

64-
// the OAuth flow has finished, so we can start using the API
65133
ApiConnector::initializeOAuth($penneoOAuth);
66134

67135
$casefile = new CaseFile();
68136
$casefile->setTitle('new test casefile from PHP');
69137
CaseFile::persist($casefile);
138+
139+
header('Content-Type: text/plain; charset=utf-8');
140+
echo 'OK — Case file created. id=' . (string) $casefile->getId() . PHP_EOL;

docs/programmatic_oauth_example.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,5 @@
2828
$casefile = new CaseFile();
2929
$casefile->setTitle('new test casefile from PHP');
3030
CaseFile::persist($casefile);
31+
32+
var_dump($casefile);

0 commit comments

Comments
 (0)