Skip to content

Commit 6b16172

Browse files
committed
fix: improved refund experience and fixed some math issues
1 parent c7d16ad commit 6b16172

File tree

5 files changed

+131
-73
lines changed

5 files changed

+131
-73
lines changed

src/routes/+error.svelte

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script lang="ts">
2+
import { page } from "$app/state"
3+
</script>
4+
5+
{#if page.error}
6+
<main class="mx-auto my-64 flex h-96 flex-col text-center">
7+
<h1 class="text-error-500 my-4 text-xl font-bold">Error Status: {page.status}</h1>
8+
<div class="text-error-500 mx-auto max-w-3/6 text-lg">{page.error.message}</div>
9+
</main>
10+
{/if}

src/routes/dashboard/[slug]/+error.svelte

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
import { page } from "$app/state"
33
</script>
44

5-
<main class="m-4 h-96 text-center">
6-
{#if page.error}
7-
<h1 class="my-16 text-error-500">{page.error.message}</h1>
8-
{/if}
9-
</main>
5+
{#if page.error}
6+
<main class="mx-auto my-64 flex h-96 flex-col text-center">
7+
<h1 class="text-error-500 my-4 text-xl font-bold">Error Status: {page.status}</h1>
8+
<div class="text-error-500 mx-auto max-w-3/6 text-lg">{page.error.message}</div>
9+
</main>
10+
{/if}

src/routes/subscriptions/+page.server.ts

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -271,16 +271,42 @@ export const actions = {
271271
)
272272
}
273273

274-
const anchorDate = new Date(subscription.billing_cycle_anchor * 1000)
275-
const now = Date.now()
276-
const diffMs = now - anchorDate.getTime()
274+
const items = subscription.items.data
275+
if (items.length != 1)
276+
error(
277+
500,
278+
"Subscription has multiple items only 1 was expected! Refresh the page, if this keeps happening, please contact [email protected]"
279+
)
280+
281+
const DAY = 24 * 3600000
282+
const tenDayMS = 10 * DAY
283+
284+
const start_date = items[0].current_period_start * 1000
285+
const end_date = items[0].current_period_end * 1000
286+
287+
const intervalMs = end_date - start_date
288+
const tenPercentMs = intervalMs * 0.1
289+
const windowMs = Math.min(tenPercentMs, tenDayMS)
277290

278-
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24))
291+
const elapsedSinceStartMs = Date.now() - start_date
279292

280-
if (diffDays > 0) {
293+
if (elapsedSinceStartMs < DAY && elapsedSinceStartMs <= windowMs) {
294+
const endWindow = new Date(start_date + DAY + windowMs)
281295
error(
282-
500,
283-
"The subscription you want to refund is outside of the refund window. Refresh the page, if this keeps happening, please contact [email protected]"
296+
403,
297+
"You need to wait 1 day to attempt to refund this payment and you will have until" +
298+
endWindow.toLocaleString() +
299+
" to request it. If you think this is an error refresh the page, if this keeps happening, please contact [email protected]"
300+
)
301+
}
302+
303+
if (elapsedSinceStartMs > windowMs) {
304+
const endWindow = new Date(start_date + DAY + windowMs)
305+
error(
306+
403,
307+
"The subscription you want to refund is outside of the refund window, you could only refund until " +
308+
endWindow.toLocaleString() +
309+
". If you think this is an error refresh the page, if this keeps happening, please contact [email protected]"
284310
)
285311
}
286312

@@ -317,7 +343,7 @@ export const actions = {
317343
)
318344
}
319345

320-
const refund = amount - Math.min(amount * 0.15, 500)
346+
const refund = Math.round(amount - Math.min(amount * 0.15, 500))
321347

