# smartlink-script.js — Integration Guide

Framework-agnostic Smartlink embed. No npm, no build step, no framework required.
Drop the file in your project and include it with a `<script>` tag.

---

## Quick start

```html
<!DOCTYPE html>
<html>
  <head>
    <script src="smartlink-script.js"></script>
  </head>
  <body>

    <!-- 1. A container element where the iframe will be injected -->
    <div id="smartlink-container"></div>

    <script>
      var smartlink = createSmartlink({
        container: document.getElementById('smartlink-container'),
        hash: 'YOUR_HASH',

        prePaymentMethod: async function (quoteId) {
          const res = await fetch('/api/payments', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ quoteId }),
          });
          const data = await res.json();
          return { success: true, payment_id: data.id };
        },

        onPaymentSuccess: function (paymentData) {
          console.log('Payment complete', paymentData);
        },
      });
    </script>

  </body>
</html>
```

---

## Options reference

| Option | Type | Required | Description |
|---|---|---|---|
| `container` | `HTMLElement` | Yes | DOM element where the iframe is appended. |
| `hash` | `string` | Yes | Smartlink hash that identifies your flow. |
| `pin` | `string` | No | Optional PIN for the Smartlink flow. |
| `env` | `string` | No | `'int'` · `'qa'` · `'default'`. Auto-detected from `window.location` if omitted (see [Environment detection](#environment-detection)). |
| `prePaymentMethod` | `async (quoteId) => { success, payment_id }` | Yes | Called when the iframe needs a payment ID. Must return an object with `success: boolean` and `payment_id: string`. |
| `onPaymentSuccess` | `(paymentData: string) => void` | No | Called when the payment step completes successfully. |
| `onCancelled` | `() => void` | No | Called when the user cancels or closes the flow. |
| `onError` | `({ message, details }) => void` | No | Called when the iframe reports an error. |
| `customStyle` | `Object` | No | CSS properties applied directly to the iframe element (e.g. `{ width: '100%', height: '700px' }`). |

---

## Return value

`createSmartlink` returns an object with a single method:

```js
var smartlink = createSmartlink({ ... });

// Removes the iframe from the DOM and unbinds all event listeners.
smartlink.destroy();
```

Call `destroy()` when the user navigates away, closes a modal, or whenever you need to tear down the embed.

---

## Environment detection

If `env` is not provided, the script inspects `window.location.href` and maps it automatically:

| URL contains | Resolved env | Iframe target |
|---|---|---|
| `integrations` | `int` | `distributors.integrations.habit.io` |
| `localhost` | `int` | `distributors.integrations.habit.io` |
| `qa` | `qa` | `distributors.qa.habit.io` |
| anything else | `default` | `distributors.habit.io` |

> **Local development** is intentionally routed to `int` (not a local iframe server) so that developers always test against a real hosted environment.

To override explicitly:

```js
createSmartlink({ ..., env: 'qa' });
```

---

## Default iframe dimensions

The iframe is injected with the same default dimensions as the React component:

```
width:   320px
height:  650px
```

The iframe will update its own height dynamically via `SMARTLINK_RESIZE` messages as the flow progresses. You can set initial overrides via `customStyle`:

```js
createSmartlink({
  ...
  customStyle: { width: '100%', maxWidth: '480px', height: '700px' },
});
```

---

## Framework-specific examples

### Vue 3

```js
import { onMounted, onUnmounted, ref } from 'vue';

const containerRef = ref(null);
let smartlink = null;

onMounted(() => {
  smartlink = createSmartlink({
    container: containerRef.value,
    hash: 'YOUR_HASH',
    prePaymentMethod: (quoteId) => myApi.createPayment(quoteId),
    onPaymentSuccess: (data) => emit('success', data),
  });
});

onUnmounted(() => smartlink?.destroy());
```

```html
<template>
  <div ref="containerRef" />
</template>
```

### Angular

```ts
import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core';

@Component({ template: '<div #container></div>' })
export class SmartlinkComponent implements OnInit, OnDestroy {
  @ViewChild('container', { static: true }) containerRef!: ElementRef;
  private smartlink: any;

  ngOnInit() {
    this.smartlink = (window as any).createSmartlink({
      container: this.containerRef.nativeElement,
      hash: 'YOUR_HASH',
      prePaymentMethod: (quoteId: string) => this.paymentService.create(quoteId),
      onPaymentSuccess: (data: string) => this.handleSuccess(data),
    });
  }

  ngOnDestroy() {
    this.smartlink?.destroy();
  }
}
```

### Svelte

```svelte
<script>
  import { onMount, onDestroy } from 'svelte';

  let container;
  let smartlink;

  onMount(() => {
    smartlink = createSmartlink({
      container,
      hash: 'YOUR_HASH',
      prePaymentMethod: (quoteId) => api.createPayment(quoteId),
      onPaymentSuccess: (data) => console.log(data),
    });
  });

  onDestroy(() => smartlink?.destroy());
</script>

<div bind:this={container} />
```

---

## Message protocol

The script handles all `postMessage` communication internally. For reference, these are the messages exchanged with the iframe:

| Direction | Message type | When |
|---|---|---|
| iframe → parent | `SMARTLINK_READY` | Iframe has mounted and is ready to start |
| parent → iframe | `SMARTLINK_INIT` | Sent immediately after `SMARTLINK_READY` |
| iframe → parent | `SMARTLINK_PREPAYMENT_METHOD` | Iframe needs a `payment_id` for a given `quote_id` |
| parent → iframe | `SMARTLINK_PREPAYMENT_METHOD_COMPLETE` | Parent resolved `prePaymentMethod` and sends back `{ success, payment_id }` |
| iframe → parent | `SMARTLINK_RESIZE` | Iframe content changed height/width |
| iframe → parent | `SMARTLINK_STEP_COMPLETE` | A step finished (payment success fires `onPaymentSuccess`) |
| iframe → parent | `SMARTLINK_CANCELLED` | User cancelled the flow |
| iframe → parent | `SMARTLINK_ERROR` | An error occurred inside the iframe |

All messages are origin-validated — only messages from the expected Smartlink domain are accepted.
