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 + } + } +}