npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

mojistudio-card-api

v4.0.0

Published

Card Partner API Client - Pure TypeScript, type-safe, no dependencies. Build request objects for Card Exchange, Serial Check, Buy Card, and Topup APIs.

Downloads

41

Readme

mojistudio-card-api v3

npm version npm downloads TypeScript license

Pure TypeScript API client library - Zero dependencies, type-safe, production-ready.

Thư viện hỗ trợ tích hợp API đổi thẻ cào, kiểm tra seri, mua thẻ, và nạp topup. Viết 100% TypeScript, không dependency, mọi function đều pure functions.

✨ Features

  • 📘 100% TypeScript - Full type safety, IntelliSense
  • 🚀 Zero Dependencies - No node_modules bloat (only Node.js built-in crypto)
  • 🎯 Pure Functions - Request builders, no HTTP calls, no side effects
  • 📦 Tree-shakeable - Import only what you need
  • 💪 Production Ready - Fully tested, type-safe
  • 🔐 Secure - Built-in MD5 signature generation
  • 📚 Well-documented - JSDoc for every method

📦 Installation

npm install mojistudio-card-api

🚀 Quick Start

Create API Client

import { CardAPI } from "mojistudio-card-api";

// Create instance
const api = new CardAPI({
  partnerKey: "your_partner_key",
  partnerId: "your_partner_id",
  domain: "http://api.example.com", // or separate domains
});

Build Requests (No HTTP calls!)

// Build a card submission request
const request = api.buildSubmitCardRequest({
  telco: "VIETTEL",
  code: "312821445892982",
  serial: "10004783347874",
  amount: "50000",
  requestId: "REQ" + Date.now(),
});

// Returns:
// {
//   path: "/api/charging",
//   method: "POST",
//   headers: { "Content-Type": "application/x-www-form-urlencoded" },
//   body: "partner_id=xxx&telco=VIETTEL&code=...&signature=xxx",
//   signature: "xxx"
// }

// You handle the HTTP request yourself:
// - Use fetch, axios, node-fetch, whatever you prefer
// - Send to: domain + request.path
// - Include request headers
// - Send request.body as POST data

📖 Configuration

const api = new CardAPI({
  partnerKey: "your_key", // Required
  partnerId: "your_id", // Required
  domain: "http://api.example.com", // Domain chung
  domainPost: "...", // Domain riêng cho đổi thẻ
  domainBuy: "...", // Domain riêng cho mua thẻ
  domainTopup: "...", // Domain riêng cho topup
  timeout: 30000, // Timeout mặc định 30s
});

Priority: Specific domain > Common domain

// Example: Use one domain, but override topup
const api = new CardAPI({
  domain: "http://api.example.com",
  domainTopup: "http://different-topup.com", // This takes priority
});

📚 API Methods

All methods return APIRequest object with:

  • path: API endpoint path
  • method: HTTP method (GET/POST)
  • headers: Required headers
  • body: Request body (for POST)
  • signature: Calculated signature

Card Exchange (ĐỔI THẺ)

// Submit card for charging
const req1 = api.buildSubmitCardRequest({
  telco: "VIETTEL",
  code: "312821445892982",
  serial: "10004783347874",
  amount: "50000",
  requestId: "REQ001",
});

// Check card status
const req2 = api.buildCheckCardStatusRequest({
  telco: "VIETTEL",
  code: "312821445892982",
  serial: "10004783347874",
  amount: "50000",
  requestId: "REQ001",
});

// Get card prices
const req3 = api.buildGetCardPricesRequest();

Serial Check (KIỂM TRA SERI)

const request = api.buildCheckSerialRequest({
  telco: "VIETTEL",
  serial: "20000203625855",
});

Buy Card (MUA THẺ)

// Buy card
const req1 = api.buildBuyCardRequest({
  serviceCode: "Viettel",
  walletNumber: "0081083966",
  value: "10000",
  qty: "2",
  requestId: "BUY001",
});

// Check availability
const req2 = api.buildCheckCardAvailabilityRequest({
  serviceCode: "Viettel",
  value: "10000",
  qty: "2",
});

// Redownload card
const req3 = api.buildRedownloadCardRequest({
  requestId: "BUY001",
  orderCode: "S61797A53BCEEF",
});

