سامانه مودیان

مرجع فنی کامل سامانه مودیان - ساختار API، رمزنگاری و پیاده‌سازی

2025-12-17۲۵ دقیقه مطالعهتیم فنی بارکد

مرجع فنی کامل سامانه مودیان

این راهنما بر اساس سند دستورالعمل فنی نحوه اتصال به سامانه مودیان (نسخه ۱۴۰۱۰۴۲۲) تهیه شده و شامل تمام جزئیات فنی مورد نیاز برای پیاده‌سازی اتصال به سامانه است.


فهرست مطالب

  1. معماری کلی سامانه
  2. ساختار شناسه یکتای مالیاتی (taxid)
  3. فرآیند رمزنگاری و امضا
  4. ساختار JSON صورتحساب
  5. انواع صورتحساب
  6. فیلدهای سرصورتحساب (Header)
  7. فیلدهای اقلام (Body)
  8. ساختار بسته (Packet)
  9. API Endpoints
  10. کدهای خطا
  11. الگوریتم Verhoeff

معماری کلی سامانه

سامانه مودیان از معماری RESTful API استفاده می‌کند. ارتباط به دو صورت انجام می‌شود:

روش همگام (Sync)

  • برای دریافت توکن، استعلام، دریافت لیست کالا
  • پاسخ فوری دریافت می‌شود

روش غیرهمگام (Async)

  • برای ارسال صورتحساب
  • فقط شماره مرجع دریافت می‌شود
  • استعلام وضعیت جداگانه
┌──────────────┐      ┌──────────────┐      ┌──────────────┐
│   Client     │──────▶│   Gateway    │──────▶│  Tax Core    │
│  (Your App)  │◀──────│   (tp.tax)   │◀──────│   Server     │
└──────────────┘      └──────────────┘      └──────────────┘
        │                    │                      │
        │  1. GET_TOKEN      │                      │
        │────────────────────▶                      │
        │◀────────────────────                      │
        │                    │                      │
        │  2. SEND_INVOICE   │                      │
        │────────────────────▶ (Async)              │
        │◀────────────────────                      │
        │  referenceNumber   │                      │
        │                    │                      │
        │  3. INQUIRY        │                      │
        │────────────────────▶ (Sync)               │
        │◀────────────────────                      │
        │  Final Status      │                      │

شناسه یکتای مالیاتی (taxid)

شناسه یکتای مالیاتی یک رشته ۲۲ کاراکتری است که برای هر صورتحساب یکتا است.

فرمول

taxid = fiscalId (6) + serial (10 hex) + date (6)

اجزاء

جزءطولتوضیحمثال
fiscalId6شناسه حافظه مالیاتیA1B2C3
serial10شماره سریال (hex)0000000001
date6تاریخ شمسی (YYMMDD)040315

مثال

A1B2C3 + 0000000001 + 040315 = A1B2C30000000001040315

کد پیاده‌سازی (TypeScript)

import moment from 'moment-jalaali';

function generateTaxId(
    fiscalId: string,    // 6 chars
    serial: number       // incrementing number
): string {
    // Serial to 10-char hex
    const serialHex = serial.toString(16).toUpperCase().padStart(10, '0');
    
    // Jalali date YYMMDD
    const now = moment();
    const year = (now.jYear() % 100).toString().padStart(2, '0');
    const month = (now.jMonth() + 1).toString().padStart(2, '0');
    const day = now.jDate().toString().padStart(2, '0');
    const dateStr = `${year}${month}${day}`;
    
    return `${fiscalId}${serialHex}${dateStr}`;
}

// Example usage
const taxid = generateTaxId('A1B2C3', 1);
// Result: A1B2C30000000001040315

فرآیند رمزنگاری و امضا

مراحل آماده‌سازی صورتحساب

شکل ۱: نمودار آماده‌سازی صورتحساب

┌─────────────────────────────────────────────────────────────┐
│ 1. ساخت JSON صورتحساب                                       │
├─────────────────────────────────────────────────────────────┤
│ 2. نرمال‌سازی JSON (جدول ۱)                                  │
├─────────────────────────────────────────────────────────────┤
│ 3. امضای دیجیتال با RSA-SHA256                              │
├─────────────────────────────────────────────────────────────┤
│ 4. تولید کلید متقارن (AES-256) و IV (12 bytes)              │
├─────────────────────────────────────────────────────────────┤
│ 5. رمزگذاری داده با AES-256-GCM                             │
├─────────────────────────────────────────────────────────────┤
│ 6. رمزگذاری کلید متقارن با کلید عمومی سرور (RSA-OAEP)       │
├─────────────────────────────────────────────────────────────┤
│ 7. ساخت بسته نهایی                                          │
└─────────────────────────────────────────────────────────────┘

جدول ۱: نرمال‌سازی JSON

