Split stripe-webhook into multiple files
This commit is contained in:
parent
9ae0020279
commit
c402670d4d
|
@ -2,7 +2,6 @@
|
|||
require('dotenv').config()
|
||||
|
||||
const {verify} = require('hcaptcha')
|
||||
const mailer = require('./lib/mailer.js')
|
||||
|
||||
module.exports = async (req, res) => {
|
||||
|
||||
|
@ -18,7 +17,7 @@ module.exports = async (req, res) => {
|
|||
// Send email
|
||||
let mail_res; try {
|
||||
console.log(`Sending email from to ${process.env.BUGS_MAIL_FROM}...`)
|
||||
mail_res = await mailer.sendMail({
|
||||
mail_res = await require('../lib/mailer').sendMail({
|
||||
from: process.env.BUGS_MAIL_FROM,
|
||||
to: process.env.ADMIN_EMAIL,
|
||||
subject: 'Bug found!',
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
require('dotenv').config()
|
||||
|
||||
const {verify} = require('hcaptcha')
|
||||
const mailer = require('./lib/mailer.js')
|
||||
|
||||
module.exports = async (req, res) => {
|
||||
// console.log(`Received token: ${req.body['token']}`)
|
||||
|
@ -26,7 +25,7 @@ module.exports = async (req, res) => {
|
|||
// Send email
|
||||
let mail_res; try {
|
||||
console.log(`Sending email from ${from} to ${process.env.CONTACT_MAIL_TO}...`)
|
||||
mail_res = await mailer.sendMail({
|
||||
mail_res = await require('../lib/mailer').sendMail({
|
||||
from: process.env.CONTACT_MAIL_FROM,
|
||||
replyTo: from,
|
||||
to: process.env.CONTACT_MAIL_TO,
|
||||
|
|
|
@ -13,10 +13,10 @@ app.listen(process.env.API_PORT||80)
|
|||
app.get('/', (req, res) => res.sendStatus(200))
|
||||
|
||||
// Create new order
|
||||
app.post('/order', jsonBodyParser, cors, require('./order.js'))
|
||||
app.post('/order', jsonBodyParser, cors, require('./order'))
|
||||
|
||||
// Send email through contact form
|
||||
app.post('/contact', jsonBodyParser, cors, require('./contact.js'))
|
||||
app.post('/contact', jsonBodyParser, cors, require('./contact'))
|
||||
|
||||
// Send bug report through disclosure form
|
||||
app.post('/bug', jsonBodyParser, cors, require('./bug.js'))
|
||||
app.post('/bug', jsonBodyParser, cors, require('./bug'))
|
||||
|
|
|
@ -8,11 +8,7 @@ const ORDERS_DIR = `${__dirname}/../orders`
|
|||
// TODO: Use an eleventy hook to save this outside the public _site dir
|
||||
const PRODUCTS_FILE = `${__dirname}/../_site/assets/data/products.json`
|
||||
|
||||
const formatUSD = (v) => v.toLocaleString(undefined, {
|
||||
style: 'currency', currency: 'USD' })
|
||||
|
||||
// https://stackoverflow.com/a/57708635/3006854
|
||||
const fileExists = async (f) => !!(await fs.stat(f).catch(e=>false))
|
||||
const formatUSD = require('../lib/formatUSD')
|
||||
|
||||
// Read json files
|
||||
const readDataFile = async (path) => {
|
||||
|
@ -43,7 +39,7 @@ const getUspsRates = (zip, weight, volume) => {
|
|||
// Check if order.id exists in fs already
|
||||
const generateOrderId = async () => {
|
||||
const id = `r${Math.round(Math.random()*1000000000)}`
|
||||
if (await fileExists(`${ORDERS_DIR}/${id}.json`))
|
||||
if (await require('../lib/fileExists')(`${ORDERS_DIR}/${id}.json`))
|
||||
return generateOrderId()
|
||||
else return id
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
const fs = require('fs').promises
|
||||
// https://stackoverflow.com/a/57708635/3006854
|
||||
module.exports = async (f) => !!(await fs.stat(f).catch(e=>false))
|
|
@ -0,0 +1,2 @@
|
|||
module.exports = (v) => v.toLocaleString(undefined, {
|
||||
style: 'currency', currency: 'USD' })
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = async (cmd) =>
|
||||
await require('child_process').exec(cmd, (err, stdout, stderr) => {
|
||||
if (err) console.error(err.message)
|
||||
if (stderr) console.log(stderr)
|
||||
console.log(stdout)
|
||||
})
|
|
@ -11,7 +11,7 @@
|
|||
"serve": "npx @11ty/eleventy --quiet --serve",
|
||||
"json": "npx @11ty/eleventy --to=json",
|
||||
"api": "node api/index.js",
|
||||
"stripe-webhook": "node stripe-webhook.js"
|
||||
"stripe-webhook": "node stripe-webhook/index.js"
|
||||
},
|
||||
"author": {
|
||||
"name": "Keith Irwin",
|
||||
|
|
|
@ -1,138 +0,0 @@
|
|||
'use strict'
|
||||
require('dotenv').config()
|
||||
const fs = require('fs').promises
|
||||
const axios = require('axios')
|
||||
const express = require('express')
|
||||
const app = express()
|
||||
|
||||
const ORDERS_DIR = `${__dirname}/orders`
|
||||
const SHOP_DIR = `${__dirname}/_src/shop`
|
||||
const SOLD_DIR = `${__dirname}/sold`
|
||||
|
||||
// Mailserver
|
||||
const mailer = require('nodemailer').createTransport({
|
||||
host: process.env.MAIL_SERVER,
|
||||
port: 587,
|
||||
auth: {
|
||||
user: process.env.MAIL_USER,
|
||||
pass: process.env.MAIL_PASS,
|
||||
},
|
||||
tls: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
})
|
||||
|
||||
// Run a command
|
||||
const run = async (cmd) =>
|
||||
await require('child_process').exec(cmd, (err, stdout, stderr) => {
|
||||
if (err) console.error(err.message)
|
||||
if (stderr) console.log(stderr)
|
||||
console.log(stdout)
|
||||
})
|
||||
|
||||
// Format float to USD string
|
||||
const formatUSD = (v) => v.toLocaleString(undefined, {
|
||||
style: 'currency', currency: 'USD' })
|
||||
|
||||
// Start webhook forwarder so we don't need a public endpoint
|
||||
run(`stripe listen --api-key '${process.env.STRIPE_SEC}' --forward-to 'http://localhost:${process.env.HOOK_PORT}/' --format 'JSON'`)
|
||||
|
||||
// Receive that webhook
|
||||
app.listen(process.env.HOOK_PORT)
|
||||
|
||||
app.post('/', express.json(), async (req) => {
|
||||
|
||||
if (req.body.data.object.object==='charge') {
|
||||
|
||||
// Check if paid
|
||||
if (!req.body.data.object.paid)
|
||||
return console.log(`[${req.body.id}] Charge unpaid!`)
|
||||
|
||||
// Get order ID
|
||||
const orderId = req.body.data.object.metadata.id
|
||||
if (orderId == null)
|
||||
return console.error(`[${req.body.id}] Charge has no metadata.id!`)
|
||||
else
|
||||
console.log(`[${req.body.id}] Charge paid for order ${orderId}`)
|
||||
|
||||
// Get order file
|
||||
const orderFile = `${ORDERS_DIR}/${orderId}.json`
|
||||
let order; try {
|
||||
order = await JSON.parse(await fs.readFile(orderFile))
|
||||
} catch (err) {
|
||||
return console.error(`[${req.body.id}] Failed to retrieve order from ${orderFile}:\n${err}`)
|
||||
}
|
||||
|
||||
// Save paidDate to order
|
||||
try { order.paidDate = new Date()
|
||||
fs.writeFile(orderFile, JSON.stringify(order,null,2))
|
||||
} catch (err) {
|
||||
console.error(`[${req.body.id}] Failed to write paidDate to ${orderFile}:\n${err}`)
|
||||
}
|
||||
|
||||
// Email customer
|
||||
const customer_mail_items_string = order.items.reduce((acc,cur) =>
|
||||
acc += `${cur.qty} ${formatUSD(cur.price)} ${cur.subtotal} ${cur.name}\n`
|
||||
, 'Qty Price Subtotal Item\n')
|
||||
let customer_mail_res; try {
|
||||
console.log(`Emailing order ${order.id} confirmation to customer at ${order.contact.email}`)
|
||||
customer_mail_res = await mailer.sendMail({
|
||||
from: process.env.SALES_MAIL_FROM,
|
||||
to: order.contact.email,
|
||||
replyTo: process.env.SALES_EMAIL,
|
||||
subject: `Order ${order.id} for ${formatUSD(order.total)} has been processed`,
|
||||
text: `Dear ${order.contact.name},\n\nThank you for your payment. We will ship your order as soon as possible and send the tracking info to this email address. For now, keep this copy of your order details as proof of your payment. \n\n---\n\nITEMS: \n${customer_mail_items_string}\nTOTALS: \nSubtotal: ${formatUSD(order.subtotal)}\nTax: ${formatUSD(order.taxAmount)}\nShipping: ${formatUSD(order.shipping.amount)}\nProcessing: ${formatUSD(order.processing)}\n\nCONTACT: \n${order.contact.name}\n${order.contact.phone}\n${order.contact.email}\n\nSHIPPING ADDRESS:\n${order.shipping.address.name}\n${order.shipping.address.addr1}${(order.shipping.address.addr2)?'\n'+order.shipping.address.addr2:''}\n${order.shipping.address.city}, ${order.shipping.address.state} ${order.shipping.address.zip}\n\n---\n\nFor questions, comments, or to cancel this order, reply to this email or call 719-936-7778 x1. \n\nThank you!\n\n`,
|
||||
})
|
||||
} catch (err) { console.error(err) }
|
||||
console.log(`Sent email ${customer_mail_res.messageId} to customer for order ${order.id}`)
|
||||
|
||||
// Ntfy sales team
|
||||
axios.post(
|
||||
`${process.env.NTFY_DOMAIN}/${process.env.NTFY_TOPIC}`,
|
||||
`Order ${order.id} needs ${order.items.length} items shipped.`,
|
||||
{ headers: {
|
||||
Title: `Order for ${formatUSD(order.total)} processed`,
|
||||
Tags: 'moneybag,package'
|
||||
} }
|
||||
)
|
||||
|
||||
// Email sales team
|
||||
const sales_mail_items_string = order.items.reduce((acc,cur) =>
|
||||
acc += `${cur.pid}/${cur.sid} ${cur.qty} ${cur.weight} ${cur.volume}\n`
|
||||
, 'PID/SID qty lbs in³\n')
|
||||
let sales_mail_res; try {
|
||||
console.log(`Emailing sales team at ${process.env.SALES_EMAIL} about order ${order.id}`)
|
||||
sales_mail_res = await mailer.sendMail({
|
||||
from: process.env.SALES_MAIL_FROM,
|
||||
to: process.env.SALES_EMAIL,
|
||||
subject: `Order for ${formatUSD(order.total)} processed`,
|
||||
text: `Order ${order.id} needs ${order.items.length} items shipped: \n\n${sales_mail_items_string}\nSHIP TO:\n${order.shipping.address.name}\n${order.shipping.address.addr1}${(order.shipping.address.addr2)?'\n'+order.shipping.address.addr2:''}\n${order.shipping.address.city}, ${order.shipping.address.state} ${order.shipping.address.zip}\n\nCONTACT:\n${order.contact.name}\n${order.contact.phone}\n${order.contact.email}\n`,
|
||||
})
|
||||
} catch (err) { console.error(err) }
|
||||
console.log(`Sent email ${sales_mail_res.messageId} to sales team for order ${order.id}`)
|
||||
|
||||
// Remove single products from store
|
||||
const getSoldSids = new Promise((resolve, reject) => {
|
||||
order.items.forEach(async (item,i,arr) => {
|
||||
let sids = []
|
||||
if (item.sid) {
|
||||
console.log(`Moving sid ${item.sid} from ${SHOP_DIR} to ${SOLD_DIR}`)
|
||||
sids.push(item.sid)
|
||||
await run(`find ${SHOP_DIR} -type d -name ${item.sid} -exec mv {} ${SOLD_DIR}/${item.sid} \\;`)
|
||||
}
|
||||
if (i===arr.length -1) resolve(sids)
|
||||
})
|
||||
})
|
||||
let sold_sids; try {
|
||||
sold_sids = await getSoldSids
|
||||
if (sold_sids.length>0) {
|
||||
run('npx @11ty/eleventy --quiet')
|
||||
await run('git add _src/shop')
|
||||
await run(`git commit -m 'Sold ${sold_sids.join()}'`)
|
||||
run('git push')
|
||||
}
|
||||
} catch (err) { console.error(err) }
|
||||
|
||||
}
|
||||
|
||||
})
|
|
@ -0,0 +1,17 @@
|
|||
'use strict'
|
||||
require('dotenv').config()
|
||||
formatUSD = require('../lib/formatUSD')
|
||||
const customer_mail_items_string = order.items.reduce((acc,cur) =>
|
||||
acc += `${cur.qty} ${formatUSD(cur.price)} ${cur.subtotal} ${cur.name}\n`
|
||||
, 'Qty Price Subtotal Item\n')
|
||||
let customer_mail_res; try {
|
||||
console.log(`Emailing order ${order.id} confirmation to customer at ${order.contact.email}`)
|
||||
customer_mail_res = await require('../lib/mailer').sendMail({
|
||||
from: process.env.SALES_MAIL_FROM,
|
||||
to: order.contact.email,
|
||||
replyTo: process.env.SALES_EMAIL,
|
||||
subject: `Order ${order.id} for ${formatUSD(order.total)} has been processed`,
|
||||
text: `Dear ${order.contact.name},\n\nThank you for your payment. We will ship your order as soon as possible and send the tracking info to this email address. For now, keep this copy of your order details as proof of your payment. \n\n---\n\nITEMS: \n${customer_mail_items_string}\nTOTALS: \nSubtotal: ${formatUSD(order.subtotal)}\nTax: ${formatUSD(order.taxAmount)}\nShipping: ${formatUSD(order.shipping.amount)}\nProcessing: ${formatUSD(order.processing)}\n\nCONTACT: \n${order.contact.name}\n${order.contact.phone}\n${order.contact.email}\n\nSHIPPING ADDRESS:\n${order.shipping.address.name}\n${order.shipping.address.addr1}${(order.shipping.address.addr2)?'\n'+order.shipping.address.addr2:''}\n${order.shipping.address.city}, ${order.shipping.address.state} ${order.shipping.address.zip}\n\n---\n\nFor questions, comments, or to cancel this order, reply to this email or call 719-936-7778 x1. \n\nThank you!\n\n`,
|
||||
})
|
||||
} catch (err) { console.error(err) }
|
||||
console.log(`Sent email ${customer_mail_res.messageId} to customer for order ${order.id}`)
|
|
@ -0,0 +1,15 @@
|
|||
'use strict'
|
||||
require('dotenv').config()
|
||||
const sales_mail_items_string = order.items.reduce((acc,cur) =>
|
||||
acc += `${cur.pid}/${cur.sid} ${cur.qty} ${cur.weight} ${cur.volume}\n`
|
||||
, 'PID/SID qty lbs in³\n')
|
||||
let sales_mail_res; try {
|
||||
console.log(`Emailing sales team at ${process.env.SALES_EMAIL} about order ${order.id}`)
|
||||
sales_mail_res = await require('../lib/mailer').sendMail({
|
||||
from: process.env.SALES_MAIL_FROM,
|
||||
to: process.env.SALES_EMAIL,
|
||||
subject: `Order for ${require('../lib/formatUSD')(order.total)} processed`,
|
||||
text: `Order ${order.id} needs ${order.items.length} items shipped: \n\n${sales_mail_items_string}\nSHIP TO:\n${order.shipping.address.name}\n${order.shipping.address.addr1}${(order.shipping.address.addr2)?'\n'+order.shipping.address.addr2:''}\n${order.shipping.address.city}, ${order.shipping.address.state} ${order.shipping.address.zip}\n\nCONTACT:\n${order.contact.name}\n${order.contact.phone}\n${order.contact.email}\n`,
|
||||
})
|
||||
} catch (err) { console.error(err) }
|
||||
console.log(`Sent email ${sales_mail_res.messageId} to sales team for order ${order.id}`)
|
|
@ -0,0 +1,58 @@
|
|||
'use strict'
|
||||
require('dotenv').config()
|
||||
const fs = require('fs').promises
|
||||
const express = require('express')
|
||||
const app = express()
|
||||
|
||||
const ORDERS_DIR = `${__dirname}/orders`
|
||||
|
||||
// Start webhook forwarder so we don't need a public endpoint
|
||||
require('../lib/run.js')(`stripe listen --api-key '${process.env.STRIPE_SEC}' --forward-to 'http://localhost:${process.env.HOOK_PORT}/' --format 'JSON'`)
|
||||
|
||||
// Receive that webhook
|
||||
app.listen(process.env.HOOK_PORT)
|
||||
|
||||
app.post('/', express.json(), async (req) => {
|
||||
if (req.body.data.object.object==='charge') {
|
||||
|
||||
// Check if paid
|
||||
if (!req.body.data.object.paid)
|
||||
return console.log(`[${req.body.id}] Charge unpaid!`)
|
||||
|
||||
// Get order ID
|
||||
const orderId = req.body.data.object.metadata.id
|
||||
if (orderId == null)
|
||||
return console.error(`[${req.body.id}] Charge has no metadata.id!`)
|
||||
else
|
||||
console.log(`[${req.body.id}] Charge paid for order ${orderId}`)
|
||||
|
||||
// Get order file
|
||||
const orderFile = `${ORDERS_DIR}/${orderId}.json`
|
||||
let order; try {
|
||||
order = await JSON.parse(await fs.readFile(orderFile))
|
||||
} catch (err) {
|
||||
return console.error(`[${req.body.id}] Failed to retrieve order from ${orderFile}:\n${err}`)
|
||||
}
|
||||
|
||||
// Save paidDate to order
|
||||
try { order.paidDate = new Date()
|
||||
fs.writeFile(orderFile, JSON.stringify(order,null,2))
|
||||
} catch (err) {
|
||||
console.error(`[${req.body.id}] Failed to write paidDate to ${orderFile}:\n${err}`)
|
||||
}
|
||||
|
||||
// Email customer
|
||||
require('./email-customer')()
|
||||
|
||||
// Ntfy sales team
|
||||
require('./ntfy-sales')()
|
||||
|
||||
// Email sales team
|
||||
require('./email-sales')()
|
||||
|
||||
// Remove single products from store
|
||||
require('./remove-sids')()
|
||||
|
||||
}
|
||||
|
||||
})
|
|
@ -0,0 +1,11 @@
|
|||
'use strict'
|
||||
require('dotenv').config()
|
||||
const axios = require('axios')
|
||||
axios.post(
|
||||
`${process.env.NTFY_DOMAIN}/${process.env.NTFY_TOPIC}`,
|
||||
`Order ${order.id} needs ${order.items.length} items shipped.`,
|
||||
{ headers: {
|
||||
Title: `Order for ${require('../lib/formatUSD')(order.total)} processed`,
|
||||
Tags: 'moneybag,package'
|
||||
} }
|
||||
)
|
|
@ -0,0 +1,25 @@
|
|||
'use strict'
|
||||
require('dotenv').config()
|
||||
const run = require('../lib/run')
|
||||
const SHOP_DIR = `${__dirname}/_src/shop`
|
||||
const SOLD_DIR = `${__dirname}/sold`
|
||||
const getSoldSids = new Promise((resolve) => {
|
||||
order.items.forEach(async (item,i,arr) => {
|
||||
let sids = []
|
||||
if (item.sid) {
|
||||
console.log(`Moving sid ${item.sid} from ${SHOP_DIR} to ${SOLD_DIR}`)
|
||||
sids.push(item.sid)
|
||||
await run(`find ${SHOP_DIR} -type d -name ${item.sid} -exec mv {} ${SOLD_DIR}/${item.sid} \\;`)
|
||||
}
|
||||
if (i===arr.length -1) resolve(sids)
|
||||
})
|
||||
})
|
||||
let sold_sids; try {
|
||||
sold_sids = await getSoldSids
|
||||
if (sold_sids.length>0) {
|
||||
run('npx @11ty/eleventy --quiet')
|
||||
await run('git add _src/shop')
|
||||
await run(`git commit -m 'Sold ${sold_sids.join()}'`)
|
||||
run('git push')
|
||||
}
|
||||
} catch (err) { console.error(err) }
|
Loading…
Reference in New Issue