Topup (NẠP TOPUP)

// Create topup order
const req1 = api.buildCreateTopupOrderRequest({
  serviceCode: "vinatt",
  amount: "10000",
  qty: "1",
  requestId: "TOP001",
  accountInfo: { phone: "0943793984" },
});

// Check topup status
const req2 = api.buildGetTopupStatusRequest({
  requestId: "TOP001",
  orderCode: "R625931CC50F71",
});

// Get product list
const req3 = api.buildGetProductListRequest();

// Get balance
const req4 = api.buildGetBalanceRequest();

🔧 Advanced Usage

Multiple Instances

// Different environments
const liveAPI = new CardAPI({
  partnerKey: "live_key",
  partnerId: "live_id",
  domain: "https://api.production.com",
});

const testAPI = new CardAPI({
  partnerKey: "test_key",
  partnerId: "test_id",
  domain: "https://api-test.staging.com",
});

// Different vendors
const vendor1 = new CardAPI({
  partnerKey: "vendor1_key",
  partnerId: "vendor1_id",
  domain: "https://vendor1-api.com",
});

Using with HTTP Client

import fetch from "node-fetch"; // or axios, http-client, etc.

const api = new CardAPI({
  partnerKey: "xxx",
  partnerId: "yyy",
  domain: "http://api.example.com",
});

// Build request
const req = api.buildSubmitCardRequest({
  telco: "VIETTEL",
  code: "xxx",
  serial: "xxx",
  amount: "50000",
  requestId: "REQ001",
});

// Send with fetch (or any HTTP client)
const response = await fetch(`${api.getDomain("post")}${req.path}`, {
  method: req.method,
  headers: req.headers,
  body: req.body,
  timeout: 30000,
});

const data = await response.json();
console.log(data);

Utility Functions

import {
  generateSignature,
  buildSignature,
  buildQueryString,
  buildURL,
} from "mojistudio-card-api";

// Generate MD5 signature
const sig = generateSignature("data_to_hash");

// Build signature from parameters
const sig2 = buildSignature(
  {
    partner_id: "xxx",
    telco: "VIETTEL",
    code: "xxx",
  },
  "partner_key",
);

// Build query string
const qs = buildQueryString({
  param1: "value1",
  param2: "value2",
});

// Build full URL
const url = buildURL("http://api.example.com", "/api/charging", {
  partner_id: "xxx",
});

🎯 Key Differences from v2

v2 (Old)

// Had HTTP calls built-in
const api = new CardAPI();
const result = await api.submitCard({...}); // Made HTTP request

v3 (New)

// Pure functions - you control HTTP
const api = new CardAPI({...});
const request = api.buildSubmitCardRequest({...}); // Returns request object
// You send the HTTP request yourself

Benefits

  • Framework agnostic - Use with fetch, axios, http-client, etc.
  • Better testing - Mock at function level, no HTTP mocking needed
  • More control - Implement your own retry logic, logging, etc.
  • Smaller package - No HTTP client bloat
  • Type safety - Full TypeScript support

🔒 Security

  • All signatures calculated with MD5
  • No external dependencies (only Node.js crypto)
  • Parameters validated before building requests
  • Configuration errors throw early

📝 Error Handling

try {
  const request = api.buildSubmitCardRequest({
    // Missing required parameter
    telco: "VIETTEL",
    code: "xxx",
    // Missing: serial, amount, requestId
  });
} catch (error) {
  console.error(error.message);
  // "Missing required parameters: serial, amount, requestId"
}

🧪 Examples

See the usage examples below in this README.

📄 API Documentation

Full API documentation: card-api.md

🤝 Support

  • GitHub Issues: https://github.com/MojiStudioVn/mojistudio-cardApi/issues
  • Email: [email protected]
  • NPM: https://www.npmjs.com/package/mojistudio-card-api

📝 License

MIT


Made with ❤️ by MojiStudio

⚙️ Cấu hình

✨ Linh hoạt - 2 cách config

1️⃣ Config trực tiếp (Đơn giản nhất)

const api = new CardAPI({
  partnerKey: "your_key",
  partnerId: "your_id",
  domain: "http://api.example.com", // Domain chung
});

2️⃣ Domain riêng cho từng chức năng