ردیفقاعدهمثال
1کلیدها به ترتیب الفبایی (ASCII){"a":1,"b":2} نه {"b":2,"a":1}
2بدون فاصله اضافی{"a":1} نه { "a" : 1 }
3اعداد بدون صفر اضافی1.5 نه 1.50
4رشته‌ها با escape مناسب"text\"quoted"
5بدون BOMUTF-8 without BOM

کد نرمال‌سازی

function normalizeJson(data: any): string {
    if (data === null) return 'null';
    if (typeof data === 'boolean') return data ? 'true' : 'false';
    if (typeof data === 'number') return data.toString();
    if (typeof data === 'string') return JSON.stringify(data);
    
    if (Array.isArray(data)) {
        const items = data.map(item => normalizeJson(item));
        return `[${items.join(',')}]`;
    }
    
    // Sort keys alphabetically
    const sortedKeys = Object.keys(data).sort();
    const parts = sortedKeys.map(key => {
        const value = normalizeJson(data[key]);
        return `"${key}":${value}`;
    });
    
    return `{${parts.join(',')}}`;
}

امضای دیجیتال (RSA-SHA256)

import * as crypto from 'crypto';

function signData(normalizedJson: string, privateKey: string): string {
    const sign = crypto.createSign('RSA-SHA256');
    sign.update(normalizedJson, 'utf8');
    const signature = sign.sign(privateKey, 'base64');
    return signature;
}

رمزگذاری AES-256-GCM

function encryptData(
    data: string, 
    symmetricKey: Buffer,  // 32 bytes
    iv: Buffer             // 12 bytes
): { encrypted: string; authTag: string } {
    const cipher = crypto.createCipheriv('aes-256-gcm', symmetricKey, iv);
    
    let encrypted = cipher.update(data, 'utf8', 'base64');
    encrypted += cipher.final('base64');
    
    const authTag = cipher.getAuthTag().toString('base64');
    
    return { encrypted, authTag };
}

رمزگذاری کلید متقارن با RSA

function encryptSymmetricKey(
    symmetricKey: Buffer, 
    serverPublicKey: string
): string {
    const encrypted = crypto.publicEncrypt(
        {
            key: serverPublicKey,
            padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
            oaepHash: 'sha256',
        },
        symmetricKey
    );
    return encrypted.toString('base64');
}

ساختار JSON صورتحساب

فرمت کلی

{
    "header": {
        "taxid": "A1B2C30000000001040315",
        "indatim": 1702800000000,
        "indati2m": 1702800000000,
        "inty": 1,
        "inp": 1,
        "ins": 1,
        "tins": "14001234567",
        "tob": 2,
        "bid": "1234567890",
        "tinb": "14009876543",
        "sbc": "1234567890",
        "bbc": "0987654321",
        "bpc": "1234567890",
        "ft": 1,
        "bpn": "09121234567",
        "scln": "خیابان ولیعصر",
        "scc": "",
        "crn": "",
        "billid": "",
        "tprdis": 1000000,
        "tdis": 50000,
        "tadis": 950000,
        "tvam": 95000,
        "todam": 0,
        "tbill": 1045000,
        "setm": 1,
        "cap": 1045000,
        "insp": 0,
        "tvop": 0,
        "tax17": 0,
        "inno": "0000000001"
    },
    "body": [
        {
            "sstid": "2720000044801",
            "sstt": "محصول نمونه",
            "am": 2,
            "mu": "164",
            "fee": 500000,
            "cfee": 0,
            "cut": 0,
            "exr": 0,
            "prdis": 1000000,
            "dis": 50000,
            "adis": 950000,
            "vra": 10,
            "vam": 95000,
            "odt": null,
            "odr": 0,
            "odam": 0,
            "olt": null,
            "olr": 0,
            "olam": 0,
            "consfee": 0,
            "spro": 0,
            "bros": 0,
            "tcpbs": 0,
            "cop": 0,
            "vop": 0,
            "bsrn": "",
            "tsstam": 1045000
        }
    ],
    "payments": []
}

انواع صورتحساب

نوع صورتحساب (inty)

کدنوعتوضیح
1نوع ۱خریدار حقیقی/حقوقی شناخته شده (B2B)
2نوع ۲خریدار حقیقی ناشناس (B2C)
3نوع ۳صادراتی

الگوی صورتحساب (inp)

کدالگوکاربرد
1فروشصورتحساب عادی
2برگشت از فروشکالای برگشتی
3ابطالیابطال کامل صورتحساب
4اصلاحیتصحیح جزئی

موضوع صورتحساب (ins)

کدموضوع
1اصلی
2الحاقی
3ابطالی
4اصلاحی
5برگشت از فروش

فیلدهای سرصورتحساب (Header)

