{
return new Promise((resolve, reject) => {
// Show input modal
const modal = createModal({
title: 'Введите номер карты',
placeholder: 'LC-123456',
pattern: /^LC-\d{6}$/,
onSubmit: (value) => {
if (/^LC-\d{6}$/.test(value)) {
resolve(value);
} else {
window.Telegram.WebApp.showAlert('Неверный формат номера карты');
}
},
onCancel: () => {
reject(new Error('User cancelled'));
}
});
modal.show();
});
}
```
**Manual Input UI:**
```svelte
{#if error}
{error}
{/if}
```
**Best Practices:**
- Always provide fallback option
- Use clear validation messages
- Pre-fill format hints (placeholder)
- Add input masks for better UX
- Validate on both frontend and backend
---
### 9. Integration with Telegram & Backend
Complete integration flow for Telegram Mini App loyalty system.
**Frontend (Telegram Mini App):**
```typescript
// Customer shows loyalty card
async function showMyCard() {
const user = window.Telegram.WebApp.initDataUnsafe.user;
// Generate card QR
const response = await fetch('/api/qr/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'card',
userId: user.id
})
});
const { qrDataURL } = await response.json();
// Display QR in modal
showQRModal(qrDataURL, 'Покажите QR-код кассиру');
}
// Cashier scans customer card
async function scanCustomerCard() {
const qrData = await scanQRCode({
text: 'Отсканируйте карту клиента'
});
// Validate on backend
const response = await fetch('/api/qr/validate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ qrData })
});
const { valid, data } = await response.json();
if (valid && data.type === 'card') {
// Show customer info
displayCustomer(data.cardNumber);
}
}
```
**Backend (SvelteKit API):**
```typescript
// Generate QR endpoint
export const POST: RequestHandler = async ({ request }) => {
const { type, userId } = await request.json();
// Get user from database
const user = await db.query.users.findFirst({
where: eq(users.telegram_id, userId)
});
if (!user) {
return json({ error: 'User not found' }, { status: 404 });
}
// Generate QR
const qrDataURL = await generateLoyaltyQR({
type: 'card',
cardNumber: user.card_number,
customerId: user.id,
tier: user.tier,
secretKey: QR_SECRET_KEY
});
return json({ qrDataURL });
};
// Validate QR endpoint
export const POST: RequestHandler = async ({ request }) => {
const { qrData } = await request.json();
try {
const parsed = await validateQR(qrData, {
secretKey: QR_SECRET_KEY
});
return json({ valid: true, data: parsed });
} catch (error: any) {
return json({ valid: false, error: error.message }, { status: 400 });
}
};
```
---
## Resources
### scripts/
- **qr_generator.ts** - QR generation with AES-256 encryption and HMAC signatures
- **qr_validator.ts** - QR validation with signature verification and expiry checks
- **qr_scanner.ts** - Telegram WebApp scanner integration with error handling
### references/
- **qr_formats.md** - Complete QR data format specifications for all types (card, transaction, coupon, referral)
- **security_guide.md** - Security best practices for QR systems (encryption, key management, anti-fraud)
---
## Quick Reference
**Generate QR:**
```typescript
const qr = await generateLoyaltyQR({
type: 'card',
cardNumber: 'LC-123456',
secretKey: process.env.QR_SECRET_KEY
});
```
**Scan QR (Telegram):**
```typescript
const data = await scanQRCode({ text: 'Сканируйте QR-код' });
```
**Validate QR:**
```typescript
const parsed = await validateQR(data, { secretKey: process.env.QR_SECRET_KEY });
```
**Critical Rules:**
- ALWAYS encrypt sensitive data in QR payload
- ALWAYS validate signature before decrypting
- ALWAYS check expiry for time-limited QR codes
- ALWAYS mark one-time QR as used atomically
- PROVIDE fallback to manual input