client_secret, it is
sent the way curl -u sk_live_...: sends it.
Register a Stripe App builds the app and tests it.
The server
stripe_server.py
secret_key_basic is the secret key as Basic auth. default_expires_in is the
lifetime Stripe’s token response leaves out: Refresh behaviour.
revocation uninstalls the app, which is how Stripe ends a grant:
Disconnecting.
Connecting a user
stripe_users.py
Before you publish
Until the app is published, that consent link is not active. Stripe honours only the link on the app’s External test tab, whose path names a channel:/oauth/v2/chnlink_.../authorize. Point the flow at that path while you test,
and install from an account other than the one that owns the app. Stripe issues
a token for the owner’s own account and then refuses it on every call.
Refresh behaviour
The access token is a restricted key,rk_live_... or rk_test_..., limited
to the permissions in the app’s manifest, and it goes out as Bearer like any
other. Stripe access tokens last an hour, and its token response doesn’t say
so. The constant declares it with default_expires_in, so the client renews
each token shortly before the hour instead of learning from a refused call.
Stripe’s refresh tokens roll on every use, and a refresh revokes the previous
access token within seconds. So on_refresh stores the new access token, its
expiry and the refresh token, and stored_grant reads them back: every
process uses the token one refresh produced instead of refreshing on its own.
Processes that find the token due at the same moment still refresh together,
and each refresh revokes the token of the one before. Serving from more than
one process, pass refresh_lock as well:
A lock around the refresh has the
code and what it measured without one.
A pasted restricted key
Not every product wants to publish a Stripe App. The other path is a restricted key (rk_...):
the user creates it with only the permissions your tools use and pastes it, and
stripe_for returns StaticTokenProvider(row.api_key). Many tools start this way.
Each tool declares the permission
Stripe’s reference names
for its endpoint, as the stripe.RESTRICTED_KEY
kind. “Write permissions imply read
permissions”, so a reader takes either:
stripe_restricted_key.py
InsufficientScope with the permission
Stripe names (“Having the ‘rak_charge_read’ permission would allow this request
to continue”). Seven endpoints are missing from Stripe’s table and take their
resource’s permission, as every neighbouring endpoint does. Stripe’s own
refusals confirmed six of them, and 62 of the pack’s tools in all, against a
test-mode key holding no permission the pack uses
(scripts/live_stripe_permissions_check.py). Creating a refund is the one that
cannot be asked this way: Stripe looks the charge up first, and tells a key that
cannot read charges that the charge does not exist.
A Connect platform is different again. It acts on its connected accounts with
its own key and a Stripe-Account header, which is
per call, not a credential.
Disconnecting
Stripe has no endpoint that revokes a token: a grant is an install of your app, and it ends when the install does. Stripe’s App Installs API uninstalls it, andStripeAppUninstall is that, declared as the revocation. It looks the install
up for the stripe_user_id Stripe’s token response carries, then uninstalls
it, with your secret key. The client keeps the account from the exchange or any
refresh, and refreshes once first if it has seen neither, so nothing extra needs
storing. The call is the same as for every other provider:
stripe_disconnect.py
200, the install was gone within about
fifteen seconds, the access and refresh tokens were refused, Stripe sent
apps.install.deleted, and the next install from your link was a fresh one. An
account without the app returns False; a wrong app ID or another account’s
key raises.
revoke() returns once Stripe accepts the uninstall (status: uninstalling),
not once it is done. Charter stops using the grant at once; Stripe finishes
seconds later, 10 to 15 measured, and until then the old access token still
works. Treat the disconnect the way Stripe’s own states suggest: delete your
row, show the user “Disconnecting”, and offer Reconnect when
apps.install.deleted arrives, which your Connect webhook endpoint receives
with account set to theirs. Reconnecting before then can land on the install
still being removed. A script or a test that must not go on until the install
is gone declares StripeAppUninstall(app="app_...", wait_seconds=30) instead;
still uninstalling at the deadline is logged, and the call still returns
True. It waits holding the client’s refresh lock, which is one more reason to
keep it out of a server.
Not Connect’s oauth/deauthorize: Stripe documents it for Standard accounts
only, and measured against an app’s grant it ended the access but left the app
installed, so your install link answered “already installed” and issued tokens
Stripe refused until the user uninstalled the app from their Dashboard.
A pasted restricted key was made by the user, and only they can delete it, in
their Dashboard. Delete your row.
Where the rest is
- Register a Stripe App — the CLI, the manifest, the test link
- Getting the grant — the route, the session,
state, PKCE - Connect your users’ accounts — every pack’s providers, side by side
- Packs — the Stripe tools