Skip to content

Commit f06d7d7

Browse files
committed
add code redeem
1 parent 4d7cfbb commit f06d7d7

File tree

9 files changed

+120
-16
lines changed

9 files changed

+120
-16
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<script lang="ts" setup>
2+
import type { TApiSettings } from '@schema/ApiSettingsSchema';
3+
import type { SettingsTemplateEntry } from './Template.vue';
4+
5+
const { project } = useProject();
6+
7+
const entries: SettingsTemplateEntry[] = [
8+
{ id: 'acodes', title: 'Appsumo codes', text: 'Redeem appsumo codes' },
9+
]
10+
11+
const { createAlert } = useAlert()
12+
13+
const currentCode = ref<string>("");
14+
const redeeming = ref<boolean>(false);
15+
16+
const valid_codes = useFetch('/api/pay/valid_codes', signHeaders({ 'x-pid': project.value?._id.toString() ?? '' }));
17+
18+
async function redeemCode() {
19+
redeeming.value = true;
20+
try {
21+
const res = await $fetch<TApiSettings>('/api/pay/redeem_appsumo_code', {
22+
method: 'POST', ...signHeaders({
23+
'Content-Type': 'application/json',
24+
'x-pid': project.value?._id.toString() ?? ''
25+
}),
26+
body: JSON.stringify({ code: currentCode.value })
27+
});
28+
createAlert('Success', 'Code redeem success.', 'far fa-check', 5000);
29+
valid_codes.refresh();
30+
} catch (ex: any) {
31+
createAlert('Error', ex?.response?.statusText || 'Unexpected error. Contact support.', 'far fa-error', 5000);
32+
} finally {
33+
currentCode.value = '';
34+
}
35+
redeeming.value = false;
36+
}
37+
38+
</script>
39+
40+
41+
<template>
42+
<SettingsTemplate :entries="entries" :key="project?.name || 'NONE'">
43+
<template #acodes>
44+
<div class="flex items-center gap-4">
45+
<LyxUiInput class="w-full px-4 py-2" placeholder="Appsumo code" v-model="currentCode"></LyxUiInput>
46+
<LyxUiButton v-if="!redeeming" :disabled="currentCode.length == 0" @click="redeemCode()" type="primary">
47+
Redeem
48+
</LyxUiButton>
49+
<div v-if="redeeming">
50+
Redeeming...
51+
</div>
52+
</div>
53+
<div class="text-lyx-text-darker mt-1 text-[.9rem] poppins">
54+
Redeemed codes: {{ valid_codes.data.value?.count || '0' }}
55+
</div>
56+
</template>
57+
</SettingsTemplate>
58+
</template>

