From e3528f7471dccfbc4e0dd97af791eb302c1258e4 Mon Sep 17 00:00:00 2001 From: coyotte508 Date: Thu, 1 Jun 2023 23:35:18 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=A6=BA=20Prevent=20subscribing=20twice=20?= =?UTF-8?q?to=20same=20thing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 3 -- src/lib/server/runtime-config.ts | 2 +- src/lib/types/Order.ts | 2 -- src/lib/types/PaidSubscription.ts | 2 +- src/routes/admin/config/+page.server.ts | 8 +++-- src/routes/checkout/+page.server.ts | 46 ++++++++++++++++++++++++- 6 files changed, 53 insertions(+), 10 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index f9dcb5d96..4180dd73a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,8 +9,5 @@ }, "[css]": { "editor.defaultFormatter": "vscode.css-language-features" - }, - "[typescript]": { - "editor.defaultFormatter": "rvest.vs-code-prettier-eslint" } } diff --git a/src/lib/server/runtime-config.ts b/src/lib/server/runtime-config.ts index a1eb3bdec..73e34d52f 100644 --- a/src/lib/server/runtime-config.ts +++ b/src/lib/server/runtime-config.ts @@ -4,7 +4,7 @@ import { collections } from './database'; const defaultConfig = { BTC_EUR: 30_000, orderNumber: 0, - subscriptionDuration: "month" as "month" | "day" | "hour", + subscriptionDuration: 'month' as 'month' | 'day' | 'hour', subscriptionReminderSeconds: 24 * 60 * 60, checkoutButtonOnProductPage: true, diff --git a/src/lib/types/Order.ts b/src/lib/types/Order.ts index cb6caa73e..d672b4cae 100644 --- a/src/lib/types/Order.ts +++ b/src/lib/types/Order.ts @@ -2,7 +2,6 @@ import type { Product } from './Product'; import type { Currency } from './Currency'; import type { CountryAlpha3 } from './Country'; import type { Timestamps } from './Timestamps'; -import type { ObjectId } from 'mongodb'; export interface Order extends Timestamps { /** @@ -16,7 +15,6 @@ export interface Order extends Timestamps { items: Array<{ product: Product; quantity: number; - subscriptionId?: ObjectId; }>; shippingAddress?: { diff --git a/src/lib/types/PaidSubscription.ts b/src/lib/types/PaidSubscription.ts index a6ab58c72..e7de5a752 100644 --- a/src/lib/types/PaidSubscription.ts +++ b/src/lib/types/PaidSubscription.ts @@ -11,6 +11,6 @@ export interface PaidSubscription extends Timestamps { npub: string; productId: string; - paidUntil?: Date; + paidUntil: Date; lastRemindedAt?: Date; } diff --git a/src/routes/admin/config/+page.server.ts b/src/routes/admin/config/+page.server.ts index d9270b05b..b21a550e2 100644 --- a/src/routes/admin/config/+page.server.ts +++ b/src/routes/admin/config/+page.server.ts @@ -21,8 +21,12 @@ export const actions = { .object({ checkoutButtonOnProductPage: z.boolean({ coerce: true }), discovery: z.boolean({ coerce: true }), - subscriptionDuration: z.enum(["month", "day", "hour"]), - subscriptionReminderSeconds: z.number({coerce: true}).int().min(0).max(24 * 60 * 60 * 7) + subscriptionDuration: z.enum(['month', 'day', 'hour']), + subscriptionReminderSeconds: z + .number({ coerce: true }) + .int() + .min(0) + .max(24 * 60 * 60 * 7) }) .parse({ checkoutButtonOnProductPage: formData.get('checkoutButtonOnProductPage'), diff --git a/src/routes/checkout/+page.server.ts b/src/routes/checkout/+page.server.ts index 4112972dd..10bba3156 100644 --- a/src/routes/checkout/+page.server.ts +++ b/src/routes/checkout/+page.server.ts @@ -4,11 +4,12 @@ import { lndCreateInvoice } from '$lib/server/lightning.js'; import { paymentMethods } from '$lib/server/payment-methods.js'; import { COUNTRY_ALPHA3S } from '$lib/types/Country'; import { error, redirect } from '@sveltejs/kit'; -import { addHours, differenceInSeconds } from 'date-fns'; +import { addHours, differenceInSeconds, subSeconds } from 'date-fns'; import { z } from 'zod'; import { bech32 } from 'bech32'; import { ORIGIN } from '$env/static/private'; import { toSatoshis } from '$lib/utils/toSatoshis.js'; +import { runtimeConfig } from '$lib/server/runtime-config.js'; export function load() { return { @@ -104,6 +105,49 @@ export const actions = { const orderId = crypto.randomUUID(); + const subscriptions = cart.items.filter((item) => byId[item.productId].type === 'subscription'); + + for (const subscription of subscriptions) { + const product = byId[subscription.productId]; + + if (subscription.quantity > 1) { + throw error( + 400, + 'Cannot order more than one of a subscription at a time for product: ' + product.name + ); + } + + const existingSubscription = await collections.paidSubscriptions.findOne({ + npub: npubAddress, + productId: product._id + }); + + if (existingSubscription) { + if ( + subSeconds(existingSubscription.paidUntil, runtimeConfig.subscriptionReminderSeconds) > + new Date() + ) { + throw error( + 400, + 'You already have an active subscription for this product: ' + product.name + ); + } + } + + if ( + await collections.orders.countDocuments( + { + 'notifications.paymentStatus.npub': npubAddress, + 'items.product._id': product._id, + 'payment.status': 'pending' + }, + { limit: 1 } + ) + ) { + throw error(400, 'You already have a pending order for this product: ' + product.name); + } + } + await withTransaction(async (session) => { const res = await collections.runtimeConfig.findOneAndUpdate( { _id: 'orderNumber' },