Skip to content

Commit 8449827

Browse files
committed
feat: add idv check for store
1 parent f585eda commit 8449827

File tree

4 files changed

+156
-1
lines changed

4 files changed

+156
-1
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<script lang="ts">
2+
import Button from "./Button.svelte";
3+
4+
const {
5+
onClose,
6+
}: {
7+
onClose: () => void;
8+
} = $props();
9+
</script>
10+
11+
<div class="modal-overlay" onclick={(e) => e.target === e.currentTarget && onClose()}>
12+
<div class="modal-content">
13+
<h2 class="modal-title">Verification Required</h2>
14+
<p class="modal-text">
15+
You must be verified eligible to purchase items from the shop.
16+
Please complete verification before making a purchase.
17+
</p>
18+
<div class="button-container">
19+
<Button
20+
label="Close"
21+
onclick={onClose}
22+
color="black"
23+
/>
24+
</div>
25+
</div>
26+
</div>
27+
28+
<style>
29+
.modal-overlay {
30+
position: fixed;
31+
inset: 0;
32+
background: rgba(0, 0, 0, 0.7);
33+
display: flex;
34+
align-items: center;
35+
justify-content: center;
36+
z-index: 1000;
37+
}
38+
39+
.modal-content {
40+
background: #5E5087;
41+
border: 2px solid #fee1c0;
42+
border-radius: 16px;
43+
padding: 40px;
44+
max-width: 500px;
45+
width: 90%;
46+
display: flex;
47+
flex-direction: column;
48+
gap: 24px;
49+
}
50+
51+
.modal-title {
52+
font-family: "Moga", sans-serif;
53+
font-size: 36px;
54+
color: #fee1c0;
55+
text-align: center;
56+
letter-spacing: -0.99px;
57+
margin: 0;
58+
line-height: 1.2;
59+
}
60+
61+
.modal-text {
62+
font-family: "PT Sans", sans-serif;
63+
font-size: 18px;
64+
color: white;
65+
text-align: center;
66+
letter-spacing: -0.264px;
67+
line-height: 1.5;
68+
margin: 0;
69+
}
70+
71+
.button-container {
72+
display: flex;
73+
justify-content: center;
74+
margin-top: 8px;
75+
}
76+
</style>
77+

lark-ui/src/lib/auth.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,26 @@ export async function getShopBalance(fetchFn: FetchFunction = fetch): Promise<Sh
524524
return null;
525525
}
526526