فیلدنوعالزامیتوضیح
taxidstring(22)شناسه یکتای مالیاتی
indatimnumberتاریخ و زمان صدور (Unix ms)
indati2mnumberتاریخ و زمان ایجاد (Unix ms)
intynumberنوع صورتحساب (1-3)
innostring(10)شماره صورتحساب داخلی (hex)
irtaxidstring(22)شناسه مرجع (برای ابطال/اصلاح)
inpnumberالگوی صورتحساب (1-4)
insnumberموضوع صورتحساب (1-5)
tinsstringکد اقتصادی فروشنده
tinbstringکد اقتصادی خریدار
bidstringشناسه ملی خریدار
sbcstring(10)کد پستی فروشنده
bbcstring(10)کد پستی خریدار
tprdisnumberجمع مبلغ قبل از تخفیف
tdisnumberجمع تخفیفات
tadisnumberجمع مبلغ بعد از تخفیف
tvamnumberجمع مالیات بر ارزش افزوده
todamnumberجمع سایر مالیات‌ها
tbillnumberجمع کل صورتحساب
setmnumberروش تسویه (1=نقد, 2=نسیه, 3=ترکیب)
capnumberمبلغ پرداختی نقدی
inspnumberمبلغ نسیه

فیلدهای اقلام (Body)

فیلدنوعالزامیتوضیح
sstidstring(13)شناسه کالا/خدمت
ssttstringشرح کالا/خدمت
amnumberتعداد/مقدار
mustringواحد اندازه‌گیری
feenumberمبلغ واحد (قبل از تخفیف)
prdisnumberمبلغ قبل از تخفیف (fee × am)
disnumberتخفیف
adisnumberمبلغ بعد از تخفیف
vranumberنرخ مالیات (%)
vamnumberمبلغ مالیات
tsstamnumberجمع کل قلم

فرمول‌های محاسبه

prdis = fee × am                    # مبلغ قبل از تخفیف
adis = prdis - dis                  # مبلغ بعد از تخفیف
vam = round(adis × vra / 100)       # مالیات بر ارزش افزوده
tsstam = adis + vam + odam + olam   # جمع کل قلم

# سرجمع:
tprdis = Σ(prdis)                   # جمع مبلغ قبل از تخفیف
tdis = Σ(dis)                       # جمع تخفیفات
tadis = tprdis - tdis               # جمع مبلغ بعد از تخفیف
tvam = Σ(vam)                       # جمع مالیات
tbill = tadis + tvam + todam        # جمع کل صورتحساب

ساختار بسته (Packet)

ساختار Request

{
    "packets": [
        {
            "uid": "550e8400-e29b-41d4-a716-446655440000",
            "packetType": "INVOICE.V01",
            "retry": false,
            "data": "BASE64_ENCRYPTED_DATA",
            "encryptionKeyId": "server-key-id-123",
            "symmetricKey": "BASE64_ENCRYPTED_SYMMETRIC_KEY",
            "iv": "BASE64_IV_12_BYTES",
            "fiscalId": "A1B2C3",
            "dataSignature": "BASE64_SIGNATURE"
        }
    ],
    "signature": "BASE64_REQUEST_SIGNATURE"
}

ساختار Response (Async)

{
    "signature": "",
    "signatureKeyId": "",
    "timestamp": 1702800000000,
    "result": {
        "uid": "550e8400-e29b-41d4-a716-446655440000",
        "referenceNumber": "REF-12345",
        "errorCode": null,
        "errorDetail": null
    }
}

API Endpoints

آدرس پایه

Production: https://tp.tax.gov.ir/req/api/self-tsp
Sandbox:    https://sandboxrc.tax.gov.ir/req/api/self-tsp

متدهای غیرهمگام (Async)

EndpointHTTPتوضیح
/sync/SEND_INVOICEPOSTارسال صورتحساب

متدهای همگام (Sync)

EndpointHTTPتوضیح
/sync/GET_TOKENPOSTدریافت توکن
/sync/INQUIRY_BY_UID/{uid}GETاستعلام با uid
/sync/INQUIRY_BY_REFERENCE_NUMBER/{ref}GETاستعلام با شماره مرجع
/sync/GET_FISCAL_INFORMATIONGETاطلاعات حافظه مالیاتی
/sync/GET_ECONOMIC_CODE_INFORMATION/{code}GETاستعلام کد اقتصادی
/sync/GET_SERVICE_STUFF_LISTGETلیست کالا/خدمات
/sync/GET_SERVER_INFORMATIONGETاطلاعات سرور

کدهای خطا

خطاهای عمومی (00XXX)

کدپیامراه‌حل
00000موفق-
00001خطای داخلی سرورصبر و تلاش مجدد
00002درخواست نامعتبربررسی ساختار JSON
00003احراز هویت ناموفقتوکن جدید دریافت کنید
00004دسترسی غیرمجازبررسی مجوزها
00005محدودیت نرخصبر و تلاش مجدد
00006خطای رمزگذاریبررسی کلیدها و الگوریتم
00600خطای امضابررسی نرمال‌سازی JSON

