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
2 changes: 2 additions & 0 deletions demo/self-report/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
vendor/
README.md
2 changes: 2 additions & 0 deletions demo/self-report/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
vendor/
fly.toml
82 changes: 82 additions & 0 deletions demo/self-report/DEPLOY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Deploying the Self-Report Demo Site (Fly.io)

The live instance is one always-warm 256 MB machine (~$2–3/mo), TLS
automatic, remote builds (no local arch concerns). Its hostname is
deliberately kept out of this public repo — find it in the sandbox
merchant-site registration (or ask the team). `fly.toml.example` is the
canonical config template; it pins `auto_stop_machines = 'off'` /
`min_machines_running = 1` because a probe target must never cold-start.

> An AWS App Runner variant of this runbook existed previously; it was
> dropped after IAM friction (`iam:PassRole`) — see git history if ever
> needed.

## One-time setup

```bash
brew install flyctl
flyctl auth login # browser flow (signup included)

cd demo/self-report
cp fly.toml.example fly.toml # fly.toml is gitignored — app name stays local
flyctl apps create <your-app> # then set the same name in fly.toml
flyctl secrets set SUPERTAB_MERCHANT_API_KEY=placeholder SUPERTAB_ENFORCEMENT=observe --stage
flyctl deploy --ha=false # single machine; fly.toml does the rest
```

The API key starts as `placeholder` on purpose: sandbox registration needs
the site's domain, which only exists after the first deploy. Everything
probe-related works meanwhile — `/healthz` short-circuits before the config
check and challenge verification uses the public platform JWKS; only
analytics delivery would 401-and-drop.

## Smoke test

```bash
HOST=<your-app>.fly.dev
curl -s https://$HOST/healthz # → ok
curl -si https://$HOST/.well-known/supertab/status | head -5 # → 404 {"supertab":true}, no-store
curl -s https://$HOST/ | grep "SDK version" # → the pinned SDK version
```

## Register the site in sandbox and set the real key (required)

1. Register `https://<your-app>.fly.dev` as a merchant
website in the **sandbox** environment — registration issues the
merchant API key. The backend only mints status challenges with `aud` =
a registered origin; unregistered probes silently get the decoy.
2. Swap in the real key (this alone triggers a redeploy, ~30 s):

```bash
flyctl secrets set SUPERTAB_MERCHANT_API_KEY=<real-key>
```

## The end-to-end probe

Fire a backend live-health check (`self_report`) for the registered site.
Expected: `200` with `runtime: null`, `sdkVersion`,
`component: {kind: "php-sdk", version}`, `enforcement: "observe"`,
`eventReporting: true`.