dashboard/pages/settings.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const items = [
77
{ label: 'General', slot: 'general' },
88
{ label: 'Members', slot: 'members' },
99
{ label: 'Billing', slot: 'billing' },
10+
{ label: 'Codes', slot: 'codes' },
1011
{ label: 'Account', slot: 'account' }
1112
]
1213
@@ -27,6 +28,9 @@ const items = [
2728
<template #billing>
2829
<SettingsBilling :key="refreshKey"></SettingsBilling>
2930
</template>
31+
<template #codes>
32+
<SettingsCodes :key="refreshKey"></SettingsCodes>
33+
</template>
3034
<template #account>
3135
<SettingsAccount :key="refreshKey"></SettingsAccount>
3236
</template>

dashboard/server/api/pay/redeem_code.post.ts renamed to dashboard/server/api/pay/redeem_appsumo_code.post.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import { getPlanFromId } from "@data/PREMIUM";
2-
import StripeService from '~/server/services/StripeService';
32
import { PREMIUM_PLAN } from "../../../../shared/data/PREMIUM";
4-
import { checkAppsumoCode, useAppsumoCode } from "~/server/services/AppsumoService";
5-
6-
3+
import { canTryAppsumoCode, checkAppsumoCode, useAppsumoCode, useTryAppsumoCode } from "~/server/services/AppsumoService";
4+
import StripeService from '~/server/services/StripeService';
75

86
function getPlanToActivate(current_plan_id: number) {
97
if (current_plan_id === PREMIUM_PLAN.FREE.ID) {
@@ -28,24 +26,26 @@ export default defineEventHandler(async event => {
2826
const data = await getRequestData(event, { requireSchema: false, allowGuests: false, allowLitlyx: false });
2927
if (!data) return;
3028

31-
const { project, pid } = data;
29+
const { project, pid, user } = data;
3230

3331
const body = await readBody(event);
3432

3533
const { code } = body;
3634

37-
const valid = await checkAppsumoCode(code);
35+
const canTry = await canTryAppsumoCode(pid);
36+
if (!canTry) return setResponseStatus(event, 400, 'You tried too much codes. Please contact support.');
37+
await useTryAppsumoCode(pid, code);
3838

39-
if (!valid) return setResponseStatus(event, 400, 'Current plan not found');
39+
const valid = await checkAppsumoCode(code);
40+
if (!valid) return setResponseStatus(event, 400, 'Code not valid');
4041

4142
const currentPlan = getPlanFromId(project.premium_type);
4243
if (!currentPlan) return setResponseStatus(event, 400, 'Current plan not found');
4344
const planToActivate = getPlanToActivate(currentPlan.ID);
4445
if (!planToActivate) return setResponseStatus(event, 400, 'Cannot use code on current plan');
4546

46-
await StripeService.deleteSubscription(project.subscription_id);
4747
await StripeService.createSubscription(project.customer_id, planToActivate.ID);
4848

49-
await useAppsumoCode(code);
49+
await useAppsumoCode(pid, code);
5050

5151
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { AppsumoCodeTryModel } from "@schema/AppsumoCodeTrySchema";
2+
3+
export default defineEventHandler(async event => {
4+
5+
const data = await getRequestData(event, { requireSchema: false, allowGuests: false, allowLitlyx: false });
6+
if (!data) return;
7+
8+
const { pid } = data;
9+
10+
const tryRes = await AppsumoCodeTryModel.findOne({ project_id: pid }, { valid_codes: 1 });
11+
if (!tryRes) return { count: 0 }
12+
return { count: tryRes.valid_codes.length }
13+
14+
});

dashboard/server/api/pay/webhook.post.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ async function onPaymentSuccess(event: Event.InvoicePaidEvent) {
133133
if (!price) return { error: 'Price not found' }
134134

135135
const PLAN = getPlanFromPrice(price, StripeService.testMode || false);
136-
if (!PLAN) return { error: 'Plan not found' }
136+
if (!PLAN) return { error: `Plan not found. Price: ${price}. TestMode: ${StripeService.testMode}` }
137137

138138
await addSubscriptionToProject(project._id.toString(), PLAN, subscription_id, currentSubscription.current_period_start, currentSubscription.current_period_end)
139139

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
11

22

3-
import { AppsumoCodeModel } from '@schema/AppsumoCode'
3+
import { AppsumoCodeModel } from '@schema/AppsumoCodeSchema';
4+
import { AppsumoCodeTryModel } from '@schema/AppsumoCodeTrySchema';
5+
6+
7+
export async function canTryAppsumoCode(project_id: string) {
8+
const tries = await AppsumoCodeTryModel.findOne({ project_id });
9+
if (!tries) return true;
10+
if (tries.codes.length >= 30) return false;
11+
return true;
12+
}
13+
14+
export async function useTryAppsumoCode(project_id: string, code: string) {
15+
await AppsumoCodeTryModel.updateOne({ project_id }, { $push: { codes: code } }, { upsert: true });
16+
}
417

518
export async function checkAppsumoCode(code: string) {
619
const target = await AppsumoCodeModel.exists({ code, used_at: { $exists: false } });
720
return target;
821
}
922

10-
11-
export async function useAppsumoCode(code: string) {
23+
export async function useAppsumoCode(project_id: string, code: string) {
24+
await AppsumoCodeTryModel.updateOne({ project_id }, { $push: { valid_codes: code } }, { upsert: true });
1225
await AppsumoCodeModel.updateOne({ code }, { used_at: Date.now() });
1326
}

shared/data/PREMIUM.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,23 +131,23 @@ export const PREMIUM_PLAN: Record<PREMIUM_TAG, PREMIUM_DATA> = {
131131
ID: 6001,
132132
COUNT_LIMIT: 50_000,
133133
AI_MESSAGE_LIMIT: 30,
134-
PRICE: '',
134+
PRICE: 'price_1QIXwbB2lPUiVs9VKSsoksaU',
135135
PRICE_TEST: '',
136136
COST: 0
137137
},
138138
APPSUMO_ACCELERATION: {
139139
ID: 6002,
140140
COUNT_LIMIT: 150_000,
141141
AI_MESSAGE_LIMIT: 100,
142-
PRICE: '',
142+
PRICE: 'price_1QIXxRB2lPUiVs9VrjaVRoOl',
143143
PRICE_TEST: '',
144144
COST: 0
145145
},
146146
APPSUMO_GROWTH: {
147147
ID: 6003,
148148
COUNT_LIMIT: 500_000,
149149
AI_MESSAGE_LIMIT: 3_000,
150-
PRICE: '',
150+
PRICE: 'price_1QIXy8B2lPUiVs9VQBOUPAoE',
151151
PRICE_TEST: '',
152152
COST: 0
153153
},
File renamed without changes.

shared/schema/AppsumoCodeTrySchema.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { model, Schema, Types } from 'mongoose';
2+
3+
export type TAppsumoCodeTry = {
4+
project_id: Types.ObjectId,
5+
codes: string[],
6+
valid_codes: string[],
7+
}
8+
9+
const AppsumoCodeTrySchema = new Schema<TAppsumoCodeTry>({
10+
project_id: { type: Schema.Types.ObjectId, required: true, unique: true, index: 1 },
11+
codes: [{ type: String }],
12+
valid_codes: [{ type: String }]
13+
});
14+
15+
export const AppsumoCodeTryModel = model<TAppsumoCodeTry>('appsumo_codes_tries', AppsumoCodeTrySchema);

0 commit comments

Comments
 (0)