const api = new CardAPI({
  partnerKey: "your_key",
  partnerId: "your_id",
  domainPost: "http://card-exchange-api.com", // Đổi thẻ
  domainBuy: "https://buy-card-api.com", // Mua thẻ
  domainTopup: "http://topup-api.com", // Nạp topup
});

🎯 Scale với nhiều instances

// API cho môi trường LIVE
const liveAPI = new CardAPI({
  partnerKey: "live_key",
  partnerId: "live_id",
  domain: "https://api.production.com",
});

// API cho môi trường TEST
const testAPI = new CardAPI({
  partnerKey: "test_key",
  partnerId: "test_id",
  domain: "https://api-test.staging.com",
});

// API riêng cho từng vendor
const vendor1API = new CardAPI({
  partnerKey: "vendor1_key",
  partnerId: "vendor1_id",
  domain: "https://vendor1-api.com",
});

const vendor2API = new CardAPI({
  partnerKey: "vendor2_key",
  partnerId: "vendor2_id",
  domain: "https://vendor2-api.com",
});

// Dùng song song
const [balance1, balance2] = await Promise.all([
  vendor1API.getBalance(),
  vendor2API.getBalance(),
]);

📖 API Methods

🎴 Đổi thẻ (Card Exchange)

// Gửi thẻ cào lên hệ thống
const result = await api.submitCard({
  telco: "VIETTEL",
  code: "312821445892982",
  serial: "10004783347874",
  amount: "50000",
  requestId: "REQ" + Date.now(),
});

// Kiểm tra trạng thái thẻ đã gửi
const status = await api.checkCardStatus({
  telco: "VIETTEL",
  code: "312821445892982",
  serial: "10004783347874",
  amount: "50000",
  requestId: "323233",
});

// Lấy giá chiết khấu thẻ
const prices = await api.getCardPrices();

✅ Kiểm tra Seri

// Kiểm tra seri thẻ có hợp lệ không
const serialCheck = await api.checkSerial({
  telco: "VIETTEL",
  serial: "20000203625855",
});

💳 Mua thẻ (Buy Card)

// Mua thẻ cào điện thoại
const buyResult = await api.buyCard({
  serviceCode: "Viettel",
  walletNumber: "0081083966",
  value: "10000",
  qty: "2",
  requestId: "BUY" + Date.now(),
});

// Kiểm tra tồn kho thẻ
const availability = await api.checkCardAvailability({
  serviceCode: "Viettel",
  value: "10000",
  qty: "2",
});

// Tải lại thẻ nếu bị mất
const redownload = await api.redownloadCard({
  requestId: "113",
  orderCode: "S61797A53BCEEF",
});

📱 Nạp Topup

// Tạo lệnh nạp tiền điện thoại
const topupOrder = await api.createTopupOrder({
  serviceCode: "vinatt",
  amount: "10000",
  qty: "1",
  requestId: "TOP" + Date.now(),
  accountInfo: { phone: "0943793984" },
});

// Kiểm tra trạng thái nạp
const topupStatus = await api.getTopupStatus({
  requestId: "116",
  orderCode: "R625931CC50F71",
});

// Lấy danh sách sản phẩm topup
const products = await api.getProductList();

// Kiểm tra số dư tài khoản
const balance = await api.getBalance();

🔧 Cấu hình nâng cao

Tạo nhiều instance với config khác nhau

const { CardAPI } = require("mojistudio-card-api");

// Instance cho đổi thẻ
const cardExchangeAPI = new CardAPI({
  partnerKey: "key1",
  partnerId: "id1",
  domainPost: "http://card-exchange-api.com",
});

// Instance cho mua thẻ
const buyCardAPI = new CardAPI({
  partnerKey: "key2",
  partnerId: "id2",
  domainBuy: "http://buy-api.com",
});

// Instance cho topup
const topupAPI = new CardAPI({
  partnerKey: "key3",
  partnerId: "id3",
  domainTopup: "http://topup-api.com",
});

// Sử dụng từng API riêng
await cardExchangeAPI.submitCard({...});
await buyCardAPI.buyCard({...});
await topupAPI.createTopupOrder({...});

Config với timeout tùy chỉnh

