The RefCampaign browser SDK is passive until your consent-management platform (CMP) sends the visitor's attribution choice. Loading the script alone does not create an attribution session or capture a visit.

## Quickstart

<Steps>
  <Step>
    ### Load the passive SDK

    ```html
    <script src="https://sdk.refcampaign.com/v4.js?s=rcst_PUBLIC_SITE_TOKEN" async></script>
    ```
  </Step>

  <Step>
    ### Forward the CMP decision

    Call the SDK once after the CMP resolves its persisted choice, then after every grant or withdrawal:

    ```js
    async function applyAttributionConsent(granted) {
      return window.RefCampaignBrowser.setConsent({ attribution: granted })
    }
    ```

    A grant sends one install heartbeat from the configured merchant origin. Attribution readiness is confirmed separately from the authenticated merchant checklist in RefCampaign. On a URL such as `https://merchant.example/?ref=fabrice`, the grant also creates a first-party `_rc_sid` session for 90 days. A refusal sends no browser request. A later withdrawal deletes the session.
  </Step>

  <Step>
    ### Send the consented session to checkout

    ```ts
    const sessionId = window.RefCampaignBrowser.getSessionId()

    await fetch('/api/checkout', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ priceId, sessionId }),
    })
    ```

    On the server, attach `refcampaign_session` metadata only when `sessionId` is present. Checkout must continue normally without it.
  </Step>
</Steps>

## npm installation

```bash
pnpm add @refcampaign/sdk
```

```ts
import { RefCampaignBrowser } from '@refcampaign/sdk'

RefCampaignBrowser.configure({
  siteToken: 'rcst_PUBLIC_SITE_TOKEN',
  debug: true,
})

await RefCampaignBrowser.setConsent({ attribution: cmpChoice === 'granted' })
```

Choose one browser path: CDN or npm. The public site token is safe for browser use; never expose a RefCampaign secret key there.

## Consent lifecycle

Your CMP remains the source of truth. RefCampaign does not store the consent choice.

* Initial grant or persisted grant: call `setConsent({ attribution: true })`.
* Initial refusal: call `setConsent({ attribution: false })`.
* Reload: replay the persisted CMP decision.
* Withdrawal: call the false branch immediately.

The merchant is responsible for obtaining consent and retaining any evidence required by applicable law. If consent is refused, the browser visit is not attributed automatically. You may still attribute a conversion using information independently known by your server, such as a coupon, an explicit referral code, or an affiliate code already attached to the order.

## Stripe attribution

For a Checkout subscription, preserve the metadata on both the Checkout Session and Subscription:

```ts
const metadata = sessionId ? { refcampaign_session: sessionId } : {}

const checkout = await stripe.checkout.sessions.create({
  mode: 'subscription',
  line_items: [{ price: priceId, quantity: 1 }],
  metadata,
  subscription_data: { metadata },
  success_url: 'https://merchant.example/success',
  cancel_url: 'https://merchant.example/pricing',
})
```

For a one-time payment, add the same metadata to the Payment Intent.

## Identify a consented click

After login or signup, you can attach a client-side SHA-256 email hash to the current consented click:

```ts
await RefCampaignBrowser.identify(currentUser.email)
```

The plaintext email never leaves the browser. Without a consented session, this call is a no-op.

## Manual conversions

Use the server client for non-Stripe payments or independently known attribution:

```ts
import { RefCampaignServer } from '@refcampaign/sdk'

const rc = new RefCampaignServer(process.env.REFCAMPAIGN_SECRET_KEY!)

await rc.trackConversion({
  orderId: 'ord_42',
  sessionId,
  // Or affiliateCode when your server already knows the partner.
  amount: 4900,
  currency: 'EUR',
  metadata: { payment_method: 'paypal' },
})
```

Amounts are integer cents. `orderId` is the idempotence key.

Report a full or partial refund with the same order id:

```ts
await rc.refundConversion({ orderId: 'ord_42' })

await rc.refundConversion({
  orderId: 'ord_42',
  amount: 1000,
  reason: 'partial return',
})
```

## Verification checklist

1. Refuse on `?ref=fabrice`: no capture request and no attribution session.
2. Grant: one capture request and `_rc_sid` available to the consented checkout flow.
3. Reload: replayed CMP grant exposes the existing session.
4. Withdraw: the session is deleted.
5. Complete checkout without a session: the purchase still succeeds.
