truewallet-angpao
v1.1.8
Published
Redeem TrueMoney Angpao programmatically with TypeScript
Maintainers
Readme
🧧 TrueWallet Angpao Redemption
🎁 A lightweight TypeScript library for redeeming TrueMoney Angpao (red envelope) voucher codes with full CORS support
✨ Features
- 🚀 TypeScript Support - Full type safety with IntelliSense
- 🌐 CORS Handling - Both client-side and server-side implementations
- ⚡ Zero Dependencies - Minimal footprint (client version)
- 🔧 Easy Integration - React hooks and Express middleware included
- 📱 Mobile Ready - Works on all platforms
- 🛡️ Error Handling - Comprehensive error messages in Thai/English
🚀 Quick Start
Installation
# npm
npm install truemoney-angpao
# yarn
yarn add truemoney-angpao
# pnpm
pnpm add truemoney-angpao
# bun
bun add truemoney-angpaoBasic Usage (Server-side)
import { redeemAngpao } from 'truemoney-angpao';
async function example() {
const result = await redeemAngpao('0891234567', 'ABC123DEF456');
if (result.status === 'SUCCESS') {
console.log(`💰 Redeemed ${result.amount} Baht!`);
} else {
console.error('❌', result.message);
}
}Client-side Usage (Browser)
import { clientRedeemAngpao } from 'truemoney-angpao';
async function redeemInBrowser() {
const result = await clientRedeemAngpao('0891234567', 'ABC123DEF456');
switch (result.status) {
case 'SUCCESS':
console.log(`✅ Success: ${result.amount} Baht`);
break;
case 'CORS_ERROR':
console.log('🚫 CORS Error - Use proxy server');
break;
case 'FAIL':
console.error('❌', result.message);
break;
}
}📚 Complete Examples
🖥️ Express Server Setup
import express from 'express';
import cors from 'cors';
import { redeemAngpao } from 'truemoney-angpao';
const app = express();
// Enable CORS for all routes
app.use(cors());
app.use(express.json());
// Redemption endpoint
app.post('/api/redeem', async (req, res) => {
const { phone, voucherCode } = req.body;
// Validation
if (!phone || !voucherCode) {
return res.status(400).json({
error: 'Phone and voucherCode are required'
});
}
try {
const result = await redeemAngpao(phone, voucherCode);
res.json(result);
} catch (error) {
console.error('Redemption error:', error);
res.status(500).json({
status: 'FAIL',
message: (error as Error).message
});
}
});
// Health check
app.get('/', (req, res) => {
res.json({ message: '🧧 TrueMoney Angpao API Ready!' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`🚀 Server running on http://localhost:${PORT}`);
});⚛️ React Hook Implementation
// hooks/useAngpaoRedemption.ts
import { useState, useCallback } from 'react';
import { clientRedeemAngpao, ClientRedeemResult } from 'truemoney-angpao';
interface UseAngpaoRedemption {
redeem: (phone: string, voucherCode: string) => Promise<ClientRedeemResult>;
loading: boolean;
error: string | null;
result: ClientRedeemResult | null;
reset: () => void;
}
export function useAngpaoRedemption(): UseAngpaoRedemption {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [result, setResult] = useState<ClientRedeemResult | null>(null);
const redeem = useCallback(async (phone: string, voucherCode: string) => {
setLoading(true);
setError(null);
setResult(null);
try {
const response = await clientRedeemAngpao(phone, voucherCode);
setResult(response);
if (response.status === 'FAIL') {
setError(response.message || 'Redemption failed');
}
return response;
} catch (err) {
const message = (err as Error).message;
setError(message);
return { status: 'FAIL' as const, message };
} finally {
setLoading(false);
}
}, []);
const reset = useCallback(() => {
setLoading(false);
setError(null);
setResult(null);
}, []);
return { redeem, loading, error, result, reset };
}🎨 React Component Example
// components/AngpaoRedeemer.tsx
import React, { useState } from 'react';
import { useAngpaoRedemption } from '../hooks/useAngpaoRedemption';
export function AngpaoRedeemer() {
const [phone, setPhone] = useState('');
const [voucherCode, setVoucherCode] = useState('');
const { redeem, loading, error, result, reset } = useAngpaoRedemption();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!phone || !voucherCode) {
alert('กรุณากรอกเบอร์โทรและรหัสอั่งเปา');
return;
}
const response = await redeem(phone, voucherCode);
if (response.status === 'SUCCESS') {
alert(`🎉 แลกสำเร็จ! ได้รับ ${response.amount} บาท`);
} else if (response.status === 'CORS_ERROR') {
alert('❌ ข้อผิดพลาด CORS - กรุณาใช้ Proxy Server');
}
};
return (
<div className="max-w-md mx-auto p-6 bg-white rounded-lg shadow-lg">
<h2 className="text-2xl font-bold text-center mb-6 text-red-600">
🧧 แลกอั่งเปา TrueMoney
</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
เบอร์โทรศัพท์
</label>
<input
type="tel"
value={phone}
onChange={(e) => setPhone(e.target.value)}
placeholder="089xxxxxxx"
className="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-red-500 focus:border-transparent"
maxLength={10}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
รหัสอั่งเปา
</label>
<input
type="text"
value={voucherCode}
onChange={(e) => setVoucherCode(e.target.value.toUpperCase())}
placeholder="ABC123DEF456"
className="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-red-500 focus:border-transparent"
/>
</div>
<button
type="submit"
disabled={loading}
className="w-full bg-red-600 text-white py-3 px-4 rounded-md hover:bg-red-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{loading ? '⏳ กำลังแลก...' : '🎁 แลกอั่งเปา'}
</button>
</form>
{error && (
<div className="mt-4 p-3 bg-red-50 border border-red-200 rounded-md">
<p className="text-red-700 text-sm">❌ {error}</p>
</div>
)}
{result && result.status === 'SUCCESS' && (
<div className="mt-4 p-3 bg-green-50 border border-green-200 rounded-md">
<p className="text-green-700 text-sm">
✅ แลกสำเร็จ! ได้รับ <strong>{result.amount} บาท</strong>
</p>
</div>
)}
{result && result.status === 'CORS_ERROR' && (
<div className="mt-4 p-3 bg-yellow-50 border border-yellow-200 rounded-md">
<p className="text-yellow-700 text-sm">
⚠️ CORS Error: กรุณาใช้ Proxy Server สำหรับการใช้งานจริง
</p>
</div>
)}
{(error || result) && (
<button
onClick={reset}
className="mt-2 text-sm text-gray-500 hover:text-gray-700 underline"
>
ล้างข้อมูล
</button>
)}
</div>
);
}📋 API Reference
Server-side Functions
redeemAngpao(phone: string, voucherCode: string): Promise<RedeemResult>
Redeems an Angpao voucher using Node.js (no CORS issues).
Parameters:
phone: Thai mobile number (e.g.,'0891234567')voucherCode: Angpao voucher code (e.g.,'ABC123DEF456')
Returns:
type RedeemResult =
| { status: 'SUCCESS'; amount: number }
| { status: 'FAIL'; message: string };Client-side Functions
clientRedeemAngpao(phone: string, voucherCode: string): Promise<ClientRedeemResult>
Redeems an Angpao voucher in the browser (may have CORS issues).
Returns:
type ClientRedeemResult =
| { status: 'SUCCESS'; amount: number }
| { status: 'FAIL'; message: string }
| { status: 'CORS_ERROR'; message: string };🔧 Configuration
Environment Variables
# Optional: Set custom API endpoint
TRUEMONEY_API_URL=https://gift.truemoney.com
# Server port
PORT=3000TypeScript Configuration
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}🛡️ Error Handling
The library provides detailed error messages for common scenarios:
| Error Type | Description | Solution |
|------------|-------------|----------|
| CORS_ERROR | Cross-origin request blocked | Use server-side proxy |
| INVALID_VOUCHER | Invalid voucher code format | Check code format |
| EXPIRED_VOUCHER | Voucher has expired | Use valid voucher |
| ALREADY_REDEEMED | Voucher already used | Use new voucher |
| NETWORK_ERROR | Connection failed | Check internet connection |
🚀 Deployment
Vercel
{
"functions": {
"api/**/*.ts": {
"runtime": "@vercel/node"
}
}
}Netlify
[build]
functions = "netlify/functions"
[[redirects]]
from = "/api/*"
to = "/.netlify/functions/:splat"
status = 200Railway
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["npm", "start"]🧪 Testing
# Install dev dependencies
npm install -D jest @types/jest ts-jest
# Run tests
npm test
# Run with coverage
npm run test:coverageExample test:
import { redeemAngpao } from '../src/redeemAngpao';
describe('redeemAngpao', () => {
it('should handle successful redemption', async () => {
const result = await redeemAngpao('0891234567', 'VALID_CODE');
expect(result.status).toBe('SUCCESS');
expect(result.amount).toBeGreaterThan(0);
});
it('should handle invalid voucher', async () => {
const result = await redeemAngpao('0891234567', 'INVALID_CODE');
expect(result.status).toBe('FAIL');
expect(result.message).toContain('invalid');
});
});🤝 Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development Setup
# Clone the repo
git clone https://github.com/CrypticDay/truemoney-angpao.git
cd truemoney-angpao
# Install dependencies
npm install
# Start development
npm run dev
# Build
npm run build
# Test
npm test📈 Roadmap
- [x] ✅ TypeScript support
- [x] ✅ CORS handling
- [x] ✅ React hooks
- [x] ✅ Express middleware
- [ ] 🔄 CLI tool
- [ ] 🔄 Batch redemption
- [ ] 🔄 Rate limiting
- [ ] 🔄 Webhook support
- [ ] 🔄 Mobile app SDK
📄 License
MIT © 2025 - See LICENSE file for details.
🙏 Acknowledgments
- TrueMoney team for providing the API
- Thai developer community for feedback and suggestions
- Contributors who helped improve this library
Made with ❤️ for the Thai developer community
