Skip to content

C# Sample

This sample uses .NET HttpClient. It includes signing, POST JSON, GET Query, and examples for creating a collect order, creating a payout order, and querying merchant info.

csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

public sealed class SkynetClient
{
    private readonly string baseUrl;
    private readonly int mchId;
    private readonly string apiToken;
    private readonly HttpClient httpClient;

    public SkynetClient(string baseUrl, int mchId, string apiToken, HttpClient? httpClient = null)
    {
        this.baseUrl = baseUrl.TrimEnd('/');
        this.mchId = mchId;
        this.apiToken = apiToken;
        this.httpClient = httpClient ?? new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
    }

    public Task<Dictionary<string, JsonElement>> CreateCollectOrder()
    {
        return Post("/api/v1/mch/pmt-orders", new Dictionary<string, object?>
        {
            ["trans_id"] = $"ORDER-{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}",
            ["currency"] = "VND",
            ["amount"] = "100.00",
            ["channel"] = "bank",
            ["callback_url"] = "https://merchant.example.com/callback/collect",
            ["return_url"] = "https://merchant.example.com/payment/result",
            ["remarks"] = "collect demo",
        });
    }

    public Task<Dictionary<string, JsonElement>> CreatePayoutOrder()
    {
        return Post("/api/v1/mch/wdl-orders", new Dictionary<string, object?>
        {
            ["trans_id"] = $"WDL-{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}",
            ["channel"] = "bank",
            ["amount"] = "100.00",
            ["currency"] = "VND",
            ["account_no"] = "2333667799212341",
            ["account_name"] = "NGUYEN XUAN HUNG",
            ["account_org"] = "PVCOMBANK",
            ["account_org_code"] = "970412",
            ["callback_url"] = "https://merchant.example.com/callback/payout",
            ["remarks"] = "payout demo",
        });
    }

    public Task<Dictionary<string, JsonElement>> GetMerchantInfo()
    {
        return Get("/api/v1/mch/info", new Dictionary<string, object?>());
    }

    private async Task<Dictionary<string, JsonElement>> Post(string path, Dictionary<string, object?> parameters)
    {
        var signedParameters = WithSignature(parameters);
        var content = new StringContent(
            JsonSerializer.Serialize(signedParameters),
            Encoding.UTF8,
            "application/json"
        );

        var response = await httpClient.PostAsync(baseUrl + path, content);
        return await ParseResponse(response);
    }

    private async Task<Dictionary<string, JsonElement>> Get(string path, Dictionary<string, object?> parameters)
    {
        var signedParameters = WithSignature(parameters);
        var query = string.Join("&", signedParameters.Select(kv =>
            $"{Uri.EscapeDataString(kv.Key)}={Uri.EscapeDataString(Convert.ToString(kv.Value) ?? "")}"
        ));

        var response = await httpClient.GetAsync($"{baseUrl}{path}?{query}");
        return await ParseResponse(response);
    }

    private Dictionary<string, object?> WithSignature(Dictionary<string, object?> parameters)
    {
        var signedParameters = new Dictionary<string, object?>(parameters)
        {
            ["mch_id"] = mchId,
            ["nonce"] = Guid.NewGuid().ToString("N")[..12],
            ["timestamp"] = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
        };
        signedParameters["sign"] = Sign(signedParameters);

        return signedParameters;
    }

    private string Sign(Dictionary<string, object?> parameters)
    {
        var source = new StringBuilder(apiToken);
        foreach (var kv in parameters.OrderBy(kv => kv.Key, StringComparer.Ordinal))
        {
            if (kv.Key == "sign" || kv.Value is null || Convert.ToString(kv.Value) == "")
            {
                continue;
            }

            source.Append('&').Append(kv.Key).Append('=').Append(kv.Value);
        }

        using var md5 = MD5.Create();
        var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(source.ToString()));
        return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
    }

    private static async Task<Dictionary<string, JsonElement>> ParseResponse(HttpResponseMessage response)
    {
        var responseBody = await response.Content.ReadAsStringAsync();
        if (!response.IsSuccessStatusCode)
        {
            throw new Exception($"HTTP request failed: {(int)response.StatusCode} {responseBody}");
        }

        using var document = JsonDocument.Parse(responseBody);
        var root = document.RootElement;
        if (!root.TryGetProperty("code", out var code) || code.GetInt32() != 200)
        {
            throw new Exception($"API request failed: {responseBody}");
        }

        return root.TryGetProperty("payload", out var payload)
            ? JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(payload.GetRawText()) ?? new()
            : new Dictionary<string, JsonElement>();
    }
}

var client = new SkynetClient(
    baseUrl: "https://api.example.com",
    mchId: 10001,
    apiToken: "demo_key_123456"
);

var collectOrder = await client.CreateCollectOrder();
var payoutOrder = await client.CreatePayoutOrder();
var merchantInfo = await client.GetMerchantInfo();

Released under the MIT License.