lemonsqueezy updates

This commit is contained in:
Ben Allfree 2024-01-10 10:06:29 +00:00
parent 6cb32a1d29
commit f0c30dbb66
2 changed files with 139 additions and 142 deletions

View File

@ -1,27 +1,7 @@
/// <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, mkLog } = /** @type {Lib} */ (require(`${__hooks}/lib.js`))
const audit = (key, note) => {
log(note)
const collection = $app.dao().findCollectionByNameOrId('audit')
const record = new Record(collection, {
...key,
note,
})
$app.dao().saveRecord(record)
}
const log = mkLog(`ls`)
const secret = $os.getenv('LS_WEBHOOK_SECRET')
log(`Secret`, secret)
@ -30,7 +10,8 @@ routerAdd('POST', '/api/ls', (c) => {
const data = JSON.parse(raw)
log(`payload`, JSON.stringify(data, null, 2))
const {
/** @type {WebHook} */
let {
meta: {
custom_data: { user_id },
},
@ -38,8 +19,7 @@ routerAdd('POST', '/api/ls', (c) => {
type,
attributes: {
order_number,
product_name,
product_id,
first_order_item: { product_name, /** @type {number} */ product_id },
status,
user_email: email,
},
@ -48,138 +28,143 @@ routerAdd('POST', '/api/ls', (c) => {
log({ user_id, order_number, product_name, product_id, status, email })
if (![`active`, `paid`].includes(status)) {
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_number) {
audit({ email, event: `LS_ERR` }, `No order #: ${raw}`)
return c.json(500, { status: 'no order #' })
} else {
log(`order # ok`, order_number)
}
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_number}`,
})
try {
if (!user_id) {
throw new Error(`No user ID`)
} else {
log(`user ID ok`, user_id)
}
if (![`orders`].includes(type)) {
throw new Error(`Unsupported event: ${type}`)
} else {
log(`event type ok`)
}
if (![`active`, `paid`].includes(status)) {
throw new Error(`Unsupported status: ${status}`)
} else {
log(`status ok`, status)
}
if (!order_number) {
throw new Error(`No order #`)
} else {
log(`order # ok`, order_number)
}
const user = (() => {
try {
return $app.dao().findFirstRecordByData('users', 'id', user_id)
} catch (e) {}
})()
if (!user) {
throw new Error(`User ${user_id} not found`)
} else {
log(`user record ok`, user)
}
/** @type{{[_:number]: ()=>void}} */
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) {
throw new Error(`Product ID not found: ${product_id}`)
} else {
log(`product id ok`, product_id)
}
applyEditionSpecifics()
const payment = new Record(
$app.dao().findCollectionByNameOrId('payments'),
{
user: user_id,
payment_id: `ls_${order_number}`,
},
)
$app.dao().runInTransaction((txDao) => {
txDao.saveRecord(user)
log(`saved user`)
txDao.saveRecord(payment)
log(`saved payment`)
txDao
.db()
.newQuery(
`update settings set value=value-1 where name='founders-edition-count'`,
)
.execute()
log(`saved founder count`)
const emailTemplate = $app
.dao()
.findFirstRecordByData('message_templates', `slug`, `lemon_order_email`)
const emailNotification = new Record(
$app.dao().findCollectionByNameOrId('notifications'),
{
user: user_id,
channel: `email`,
message_template: emailTemplate.getId(),
message_template_vars: { product_name },
payload: {
to: user.email(),
},
},
)
txDao.saveRecord(emailNotification)
log(`saved email notice`)
const discordTemplate = $app
.dao()
.findFirstRecordByData(
'message_templates',
`slug`,
`lemon_order_discord`,
)
const discordNotification = new Record(
$app.dao().findCollectionByNameOrId('notifications'),
{
user: user_id,
channel: `lemonbot`,
message_template: discordTemplate.getId(),
message_template_vars: { product_name },
},
)
txDao.saveRecord(discordNotification)
log(`saved discord notice`)
})
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
audit(`LS`, `Order ${order_number} ${product_name} processed.`, {
email,
user: user_id,
})
} catch (e) {
audit({ email, event: `LS_ERR` }, `Failed to notify discord: ${e}: ${raw}`)
return c.json(500, { status: 'failed to notify discord' })
audit(`LS_ERR`, `${e}`, {
email,
user: user_id,
raw_payload: raw,
})
return c.json(500, `${e}`)
}
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' })
})

View File

@ -0,0 +1,12 @@
interface WebHook {
meta: { custom_data: { user_id: string } }
data: {
type: 'orders' | string
attributes: {
order_number: number
first_order_item: { product_name: string; product_id: number }
status: 'active' | 'paid' | string
user_email: string
}
}
}