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();