diff --git a/src/mothership-app/pb_hooks/src/ls.pb.js b/src/mothership-app/pb_hooks/src/ls.pb.js
index f0370268..5325727b 100644
--- a/src/mothership-app/pb_hooks/src/ls.pb.js
+++ b/src/mothership-app/pb_hooks/src/ls.pb.js
@@ -1,27 +1,7 @@
-///
-
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: [
- `
Hello, welcome to the PocketHost Pro family!`,
- `
Please go find @noaxis on Discord and say hi.`,
- `
Thank you *so much!!!* for supporting PocketHost in these early days. If you have any questions or concerns, don't hesitate to reach out.`,
- `
~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' })
})
diff --git a/src/mothership-app/pb_hooks/types/lemon.d.ts b/src/mothership-app/pb_hooks/types/lemon.d.ts
new file mode 100644
index 00000000..1a1c69c2
--- /dev/null
+++ b/src/mothership-app/pb_hooks/types/lemon.d.ts
@@ -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
+ }
+ }
+}