527+
export async function checkVerificationStatus(email: string, fetchFn: FetchFunction = fetch): Promise<{ isVerified: boolean; error?: string }> {
528+
const externalApiUrl = 'https://identity.hackclub.com/api/external/check';
529+
const checkUrl = `${externalApiUrl}?email=${encodeURIComponent(email)}`;
530+
531+
try {
532+
const response = await fetchFn(checkUrl);
533+
534+
if (!response.ok) {
535+
return { isVerified: false, error: 'Failed to check verification status' };
536+
}
537+
538+
const data = await response.json();
539+
const isVerified = data.result === 'verified_eligible';
540+
541+
return { isVerified };
542+
} catch (error) {
543+
return { isVerified: false, error: 'Failed to check verification status' };
544+
}
545+
}
546+
527547
export async function purchaseShopItem(itemId: number, variantId?: number, fetchFn: FetchFunction = fetch): Promise<{ success: boolean; error?: string; transaction?: ShopTransaction; newBalance?: ShopBalance; specialAction?: string | null }> {
528548
const body: { itemId: number; variantId?: number } = { itemId };
529549
if (variantId) {

lark-ui/src/routes/app/shop/[id]/+page.svelte

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import { checkAuthStatus, getShopItem, getShopBalance, purchaseShopItem, type User, type ShopItem, type ShopBalance, type ShopItemVariant } from "$lib/auth";
55
import { page } from "$app/state";
66
import Button from "$lib/Button.svelte";
7+
import VerificationPopup from "$lib/VerificationPopup.svelte";
78
89
let user: User | null = $state<User | null>(null);
910
let item: ShopItem | null = $state(null);
@@ -14,6 +15,7 @@
1415
let success = $state('');
1516
let selectedVariantId = $state<number | null>(null);
1617
let specialAction = $state<string | null>(null);
18+
let showVerificationPopup = $state(false);
1719
1820
const itemId = parseInt(page.params.id!);
1921
@@ -40,7 +42,7 @@
4042
});
4143
4244
async function handlePurchase() {
43-
if (!item || purchasing) return;
45+
if (!item || purchasing || !user) return;
4446
4547
const hasVariants = item.variants && item.variants.length > 0;
4648
if (hasVariants && !selectedVariantId) {
@@ -66,6 +68,9 @@
6668
}
6769
} else {
6870
error = result.error || 'Purchase failed';
71+
if (result.error?.includes('verified eligible') || result.error?.includes('verify eligibility')) {
72+
showVerificationPopup = true;
73+
}
6974
}
7075
7176
purchasing = false;
@@ -181,6 +186,10 @@
181186
</div>
182187
</div>
183188
{/if}
189+
190+
{#if showVerificationPopup}
191+
<VerificationPopup onClose={() => showVerificationPopup = false} />
192+
{/if}
184193
</div>
185194

186195
<style>

owl-api/src/shop/shop.service.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,51 @@ export class ShopService {
172172
}
173173

174174
async purchaseItem(userId: number, itemId: number, variantId?: number) {
175+
console.log(`[Shop Purchase] Starting purchase for userId: ${userId}, itemId: ${itemId}, variantId: ${variantId || 'none'}`);
176+
177+
const user = await this.prisma.user.findUnique({
178+
where: { userId },
179+
select: { email: true },
180+
});
181+
182+
if (!user || !user.email) {
183+
console.error(`[Shop Purchase] User email not found for userId: ${userId}`);
184+
throw new BadRequestException('User email not found');
185+
}
186+
187+
console.log(`[Shop Purchase] Checking verification status for user: ${userId}, email: ${user.email}`);
188+
const externalApiBaseUrl = this.configService.get<string>('EXTERNAL_VERIFICATION_API_URL', 'https://identity.hackclub.com/api/external');
189+
const checkUrl = `${externalApiBaseUrl}/check?email=${encodeURIComponent(user.email)}`;
190+
console.log(`[Shop Purchase] Verification API URL: ${checkUrl}`);
191+
192+
try {
193+
const verificationResponse = await fetch(checkUrl);
194+
console.log(`[Shop Purchase] Verification API response status: ${verificationResponse.status}`);
195+
196+
if (!verificationResponse.ok) {
197+
const errorText = await verificationResponse.text().catch(() => 'Unable to read response');
198+
console.error(`[Shop Purchase] Verification API returned non-OK status: ${verificationResponse.status}, response: ${errorText}`);
199+
throw new BadRequestException('Failed to verify eligibility. Please try again later.');
200+
}
201+
202+
const verificationData = await verificationResponse.json();
203+
console.log(`[Shop Purchase] Verification API response data:`, JSON.stringify(verificationData));
204+
205+
if (verificationData.result !== 'verified_eligible') {
206+
console.warn(`[Shop Purchase] User ${userId} (${user.email}) is not verified_eligible. Result: ${verificationData.result}`);
207+
throw new BadRequestException('You must be verified eligible to purchase items from the shop');
208+
}
209+
210+
console.log(`[Shop Purchase] User ${userId} (${user.email}) verification check passed`);
211+
} catch (error) {
212+
if (error instanceof BadRequestException) {
213+
console.log(`[Shop Purchase] Verification check failed for user ${userId}: ${error.message}`);
214+
throw error;
215+
}
216+
console.error(`[Shop Purchase] Error checking verification status for user ${userId}:`, error);
217+
throw new BadRequestException('Failed to verify eligibility. Please try again later.');
218+
}
219+
175220
const item = await this.prisma.shopItem.findUnique({
176221
where: { itemId },
177222
include: {
@@ -231,6 +276,7 @@ export class ShopService {
231276
);
232277
}
233278

279+
console.log(`[Shop Purchase] Creating transaction for userId: ${userId}, itemId: ${itemId}, cost: ${cost}`);
234280
const transaction = await this.prisma.transaction.create({
235281
data: {
236282
userId,
@@ -245,7 +291,9 @@ export class ShopService {
245291
},
246292
});
247293

294+
console.log(`[Shop Purchase] Transaction created successfully: transactionId ${transaction.transactionId}`);
248295
const newBalance = await this.getUserBalance(userId);
296+
console.log(`[Shop Purchase] New balance for userId ${userId}: ${newBalance.balance} hours`);
249297

250298
let specialAction: string | null = null;
251299

@@ -298,6 +346,7 @@ export class ShopService {
298346
}
299347
}
300348

349+
console.log(`[Shop Purchase] Purchase completed successfully for userId: ${userId}, transactionId: ${transaction.transactionId}, specialAction: ${specialAction || 'none'}`);
301350
return {
302351
transaction,
303352
newBalance,

0 commit comments

Comments
 (0)