@habit.analytics/habit-smartlink-reactcomponent
Version:
A React component for Habit SmartLink integration.
215 lines (159 loc) • 6.31 kB
Markdown
# 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.