# Stripe widgets (https://docs.getaviato.com/integrations/widgets)



Provider widgets appear beneath the overview on a record page, including custom summaries.
Configure them in your SDK. Your browser calls **your agent**, which resolves the linked
record and calls Stripe directly. Provider credentials and responses never pass through
Aviato's API, servers, or databases. The ordinary audit trail records the local record read
and its outcome, without provider IDs, credentials, or response bodies.

These APIs are included in the upcoming **0.2.0** SDKs and require an agent build with
provider-widget support. This documentation tracks the current source; committing these
changes does not publish the SDK packages or agent binary. The earlier 0.1.x releases do
not provide `StripePlugin`.

## Configure a read-only Stripe key [#configure-a-read-only-stripe-key]

Create a [Stripe restricted API key](https://docs.stripe.com/keys) with **Read** access to
Customers, Subscriptions, and Invoices as needed, with all other permissions set to **None**.
Do not grant any Write permissions. Use a test-mode key with test-mode IDs, or a live-mode key
with live-mode IDs. You can further restrict the key to your agent's outbound IP addresses.

Set `STRIPE_WIDGET_KEY` in the **customer agent's environment**, through your deployment's
secret manager. Never put its value into widget configuration, source control, the dashboard,
or an Aviato environment setting. Restart the agent after changing its environment.

The adapter accepts only `rk_test_…` or `rk_live_…` keys and makes only GET requests.
`readOnly: true` is your confirmation of the permissions configured in Stripe. It is **not
an automatic verification of key permissions**: the public Stripe API does not provide a
supported permission-introspection endpoint for a supplied key. A restricted key can still
have write permissions if configured incorrectly. Aviato does not attempt write requests to
test the key. Keep this distinction in mind when enforcing your own credential policy.

## TypeScript [#typescript]

`StripePlugin` implements the shared `IntegrationPlugin` interface. Configure credentials
once, add typed widgets, then install the integration with `plugin.use()`:

```ts
import { StripePlugin } from "@getaviato/sdk";

const stripe = new StripePlugin({
    credentialEnv: "STRIPE_WIDGET_KEY",
    readOnly: true,
});
const customerId = { field: "stripe_customer_id" };

plugin.use(stripe
    .customer({ collection: "customers", name: "stripe-customer", customerId })
    .subscriptions({ collection: "customers", name: "stripe-subscriptions", customerId })
    .invoices({ collection: "customers", name: "stripe-invoices", customerId }));
```

To show billing on an order, use `collection: "orders"` and
`customerId: { field: "stripe_customer_id", relation: "customer" }`. The relation must be a
`belongsTo` relation in your agent schema (introspected or declared using relation hints).
This first version supports one relation hop. The binding always comes from your database,
never a browser-supplied Stripe ID. Set `title` for custom copy, or omit it for a localized default.

Single-entity methods distinguish their binding types:

```ts
stripe.subscription({
    collection: "subscriptions", name: "stripe-subscription",
    subscriptionId: { field: "stripe_subscription_id" },
});
stripe.invoice({
    collection: "invoices", name: "stripe-invoice",
    invoiceId: { field: "stripe_invoice_id" },
});
plugin.use(stripe);
```

Passing `customerId` to `.subscription()`, or `invoiceId` to `.invoices()`, is a TypeScript
error. Provider and resource strings are handled by the plugin, not repeated in application code.

## Go [#go]

```go
stripe, err := aviato.NewStripePlugin(aviato.StripePluginOptions{
    CredentialEnv: "STRIPE_WIDGET_KEY", ReadOnly: true,
})
if err != nil {
    return err
}
stripe.Invoices(aviato.StripeCustomerWidget{
    Collection: "customers", Name: "stripe-invoices", Title: "Stripe invoices",
    CustomerID: aviato.RecordBinding{Field: "stripe_customer_id"},
})
if err := plugin.Use(stripe); err != nil {
    return err
}
```

`StripePlugin` satisfies `IntegrationPlugin`. `Customer`, `Subscriptions`, and `Invoices`
accept `StripeCustomerWidget`. `Subscription` accepts `StripeSubscriptionWidget` with
`SubscriptionID`; `Invoice` accepts `StripeInvoiceWidget` with `InvoiceID`.
Set `Relation: "customer"` on `RecordBinding` to resolve a related record.

## Laravel [#laravel]

```php
use Aviato\Laravel\Facades\Aviato;
use Aviato\Laravel\Integrations\RecordBinding;
use Aviato\Laravel\Integrations\StripePlugin;

$stripe = new StripePlugin(credentialEnv: 'STRIPE_WIDGET_KEY', readOnly: true);
$stripe->invoices(
    collection: 'customers',
    name: 'stripe-invoices',
    customerId: new RecordBinding(field: 'stripe_customer_id'),
    title: 'Stripe invoices',
);
Aviato::use($stripe);
```

`StripePlugin` implements `IntegrationPlugin`. The single-entity methods are
`subscription(..., subscriptionId: new RecordBinding(...))` and
`invoice(..., invoiceId: new RecordBinding(...))`. Use `relation: 'customer'` on the binding
for a related record. The key belongs in the agent's process environment even when your
plugin runs inside Laravel.

All SDKs snapshot an integration when it is installed. Later builder changes take effect only
when you call `use` / `Use` again. Registration is atomic: an invalid declaration installs no
widgets from that call. Re-registering a collection/name pair replaces that widget; it does
not remove other widgets already registered on the host.

## Available widgets [#available-widgets]

| Provider | Resource        | Binding value | Display                                                             |
| -------- | --------------- | ------------- | ------------------------------------------------------------------- |
| `stripe` | `customer`      | `cus_…`       | Name, email, Stripe ID, creation date                               |
| `stripe` | `subscriptions` | `cus_…`       | Latest 20 subscriptions, including canceled subscriptions           |
| `stripe` | `invoices`      | `cus_…`       | Latest 20 invoices, status, amount due, creation date               |
| `stripe` | `subscription`  | `sub_…`       | One subscription, status, creation and scheduled cancellation dates |
| `stripe` | `invoice`       | `in_…`        | One invoice                                                         |

Use the corresponding typed method to register each widget.
Names are unique within a collection; registering the same name again replaces it. The agent
exposes at most 20 valid widgets per collection. Each widget can reference a different
credential environment variable, for example for separate Stripe accounts.

The list widgets are bounded previews, not paginated browsers. A notice appears when more
records exist. Refresh fetches current data. There are no refund, payment, cancellation,
subscription-editing, hosted-invoice, or download actions.

## Permissions and failures [#permissions-and-failures]

Reading a widget requires access to the local record and its binding column. Related bindings
also require access to the relation's foreign-key columns, the target record, and the target
binding column. A null or empty binding displays an unlinked state. An inaccessible related
record reveals no provider data. Provider responses are projected onto a small display-field
allowlist; metadata, payment methods, addresses, arbitrary HTML and URLs are excluded.

Read-only refers to **provider writes**. Access to the provider data follows access to the
binding column: users who can read it can read that widget. Restrict the binding column when
billing details should only be visible to selected roles.

The agent pins the Stripe API version to `2025-09-30.clover`, limits each request to ten seconds
and one MiB, rejects redirects, and never forwards raw provider errors. For an unavailable
widget, check the agent environment, key permissions, mode, and the configured ID column.
No provider data is persisted by the widget system. The browser retains it only while the
widget is mounted; HTTP responses use `Cache-Control: no-store`.

## Future providers [#future-providers]

Each SDK exposes a common `IntegrationPlugin` interface with a `widgets()` / `Widgets()`
method. Provider classes implement that contract and expose their own typed builders.
TypeScript implementations return `WidgetInput` declarations, Go implementations return
`[]WidgetDefinition`, and Laravel implementations return `IntegrationWidget` objects.
Applications use `StripePlugin`; these lower-level declarations are for integration authors.

Serialized definitions separate provider, resource, record binding, and credential reference.
The customer-agent provider registry owns each provider's supported resources, fixed outbound
endpoints, credential policy, and response projection. All providers reuse record authorization
and a versioned, bounded table format with text, status, date, and money cells. Adding a provider
does not require new SDK methods or an Aviato-side proxy. Intercom and Zendesk are not included
in this release; each needs its own provider implementation and credential-policy review.