Note: the backend resolves only `ts-sdk` against a registry so far
(laterpay/supertab-connect#1094); `php-sdk` degrades to "show version, no
nudge" until its resolver lands. Expected, not a failure.

## Updating (each new SDK release)

```bash
cd demo/self-report
# bump the pin in composer.json, then:
composer update getsupertab/connect-sdk-php
flyctl deploy --ha=false
```

Commit the pin + lockfile change back to the repo.

**Config experiments** (no rebuild): `flyctl secrets set
SUPERTAB_ENFORCEMENT=enforce` (or `SUPERTAB_ANALYTICS=0`,
`SUPERTAB_BASE_URL=…`) — each set redeploys, and the next probe reflects
the new values.

**Ops one-liners**: `flyctl status` (machine state), `flyctl logs`
(live tail), `flyctl apps destroy <your-app>` (teardown).
20 changes: 20 additions & 0 deletions demo/self-report/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Dependencies — resolved from the committed lockfile for reproducible builds.
FROM composer:2 AS deps
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-interaction --no-progress

FROM php:8.3-apache

# The app listens on 8080 (see fly.toml's internal_port; also the default
# for most container platforms); route every request that isn't an
# existing file to the front controller.
RUN sed -i 's/^Listen 80$/Listen 8080/' /etc/apache2/ports.conf \
&& sed -i 's/<VirtualHost \*:80>/<VirtualHost *:8080>/' /etc/apache2/sites-available/000-default.conf \
&& printf 'FallbackResource /index.php\nSetEnvIf Authorization "(.+)" HTTP_AUTHORIZATION=$1\n' > /etc/apache2/conf-available/fallback.conf \
&& a2enconf fallback

COPY --from=deps /app/vendor /var/www/html/vendor
COPY index.php /var/www/html/

EXPOSE 8080
61 changes: 61 additions & 0 deletions demo/self-report/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Self-Report Demo Site

Vanilla-PHP publisher for testing the `/.well-known/supertab/status`
self-report endpoint end-to-end against the real (sandbox) Supertab Connect
API. Every request flows through `SupertabConnect::handleRequest()` — the
status endpoint is served by the SDK itself, with zero endpoint-specific
code in this app. It also serves as the canonical "plain PHP" integration
reference.

Unlike the sibling `demo/` CLI demo (self-contained, mock API), this app
pins the **released Packagist SDK** and talks to the real API. Testing a new
SDK release = bump the pin in `composer.json`, rebuild, push.

## Configuration

| Env var | Default | Purpose |
|---------|---------|---------|
| `SUPERTAB_MERCHANT_API_KEY` | — (required) | Sandbox merchant API key |
| `SUPERTAB_BASE_URL` | `https://api-connect.sbx.supertab.co` | API base URL |
| `SUPERTAB_ENFORCEMENT` | `observe` | `disabled` \| `observe` \| `enforce` — reflected in the status payload |
| `SUPERTAB_ANALYTICS` | on (`0`/`false`/`off` to disable) | Toggles analytics → the payload's `eventReporting` |

## Run locally

```bash
composer install
SUPERTAB_MERCHANT_API_KEY=<key> php -S localhost:8080 index.php
```

Smoke checks:

```bash
curl -s localhost:8080/healthz # → ok
curl -si localhost:8080/.well-known/supertab/status | head -5 # → 404 {"supertab":true}
curl -s localhost:8080/ | head -3 # → demo HTML page
```

## Deploy

See [DEPLOY.md](DEPLOY.md) — the site runs on Fly.io as one always-warm
machine (`fly.toml.example` committed here; the live hostname is kept out
of the repo — see the sandbox merchant-site registration).

## Register the site (required for probes)

The backend only mints status challenges (`aud` = origin) for origins it
knows. Register the service URL — `https://<your-app>.fly.dev` — as a
merchant website in **sandbox**. If the URL changes (app recreated),
re-register.

## Probe flow

1. Unauthenticated: `curl -si https://<host>/.well-known/supertab/status`
→ `404` + `{"supertab":true}` + `Cache-Control: no-store` (decoy).
2. Garbage bearer: same decoy, never a 500 (challenge verification fails
closed).
3. Backend live-health probe for the registered site → `200` with
`{runtime, sdkVersion, component: {kind: "php-sdk", version},
enforcement, eventReporting}`.
4. Flip `SUPERTAB_ENFORCEMENT` / `SUPERTAB_ANALYTICS` on the service →
next probe reflects the change.
11 changes: 11 additions & 0 deletions demo/self-report/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "supertab/self-report-demo",
"description": "Vanilla-PHP demo site for testing the /.well-known/supertab/status self-report endpoint against the real API.",
"type": "project",
"license": "MIT",
"require": {
"php": ">=8.1",
"getsupertab/connect-sdk-php": "1.4.0-beta.9"
},
"minimum-stability": "stable"
}
146 changes: 146 additions & 0 deletions demo/self-report/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 29 additions & 0 deletions demo/self-report/fly.toml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Fly.io config template for the self-report demo site (see DEPLOY.md).
# Copy to fly.toml and set your app name (the real deployment's name/URL
# is deliberately kept out of the repo):
# cp fly.toml.example fly.toml && fly apps create <your-app>
# Always-warm single machine: this is a probe target — it must never
# scale to zero or backend status probes would hit cold starts.

app = 'REPLACE-WITH-YOUR-APP-NAME'
primary_region = 'fra'

[build]

[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = 'off'
auto_start_machines = true
min_machines_running = 1

[[http_service.checks]]
interval = '30s'
timeout = '5s'
grace_period = '10s'
method = 'GET'
path = '/healthz'

[[vm]]
size = 'shared-cpu-1x'
memory = '256mb'
Loading
Loading