const api = new CardAPI({
  partnerKey: "your_key",
  partnerId: "your_id",
  domain: "http://api.example.com",
  timeout: 60000, // 60 seconds
});

Xử lý lỗi

try {
  const result = await api.submitCard({
    telco: "VIETTEL",
    code: "312821445892982",
    serial: "10004783347874",
    amount: "50000",
    requestId: "REQ" + Date.now(),
  });
  console.log("Success:", result);
} catch (error) {
  if (error.message.includes("Configuration Error")) {
    console.error("Config lỗi:", error.message);
    // Fix config và retry
  } else if (error.statusCode) {
    console.error("HTTP Error:", error.statusCode);
  } else {
    console.error("Error:", error.message);
  }

  if (error.response) {
    console.error("Response:", error.response);
  }
}

🔔 Callback Handler

Xử lý callback từ hệ thống bằng cách tạo endpoint trên server:

const express = require("express");
const crypto = require("crypto");
const app = express();

app.use(express.json());

app.post("/callback/charge", (req, res) => {
  const callback = req.body;

  // Verify signature
  const partnerKey = "your_key"; // Lấy từ cấu hình bạn đang dùng
  const sign = crypto
    .createHash("md5")
    .update(partnerKey + callback.code + callback.serial)
    .digest("hex");

  if (sign !== callback.callback_sign) {
    return res.status(400).json({ error: "Invalid signature" });
  }

  // Xử lý callback
  console.log("Card status:", callback.status);
  console.log("Telco:", callback.telco);
  console.log("Amount:", callback.amount);
  console.log("Message:", callback.message);

  // Cập nhật database của bạn
  // ...

  res.json({ status: "received" });
});

app.listen(3000, () => console.log("Callback server running on port 3000"));

📋 Tham số cấu hình

| THAM SỐ | MỤC ĐÍCH | BẮT BUỘC | VÍ DỤ | | ----------- | --------------------------- | -------- | ---------------------- | | partnerKey | Partner key từ nhà cung cấp | ✅ | your_key_here | | partnerId | Partner ID | ✅ | your_id_here | | domain | Domain chung cho tất cả API | ⚠️ | http://api.example.com | | domainPost | Domain riêng cho ĐỔI THẺ | ❌ | http://card-api.com | | domainBuy | Domain riêng cho MUA THẺ | ❌ | https://buy-api.com | | domainTopup | Domain riêng cho NẠP TOPUP | ❌ | http://topup-api.com | | timeout | Timeout request (ms) | ❌ | 30000 |

⚠️ Lưu ý: Cần có ít nhất domain HOẶC (domainPost + domainBuy + domainTopup)

🔧 Troubleshooting

❌ Lỗi: "Invalid signature"

Kiểm tra thứ tự ghép tham số khi tính MD5:

  • Card Exchange: md5(partner_key + code + serial)
  • Buy Card: md5(partner_key + partner_id + command + request_id)
  • Topup: md5(partner_key + partner_id + command + request_id)

⏱️ Lỗi: Request timeout

Tăng timeout trong config:

const api = new CardAPI({
  partnerKey: "your_key",
  partnerId: "your_id",
  domain: "http://api.example.com",
  timeout: 60000, // 60 giây
});

🔑 Lỗi: Authentication failed

  • ✅ Kiểm tra PARTNER_KEYPARTNER_ID đúng chưa
  • ✅ Xác nhận IP của server đã được whitelist
  • ✅ Kiểm tra format dữ liệu gửi đi (telco, amount, serial phải đúng định dạng)

🌐 Lỗi: Domain not configured

Đảm bảo đã config đủ domain trong constructor:

// Cách 1: Dùng domain chung
const api = new CardAPI({
  partnerKey: "your_key",
  partnerId: "your_id",
  domain: "http://api.example.com",
});

// HOẶC Cách 2: Domain riêng
const api2 = new CardAPI({
  partnerKey: "your_key",
  partnerId: "your_id",
  domainPost: "http://card-api.com",
  domainBuy: "http://buy-api.com",
  domainTopup: "http://topup-api.com",
});

📚 Tài liệu API đầy đủ

Xem file card-api.md để biết chi tiết về tất cả các API endpoints, parameters, và response formats.

📝 License

MIT

🤝 Support


Made with ❤️ by MojiStudio