@icare1/drug-portal-sdk
v0.1.15
Published
TypeScript SDK for Vietnam National Drug Portal APIs (CSDL Dược QĐ 522 & Cổng Đơn Thuốc QĐ 228)
Maintainers
Readme
Drug Portal SDK
TypeScript SDK for Vietnam National Drug Portal APIs — CSDL Dược (QĐ 522) & Cổng Đơn Thuốc (QĐ 228).
npm install @icare1/drug-portal-sdkLive Demo & Test App
We built a professional Next.js + Tailwind CSS clinical dashboard test application demonstrating this SDK integration:
- 🔗 Vercel Test App Link: https://drug-portal-test-app-kohl.vercel.app/
- 🐙 GitHub Repository: https://github.com/vuduyviet1110/drug-portal-test-app
Features
- ✅ CSDL Dược QĐ 522: OAuth login, auto-refresh token, drug catalog search, master data lookups, stock-in/out/taking with polling.
- ✅ Cổng Đơn Thuốc QĐ 228: Prescription lookup, sale quantity updates (UC05) with retry.
- ✅ Zod Input Validation: In-built client-side schemas preventing bad API requests before hitting remote servers.
- ✅ Offline Mock Mode: Toggleable offline mock stubs via
useMockconfig for local development and testing. - ✅ Advanced Proxy Routing: Out-of-the-box support for HTTP/HTTPS/SOCKS5 proxies, tailored to work under serverless platforms (Vercel, AWS Lambda) by bypassing Next.js global
fetchpatches. - ✅ Robust HTTP: Retry with exponential backoff (429/5xx), structured logging with trace ID, secret masking.
- ✅ TypeScript-first: Full type safety, ESM + CJS +
.d.tssupport. - ✅ Node 18+: Zero external runtime dependencies.
Quick Start
import { DrugPortalClient } from '@icare1/drug-portal-sdk';
const client = new DrugPortalClient({
environment: 'sandbox', // or 'production'
csdlDuoc: {
username: 'your-username',
password: 'your-password',
},
qd228: {
appName: 'your-app-name',
appKey: 'your-app-key',
},
});
// Search drugs (POS portal first, fallback to master catalog)
const drugs = await client.csdlDuoc.drugs.search('paracetamol');
console.log(drugs.items); // [{ id, name, registrationNumber, source }]
// Get drug detail
const detail = await client.csdlDuoc.drugs.getDetail(drugs.items[0].id);
console.log(detail.name, detail.packagings);
// Stock-in (nhập kho)
const result = await client.csdlDuoc.inventory.stockIn({
items: [{
drugId: 'DRUG-001',
unitId: 'U-001',
quantity: 100,
batchNo: 'LOT-2024-001',
expiryDate: '2025-12-31',
manufacturer: { id: 'M-001', name: 'Pharma Corp' },
}],
reason: 'supplier',
referenceNumber: 'PO-2024-001',
});
console.log(result.transactionId, result.status); // 'completed'
// Lookup prescription (QĐ 228)
const rx = await client.qd228.prescriptions.get('DH001');
console.log(rx.items); // drug list in prescriptionConfiguration
new DrugPortalClient({
environment: 'sandbox' | 'production',
// CSDL Dược (QĐ 522) — optional
csdlDuoc: {
username: string,
password: string,
storeId?: string, // for transaction payloads
warehouseCode?: string, // for transaction payloads
practiceLicenseCode?: string, // GCN đủ điều kiện SX/KD thuốc
},
// Cổng Đơn Thuốc (QĐ 228) — optional
qd228: {
appName: string,
appKey: string,
},
// Advanced Configurations
useMock?: boolean, // Enable offline mock mode (runs Zod validation first)
proxyUrl?: string, // Proxy URL (supports SOCKS5 / HTTP / HTTPS)
csdlDuocBaseUrl?: string, // override API URL
nationalRxBaseUrl?: string, // override QĐ 228 URL
retry?: {
maxRetries: 3,
baseDelayMs: 5000,
timeoutMs: 30000,
},
tokenTtlHours?: 23,
onTokenChange?: (token, expiresAt) => void,
cachedToken?: string,
cachedTokenExpiresAt?: Date,
});Advanced Features
1. In-built Input Validation (Zod)
All inventory transactions (stockIn, stockOut, stockTaking) are validated locally against Zod schemas before making any remote HTTP calls. This captures bad payloads instantly, preventing rate-limiting blocks from CSDL Dược servers.
try {
await client.csdlDuoc.inventory.stockIn({
items: [{
drugId: 'DRUG-001',
unitId: 'U-001',
quantity: -5, // ❌ Will throw ZodError immediately (quantity must be positive)
}],
reason: 'supplier'
});
} catch (error) {
console.log(error.issues); // Lists exact missing/invalid parameters (e.g. quantity must be positive)
}2. Offline Mock Mode (useMock: true)
If useMock: true is configured, the SDK runs all Zod input validation rules first, but intercepts remote operations and immediately returns mock completed transactions. Excellent for CI/CD runs, offline local development, or fast sandbox prototyping.
const client = new DrugPortalClient({
environment: 'sandbox',
useMock: true,
});
// Zod validation runs first, then mock response is returned in <1ms without internet
const result = await client.csdlDuoc.inventory.stockIn({
items: [{ drugId: 'DRUG-001', unitId: 'U-001', quantity: 10 }],
reason: 'supplier',
supplierId: 'SUPP-001'
});
console.log(result.status); // "completed"
console.log(result.transactionId); // "tx-mock-in-1784525137036"3. Serverless & AWS Firewall Bypassing (Proxy Support)
Vietnamese regulatory servers (api-sandbox.csdlduoc.com.vn, donthuocquocgia.vn) block foreign cloud IP addresses (Vercel, AWS Lambda, GCP). The SDK offers robust proxy support (HTTP, HTTPS, and SOCKS5) along with an automatic public Vietnam proxy fallback scraper.
Manual Proxy Setup
const client = new DrugPortalClient({
environment: 'production',
proxyUrl: 'http://username:password@vietnam-proxy-ip:8080', // HTTP proxy
// proxyUrl: 'socks5://vietnam-proxy-ip:1080', // SOCKS5 proxy
csdlDuoc: { ... },
});Automatic Fallback Proxy Scraper
If you do not have a paid/private Vietnam proxy, the SDK can automatically scan for free public Vietnamese proxies from multiple sources (Geonode, ProxyScrape, Proxifly, Proxy-List), test them in parallel against the target server, and select a working one.
Because free public proxies are highly unstable and serverless environments (like Vercel or AWS Lambda) are stateless, you should persist the resolved proxy in a database (e.g. SQLite, PostgreSQL) to reuse it across serverless requests without scraping again.
const client = new DrugPortalClient({
environment: 'production',
// 1. Pass the cached proxy if you have one, or leave undefined to trigger auto fallback
proxyUrl: cachedProxyInDb || undefined,
// 2. Enable automatic fallback scanning
autoFallbackProxy: !cachedProxyInDb,
// 3. Track scanning and testing progress (e.g. to stream to UI)
onProxyProgress: (step, message) => {
console.log(`[Proxy] ${step}: ${message}`);
},
// 4. Save the resolved working proxy to a database for reuse
onProxyResolved: async (proxyUrl) => {
await db.saveResolvedProxy(proxyUrl);
},
csdlDuoc: { ... },
});- Self-Healing Proxy Cache: If the resolved proxy cached in the database fails (e.g., connection timeout or network error), the SDK will automatically clear the cache, fallback to scanning again, and select a fresh proxy on the next retry.
Why it works on Vercel/Next.js:
- Next.js fetch-patch bypass: Next.js App Router patches
globalThis.fetchto support Server Components caching, which breaks proxy dispatchers. The SDK bypasses this on-the-fly by dynamically importing and using rawundicifetch whenever aproxyUrlis configured. - Hybrid Fallback Strategy: If
proxyUrlis undefined, the SDK reverts toglobalThis.fetch. This ensures local mock test environments (like MSW/nock/Vitest) continue to intercept network requests normally without breaking. - Serverless Port Support: SOCKS5 ports (e.g.,
1080) are often blocked by AWS Lambda firewall rules. Using standard HTTP/HTTPS proxies on common ports (like80,8080,3128) allows requests to route seamlessly on serverless platforms.
4. Token Caching & Persistence Store
To prevent calling POST /auth/login too frequently (which triggers rate-limits), cache and restore authentication tokens using one of our built-in TokenStore adapters:
import { DrugPortalClient, FileTokenStore, RedisTokenStore } from '@icare1/drug-portal-sdk';
import { createClient } from 'redis';
// File-system Cache Store (caches to .token_cache.json by default)
const fileStore = new FileTokenStore();
// Or Redis Cache Store
const redisClient = createClient();
const redisStore = new RedisTokenStore(redisClient);
const client = new DrugPortalClient({
environment: 'production',
csdlDuoc: { ... },
// Restore cached token on startup
...(await fileStore.get('csdl_duoc')),
// Save token changes
onTokenChange: async (token, expiresAt) => {
await fileStore.set('csdl_duoc', { accessToken: token, expiresAt });
}
});5. Unit Testing with Mock Client
If you want to configure specific custom mocks in-memory, you can import and use MockDrugPortalClient:
import { MockDrugPortalClient } from '@icare1/drug-portal-sdk';
const mockClient = new MockDrugPortalClient();
// Configure custom mock data
mockClient.mockDrugs.push({
id: '99',
name: 'Custom Mock Medicine 500mg',
registrationNumber: 'VD-999-24',
baseUnit: 'Viên',
source: 'pos',
});
// Mock lookup will search the in-memory mock store
const drugs = await mockClient.csdlDuoc.drugs.search('Custom Mock');
console.log(drugs.items[0].name); // 'Custom Mock Medicine 500mg'API Reference
DrugPortalClient
| Method | Description |
|---|---|
| csdlDuoc.drugs.search(keyword) | Search drugs (POS + fallback master) |
| csdlDuoc.drugs.getDetail(drugId) | Get full drug detail |
| csdlDuoc.masterData.getUnits() | Get unit list |
| csdlDuoc.masterData.getRoutes() | Get route list |
| csdlDuoc.inventory.stockIn(opts) | Submit stock-in + auto-poll |
| csdlDuoc.inventory.stockOut(opts) | Submit stock-out + auto-poll |
| csdlDuoc.inventory.stockTaking(opts) | Submit stock-taking + auto-poll |
| csdlDuoc.inventory.pollTransaction(type, id) | Poll existing transaction |
| qd228.prescriptions.get(code) | Lookup prescription by code |
| qd228.prescriptions.updateSaleQty(opts) | Update prescription sale qty (UC05) |
Authentication
CSDL Dược (OAuth): Auto-login on first request, token cached in memory (23h TTL), auto-refresh on 401 or expiry.
QĐ 228 (Static headers): app-name + app-key injected into every request. No refresh.
Testing
npm test # Unit tests (Vitest + msw mock)
npm run test:integration # Sandbox integration tests (needs real credentials)
npm run build # Build ESM + CJS + d.tsAPI Endpoints
| Portal | Endpoint | Method |
|---|---|---|
| CSDL Dược | POST /auth/login | OAuth login |
| CSDL Dược | POST /api/pos/product/get-paged | Drug search (POS portal) |
| CSDL Dược | GET /master/drugs | Drug catalog search |
| CSDL Dược | GET /master/drugs/{id} | Drug detail |
| CSDL Dược | POST /transactions/stock-in | Stock-in |
| CSDL Dược | POST /transactions/stock-out | Stock-out |
| CSDL Dược | POST /transactions/stock-taking | Stock-taking |
| CSDL Dược | GET /transactions/{type}/{id}/status | Poll transaction status |
| QĐ 228 | GET /api/v1/thong-tin-don-thuoc/{code} | Prescription lookup |
| QĐ 228 | POST /api/v1/cap-nhat-don-thuoc | Update sale qty |
License
MIT © 2025 iCare Health
