mirror of
https://github.com/pockethost/pockethost.git
synced 2025-03-30 15:08:30 +00:00
LemonSqueezy integration
This commit is contained in:
parent
2876c86c9f
commit
a1973a0b81
@ -5,13 +5,16 @@
|
||||
"dockerode",
|
||||
"eventsource",
|
||||
"frontends",
|
||||
"getenv",
|
||||
"maildev",
|
||||
"memorystream",
|
||||
"mothership",
|
||||
"noaxis",
|
||||
"nofile",
|
||||
"PBOUNCE",
|
||||
"pocketbase",
|
||||
"pockethost",
|
||||
"POCKETSTREAM",
|
||||
"rizzdown",
|
||||
"superadmin",
|
||||
"unzipper",
|
||||
|
@ -5,7 +5,7 @@
|
||||
import { client } from '$src/pocketbase-client'
|
||||
import FAQSection from '$src/routes/account/FAQSection.svelte'
|
||||
import PricingCard from '$src/routes/account/PricingCard.svelte'
|
||||
import { isUserLegacy, userSubscriptionType } from '$util/stores'
|
||||
import { isUserLegacy, userStore, userSubscriptionType } from '$util/stores'
|
||||
import { onMount } from 'svelte'
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
@ -71,8 +71,8 @@
|
||||
description="Want all your PocketHost projects in one place? That's what the Pro tier is all about."
|
||||
priceMonthly={[20, 'month']}
|
||||
priceAnnually={[199, 'year (save 20%)']}
|
||||
checkoutMonthURL="https://store.pockethost.io/checkout/buy/8e7cfb35-846a-4fd6-adcb-c2db5589275d"
|
||||
checkoutYearURL="https://store.pockethost.io/checkout/buy/96e4ab4b-f646-4fb2-b830-5584db983e73"
|
||||
checkoutMonthURL="https://store.pockethost.io/checkout/buy/8e7cfb35-846a-4fd6-adcb-c2db5589275d?checkout[custom][user_id]={$userStore?.id}"
|
||||
checkoutYearURL="https://store.pockethost.io/checkout/buy/96e4ab4b-f646-4fb2-b830-5584db983e73?checkout[custom][user_id]={$userStore?.id}"
|
||||
active={$userSubscriptionType === SubscriptionType.Premium}
|
||||
/>
|
||||
|
||||
@ -82,8 +82,8 @@
|
||||
description="Super elite! The Founder's Edition is our way of saying thanks for supporting PocketHost in these early days. Choose between lifetime and annual options."
|
||||
priceMonthly={[299, 'once, use forever']}
|
||||
priceAnnually={[99, 'year (save 55%)']}
|
||||
checkoutMonthURL="https://store.pockethost.io/checkout/buy/e71cbfb5-cec3-4745-97a7-d877f6776503"
|
||||
checkoutYearURL="https://store.pockethost.io/checkout/buy/e5660329-5b99-4ed6-8f36-0d387803e1d6"
|
||||
checkoutMonthURL="https://store.pockethost.io/checkout/buy/e71cbfb5-cec3-4745-97a7-d877f6776503?checkout[custom][user_id]={$userStore?.id}"
|
||||
checkoutYearURL="https://store.pockethost.io/checkout/buy/e5660329-5b99-4ed6-8f36-0d387803e1d6?checkout[custom][user_id]={$userStore?.id}"
|
||||
active={$userSubscriptionType === SubscriptionType.Lifetime}
|
||||
/>
|
||||
</div>
|
||||
|
@ -2,6 +2,7 @@ import {
|
||||
DATA_ROOT,
|
||||
DEBUG,
|
||||
DefaultSettingsService,
|
||||
LS_WEBHOOK_SECRET,
|
||||
mkContainerHomePath,
|
||||
MOTHERSHIP_APP_DIR,
|
||||
MOTHERSHIP_HOOKS_DIR,
|
||||
@ -63,6 +64,7 @@ global.EventSource = EventSource
|
||||
dev: DEBUG(),
|
||||
env: {
|
||||
DATA_ROOT: mkContainerHomePath(`data`),
|
||||
LS_WEBHOOK_SECRET: LS_WEBHOOK_SECRET(),
|
||||
},
|
||||
extraBinds: [
|
||||
`${DATA_ROOT()}:${mkContainerHomePath(`data`)}`,
|
||||
|
@ -96,6 +96,8 @@ export const SETTINGS = {
|
||||
DISCORD_POCKETSTREAM_URL: mkString(''),
|
||||
|
||||
TEST_EMAIL: mkString(),
|
||||
|
||||
LS_WEBHOOK_SECRET: mkString(''),
|
||||
}
|
||||
;(() => {
|
||||
let passed = true
|
||||
@ -219,6 +221,8 @@ export const DISCORD_POCKETSTREAM_URL = () =>
|
||||
|
||||
export const TEST_EMAIL = () => settings().TEST_EMAIL
|
||||
|
||||
export const LS_WEBHOOK_SECRET = () => settings().LS_WEBHOOK_SECRET
|
||||
|
||||
/**
|
||||
* Helpers
|
||||
*/
|
||||
|
185
src/mothership-app/pb_hooks/src/ls.pb.js
Normal file
185
src/mothership-app/pb_hooks/src/ls.pb.js
Normal file
@ -0,0 +1,185 @@
|
||||
/// <reference path="../types/types.d.ts" />
|
||||
|
||||
routerAdd('POST', '/api/ls', (c) => {
|
||||
const log = (...s) =>
|
||||
console.log(
|
||||
`*** [ls]`,
|
||||
...s.map((p) => {
|
||||
if (typeof p === 'object') return JSON.stringify(p, null, 2)
|
||||
return p
|
||||
}),
|
||||
)
|
||||
const error = (...s) => console.error(`***`, ...s)
|
||||
|
||||
const audit = (key, note) => {
|
||||
log(note)
|
||||
const collection = $app.dao().findCollectionByNameOrId('audit')
|
||||
|
||||
const record = new Record(collection, {
|
||||
...key,
|
||||
note,
|
||||
})
|
||||
|
||||
$app.dao().saveRecord(record)
|
||||
}
|
||||
|
||||
const secret = $os.getenv('LS_WEBHOOK_SECRET')
|
||||
log(`Secret`, secret)
|
||||
|
||||
const raw = readerToString(c.request().body)
|
||||
const data = JSON.parse(raw)
|
||||
log(`payload`, JSON.stringify(data, null, 2))
|
||||
|
||||
const {
|
||||
meta: {
|
||||
custom_data: { user_id },
|
||||
},
|
||||
data: {
|
||||
type,
|
||||
attributes: {
|
||||
order_id,
|
||||
product_name,
|
||||
product_id,
|
||||
status,
|
||||
user_email: email,
|
||||
},
|
||||
},
|
||||
} = data
|
||||
|
||||
log({ user_id, order_id, product_name, product_id, status, email })
|
||||
|
||||
if (status !== `active`) {
|
||||
audit({ email, event: `LS_ERR` }, `Unsupported status ${status}: ${raw}`)
|
||||
return c.json(500, { status: 'unsupported status' })
|
||||
} else {
|
||||
log(`status`, status)
|
||||
}
|
||||
|
||||
if (!user_id) {
|
||||
audit({ email, event: `LS_ERR` }, `No user ID: ${raw}`)
|
||||
return c.json(500, { status: 'no user ID' })
|
||||
} else {
|
||||
log(`user ID ok`, user_id)
|
||||
}
|
||||
|
||||
if (!order_id) {
|
||||
audit({ email, event: `LS_ERR` }, `No order ID: ${raw}`)
|
||||
return c.json(500, { status: 'no order ID' })
|
||||
} else {
|
||||
log(`order ID ok`, order_id)
|
||||
}
|
||||
|
||||
const user = (() => {
|
||||
try {
|
||||
return $app.dao().findFirstRecordByData('users', 'id', user_id)
|
||||
} catch (e) {}
|
||||
})()
|
||||
if (!user) {
|
||||
audit({ email, event: `LS_ERR` }, `User ${user_id} not found: ${raw}`)
|
||||
return c.json(500, { status: 'no user ID' })
|
||||
} else {
|
||||
log(`user record ok`, user)
|
||||
}
|
||||
|
||||
const editions = {
|
||||
// Founder's annual
|
||||
159792: () => {
|
||||
user.set(`subscription`, `premium`)
|
||||
user.set(`isFounder`, true)
|
||||
},
|
||||
// Founder's lifetime
|
||||
159794: () => {
|
||||
user.set(`subscription`, `lifetime`)
|
||||
user.set(`isFounder`, true)
|
||||
},
|
||||
// Pro annual
|
||||
159791: () => {
|
||||
user.set(`subscription`, `premium`)
|
||||
},
|
||||
// Pro monthly
|
||||
159790: () => {
|
||||
user.set(`subscription`, `premium`)
|
||||
},
|
||||
}
|
||||
|
||||
const applyEditionSpecifics = editions[product_id]
|
||||
if (!applyEditionSpecifics) {
|
||||
audit(
|
||||
{ email, user: user_id, event: `LS_ERR` },
|
||||
`Product edition ${product_id} not found: ${raw}`,
|
||||
)
|
||||
return c.json(500, { status: 'invalid product ID' })
|
||||
} else {
|
||||
log(`product id ok`, product_id)
|
||||
}
|
||||
applyEditionSpecifics()
|
||||
|
||||
const collection = $app.dao().findCollectionByNameOrId('payments')
|
||||
const payment = new Record(collection, {
|
||||
user: user_id,
|
||||
payment_id: `ls_${order_id}`,
|
||||
})
|
||||
|
||||
try {
|
||||
$app.dao().runInTransaction((txDao) => {
|
||||
txDao.saveRecord(user)
|
||||
txDao.saveRecord(payment)
|
||||
txDao
|
||||
.db()
|
||||
.newQuery(
|
||||
`update settings set value=value-1 where name='founders-edition-count'`,
|
||||
)
|
||||
.execute()
|
||||
})
|
||||
log(`database updated`)
|
||||
} catch (e) {
|
||||
audit(
|
||||
{ email, user: user_id, event: `LS_ERR` },
|
||||
`Failed to update database: ${e}: ${raw}`,
|
||||
)
|
||||
return c.json(500, { status: 'failed to update database' })
|
||||
}
|
||||
|
||||
try {
|
||||
const res = $http.send({
|
||||
url: `https://discord.com/api/webhooks/1193619183594901575/JVDfdUz2HPEUk-nG1RfI3BK2Czyx5vw1YmeH7cNfgvXbHNGPH0oJncOYqxMA_u5b2u57`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
content: `someone just subscribed to ${product_name}`,
|
||||
}),
|
||||
headers: { 'content-type': 'application/json' },
|
||||
timeout: 5, // in seconds
|
||||
})
|
||||
} catch (e) {
|
||||
audit({ email, event: `LS_ERR` }, `Failed to notify discord: ${e}: ${raw}`)
|
||||
return c.json(500, { status: 'failed to notify discord' })
|
||||
}
|
||||
log(`discord notified`)
|
||||
|
||||
try {
|
||||
$app.newMailClient().send(
|
||||
new MailerMessage({
|
||||
from: {
|
||||
address: $app.settings().meta.senderAddress,
|
||||
name: $app.settings().meta.senderName,
|
||||
},
|
||||
to: [{ address: user.email() }],
|
||||
subject: `Activated - ${product_name}`,
|
||||
html: [
|
||||
`<p>Hello, welcome to the PocketHost Pro family!`,
|
||||
`<p>Please go find @noaxis on <a href="https://discord.com/channels/1128192380500193370/1128192380500193373">Discord</a> and say hi.`,
|
||||
`<p>Thank you *so much!!!* for supporting PocketHost in these early days. If you have any questions or concerns, don't hesitate to reach out.`,
|
||||
`<p>~noaxis`,
|
||||
].join(`\n`),
|
||||
}),
|
||||
)
|
||||
} catch (e) {
|
||||
audit({ email, event: `LS_ERR` }, `Failed to send email: ${e}: ${raw}`)
|
||||
return c.json(500, { status: 'failed to send email' })
|
||||
}
|
||||
|
||||
log(`email sent`)
|
||||
audit({ email, user: user_id, event: `LS_OK` }, `Payment successful`)
|
||||
|
||||
return c.json(200, { status: 'ok' })
|
||||
})
|
@ -88,7 +88,7 @@ routerAdd('POST', '/api/sns', (c) => {
|
||||
default:
|
||||
audit(
|
||||
{ event: `SNS` },
|
||||
`Unrecognized bounce type ${bounceType}: ${data}`,
|
||||
`Unrecognized bounce type ${bounceType}: ${raw}`,
|
||||
)
|
||||
}
|
||||
break
|
||||
@ -124,12 +124,12 @@ routerAdd('POST', '/api/sns', (c) => {
|
||||
default:
|
||||
audit(
|
||||
{ event: `SNS` },
|
||||
`Unrecognized notification type ${notificationType}: ${data}`,
|
||||
`Unrecognized notification type ${notificationType}: ${raw}`,
|
||||
)
|
||||
}
|
||||
break
|
||||
default:
|
||||
audit({ event: `SNS` }, `Message ${Type} not handled: ${data}`)
|
||||
audit({ event: `SNS` }, `Message ${Type} not handled: ${raw}`)
|
||||
}
|
||||
|
||||
return c.json(200, { status: 'ok' })
|
||||
|
Loading…
x
Reference in New Issue
Block a user