Методы для работы с системными Stealth-чеками


  • GetTotalBalance — (GET) получение информации о балансах пользователя по всем доступным эмиссиям
  • GetFee — (POST) получение значения комиссии в зависимости от запрашиваемой суммы вывода
  • Withdraw — (POST) вывод на крипто-адрес с системных Stealth-чеков (в том числе с нескольких чеков)

Пример вызова на C#

---------------------------------------------------------------------------------------------------------------------------------------
непосредственный вызов: 1. проверка балансов. 2. сравнение с минимальной комиссией для перевода на адрес. 3. перевод на адрес.
---------------------------------------------------------------------------------------------------------------------------------------

private static async Task CallWebApiMethods()
{
    string key = "mySecretKeyValue"; // получать здесь https://paymer.com/ci/Personal/Settings (блок "Настройка Api-ключей")
    string name = "mySecretKeyName";

    string bc = "TRX";
    string addressToWithdraw = "TMYQbPaRSjiPEvv6txN5BEtmu9kGspnKgo";
    decimal amountToWithdraw = 10000m;
    string cult = "ru-RU";

    Random rnd = new Random();
    int extRequestId = -rnd.Next(10, 1000); // идентификатор заявки в системе вызывающего сервис

    SandboxWebApiLogic swal = new SandboxWebApiLogic(key, name);
    TotalBalancesResponse resptb = await swal.ExecuteTotalBalancesAsync(CancellationToken.None);
    if (resptb.RetCode != 0)
    {
        // ошибка, неверные параметры ввода
        return;
    }

    IssueParms issue = resptb.Issues?
        .FirstOrDefault(i =>
            i.Blockchain == bc &&
            i.NativeBalance > 0 &&
            i.OrdersSummary?.Any(o => o.Balance >= amountToWithdraw) == true);

    // у вас этот элемент будет всегда единственный. в принципе их может быть несколько
    if (issue == null)
    {
        // ошибка, выпуск с чеками с достаточным балансом и нужным блокчейном не найден
        return;
    }

    GetFeeRequest dataf = new GetFeeRequest
    {
        Blockchain = bc,
        Amount = amountToWithdraw,
        Cult = cult
    };

    GetFeeResponse respf = await swal.ExecuteGetFeeAsync(dataf, CancellationToken.None);
    if (respf.RetCode != 0)
    {
        // ошибка, неверные параметры ввода
        return;
    }

    decimal maxFeeToWithdraw = respf.SingleFee;

    if (issue.NativeBalance < maxFeeToWithdraw)
    {
        // ошибка, не хватает нативной валюты для проведения вывода. нужно пополнить (как правило 30 TRX)
        return;
    }

    WithdrawRequest dataw = new WithdrawRequest
    {
        Address = addressToWithdraw,
        Blockchain = bc,
        Amount = amountToWithdraw,
        ExtRequestId = extRequestId,
        MaxFee = maxFeeToWithdraw,
        Cult = cult
    };

    WithdrawResponse respw = await swal.ExecuteWithdrawAsync(dataw, CancellationToken.None);
    if (respw.RetCode != 0)
    {
        // ошибка при создании заявки на вывод
        return;
    }

    // txHash формируется и записывается в систему обычно через 500-1000ms
    // для получения txHash нужно вызвать повторно с тем же ExtRequestId
    Thread.Sleep(1000);
    respw = await swal.ExecuteWithdrawAsync(dataw, CancellationToken.None);

    string txHash = respw.TxParams[0].Hash;
    string txHashUrl = respw.TxParams[0].HashUrl;
}

---------------------------------------------------------------------------------------------------------------------------------------
вызовы методов
---------------------------------------------------------------------------------------------------------------------------------------

using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;

public class SandboxWebApiLogic : CallWebApi
{
    public SandboxWebApiLogic(string appKeyId, string pk)
    {
        AppKeyId = appKeyId;
        AppKeyValue = pk;
    }

    public async Task<TotalBalancesResponse> ExecuteTotalBalancesAsync(CancellationToken cancellationToken)
    {
        string url = "https://paymer.com/ci/webapi/get_total_balance";

        using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url)
        {
            Headers =
            {
                { HttpRequestHeader.ContentType.ToString(), "application/json" },
            }
        })
        {
            request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
            HttpRequestMessage signedRequestMessage = await SignRequestAsync(request);

            HttpResponseMessage response = await HttpClient.SendAsync(
                signedRequestMessage,
                HttpCompletionOption.ResponseHeadersRead,
                cancellationToken);

            response.EnsureSuccessStatusCode();

            string stream = await response.Content.ReadAsStringAsync();

            TotalBalancesResponse res =
                JsonConvert.DeserializeObject<TotalBalancesResponse>(stream);

            return res;
        }
    }

    public async Task<GetFeeResponse> ExecuteGetFeeAsync(GetFeeRequest data, CancellationToken cancellationToken)
    {
        string url = "https://paymer.com/ci/webapi/get_fee";

        using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url))
        {
            StringContent content = new StringContent(JsonConvert.SerializeObject(data));
            content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
            request.Content = content;
            request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));

            HttpRequestMessage signedRequestMessage = await SignRequestAsync(request);

            HttpResponseMessage response = await HttpClient.SendAsync(
                signedRequestMessage,
                HttpCompletionOption.ResponseHeadersRead,
                cancellationToken);

            response.EnsureSuccessStatusCode();

            string responseText = await response.Content.ReadAsStringAsync();

            GetFeeResponse res = JsonConvert.DeserializeObject<GetFeeResponse>(responseText);

            return res;
        }
    }

    public async Task<WithdrawResponse> ExecuteWithdrawAsync(WithdrawRequest data, CancellationToken cancellationToken)
    {
        string url = "https://paymer.com/ci/webapi/withdraw";

        using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url))
        {
            StringContent content = new StringContent(JsonConvert.SerializeObject(data));
            content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
            request.Content = content;
            request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));

            using (HttpRequestMessage signedRequestMessage = await SignRequestAsync(request))
            {
                HttpResponseMessage response = await HttpClient.SendAsync(
                    signedRequestMessage,
                    HttpCompletionOption.ResponseHeadersRead,
                    cancellationToken);

                response.EnsureSuccessStatusCode();

                string responseText = await response.Content.ReadAsStringAsync();

                return JsonConvert.DeserializeObject<WithdrawResponse>(responseText);
            }
        }
    }
}