خطاهای صورتحساب (01XXXXX)

کدپیام
0100501شناسه یکتای مالیاتی تکراری
0100502فرمت شناسه یکتا نامعتبر
0100503تاریخ صدور نامعتبر
0100504کد اقتصادی فروشنده نامعتبر
0100505کد پستی فروشنده نامعتبر
0100506کد اقتصادی خریدار الزامی (نوع ۱)
0100507نوع صورتحساب نامعتبر

خطاهای کالا (03XXXXX)

کدپیام
0303301شناسه کالا با نرخ مالیات مطابقت ندارد
0303302شناسه کالا یافت نشد
0303303واحد اندازه‌گیری نامعتبر

خطاهای محاسبه (04/05XXXXX)

کدپیامفرمول صحیح
0401001مالیات قلم اشتباهvam = adis × vra / 100
0501001تخفیف اشتباهadis = prdis - dis
0501002مبلغ قبل از تخفیف اشتباهprdis = fee × am
0501003جمع صورتحساب اشتباهtbill = tadis + tvam + todam

الگوریتم Verhoeff

الگوریتم Verhoeff برای اعتبارسنجی کدهای اقتصادی و شناسه ملی استفاده می‌شود.

جداول الگوریتم

// جدول ضرب d
const d = [
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
    [1, 2, 3, 4, 0, 6, 7, 8, 9, 5],
    [2, 3, 4, 0, 1, 7, 8, 9, 5, 6],
    [3, 4, 0, 1, 2, 8, 9, 5, 6, 7],
    [4, 0, 1, 2, 3, 9, 5, 6, 7, 8],
    [5, 9, 8, 7, 6, 0, 4, 3, 2, 1],
    [6, 5, 9, 8, 7, 1, 0, 4, 3, 2],
    [7, 6, 5, 9, 8, 2, 1, 0, 4, 3],
    [8, 7, 6, 5, 9, 3, 2, 1, 0, 4],
    [9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
];

// جدول جایگشت p
const p = [
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
    [1, 5, 7, 6, 2, 8, 3, 0, 9, 4],
    [5, 8, 0, 3, 7, 9, 6, 1, 4, 2],
    [8, 9, 1, 6, 0, 4, 3, 5, 2, 7],
    [9, 4, 5, 3, 1, 2, 6, 8, 7, 0],
    [4, 2, 8, 6, 5, 7, 3, 9, 0, 1],
    [2, 7, 9, 3, 8, 0, 6, 4, 1, 5],
    [7, 0, 4, 6, 9, 1, 3, 2, 5, 8],
];

// جدول معکوس
const inv = [0, 4, 3, 2, 1, 5, 6, 7, 8, 9];

اعتبارسنجی

function validateVerhoeff(num: string): boolean {
    let c = 0;
    const numArray = num.split('').reverse().map(Number);
    
    for (let i = 0; i < numArray.length; i++) {
        c = d[c][p[i % 8][numArray[i]]];
    }
    
    return c === 0;
}

چک‌لیست پیاده‌سازی

قبل از شروع

  • دریافت شناسه حافظه مالیاتی (fiscalId) از سازمان
  • ساخت جفت کلید RSA (2048 یا 4096 بیت)
  • بارگذاری کلید عمومی در سامانه
  • دریافت کلید عمومی سرور

پیاده‌سازی

  • پیاده‌سازی نرمال‌سازی JSON
  • پیاده‌سازی امضای RSA-SHA256
  • پیاده‌سازی رمزگذاری AES-256-GCM
  • پیاده‌سازی تولید taxid
  • پیاده‌سازی اعتبارسنجی Verhoeff
  • مدیریت شماره سریال با DB

تست

  • تست در محیط Sandbox
  • بررسی محاسبات صورتحساب
  • تست ابطال و اصلاح
  • تست مدیریت خطا

منابع


نرم‌افزار بارکد تمام این پیچیدگی‌ها را برای شما انجام می‌دهد. کافیست گواهی دیجیتال خود را وارد کنید و فاکتورها به صورت خودکار ارسال می‌شوند.

این مقاله را به اشتراک بگذارید:

بارکد را همین الان امتحان کنید

۱۰ روز رایگان با تمام امکانات - بدون نیاز به کارت بانکی

کدام نسخه مناسب شماست؟

گوشی اندروید

اپلیکیشن اختصاصی - مناسب فروش سیار

آیفون / آیپد

به صفحه اصلی اضافه کنید - مثل اپ استفاده کنید

کامپیوتر / لپ‌تاپ

صفحه بزرگ‌تر - مناسب کار پشت میز