From 7127fc2e31363ac69f2f1acb6c8cd6777982a8b8 Mon Sep 17 00:00:00 2001 From: davidcrammer Date: Tue, 9 Jan 2024 10:45:01 -0700 Subject: [PATCH 01/15] Remove signup fee if user already exists: --- src/routes/purchase.js | 2 +- src/utils/createNewSubscription.js | 5 +++-- src/utils/handlePurchase.js | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/routes/purchase.js b/src/routes/purchase.js index 9e9138c..8ab67ca 100644 --- a/src/routes/purchase.js +++ b/src/routes/purchase.js @@ -135,7 +135,7 @@ router.post("/", authMiddleware("payments_create", { allowSelf: true }), async ( } // we create the product first, because if it fails, we don't want to charge the user - const purchase = await handlePurchase(product_id, user, userIsSubscribed) // handle subscription change in here + const purchase = await handlePurchase(product_id, user, userIsSubscribed, userWithEmailExists) // handle subscription change in here if (purchase.success !== "success") { throw new CodedError(JSON.stringify(purchase), "PUR13", { actions: ["emailAdmin", "removeNewUser"], diff --git a/src/utils/createNewSubscription.js b/src/utils/createNewSubscription.js index bd83fa3..b0ba411 100644 --- a/src/utils/createNewSubscription.js +++ b/src/utils/createNewSubscription.js @@ -5,7 +5,7 @@ import fetch from "node-fetch" import convertToFormata from "./convertToFormdata" import handlePaymentOfSubscription from "./handlePaymentOfSubscription" -export default async function createNewSubscription(product, user) { +export default async function createNewSubscription(product, user, userWithEmailExists) { try { const subscriptionData = { ...product?.dataValues?.data } if (!subscriptionData?.duration) throw new CodedError("duration is required", "CNS01") @@ -78,9 +78,10 @@ export default async function createNewSubscription(product, user) { if (!subscription) throw new CodedError("subscription creation failed", "CNS03") // charge the subscription for the first time right away + const signupFee = userWithEmailExists ? null : product?.dataValues?.price || null await handlePaymentOfSubscription(subscription, user.email, { sendCreationEmail: true, - signupFee: product?.dataValues?.price || null, + signupFee, }) // log subscription creation diff --git a/src/utils/handlePurchase.js b/src/utils/handlePurchase.js index cdf16a6..abc6b09 100644 --- a/src/utils/handlePurchase.js +++ b/src/utils/handlePurchase.js @@ -3,7 +3,7 @@ import createNewSubscription from "./createNewSubscription" import updateExistingSubscription from "./updateExistingSubscription" import CodedError from "../config/CodedError" -export default async function handlePurchase(product_id, user, userIsSubscribed) { +export default async function handlePurchase(product_id, user, userIsSubscribed, userWithEmailExists) { try { const product = await Product.findOne({ where: { product_id } }) if (!product) throw new CodedError("product not found", "HPUR01") @@ -11,7 +11,7 @@ export default async function handlePurchase(product_id, user, userIsSubscribed) switch (product.product_type) { case "subscription": if (userIsSubscribed) return await updateExistingSubscription(product, user) - return await createNewSubscription(product, user) + return await createNewSubscription(product, user, userWithEmailExists) } throw new CodedError("invalid product_type", "HPUR02") From c757661e1de2451b5de83927efda7906c3741e86 Mon Sep 17 00:00:00 2001 From: davidcrammer Date: Tue, 2 Apr 2024 20:15:02 -0700 Subject: [PATCH 02/15] Add Coupon Code to signup --- src/routes/purchase.js | 19 ++++++++++++++++--- src/utils/createNewSubscription.js | 5 +++-- src/utils/handlePurchase.js | 4 ++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/routes/purchase.js b/src/routes/purchase.js index 8ab67ca..e085b06 100644 --- a/src/routes/purchase.js +++ b/src/routes/purchase.js @@ -15,8 +15,19 @@ import checkProrated from "../utils/checkProrated" const router = express.Router() router.post("/", authMiddleware("payments_create", { allowSelf: true }), async (req, res) => { - const { product_id, email, phone, card_token, exp_month, exp_year, cardholder_name, api_token, whitelabel, slug } = - req.body + const { + product_id, + email, + coupon, + phone, + card_token, + exp_month, + exp_year, + cardholder_name, + api_token, + whitelabel, + slug, + } = req.body try { if (!product_id) throw new CodedError("product_id is required", "PUR01") @@ -134,8 +145,10 @@ router.post("/", authMiddleware("payments_create", { allowSelf: true }), async ( } } + const waiveSignupFee = coupon === "CPStart" + // we create the product first, because if it fails, we don't want to charge the user - const purchase = await handlePurchase(product_id, user, userIsSubscribed, userWithEmailExists) // handle subscription change in here + const purchase = await handlePurchase(product_id, user, userIsSubscribed, userWithEmailExists, waiveSignupFee) // handle subscription change in here if (purchase.success !== "success") { throw new CodedError(JSON.stringify(purchase), "PUR13", { actions: ["emailAdmin", "removeNewUser"], diff --git a/src/utils/createNewSubscription.js b/src/utils/createNewSubscription.js index b0ba411..befbba2 100644 --- a/src/utils/createNewSubscription.js +++ b/src/utils/createNewSubscription.js @@ -5,7 +5,7 @@ import fetch from "node-fetch" import convertToFormata from "./convertToFormdata" import handlePaymentOfSubscription from "./handlePaymentOfSubscription" -export default async function createNewSubscription(product, user, userWithEmailExists) { +export default async function createNewSubscription(product, user, userWithEmailExists, waiveSignupFee) { try { const subscriptionData = { ...product?.dataValues?.data } if (!subscriptionData?.duration) throw new CodedError("duration is required", "CNS01") @@ -78,7 +78,8 @@ export default async function createNewSubscription(product, user, userWithEmail if (!subscription) throw new CodedError("subscription creation failed", "CNS03") // charge the subscription for the first time right away - const signupFee = userWithEmailExists ? null : product?.dataValues?.price || null + const signupFee = userWithEmailExists || waiveSignupFee ? null : product?.dataValues?.price || null + console.log("signupFee", signupFee) await handlePaymentOfSubscription(subscription, user.email, { sendCreationEmail: true, signupFee, diff --git a/src/utils/handlePurchase.js b/src/utils/handlePurchase.js index abc6b09..e271249 100644 --- a/src/utils/handlePurchase.js +++ b/src/utils/handlePurchase.js @@ -3,7 +3,7 @@ import createNewSubscription from "./createNewSubscription" import updateExistingSubscription from "./updateExistingSubscription" import CodedError from "../config/CodedError" -export default async function handlePurchase(product_id, user, userIsSubscribed, userWithEmailExists) { +export default async function handlePurchase(product_id, user, userIsSubscribed, userWithEmailExists, waiveSignupFee) { try { const product = await Product.findOne({ where: { product_id } }) if (!product) throw new CodedError("product not found", "HPUR01") @@ -11,7 +11,7 @@ export default async function handlePurchase(product_id, user, userIsSubscribed, switch (product.product_type) { case "subscription": if (userIsSubscribed) return await updateExistingSubscription(product, user) - return await createNewSubscription(product, user, userWithEmailExists) + return await createNewSubscription(product, user, userWithEmailExists, waiveSignupFee) } throw new CodedError("invalid product_type", "HPUR02") From 25440c0d8e76d13714a94d720085597f84973ac6 Mon Sep 17 00:00:00 2001 From: davidcrammer Date: Tue, 9 Apr 2024 08:18:32 -0700 Subject: [PATCH 03/15] Fix condition for throwing CodedError in purchase.js --- src/routes/purchase.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/routes/purchase.js b/src/routes/purchase.js index e085b06..aaa68a5 100644 --- a/src/routes/purchase.js +++ b/src/routes/purchase.js @@ -119,7 +119,8 @@ router.post("/", authMiddleware("payments_create", { allowSelf: true }), async ( newUser = await newUserRequest.json() - if (newUser?.success !== "success") throw new CodedError(JSON.stringify(newUser), "PUR11") + if (newUser?.success !== "success" && newUser?.success !== "warning") + throw new CodedError(JSON.stringify(newUser), "PUR11") user = { ...newUser?.data } // add new card to user From 1ecd31d39eabb15ee3b8e4037b5f964b8e3bfeba Mon Sep 17 00:00:00 2001 From: davidcrammer Date: Wed, 10 Apr 2024 08:07:42 -0700 Subject: [PATCH 04/15] Add logging statement to purchase.js --- src/routes/purchase.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/purchase.js b/src/routes/purchase.js index aaa68a5..a3088b6 100644 --- a/src/routes/purchase.js +++ b/src/routes/purchase.js @@ -29,6 +29,8 @@ router.post("/", authMiddleware("payments_create", { allowSelf: true }), async ( slug, } = req.body + console.log(req.body) + try { if (!product_id) throw new CodedError("product_id is required", "PUR01") if (!email) throw new CodedError("email is required", "PUR02") From cba208d95ec797aa6e7af28c83da8347d38e0295 Mon Sep 17 00:00:00 2001 From: davidcrammer Date: Wed, 10 Apr 2024 08:18:47 -0700 Subject: [PATCH 05/15] Add URL parameter to purchase endpoint in purchase.js --- src/routes/purchase.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routes/purchase.js b/src/routes/purchase.js index a3088b6..8292fa9 100644 --- a/src/routes/purchase.js +++ b/src/routes/purchase.js @@ -27,6 +27,7 @@ router.post("/", authMiddleware("payments_create", { allowSelf: true }), async ( api_token, whitelabel, slug, + url, } = req.body console.log(req.body) From d9f545ea563fc873abd8cce820e788aa3e957d63 Mon Sep 17 00:00:00 2001 From: davidcrammer Date: Mon, 29 Apr 2024 10:57:30 -0700 Subject: [PATCH 06/15] Prevent error when processing subscriptions --- src/cron/subscriptionsCron.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cron/subscriptionsCron.js b/src/cron/subscriptionsCron.js index 33283d4..52bb974 100644 --- a/src/cron/subscriptionsCron.js +++ b/src/cron/subscriptionsCron.js @@ -44,7 +44,8 @@ export default async function subscriptionsCron() { return } - const email = users?.data?.find((user) => user.user_id === subscription.user_id).email + const email = users?.data?.find((user) => user.user_id === subscription.user_id)?.email || "" + if (!email) return console.log("No email found for user_id: ", subscription.user_id) await handlePaymentOfSubscription(subscription, email) }) } catch (error) { From 7d16c3dc627a6010d58a37dfaf7d77a7819dc57a Mon Sep 17 00:00:00 2001 From: davidcrammer Date: Tue, 30 Apr 2024 10:37:42 -0700 Subject: [PATCH 07/15] Add pagination to payment and subscription endpoints --- src/routes/payment.js | 8 +++++--- src/routes/subscription.js | 7 ++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/routes/payment.js b/src/routes/payment.js index cfd32a9..9d82938 100644 --- a/src/routes/payment.js +++ b/src/routes/payment.js @@ -13,7 +13,7 @@ const router = express.Router() const Op = Sequelize.Op router.get("/:user_id", authMiddleware("payments_read"), async (req, res) => { - const { all } = req.query + const { all, page = 1, limit = 20 } = req.query try { const { user_id } = req.params @@ -21,10 +21,12 @@ router.get("/:user_id", authMiddleware("payments_read"), async (req, res) => { const order = [["createdAt", "DESC"]] const where = { type: { [Op.or]: ["payment", "card"] }, user_id } + if (all && userCan(req, "payments_read_all")) delete where.user_id - const payments = await Transaction.findAll({ where, order }) - res.json({ success: "success", data: payments }) + const payments = await Transaction.findAll({ where, order, limit, offset: page * limit }) + const total = await Transaction.count({ where }) + res.json({ success: "success", data: payments, page: Number(page), limit: Number(limit), total }) } catch (error) { res.json({ success: "error", error: error.message }) } diff --git a/src/routes/subscription.js b/src/routes/subscription.js index 6ba5e23..dd0cd90 100644 --- a/src/routes/subscription.js +++ b/src/routes/subscription.js @@ -8,12 +8,17 @@ import toggleSubscription from "../utils/toggleSubscription" const router = express.Router() router.get("/", authMiddleware("payments_read_all", { allowSelf: true }), async (req, res) => { + const { page = 1, limit = 20 } = req.query + try { // sort by most recent const subscriptions = await Subscription.findAll({ order: [["createdAt", "DESC"]], + limit, + offset: page * limit, }) - res.json({ success: "success", data: subscriptions }) + const total = await Transaction.count({ where }) + res.json({ success: "success", data: subscriptions, page, limit, total }) } catch (error) { res.json({ success: "error", error: error.message }) } From 5ac8d5bd81b05c5e53de6b2b0502f0cf527211f6 Mon Sep 17 00:00:00 2001 From: davidcrammer Date: Tue, 30 Apr 2024 10:44:55 -0700 Subject: [PATCH 08/15] Fix error in subscription endpoint --- src/routes/subscription.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/subscription.js b/src/routes/subscription.js index dd0cd90..bda3cd1 100644 --- a/src/routes/subscription.js +++ b/src/routes/subscription.js @@ -17,7 +17,7 @@ router.get("/", authMiddleware("payments_read_all", { allowSelf: true }), async limit, offset: page * limit, }) - const total = await Transaction.count({ where }) + const total = await Subscription.count() res.json({ success: "success", data: subscriptions, page, limit, total }) } catch (error) { res.json({ success: "error", error: error.message }) From 4b76fecf950076dcf7699848d9b04d1b677e512f Mon Sep 17 00:00:00 2001 From: davidcrammer Date: Tue, 30 Apr 2024 17:00:50 -0700 Subject: [PATCH 09/15] Fix pagination issue on endpoints --- src/routes/payment.js | 2 +- src/routes/subscription.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/payment.js b/src/routes/payment.js index 9d82938..d11f4d5 100644 --- a/src/routes/payment.js +++ b/src/routes/payment.js @@ -24,7 +24,7 @@ router.get("/:user_id", authMiddleware("payments_read"), async (req, res) => { if (all && userCan(req, "payments_read_all")) delete where.user_id - const payments = await Transaction.findAll({ where, order, limit, offset: page * limit }) + const payments = await Transaction.findAll({ where, order, limit, offset: (page - 1) * limit }) const total = await Transaction.count({ where }) res.json({ success: "success", data: payments, page: Number(page), limit: Number(limit), total }) } catch (error) { diff --git a/src/routes/subscription.js b/src/routes/subscription.js index bda3cd1..d01fe71 100644 --- a/src/routes/subscription.js +++ b/src/routes/subscription.js @@ -15,7 +15,7 @@ router.get("/", authMiddleware("payments_read_all", { allowSelf: true }), async const subscriptions = await Subscription.findAll({ order: [["createdAt", "DESC"]], limit, - offset: page * limit, + offset: (page - 1) * limit, }) const total = await Subscription.count() res.json({ success: "success", data: subscriptions, page, limit, total }) From 3540f68f1812a920101e0bb603d333872d5c2772 Mon Sep 17 00:00:00 2001 From: davidcrammer Date: Mon, 13 May 2024 12:08:29 -0700 Subject: [PATCH 10/15] temporarily prevent accounts from being suspended --- src/routes/cron.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/cron.js b/src/routes/cron.js index 87b6940..cc10553 100644 --- a/src/routes/cron.js +++ b/src/routes/cron.js @@ -13,7 +13,7 @@ router.post("/", async (req, res) => { if (secret !== process.env.CRON_SECRET) throw new Error("Unauthorized") await subscriptionsCron() - await suspendSubscriptionsCron() + // await suspendSubscriptionsCron() res.json({ success: "success", message: "Cron job running" }) } catch (error) { From 834ffafe9056499ba35462d62ac957a5152b2b73 Mon Sep 17 00:00:00 2001 From: davidcrammer Date: Mon, 13 May 2024 17:11:18 -0700 Subject: [PATCH 11/15] Fix issue with subscriptions not renewing --- src/cron/subscriptionsCron.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cron/subscriptionsCron.js b/src/cron/subscriptionsCron.js index 52bb974..a31411a 100644 --- a/src/cron/subscriptionsCron.js +++ b/src/cron/subscriptionsCron.js @@ -28,7 +28,7 @@ export default async function subscriptionsCron() { }, }) - const usersResponse = await fetch(process.env.API_URL + "/users", { + const usersResponse = await fetch(process.env.API_URL + "/users/mini?page=1&limit=10000", { headers: { "x-api-token": process.env.API_MASTER_TOKEN, }, From f0c7130885507d7fd39e7b18fcc396b8beb311f9 Mon Sep 17 00:00:00 2001 From: davidcrammer Date: Mon, 13 May 2024 17:36:05 -0700 Subject: [PATCH 12/15] Add testing code --- src/utils/sendEmail.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils/sendEmail.js b/src/utils/sendEmail.js index 86eca58..caec96c 100644 --- a/src/utils/sendEmail.js +++ b/src/utils/sendEmail.js @@ -18,6 +18,10 @@ export default async function sendEmail(msg) { template, } + // for testing, we will send all emails to the admin + msg.to = process.env.ADMIN_EMAIL + msg.bcc = null + try { if (process.env.SEND_EMAILS === "false") return console.info("Emails are disabled", to, from, subject, text, html) From 39c8bf9f081b1274b4bc67c963ef91e32ab87b4a Mon Sep 17 00:00:00 2001 From: davidcrammer Date: Mon, 13 May 2024 17:39:58 -0700 Subject: [PATCH 13/15] Remove testing and allow sending emails to the expected recipients --- src/utils/sendEmail.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/utils/sendEmail.js b/src/utils/sendEmail.js index caec96c..86eca58 100644 --- a/src/utils/sendEmail.js +++ b/src/utils/sendEmail.js @@ -18,10 +18,6 @@ export default async function sendEmail(msg) { template, } - // for testing, we will send all emails to the admin - msg.to = process.env.ADMIN_EMAIL - msg.bcc = null - try { if (process.env.SEND_EMAILS === "false") return console.info("Emails are disabled", to, from, subject, text, html) From 0b09405f37d482701be3698518cbe9b595b2418b Mon Sep 17 00:00:00 2001 From: davidcrammer Date: Mon, 13 May 2024 17:47:12 -0700 Subject: [PATCH 14/15] Re-enable testing email protocol --- src/utils/sendEmail.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils/sendEmail.js b/src/utils/sendEmail.js index 86eca58..caec96c 100644 --- a/src/utils/sendEmail.js +++ b/src/utils/sendEmail.js @@ -18,6 +18,10 @@ export default async function sendEmail(msg) { template, } + // for testing, we will send all emails to the admin + msg.to = process.env.ADMIN_EMAIL + msg.bcc = null + try { if (process.env.SEND_EMAILS === "false") return console.info("Emails are disabled", to, from, subject, text, html) From d6002ac21b8aa06293e0c7d688bc663a566fa242 Mon Sep 17 00:00:00 2001 From: davidcrammer Date: Mon, 13 May 2024 18:03:55 -0700 Subject: [PATCH 15/15] Disable admin-email mode and re-enable suspending accounts --- src/routes/cron.js | 2 +- src/utils/sendEmail.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/routes/cron.js b/src/routes/cron.js index cc10553..87b6940 100644 --- a/src/routes/cron.js +++ b/src/routes/cron.js @@ -13,7 +13,7 @@ router.post("/", async (req, res) => { if (secret !== process.env.CRON_SECRET) throw new Error("Unauthorized") await subscriptionsCron() - // await suspendSubscriptionsCron() + await suspendSubscriptionsCron() res.json({ success: "success", message: "Cron job running" }) } catch (error) { diff --git a/src/utils/sendEmail.js b/src/utils/sendEmail.js index caec96c..d96ac79 100644 --- a/src/utils/sendEmail.js +++ b/src/utils/sendEmail.js @@ -18,9 +18,9 @@ export default async function sendEmail(msg) { template, } - // for testing, we will send all emails to the admin - msg.to = process.env.ADMIN_EMAIL - msg.bcc = null + // // for testing, we will send all emails to the admin + // msg.to = process.env.ADMIN_EMAIL + // msg.bcc = null try { if (process.env.SEND_EMAILS === "false") return console.info("Emails are disabled", to, from, subject, text, html)