اگر صاحب یک کسبوکار هستید یا بهعنوان توسعهدهنده نرمافزار کار میکنید، احتمالاً با سامانه مودیان مالیاتی آشنا شدهاید. این سامانه که توسط سازمان امور مالیاتی کشور راهاندازی شده، یکی از بزرگترین تحولات دیجیتال در حوزه مالیات ایران است. از همه کسبوکارهایی که مشمول قانون پایانههای فروشگاهی و سامانه مودیان هستند، انتظار میرود صورتحسابهای الکترونیکی خود را از طریق این سامانه ثبت و ارسال کنند.
اما سوال اصلی اینجاست: چطور میتوان نرمافزار حسابداری یا فروش موجود را که با ASP.NET توسعه یافته، به سامانه مودیان وصل کرد؟ پاسخ این سوال را در این مقاله بهصورت کامل، گامبهگام و با کد نمونه آماده کردهایم.
پیش از اینکه وارد کدنویسی شویم، باید معماری سامانه مودیان را درک کنیم. سازمان امور مالیاتی یک API مبتنی بر REST طراحی کرده که با استانداردهای بینالمللی همخوانی دارد. ارتباط با این API به سه مرحله اصلی تقسیم میشود:
نکته مهم: تمام ارتباطات با سامانه مودیان باید رمزگذاریشده (HTTPS) باشند و دادهها با کلید خصوصی شما امضا شوند. بنابراین مدیریت صحیح کلیدهای رمزنگاری یکی از حساسترین بخشهای این پروژه است.
قبل از نوشتن اولین خط کد، مطمئن شوید موارد زیر را در اختیار دارید:
یک پروژه ASP.NET Core Web API جدید بسازید و پکیجهای ضروری را نصب کنید. در Package Manager Console دستورات زیر را اجرا کنید:
Install-Package Portable.BouncyCastle Install-Package jose-jwt Install-Package Newtonsoft.Json Install-Package System.Security.Cryptography.X509Certificates
این پکیجها برای رمزنگاری، امضای دیجیتال، تولید JWT و کار با JSON ضروری هستند.
برای ارتباط با سامانه مودیان، ابتدا باید یک توکن دسترسی (Access Token) دریافت کنید. این توکن از طریق درخواست POST به endpoint احراز هویت و با ارائه امضای دیجیتال صادر میشود.
یک کلاس سرویس به نام MoadianAuthService بسازید:
public class MoadianAuthService
{
private readonly HttpClient _httpClient;
private readonly IConfiguration _config;
private string _cachedToken;
private DateTime _tokenExpiry;
public MoadianAuthService(HttpClient httpClient, IConfiguration config)
{
_httpClient = httpClient;
_config = config;
}
public async Task GetAccessTokenAsync()
{
if (!string.IsNullOrEmpty(_cachedToken)
&& DateTime.UtcNow < _tokenExpiry)
return _cachedToken;
var privateKey = LoadPrivateKey(_config[“Moadian:PrivateKeyPath”]);
var uid = _config[“Moadian:UID”];
// ساخت payload برای JWT
var payload = new Dictionary
{
{ “uid”, uid },
{ “iat”, DateTimeOffset.UtcNow.ToUnixTimeSeconds() },
{ “exp”, DateTimeOffset.UtcNow.AddMinutes(30).ToUnixTimeSeconds() }
};
var token = JWT.Encode(payload, privateKey, JwsAlgorithm.RS256);
var requestBody = new { token = token };
var response = await _httpClient.PostAsJsonAsync(
“https://tp.tax.gov.ir/req/api/self-tsp/token”, requestBody);
var result = await response.Content.ReadFromJsonAsync();
_cachedToken = result.Token;
_tokenExpiry = DateTime.UtcNow.AddMinutes(25);
return _cachedToken;
}
private RSA LoadPrivateKey(string keyPath)
{
var pem = File.ReadAllText(keyPath);
var rsa = RSA.Create();
rsa.ImportFromPem(pem);
return rsa;
}
}
سامانه مودیان دو نوع صورتحساب پشتیبانی میکند: نوع اول (فروش به اشخاص حقوقی) و نوع دوم (فروش به اشخاص حقیقی). ساختار JSON هر دو مشابه است. مدل اصلی را به این شکل تعریف کنید:
public class InvoiceHeader
{
[JsonProperty(“taxid”)]
public string TaxId { get; set; } // شناسه یکتای مالیاتی
[JsonProperty(“indatim”)]
public long InDateTime { get; set; } // تاریخ و ساعت صورتحساب (Unix timestamp)
[JsonProperty(“indati2m”)]
public long InDateTime2 { get; set; }
[JsonProperty(“inno”)]
public string InvoiceNumber { get; set; } // شماره صورتحساب
[JsonProperty(“irtaxid”)]
public string SellerTaxId { get; set; } // شناسه مالیاتی فروشنده
[JsonProperty(“inp”)]
public int InvoicePattern { get; set; } // الگوی صورتحساب (1 یا 2)
[JsonProperty(“ins”)]
public int InvoiceSubject { get; set; } // موضوع صورتحساب
}
public class InvoiceBody
{
[JsonProperty(“sstid”)]
public string GoodServiceId { get; set; } // شناسه کالا/خدمت
[JsonProperty(“sstt”)]
public string GoodServiceName { get; set; }
[JsonProperty(“am”)]
public decimal Amount { get; set; } // مقدار
[JsonProperty(“mu”)]
public int Unit { get; set; } // واحد
[JsonProperty(“fee”)]
public long UnitPrice { get; set; } // مبلغ واحد (ریال)
[JsonProperty(“tsstam”)]
public long TotalAmount { get; set; } // جمع مبلغ کالا
}
مهمترین بخش پیادهسازی، رمزگذاری محتوای صورتحساب است. سامانه مودیان از رمزگذاری ترکیبی استفاده میکند: ابتدا دادهها با AES رمز میشوند، سپس کلید AES با RSA عمومی سامانه رمزگذاری میشود.
public class MoadianInvoiceService
{
private readonly HttpClient _httpClient;
private readonly MoadianAuthService _authService;
public async Task SendInvoiceAsync(Invoice invoice)
{
var token = await _authService.GetAccessTokenAsync();
// سریالسازی صورتحساب
var invoiceJson = JsonConvert.SerializeObject(invoice);
var invoiceBytes = Encoding.UTF8.GetBytes(invoiceJson);
// تولید کلید AES تصادفی
using var aes = Aes.Create();
aes.KeySize = 256;
aes.GenerateKey();
aes.GenerateIV();
// رمزگذاری با AES
var encryptedData = EncryptWithAes(invoiceBytes, aes.Key, aes.IV);
// رمزگذاری کلید AES با RSA عمومی سامانه
var encryptedKey = EncryptKeyWithRsa(aes.Key, GetServerPublicKey());
// امضای صورتحساب با کلید خصوصی
var signature = SignData(invoiceBytes, LoadPrivateKey());
var packet = new
{
header = new { },
body = new
{
encryptedInvoice = Convert.ToBase64String(encryptedData),
symmetricKey = Convert.ToBase64String(encryptedKey),
iv = Convert.ToBase64String(aes.IV),
fiscalId = invoice.Header.TaxId,
dataSignature = Convert.ToBase64String(signature)
}
};
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue(“Bearer”, token);
var response = await _httpClient.PostAsJsonAsync(
“https://tp.tax.gov.ir/req/api/self-tsp/async/normal-enqueue”,
new { packets = new[] { packet } });
return await response.Content.ReadFromJsonAsync();
}
}
ارسال صورتحساب به سامانه مودیان آسنکرون (Asynchronous) است. یعنی پس از ارسال، یک uid دریافت میکنید و باید بعداً وضعیت آن را پیگیری کنید. برای این کار از endpoint زیر استفاده میشود:
public async TaskCheckInvoiceStatusAsync(string referenceId) { var token = await _authService.GetAccessTokenAsync(); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(“Bearer”, token); var response = await _httpClient.GetAsync( $“https://tp.tax.gov.ir/req/api/self-tsp/inquiry/invoice-status” + $“?referenceNumber={referenceId}”); var statusResult = await response.Content .ReadFromJsonAsync (); return statusResult?.Data?.Status switch { “SUCCESS” => InvoiceStatus.Confirmed, “FAILED” => InvoiceStatus.Rejected, “PENDING” => InvoiceStatus.Processing, _ => InvoiceStatus.Unknown }; }
در پروژههای واقعی، مدیریت خطا اهمیت حیاتی دارد. سامانه مودیان کدهای خطای استانداردی دارد که باید آنها را به درستی handle کنید:
توصیه میشود از پکیج Polly برای پیادهسازی الگوی Retry با exponential backoff استفاده کنید:
// در Program.cs builder.Services.AddHttpClient() .AddPolicyHandler(GetRetryPolicy()); static IAsyncPolicy GetRetryPolicy() { return HttpPolicyExtensions .HandleTransientHttpError() .OrResult(msg => msg.StatusCode == HttpStatusCode.TooManyRequests) .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); }
یک اشتباه رایج این است که توسعهدهندگان فقط به ارسال فکر میکنند و سیستم ذخیرهسازی مناسبی طراحی نمیکنند. حتماً جدولی برای ردیابی وضعیت هر صورتحساب در دیتابیس خود داشته باشید:
🚀 آیا سایت شما هم در صفحه اول گوگل دیده میشود؟
رقبای شما الان درحال جذب مشتریانی هستند که شما میتوانستید داشته باشید. سئوی سایت خود را به متخصصان ما بسپارید و شاهد رشد واقعی کسبوکارتان باشید.
📞 همین حالا برای مشاوره رایگان تماس بگیرید:
09190994063 | 09376846692
در سیستمهای با بار بالا، بهتر است ارسال صورتحساب را به یک Background Service یا Queue Worker واگذار کنید. این کار از timeout شدن request کاربر جلوگیری میکند و قابلیت retry اتوماتیک را فراهم میآورد:
public class MoadianWorkerService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger _logger;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using var scope = _serviceProvider.CreateScope();
var invoiceRepo = scope.ServiceProvider
.GetRequiredService();
var moadianService = scope.ServiceProvider
.GetRequiredService();
// دریافت صورتحسابهای pending
var pendingInvoices = await invoiceRepo
.GetPendingInvoicesAsync(batchSize: 50);
foreach (var invoice in pendingInvoices)
{
try
{
var result = await moadianService
.SendInvoiceAsync(invoice);
await invoiceRepo.UpdateStatusAsync(
invoice.Id, InvoiceStatus.Sent, result.ReferenceId);
}
catch (Exception ex)
{
_logger.LogError(ex,
“Error sending invoice {InvoiceId}”, invoice.Id);
await invoiceRepo.IncrementRetryCountAsync(invoice.Id);
}
}
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
}
}
}
هیچوقت مستقیم روی سامانه اصلی (production) تست نکنید! سازمان امور مالیاتی یک محیط sandbox در آدرس sandbox.tax.gov.ir فراهم کرده است. برای مدیریت راحتتر محیطها، از appsettings.json استفاده کنید:
// appsettings.Development.json
{
“Moadian”: {
“BaseUrl”: “https://sandbox.tax.gov.ir”,
“UID”: “your-test-uid”,
“PrivateKeyPath”: “keys/test-private.pem”,
“ServerPublicKeyPath”: “keys/sandbox-server-public.pem”
}
}
// appsettings.Production.json
{
“Moadian”: {
“BaseUrl”: “https://tp.tax.gov.ir”,
“UID”: “your-production-uid”,
“PrivateKeyPath”: “/secure/path/private.pem”,
“ServerPublicKeyPath”: “/secure/path/server-public.pem”
}
}
امنیت در یکپارچهسازی با سامانه مودیان یک الزام قانونی و فنی است. رعایت موارد زیر ضروری است:
اگر روزانه هزاران صورتحساب دارید، این نکات performance را جدی بگیرید:
⚠️ نکته مهم برای صاحبان کسبوکار
پیادهسازی صحیح سامانه مودیان یک پروژه تخصصی است. اشتباه در ارسال صورتحسابها میتواند منجر به جریمههای مالیاتی سنگین شود. اگر تیم فنی ندارید یا نمیخواهید ریسک کنید، از متخصصان کمک بگیرید.
در این مقاله مسیر کامل یکپارچهسازی سامانه مودیان با ASP.NET را پیمودیم. از راهاندازی پروژه گرفته تا احراز هویت، ساخت مدل صورتحساب، رمزگذاری، ارسال، پیگیری وضعیت و بهینهسازی Performance.
اگر همین الان روی یک پروژه مشابه کار میکنید، پیشنهاد میکنیم:
💡 میخواهید سایت شما هم مثل رقبا در صفحه اول گوگل باشد؟
هر روزی که سایت شما در گوگل دیده نمیشود، مشتریها به جای شما پیش رقیبانتان میروند. سئوی سایت یک سرمایهگذاری مطمئن است نه یک هزینه. آیا میخواهید زنگخورهایتان چند برابر شود؟ سئوی سایت خود را به متخصصان ما بسپارید.
📱 همین حالا برای مشاوره رایگان با ما تماس بگیرید:
09190994063 | 09376846692
خیر. سامانه مودیان یک REST API استاندارد است که میتوان با هر زبان و فریمورکی به آن متصل شد. اما ASP.NET Core به دلیل پشتیبانی قوی از HttpClient، رمزنگاری و امضای دیجیتال، یکی از بهترین انتخابها برای این کار است. پکیجهای BouncyCastle و jose-jwt کار را بسیار سادهتر میکنند.
ابتدا باید کد خطا و پیام برگشتی را بررسی کنید. رایجترین دلایل رد صورتحساب عبارتند از: taxId تکراری، فرمت نادرست تاریخ، شناسه کالا/خدمت نامعتبر یا عدم تطابق اطلاعات. پس از اصلاح مشکل، صورتحساب باید با یک taxId جدید دوباره ارسال شود. taxId قبلی که رد شده قابل استفاده مجدد نیست.
معمولاً بین چند ثانیه تا چند دقیقه. اما در ساعات پرترافیک ممکن است این زمان به چند ساعت برسد. به همین دلیل توصیه میشود یک Worker Service داشته باشید که هر ۳۰ دقیقه یکبار وضعیت صورتحسابهای pending را چک کند و سیستم خود را بر اساس وضعیت async طراحی کنید.
بله، اما با محدودیت زمانی. طبق قوانین جاری، صورتحسابها باید در بازه زمانی مشخصی پس از صدور ارسال شوند. ارسال دیرهنگام ممکن است منجر به جریمه شود. برای صورتحسابهای معوق، با مشاور مالیاتی خود مشورت کنید تا از چگونگی ارسال و بازه مجاز مطلع شوید.
صورتحساب نوع اول برای فروش به اشخاص حقوقی و حقیقی دارای شماره اقتصادی است و نیاز به اطلاعات کامل خریدار دارد. صورتحساب نوع دوم برای فروشهای خرده و مصرفکننده نهایی است که هویت خریدار مشخص نیست. نوع سوم هم برای صادرکنندگان است. انتخاب نوع اشتباه یکی از دلایل رایج رد شدن صورتحساب است.
بله، اگر نیاز به سرعت عمل دارید. در NuGet چند پکیج ایرانی برای سامانه مودیان وجود دارد. اما توجه داشته باشید که قبل از استفاده، سورس کد را بررسی کنید، مطمئن شوید بهروز است (API سامانه مودیان تغییر میکند)، و از منابع معتبر دانلود کنید. در پروژههای حساس و بزرگ، پیادهسازی مستقیم توصیه میشود تا کنترل کامل داشته باشید.
مقاله بسیار جامع و کاربردی بود. ممنون از توضیحات گام به گام و ارائه کدهای نمونه. واقعاً کمککننده است.
خوشحالیم که مقاله برای شما مفید بوده است. اگر سوال یا نیاز به مشاوره بیشتری داشتید، میتوانید با ما تماس بگیرید: 09190994063 | 09376846692
پیادهسازی Background Service برای پردازش صف ارسال ایده بسیار خوبی است. آیا نمونه کد کاملتری برای آن وجود دارد؟
نمونه کد ارائه شده یک شمای کلی است. پیادهسازی کامل آن نیاز به در نظر گرفتن جزئیات بیشتری دارد که تیم متخصص ما میتواند در این زمینه به شما کمک کند: 09190994063 | 09376846692
در مورد بهینهسازی Performance در پروژههای بزرگ، آیا استفاده از ارسال دستهای (batch) تا چه تعداد صورتحساب در یک request محدودیت دارد؟
سامانه مودیان ارسال دستهای تا 50 صورتحساب را در یک درخواست پشتیبانی میکند. برای بهینهسازی بیشتر و مشاوره در پروژههای بزرگ با ما تماس بگیرید: 09190994063 | 09376846692
برای کسبوکارهای کوچک که حجم صورتحساب کمی دارند، آیا پیادهسازی این سیستم پیچیده است یا میتوانند از راهحلهای سادهتری استفاده کنند؟
برای کسبوکارهای کوچک، استفاده از راهکارهای آماده یا مشاورین متخصص میتواند مقرون به صرفهتر باشد. برای بررسی گزینهها با ما در تماس باشید: 09190994063 | 09376846692
آیا در آینده به روزرسانیهایی برای پشتیبانی از نسخههای جدیدتر .NET یا تغییرات احتمالی API سامانه مودیان خواهید داشت؟
قطعاً، ما همواره مقالات و راهنماهای خود را با آخرین تغییرات و نسخهها بهروزرسانی میکنیم. برای اطلاع از آخرین تغییرات میتوانید با ما در ارتباط باشید: 09190994063 | 09376846692
ممنون بابت تذکر استفاده از محیط Sandbox. این نکته برای جلوگیری از مشکلات احتمالی در محیط واقعی بسیار مهم است.
خواهش میکنم. رعایت این نکات امنیتی و عملیاتی برای موفقیت پروژه شما حیاتی است. برای هرگونه سوال با ما تماس بگیرید: 09190994063 | 09376846692
بخش مدیریت خطاها و نکات امنیتی بسیار ارزشمند بود. آیا راهنمایی برای حل خطاهای رایج با جزئیات بیشتر دارید؟
بله، در صورت بروز خطاهای خاص میتوانید با تیم فنی ما مشورت کنید تا راهنماییهای لازم را دریافت کنید: 09190994063 | 09376846692
در بخش پیادهسازی سرویس احراز هویت، مدت زمان انقضای توکن (30 دقیقه) آیا قابل تغییر است یا توسط سامانه مودیان تعیین میشود؟
مدت زمان انقضای توکن توسط سامانه مودیان تعیین میشود و معمولاً قابل تغییر نیست. توصیه میشود که توکن را قبل از انقضا تجدید کنید. برای جزئیات بیشتر: 09190994063 | 09376846692
در مورد مدیریت کلید خصوصی و گواهی دیجیتال، آیا استفاده از Azure Key Vault تنها راه حل پیشنهادی است یا گزینههای دیگری هم هست؟
علاوه بر Azure Key Vault، میتوانید از HashiCorp Vault یا حتی Environment Variables (با رعایت نکات امنیتی) استفاده کنید. برای راهنمایی بیشتر تماس بگیرید: 09190994063 | 09376846692
چگونه میتوانیم مطمئن شویم که 'شناسه یکتای مالیاتی' یا TaxId همیشه یکتا باقی میماند و دچار تکرار نمیشود؟ آیا راهکاری برای تولید آن هست؟
برای تضمین یکتایی TaxId، میتوانید از GUID با فرمت مشخص یا یک سیستم شمارهگذاری داخلی استفاده کنید که یکتایی را تضمین کند. برای راهنمایی در طراحی سیستم شما، با ما تماس بگیرید: 09190994063 | 09376846692
متن مقاله بسیار واضح و روان بود و تمام مراحل را به خوبی توضیح داده بود. به خصوص بخش FAQ خیلی مفید بود.
از بازخورد مثبت شما سپاسگزاریم. هدف ما ارائه محتوای کاربردی و قابل فهم برای شماست. اگر سوالی داشتید، خوشحال میشویم پاسخ دهیم: 09190994063 | 09376846692
آیا پکیجهای BouncyCastle و jose-jwt بهترین گزینهها برای رمزنگاری و امضای دیجیتال در ASP.NET هستند یا جایگزینهای بهتری هم وجود دارد؟
این پکیجها گزینههای بسیار رایج و مطمئنی هستند. البته گزینههای دیگری هم ممکن است وجود داشته باشد اما اینها پوشش خوبی از نیازها را فراهم میکنند. میتوانید برای بررسی بیشتر با ما تماس بگیرید: 09190994063 | 09376846692