## Objective

Connect existing Stripe accounts through read-only OAuth and accept direct platform push when the linked Stripe account does not expose the full sale.

## Stripe OAuth endpoints

| Endpoint | Purpose | Expected outcome |
| --- | --- | --- |
| `POST /api/stripe/oauth/start?org={slug}&mode={test\|live}&lang={locale}` | prepare OAuth connection | temporary Stripe consent `redirectUrl` |
| `GET /api/stripe/oauth/start?org={slug}&mode={test\|live}&lang={locale}` | immediate redirect helper | `302/307` redirect to Stripe |
| `GET /api/stripe/oauth/callback?code=...&state=...` | internal OAuth callback | dashboard redirect with validated connection |

## Beacon Connect endpoints

| Endpoint | Purpose | Expected outcome |
| --- | --- | --- |
| `POST /api/platform-connect/verify-mapping` | validate `externalOrgRef` | resolved Beacon organization |
| `POST /api/platform-connect/payments` | ingest payment | create/enrich `Payment` and, if sufficient, `Sale` |
| `POST /api/platform-connect/refunds` | ingest refund | create `PaymentRefund` and update status |
| `POST /api/platform-connect/disputes` | ingest dispute | create `PaymentDispute` and update status |

## Recommended operational flow

<Steps>
  <Step title="1) Start OAuth">Call `oauth/start` with organization and mode (`test|live`).</Step>
  <Step title="2) Validate callback">Beacon validates `state`, exchanges `code` for a token, and fails if the returned scope does not include `read_only`.</Step>
  <Step title="3) Ingest visible Stripe data">The linked account starts feeding `Payment.kind=CHARGE|TRANSFER|PAYOUT`, plus financial enrichment from `balance_transactions`.</Step>
  <Step title="4) Enrich missing sale context">If the linked account only shows a partial money movement, the platform sends Beacon Connect to complete `Sale` and fiscal context.</Step>
</Steps>

## Mandatory validations

- `STRIPE_EXTENSION_ENABLED=true`.
- The OAuth callback must include `read_only`; Beacon does not treat `read_write` as a functionally equivalent fallback.
- `providerEventId` is required for Beacon Connect.
- `Payment` reconciliation priority is: `stripeChargeId -> stripePaymentIntentId -> providerEventId -> (platformIntegrationId, externalPaymentId)`.

## OAuth start example

```bash
curl -X POST "https://test.beacon.pt/api/stripe/oauth/start?org=acme&mode=test&lang=en" \
  -H "Authorization: Bearer <beacon_session_token>"
```

```json
{
  "ok": true,
  "redirectUrl": "https://connect.stripe.com/oauth/authorize?...",
  "expiresAt": "2026-03-18T11:00:00.000Z"
}
```

## Beacon Connect verify-mapping example

```bash
curl -X POST "https://app.beacon.pt/api/platform-connect/verify-mapping" \
  -H "Authorization: Bearer <platform_secret>" \
  -H "Content-Type: application/json" \
  -d '{"externalOrgRef":"seller_org_123"}'
```

## Common failures and mitigation

| Signal | Likely cause | Mitigation |
| --- | --- | --- |
| OAuth callback fails | Stripe app is not configured as an Extension or does not return `read_only` | fix Stripe configuration and restart the flow |
| partial payment without `Sale` | limited visibility in the linked account | enrich via Beacon Connect |
| `429` from Beacon Connect | integration quota exceeded | respect `retryAfterSec` and use exponential backoff with jitter |

<Tip title="Safe operation">Stripe runtime always uses the platform secret with `Stripe-Account`. OAuth tokens are only kept as encrypted audit material.</Tip>
