SDK رسمی .NET

پروژه SMSDaily.Client کلاینت رسمی سبک برای Public API است و روی .NET 8 ساخته شده است. این Client به کتابخانه خارجی وابسته نیست و از HttpClient، System.Net.Http.Json و System.Text.Json استفاده می‌کند.

نسخه فعلی SDK داخل Repository منتشر شده است و هنوز ادعا نمی‌کنیم Package عمومی NuGet منتشر شده باشد. تا زمان انتشار Package رسمی، پروژه را با ProjectReference یا سورس کنترل‌شده خود استفاده کنید.

افزودن ProjectReference

اگر Repository یا پروژه SDK در کنار Solution شما قرار دارد:

<ItemGroup>
  <ProjectReference Include="..\SMSDaily.Client\SmsDaily.Client.csproj" />
</ItemGroup>

یا از CLI:

dotnet add reference ../SMSDaily.Client/SmsDaily.Client.csproj

ساخت Client

بهتر است HttpClient را از IHttpClientFactory دریافت کنید و API Key را جداگانه از Secret Store بخوانید:

using SmsDaily.Client;

builder.Services.AddHttpClient("SMSDaily", http =>
{
    http.BaseAddress = new Uri("https://service.smsdaily.ir");
});

builder.Services.AddScoped<SmsDailyClient>(services =>
{
    var factory = services.GetRequiredService<IHttpClientFactory>();
    var configuration = services.GetRequiredService<IConfiguration>();

    return new SmsDailyClient(
        factory.CreateClient("SMSDaily"),
        configuration["SMSDaily:ApiKey"]!);
});

API Key را در appsettings.json عمومی یا Repository قرار ندهید. از Secret Store، Environment Variable، Azure Key Vault یا ابزار مشابه استفاده کنید.

نمونه Environment Variable:

SMSDaily__ApiKey=YOUR_API_KEY

ارسال پیامک

var result = await smsDaily.SendMessageAsync(
    new SendMessageRequest(
        Sender: "300099000121",
        Recipient: "09121234567",
        Message: "سفارش شما آماده تحویل است.",
        ClientReference: "order-781"),
    idempotencyKey: "order-781-ready");

Console.WriteLine(result.TrackingCode);

idempotencyKey عمداً ورودی صریح متد است تا Retry همان عملیات با همان کلید انجام شود و SDK به‌صورت پنهان کلید جدید تولید نکند.

رهگیری پیام

var message = await smsDaily.GetMessageAsync(result.TrackingCode);

if (message.Status == "delivered")
{
    // تحویل ثبت شده است.
}

ارسال Pattern

var result = await smsDaily.SendPatternAsync(
    new PatternMessageRequest(
        PatternCode: "order-ready",
        Recipient: "09121234567",
        Variables: new Dictionary<string, string>
        {
            ["name"] = "علیرضا",
            ["orderNo"] = "781"
        },
        ClientReference: "order-781"),
    idempotencyKey: "order-781-pattern");

OTP با تایید سمت SMSDaily

var challenge = await smsDaily.StartServerOtpAsync(
    new OtpStartRequest(
        Mobile: "09121234567",
        PatternCode: "login-otp",
        ClientReference: "login-992"),
    idempotencyKey: "login-992-start");

var verification = await smsDaily.VerifyOtpAsync(
    challenge.TrackingCode,
    codeEnteredByUser);

if (verification.Valid)
{
    // ورود یا عملیات حساس را تایید کنید.
}

دریافت پیام‌های ورودی

var page = await smsDaily.ListInboundMessagesAsync(page: 1, pageSize: 50);

foreach (var incoming in page.Items)
{
    Console.WriteLine($"{incoming.Sender}: {incoming.Message}");
}

برای سامانه‌های رویدادمحور، Webhook گزینه بهتری از Polling مداوم است.

ایجاد Webhook

var webhook = await smsDaily.CreateWebhookAsync(
    new CreateWebhookRequest(
        "https://example.com/integrations/smsdaily/webhook",
        new[] { "message.status", "inbound.received" }));

// فقط همین بار در Secret Store ذخیره کنید.
var signingSecret = webhook.SigningSecret;

مدیریت بعدی:

var all = await smsDaily.ListWebhooksAsync();

await smsDaily.UpdateWebhookAsync(
    webhook.Id,
    new UpdateWebhookRequest(IsActive: false));

await smsDaily.TestWebhookAsync(webhook.Id);
await smsDaily.DeleteWebhookAsync(webhook.Id);

بررسی امضای Callback

SDK Helper مستقل برای HMAC دارد:

var isValid = WebhookSignature.Verify(
    signingSecret,
    timestampHeader,
    signatureHeader,
    rawRequestBody,
    tolerance: TimeSpan.FromMinutes(5));

این Helper:

  • Timestamp یونیکس را بررسی می‌کند.
  • بازه زمانی قابل قبول را کنترل می‌کند.
  • HMAC-SHA256 را روی timestamp.rawBody محاسبه می‌کند.
  • مقایسه امضا را با CryptographicOperations.FixedTimeEquals انجام می‌دهد.

مدیریت خطا

برای پاسخ‌های غیرموفق، SDK خطای SmsDailyApiException ایجاد می‌کند:

try
{
    await smsDaily.GetMessageAsync("SMS-...");
}
catch (SmsDailyApiException ex)
{
    logger.LogWarning(
        "SMSDaily error {Status} {Code}; correlation={CorrelationId}",
        (int)ex.StatusCode,
        ex.ErrorCode,
        ex.CorrelationId);
}

خصوصیات مهم Exception:

خصوصیت کاربرد
StatusCode HTTP Status Code
ErrorCode کد عمومی خطا مانند validation_error
CorrelationId شناسه پیگیری برای Log و پشتیبانی
Errors خطاهای اعتبارسنجی فیلدها
ResponseBody پاسخ خام برای عیب‌یابی کنترل‌شده

از ثبت API Key، OTP و Signing Secret در Log خودداری کنید.