Skip to content

Commit 6abc6a2

Browse files
authored
feat!: ScriptLemonSqueezy (#130)
1 parent 0398191 commit 6abc6a2

File tree

7 files changed

+220
-30
lines changed

7 files changed

+220
-30
lines changed
Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,32 @@
1+
<script lang="ts" setup>
2+
const ready = ref(false)
3+
const events = ref([])
4+
</script>
5+
16
<template>
27
<div class="not-prose w-full">
38
<div class="flex items-center justify-center p-5">
4-
<ScriptLemonSqueezyButton v-slot="attrs" href="https://harlantest.lemonsqueezy.com/buy/52a40427-36d2-4450-a514-ae80d9e1a333?embed=1">
5-
<UButton v-bind="attrs">
6-
Buy Me
9+
<ScriptLemonSqueezy @lemon-squeezy-event="e => events.push(e)" @ready="ready = true">
10+
<UButton to="https://harlantest.lemonsqueezy.com/buy/52a40427-36d2-4450-a514-ae80d9e1a333?embed=1" class="block mb-3">
11+
Buy me - $9.99
712
</UButton>
8-
</ScriptLemonSqueezyButton>
13+
<UButton to="https://harlantest.lemonsqueezy.com/buy/76bbfa74-a81a-4111-8449-4f5ad564ed76?embed=1" class="block">
14+
Buy me - pay what you want
15+
</UButton>
16+
</ScriptLemonSqueezy>
17+
</div>
18+
<div>
19+
<UAlert v-if="!ready" class="mb-5" size="sm" color="blue" variant="soft" title="Lemon Squeezy is not loaded" description="It loads in when the DOM element is within the viewport." />
20+
<UAlert v-else color="green" variant="soft" title="Lemon Squeezy is loaded">
21+
<template #description>
22+
<div class="mb-2">
23+
Buttons are live and will open the modal, tracking events:
24+
</div>
25+
<li v-for="(event, key) in events" :key="key" class="text-xs">
26+
{{ event.event }}
27+
</li>
28+
</template>
29+
</ualert>
930
</div>
1031
</div>
1132
</template>

docs/content/scripts/payments/lemon-squeezy.md

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,97 @@ links:
66
icon: i-simple-icons-github
77
to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/lemon-squeezy.ts
88
size: xs
9-
- label: "<ScriptLemonSqueezyButton>"
9+
- label: "<ScriptLemonSqueezy>"
1010
icon: i-simple-icons-github
11-
to: https://github.com/nuxt/scripts/blob/main/src/runtime/components/ScriptLemonSqueezyButton.vue
11+
to: https://github.com/nuxt/scripts/blob/main/src/runtime/components/ScriptLemonSqueezy.vue
1212
size: xs
1313
---
1414

1515
[Lemon Squeezy](https://www.lemonsqueezy.com/) is a popular payment gateway that allows you to accept payments online.
1616

17+
Nuxt Scripts provides a [useScriptLemonSqueezy](#usescriptlemonsqueezy) composable and a headless Facade Component [ScriptLemonSqueezy](#scriptlemonsqueezy) component to interact with lemon squeezy.
18+
19+
20+
## ScriptLemonSqueezy
21+
22+
The `ScriptLemonSqueezy` component is headless [Facade Component](/docs/guides/facade-components) wrapping the [useScriptLemonSqueezy](#useScriptLemonSqueezy) composable, providing a simple, performance optimized way to load Lemon Squeezy in your Nuxt app.
23+
24+
```vue
25+
<template>
26+
<ScriptLemonSqueezy>
27+
<NuxtLink href="https://harlantest.lemonsqueezy.com/buy/52a40427-36d2-4450-a514-ae80d9e1a333?embed=1">
28+
Buy me - $9.99
29+
</NuxtLink>
30+
</ScriptLemonSqueezy>
31+
</template>
32+
```
33+
34+
It works by injecting a `.lemonsqueezy-button` class onto any `a` tags within the component then loading in
35+
the Lemon Squeezy script with the `visibility` [Element Event Trigger](/docs/guides/script-triggers#element-event-triggers).
36+
37+
### Demo
38+
39+
::code-group
40+
41+
:lemon-squeezy-demo{label="Output"}
42+
43+
```vue [Input]
44+
<script lang="ts" setup>
45+
const ready = ref(false)
46+
const events = ref([])
47+
</script>
48+
49+
<template>
50+
<div class="not-prose w-full">
51+
<div class="flex items-center justify-center p-5">
52+
<ScriptLemonSqueezy @lemon-squeezy-event="e => events.push(e)" @ready="ready = true">
53+
<UButton to="https://harlantest.lemonsqueezy.com/buy/52a40427-36d2-4450-a514-ae80d9e1a333?embed=1" class="block mb-3">
54+
Buy me - $9.99
55+
</UButton>
56+
<UButton to="https://harlantest.lemonsqueezy.com/buy/76bbfa74-a81a-4111-8449-4f5ad564ed76?embed=1" class="block">
57+
Buy me - pay what you want
58+
</UButton>
59+
</ScriptLemonSqueezy>
60+
</div>
61+
<div>
62+
<UAlert v-if="!ready" class="mb-5" size="sm" color="blue" variant="soft" title="Lemon Squeezy is not loaded" description="It loads in when the DOM element is within the viewport." />
63+
<UAlert v-else color="green" variant="soft" title="Lemon Squeezy is loaded">
64+
<template #description>
65+
<div class="mb-2">
66+
Buttons are live and will open the modal, tracking events:
67+
</div>
68+
<div v-for="event in events" class="text-xs">
69+
{{ event.event }}
70+
</div>
71+
</template>
72+
</UAlert>
73+
</div>
74+
</div>
75+
</template>
76+
```
77+
78+
::
79+
80+
### Component API
81+
82+
See the [Facade Component API](/docs/guides/facade-components#facade-components-api) for full props, events, and slots.
83+
84+
### Events
85+
86+
***`lemon-squeezy-event`***
87+
88+
Events emitted by the Lemon.js script are forwarded through this event. The payload is an object with an `event` key and a `data` key.
89+
90+
```ts
91+
export type LemonSqueezyEventPayload = { event: 'Checkout.Success', data: Record<string, any> }
92+
& { event: 'Checkout.ViewCart', data: Record<string, any> }
93+
& { event: 'GA.ViewCart', data: Record<string, any> }
94+
& { event: 'PaymentMethodUpdate.Mounted' }
95+
& { event: 'PaymentMethodUpdate.Closed' }
96+
& { event: 'PaymentMethodUpdate.Updated' }
97+
& { event: string }
98+
```
99+
17100
## useScriptLemonSqueezy
18101
19102
The `useScriptLemonSqueezy` composable lets you have fine-grain control over the Lemon Squeezy SDK. It provides a way to load the Lemon Squeezy SDK and interact with it programmatically.
@@ -33,8 +116,9 @@ export interface LemonSqueezyApi {
33116
* @param options - An object with a single property, eventHandler, which is a function that will be called when Lemon.js emits an event.
34117
*/
35118
Setup: (options: {
36-
eventHandler: (event:
37-
{ event: 'Checkout.Success', data: Record<string, any> }
119+
eventHandler: (event: { event: 'Checkout.Success', data: Record<string, any> }
120+
& { event: 'Checkout.ViewCart', data: Record<string, any> }
121+
& { event: 'GA.ViewCart', data: Record<string, any> }
38122
& { event: 'PaymentMethodUpdate.Mounted' }
39123
& { event: 'PaymentMethodUpdate.Closed' }
40124
& { event: 'PaymentMethodUpdate.Updated' }

playground/pages/third-parties/lemon-squeezy.vue

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<script lang="ts" setup>
2+
import type { LemonSqueezyEventPayload } from '../../../../src/runtime/registry/lemon-squeezy'
3+
import { ref } from '#imports'
4+
5+
const ready = ref(false)
6+
const events = ref<LemonSqueezyEventPayload[]>([])
7+
function handleEvent(payload: LemonSqueezyEventPayload) {
8+
events.value.push(payload)
9+
}
10+
</script>
11+
12+
<template>
13+
<div>
14+
<ScriptLemonSqueezy @lemon-squeezy-event="handleEvent" @ready="ready = true">
15+
<UButton to="https://harlantest.lemonsqueezy.com/buy/52a40427-36d2-4450-a514-ae80d9e1a333?embed=1" class="block mb-3">
16+
Buy me - $10
17+
</UButton>
18+
<UButton to="https://harlantest.lemonsqueezy.com/buy/76bbfa74-a81a-4111-8449-4f5ad564ed76?embed=1" class="block">
19+
Buy me - pay what you want
20+
</UButton>
21+
</ScriptLemonSqueezy>
22+
<div class="my-5 font-mono">
23+
<div>Events</div>
24+
<ul>
25+
<li v-for="(event, key) in events" :key="key">
26+
{{ event.event }}
27+
</li>
28+
</ul>
29+
</div>
30+
<NuxtLink to="/third-parties/lemon-squeezy/script" class="underline">
31+
back
32+
</NuxtLink>
33+
</div>
34+
</template>
35+
36+
<script setup lang="ts">
37+
</script>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<script lang="ts" setup>
2+
import { useScriptLemonSqueezy, onMounted } from '#imports'
3+
4+
const { Setup, Refresh } = useScriptLemonSqueezy()
5+
onMounted(() => {
6+
Refresh()
7+
Setup({
8+
eventHandler: () => {},
9+
})
10+
})
11+
</script>
12+
13+
<template>
14+
<UContainer class="py-12 space-y-4">
15+
<NuxtLink to="/third-parties/lemon-squeezy/component" class="underline">
16+
Lemon Squeezy Button
17+
</NuxtLink>
18+
<UCard>
19+
<div class="flex justify-between">
20+
<NuxtLink class="underline" to="/product-1">
21+
Product 1
22+
</NuxtLink>
23+
<UButton
24+
class="lemonsqueezy-button"
25+
external
26+
to="https://store.supersaas.dev/buy/ed9818ff-506d-4378-991c-081e1ecd8087?embed=1"
27+
target="_blank"
28+
>
29+
Buy Now
30+
</UButton>
31+
</div>
32+
</UCard>
33+
<UCard>
34+
<div class="flex justify-between">
35+
<NuxtLink to="/product-1" class="underline">
36+
Product 2
37+
</NuxtLink>
38+
<UButton
39+
class="lemonsqueezy-button"
40+
external
41+
to="https://store.supersaas.dev/buy/ed9818ff-506d-4378-991c-081e1ecd8087?embed=1"
42+
target="_blank"
43+
>
44+
Buy Now
45+
</UButton>
46+
</div>
47+
</UCard>
48+
</UContainer>
49+
</template>

src/runtime/components/ScriptLemonSqueezyButton.vue renamed to src/runtime/components/ScriptLemonSqueezy.vue

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,44 @@
22
import type { ElementScriptTrigger } from '../types'
33
import { useElementScriptTrigger } from '../composables/useElementScriptTrigger'
44
import { useScriptLemonSqueezy } from '../registry/lemon-squeezy'
5+
import type { LemonSqueezyEventPayload } from '../registry/lemon-squeezy'
56
import { onMounted, ref } from '#imports'
67
78
const props = withDefaults(defineProps<{
89
trigger?: ElementScriptTrigger
9-
href: string
1010
}>(), {
1111
trigger: 'visible',
1212
})
1313
1414
const emits = defineEmits<{
15-
event: [{ event: string, data?: Record<string, any> }]
15+
ready: [ReturnType<typeof useScriptLemonSqueezy>]
16+
lemonSqueezyEvent: [LemonSqueezyEventPayload]
1617
}>()
1718
1819
const rootEl = ref<HTMLElement | null>(null)
19-
const { $script } = useScriptLemonSqueezy({
20+
const instance = useScriptLemonSqueezy({
2021
scriptOptions: {
2122
trigger: useElementScriptTrigger({ trigger: props.trigger, el: rootEl }),
2223
},
2324
})
2425
onMounted(() => {
25-
$script.then(({ Setup }) => {
26+
rootEl.value?.querySelectorAll('a[href]').forEach((a) => {
27+
a.classList.add('lemonsqueezy-button')
28+
})
29+
instance.$script.then(({ Setup, Refresh }) => {
2630
Setup({
2731
eventHandler(event) {
28-
emits('event', event)
32+
emits('lemonSqueezyEvent', event)
2933
},
3034
})
35+
Refresh()
36+
emits('ready', instance)
3137
})
3238
})
3339
</script>
3440

3541
<template>
3642
<div ref="rootEl">
37-
<slot v-bind="{ class: 'lemonsqueezy-button', href }">
38-
<a :href="href" class="lemonsqueezy-button">
39-
Buy me
40-
</a>
41-
</slot>
43+
<slot />
4244
</div>
4345
</template>

src/runtime/registry/lemon-squeezy.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,22 @@ import type { RegistryScriptInput } from '#nuxt-scripts'
33

44
export type LemonSqueezyInput = RegistryScriptInput
55

6+
export type LemonSqueezyEventPayload = { event: 'Checkout.Success', data: Record<string, any> }
7+
& { event: 'Checkout.ViewCart', data: Record<string, any> }
8+
& { event: 'GA.ViewCart', data: Record<string, any> }
9+
& { event: 'PaymentMethodUpdate.Mounted' }
10+
& { event: 'PaymentMethodUpdate.Closed' }
11+
& { event: 'PaymentMethodUpdate.Updated' }
12+
& { event: string }
13+
614
// from https://docs.lemonsqueezy.com/help/lemonjs/what-is-lemonjs
715
export interface LemonSqueezyApi {
816
/**
917
* Initialises Lemon.js on your page.
1018
* @param options - An object with a single property, eventHandler, which is a function that will be called when Lemon.js emits an event.
1119
*/
1220
Setup: (options: {
13-
eventHandler: (event:
14-
{ event: 'Checkout.Success', data: Record<string, any> }
15-
& { event: 'PaymentMethodUpdate.Mounted' }
16-
& { event: 'PaymentMethodUpdate.Closed' }
17-
& { event: 'PaymentMethodUpdate.Updated' }
18-
& { event: string }
19-
) => void
21+
eventHandler: (event: LemonSqueezyEventPayload) => void
2022
}) => void
2123
/**
2224
* Refreshes `lemonsqueezy-button` listeners on the page.

0 commit comments

Comments
 (0)