---------------------------------------------------------------------------------------------------------------------------------------
метод подписи
---------------------------------------------------------------------------------------------------------------------------------------

using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

public class CallWebApi
{
    protected string AppKeyId;
    protected string AppKeyValue;

    protected static readonly HttpClient HttpClient = new HttpClient();

    public CallWebApi(string appKeyId, string pk)
    {
        AppKeyId = appKeyId;
        AppKeyValue = pk;
    }

    public string Hash(string key)
    {
        using (var sha256 = SHA256.Create())
        {
            var hashedBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(key));
            return Convert.ToBase64String(hashedBytes);
        }
    }

    public async Task<HttpRequestMessage> SignRequestAsync(HttpRequestMessage request)
    {
        string contentMd5 = string.Empty;
        if (request.Content != null)
        {
            byte[] content = await request.Content.ReadAsByteArrayAsync();
            using (MD5 md5 = MD5.Create())
            {
                byte[] contentMd5Hash = md5.ComputeHash(content);
                request.Content.Headers.ContentMD5 = contentMd5Hash;

                contentMd5 = Convert.ToBase64String(contentMd5Hash);
            }
        }

        string hashedKey = Hash($"{AppKeyValue}{contentMd5}");
        request.Headers.Authorization =
            new AuthenticationHeaderValue(
                "APIAuth-HMAC-SHA256",
                $"{AppKeyId}:{hashedKey}");

        return request;
    }
}

---------------------------------------------------------------------------------------------------------------------------------------
структуры
---------------------------------------------------------------------------------------------------------------------------------------

// для получения баланса

[Serializable]
public class TotalBalancesRequest
{
    public TotalBalancesRequest()
    {
    }

    public decimal NeedAmount { get; set; }
    public string Blockchain { get; set; }
    public string Cult { get; set; }
}

[Serializable]
public class TotalBalancesResponse
{
    public TotalBalancesResponse()
    {
    }

    public TotalBalancesResponse(List<IssueParms> issues)
    {
        Issues = issues;
    }

    public List<IssueParms> Issues { get; set; }

    public int RetCode { get; set; }
    public string ErrorMessage { get; set; }
}

[Serializable]
public class IssueParms
{
    public IssueParms()
    {
    }

    public decimal Balance { get; set; }
    public decimal NativeBalance { get; set; }
    public string Blockchain { get; set; }
    public int CoreIssueId { get; set; }
    public List<OrderSummary> OrdersSummary { get; set; }
    public ComplianceStatus AmlStatus { get; set; }
}
    
[Serializable]
public class OrderSummary
{
    public OrderSummary()
    {
    }

    public decimal Amount { get; set; }
    public decimal Balance { get; set; }
}

// представляет допустимость транзакции
[JsonConverter (typeof (StringEnumConverter))]
public enum ComplianceStatus
{
    [EnumMember (Value = "approve")]
    Approve = 0,

    [EnumMember (Value = "reject")]
    Reject = 1,

    [EnumMember (Value = "pending")]
    Pending = 2,

    [EnumMember (Value = "error")]
    Error = 3
}

*********************************************************************************************************************

// для получения минимальной суммы комиссии, необходимой для перевода

[Serializable]
public class GetFeeRequest
{
    public GetFeeRequest()
    {
    }

    public decimal Amount { get; set; }
    public string Blockchain { get; set; }
    public string Cult { get; set; }
}

[Serializable]
public class GetFeeResponse
{
    public GetFeeResponse()
    {
    }

    public decimal TotalFee { get; set; }
    public decimal SingleFee { get; set; }
    public decimal SingleAvailableAmount { get; set; }

    public int RetCode { get; set; }
    public string ErrorMessage { get; set; }
}

*********************************************************************************************************************

// для перевода на адрес

[Serializable]
public class WithdrawRequest
{
    public WithdrawRequest()
    {
    }
    public string Address { get; set; }
    public string Blockchain { get; set; }
    public decimal Amount { get; set; }
    public decimal MaxFee { get; set; }
    public int ExtRequestId { get; set; }
    public string Cult { get; set; }
}

[Serializable]
public class WithdrawResponse
{
    public WithdrawResponse()
    {
    }

    public int RetCode { get; set; }
    public string RetDesc { get; set; }
    public TxParams[] TxParams { get; set; }
}

public class TxParams
{
    public TxParams()
    {
    }

    public TxParams(string hash, string hashUrl, int reqStatus)
    {
        Hash = hash;
        HashUrl = hashUrl;
        RequestStatus = reqStatus;
    }

    public string Hash { get; set; }
    public string HashUrl { get; set; }
    public int RequestStatus { get; set; }
}