feat: connect Tiny through its v3 API with OAuth
All checks were successful
Build and deploy / Validate source (push) Successful in 9s
Build and deploy / Integration suite on a real stack (push) Successful in 2m49s
Build and deploy / Secret scan and release gate (push) Successful in 9s
Build and deploy / Publish images and notify Portainer (push) Has been skipped

Tiny v3 replaces the v2 token adapter. An operator connects Tiny once from
the Kanban; the callback is authorised by a single-use state, because Tiny's
cross-site redirect does not carry the SameSite=Strict operator cookie.
Tokens are kept in provider_tokens, the refresh token rotates under a row
lock, and the worker keeps the connection alive while order creation is off.

Orders find or create the customer's contact by CNPJ, then POST /pedidos
with product ids from TINY_PRODUCT_TEXTIL_FOLHA, _TEXTIL_AVULSA, _UV_FOLHA
and _UV_AVULSA and numeroOrdemCompra DTF-<number>; a retry searches the
customer's recent orders for that number first. The product settings avoid a
_FILE suffix, which the secrets loader reads as a secret file path.

Production passes the application credentials through but keeps
TINY_ADAPTER fake: Tiny has no sandbox, so creating real orders waits for a
supervised test. compose.providers.yaml gives the local API and worker an
internet route for provider testing; the default local stack still has none.

Verified with the full CI integration sequence locally, including the new
tiny_oauth_test against the real database.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Cauê Faleiros
2026-09-24 12:46:09 -03:00
parent c18b9e5b87
commit e3d5558198
17 changed files with 613 additions and 131 deletions

View File

@@ -99,6 +99,20 @@ CREATE TABLE IF NOT EXISTS dtf_local.payment_intents (
created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(provider, provider_payment_id)
);
-- OAuth connections to providers (Tiny). One row per provider; the refresh
-- token rotates on use, so it lives here, never in configuration.
CREATE TABLE IF NOT EXISTS dtf_local.provider_tokens (
provider text PRIMARY KEY, access_token text NOT NULL, refresh_token text NOT NULL,
access_expires_at timestamptz NOT NULL, refresh_expires_at timestamptz,
connected_by text NOT NULL, connected_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
-- Single-use states for an operator-started OAuth connection. They protect the
-- callback, which arrives cross-site without the operator's cookie.
CREATE TABLE IF NOT EXISTS dtf_local.oauth_states (
state text PRIMARY KEY, provider text NOT NULL, operator text NOT NULL,
expires_at timestamptz NOT NULL
);
-- The print file generated from each paid item's approved layout. One row per
-- item: the worker claims it, renders, and records either the file or why the
-- item has to be prepared by hand. Regenerating replaces the row's result.