322348
try {
323349
await stripe.refunds.create({

src/routes/subscriptions/RefundModal.svelte

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@
22
import type { Price } from "$lib/types/collection"
33
import { Dialog, Portal } from "@skeletonlabs/skeleton-svelte"
44
import RotateCw from "@lucide/svelte/icons/rotate-cw"
5+
import { SvelteDate } from "svelte/reactivity"
56
67
let {
78
name,
89
username,
910
id,
10-
price
11+
price,
12+
date_end
1113
}: {
1214
name: string
1315
username: Promise<string | null>
1416
id: string
15-
price: Price | undefined
17+
price: Price
18+
date_end: string
1619
} = $props()
1720
let open = $state(false)
1821
@@ -22,18 +25,47 @@
2225
style: "currency",
2326
currency: price?.currency ?? "eur"
2427
}).format((value ?? 0) / 100)
28+
29+
const endDate = new Date(date_end)
30+
const startDate = new Date(endDate)
31+
32+
switch (price.interval) {
33+
case "week":
34+
startDate.setDate(startDate.getDate() - 7)
35+
break
36+
case "month":
37+
startDate.setMonth(startDate.getMonth() - 1)
38+
break
39+
case "year":
40+
startDate.setFullYear(startDate.getFullYear() - 1)
41+
break
42+
default:
43+
}
44+
45+
const start_date = startDate.getTime()
46+
const end_date = endDate.getTime()
47+
48+
const DAY = 24 * 3600000
49+
const tenDayMS = 10 * DAY
50+
const intervalMs = end_date - start_date
51+
const tenPercentMs = intervalMs * 0.1
52+
const windowMs = Math.min(tenPercentMs, tenDayMS)
53+
54+
const elapsedSinceStartMs = Date.now() - start_date
55+
56+
const endWindow = new Date(start_date + DAY + windowMs)
2557
</script>
2658

2759
<Dialog {open} onOpenChange={(e) => (open = e.open)}>
28-
<Dialog.Trigger class="btn preset-tonal">
60+
<Dialog.Trigger disabled={elapsedSinceStartMs > windowMs} class="btn preset-tonal">
2961
<RotateCw size="16" />
3062
<span>Refund</span>
3163
</Dialog.Trigger>
3264
<Portal>
3365
<Dialog.Backdrop class="fixed inset-0 z-50 bg-surface-50-950/50 backdrop-blur-sm" />
3466
<Dialog.Positioner class="fixed inset-0 z-50 flex items-center justify-center">
3567
<Dialog.Content
36-
class="max-h-[95%] w-[70%] max-w-fit space-y-4 overflow-y-auto card bg-surface-100-900 p-4 shadow-xl"
68+
class="max-h-[95%] w-[70%] max-w-fit space-y-4 overflow-y-auto card rounded-md bg-surface-100-900 p-12 shadow-xl"
3769
>
3870
<Dialog.Title class="flex justify-between text-2xl font-bold">
3971
<h5 class="flex flex-col gap-4 h5 lg:flex-row lg:h4">
@@ -70,11 +102,45 @@
70102
</a>.
71103
</p>
72104
<p class="my-8">The total amount you will be refunded will be roughly <b>{priceStr}</b>.</p>
105+
106+
<div class="mx-2 my-8 flex flex-col gap-4 rounded-md bg-surface-300-700 p-2 text-warning-500">
107+
{#if elapsedSinceStartMs < DAY}
108+
<p>
109+
The refund option will be available after <span class="text-error-500">1 day</span>
110+
and you will have until the
111+
<span class="text-error-500">{endWindow.toLocaleString()}</span>
112+
to request it.
113+
</p>
114+
<p>
115+
While it's not available try reaching out to
116+
<span class="text-error-500">
117+
{#await username}
118+
Loading...
119+
{:then username}
120+
{username}
121+
{/await}
122+
</span>
123+
and let him know if you are having issues!
124+
</p>
125+
{:else}
126+
<p>
127+
Refunding will be available until <span class="text-error-500">
128+
{endWindow.toLocaleString()}
129+
</span>
130+
</p>
131+
{/if}
132+
</div>
73133
</Dialog.Description>
74134

75135
<footer class="flex justify-end gap-4">
76136
<form id="refundsform" method="POST" action={"?/refund&id=" + id}>
77-
<button type="submit" class="btn preset-filled-error-500"> Refund </button>
137+
<button
138+
type="submit"
139+
disabled={elapsedSinceStartMs < DAY || elapsedSinceStartMs > windowMs}
140+
class="btn preset-filled-error-500"
141+
>
142+
Refund
143+
</button>
78144
</form>
79145

80146
<Dialog.CloseTrigger class="btn preset-tonal">Cancel</Dialog.CloseTrigger>

src/routes/subscriptions/SubscriptionTable.svelte

Lines changed: 10 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@
77
import { getPriceAmount, getPriceIntervalEx } from "$lib/utils"
88
import ScriptLinks from "./ScriptLinks.svelte"
99
import ExternalLink from "@lucide/svelte/icons/external-link"
10-
import RotateCw from "@lucide/svelte/icons/rotate-cw"
1110
import RefundModal from "./RefundModal.svelte"
12-
import { SvelteDate } from "svelte/reactivity"
1311
1412
let {
1513
data,
@@ -55,43 +53,6 @@
5553
function getPrice(id: string, prices: Price[]) {
5654
return prices.find((price) => price.id === id)
5755
}
58-
59-
function getStartDate(date_end: string, interval: string | undefined) {
60-
const endDate = new SvelteDate(date_end)
61-
const startDate = new SvelteDate(endDate)
62-
63-
switch (interval) {
64-
case "week":
65-
startDate.setDate(startDate.getDate() - 7)
66-
break
67-
case "month":
68-
startDate.setMonth(startDate.getMonth() - 1)
69-
break
70-
case "year":
71-
startDate.setFullYear(startDate.getFullYear() - 1)
72-
break
73-
default:
74-
return null
75-
}
76-
return { startDate, endDate }
77-
}
78-
79-
function inEarlyWindow(date_end: string, interval: string | undefined) {
80-
const date = getStartDate(date_end, interval)
81-
if (!date) return false
82-
83-
const DAY = 24 * 3600000
84-
85-
const { startDate, endDate } = date
86-
87-
const intervalMs = endDate.getTime() - startDate.getTime() // actual length
88-
const tenPercentMs = intervalMs * 0.05
89-
const fourteenDaysMs = 10 * DAY
90-
const windowMs = Math.min(tenPercentMs, fourteenDaysMs)
91-
92-
const elapsedSinceStartMs = Date.now() - startDate.getTime()
93-
return elapsedSinceStartMs >= DAY && elapsedSinceStartMs <= windowMs
94-
}
9556
</script>
9657

9758
<div class="mx-auto w-screen max-w-fit">
@@ -199,23 +160,17 @@
199160
</Switch>
200161
</td>
201162
<td class="text-center">
202-
{#if inEarlyWindow(date_end, priceEx?.interval)}
203-
{#if bundleArray[i]}
204-
<RefundModal
205-
name={bundleArray[i].name}
206-
username={bundleArray[i].username}
207-
{id}
208-
price={priceEx}
209-
/>
210-
{:else}
211-
{@const script = getScript(product) as ScriptProduct}
212-
<RefundModal name={script.name} username={script.username} {id} price={priceEx} />
213-
{/if}
163+
{#if bundleArray[i]}
164+
<RefundModal
165+
name={bundleArray[i].name}
166+
username={bundleArray[i].username}
167+
{id}
168+
price={priceEx!}
169+
{date_end}
170+
/>
214171
{:else}
215-
<button disabled class="btn preset-tonal">
216-
<RotateCw size="16" />
217-
<span>Refund</span>
218-
</button>
172+
{@const script = getScript(product) as ScriptProduct}
173+
<RefundModal name={script.name} username={script.username} {id} price={priceEx!} {date_end} />
219174
{/if}
220175
</td>
221176
</tr>

0 commit comments

Comments
 (0)