200 lines
6.7 KiB
C#
200 lines
6.7 KiB
C#
using Chaos.NaCl;
|
||
using Microsoft.AspNetCore.Authorization;
|
||
using Microsoft.AspNetCore.Mvc;
|
||
using Microsoft.Extensions.Caching.Memory;
|
||
using Shared;
|
||
using System;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Net.Http;
|
||
using System.Text;
|
||
using System.Text.Json;
|
||
using System.Text.RegularExpressions;
|
||
using System.Threading.Tasks;
|
||
using IO = System.IO.File;
|
||
|
||
namespace Merchant.Controllers
|
||
{
|
||
public class Streampay : MerchantController
|
||
{
|
||
static Streampay() { Directory.CreateDirectory("merchant/invoice/streampay"); }
|
||
|
||
|
||
[HttpGet]
|
||
[AllowAnonymous]
|
||
[Route("streampay/new")]
|
||
async public Task<ActionResult> Index(string email)
|
||
{
|
||
var init = AppInit.conf.Merchant.Streampay;
|
||
if (!init.enable || string.IsNullOrWhiteSpace(email))
|
||
return Content(string.Empty);
|
||
|
||
email = decodeEmail(email);
|
||
string transid = DateTime.Now.ToBinary().ToString().Replace("-", "");
|
||
|
||
if (memoryCache.TryGetValue($"streampay:{transid}", out string pay_link))
|
||
return Redirect(pay_link);
|
||
|
||
IO.WriteAllText($"merchant/invoice/streampay/{transid}", email);
|
||
|
||
var body = new
|
||
{
|
||
init.store_id,
|
||
customer = email,
|
||
external_id = transid,
|
||
description = $"Подписка на {AppInit.conf.Merchant.accessForMonths} {EndOfText("месяц", "месяца", "месяцев", AppInit.conf.Merchant.accessForMonths)}",
|
||
system_currency = "USDT",
|
||
payment_type = 2,
|
||
amount = AppInit.conf.Merchant.accessCost
|
||
};
|
||
|
||
var jsonBody = JsonSerializer.Serialize(body);
|
||
string signature = Sign(Encoding.UTF8.GetBytes(jsonBody + DateTime.UtcNow.ToString("yyyyMMdd:HHmm")), init.private_key);
|
||
|
||
using (var httpClient = new HttpClient())
|
||
{
|
||
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.streampay.org/api/payment/create")
|
||
{
|
||
Content = new StringContent(jsonBody, Encoding.UTF8, "application/json")
|
||
};
|
||
|
||
request.Headers.Add("signature", signature);
|
||
|
||
var response = await httpClient.SendAsync(request);
|
||
if (response.IsSuccessStatusCode)
|
||
{
|
||
string respData = await response.Content.ReadAsStringAsync();
|
||
string pay_url = Regex.Match(respData, "\"pay_url\":\"([^\"]+)\"").Groups[1].Value;
|
||
|
||
if (!string.IsNullOrEmpty(pay_url))
|
||
{
|
||
memoryCache.Set($"streampay:{transid}", pay_url, DateTime.Now.AddHours(2));
|
||
return Redirect(pay_url);
|
||
}
|
||
|
||
return Content(respData);
|
||
}
|
||
else if (response.StatusCode == System.Net.HttpStatusCode.Forbidden)
|
||
{
|
||
return Content("Invalid signature");
|
||
}
|
||
else if (response.StatusCode == System.Net.HttpStatusCode.NotAcceptable)
|
||
{
|
||
return Content("Invalid request data");
|
||
}
|
||
else if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
|
||
{
|
||
return Content("Internal server error");
|
||
}
|
||
}
|
||
|
||
return Content("error");
|
||
}
|
||
|
||
|
||
[HttpGet]
|
||
[AllowAnonymous]
|
||
[Route("streampay/callback")]
|
||
public ActionResult Callback()
|
||
{
|
||
string transid = Request.Query["external_id"].ToString();
|
||
|
||
if (Request.Query["status"] != "awaiting_payment")
|
||
memoryCache.Remove($"streampay:{transid}");
|
||
|
||
var merchant = AppInit.conf.Merchant;
|
||
if (!merchant.Streampay.enable || !IO.Exists($"merchant/invoice/streampay/{transid}") || Request.Query["status"] != "success")
|
||
return Ok();
|
||
|
||
var now = DateTime.UtcNow;
|
||
var queryParams = Request.Query.OrderBy(x => x.Key).Select(x => $"{x.Key}={x.Value}").ToList();
|
||
string paramsStr = string.Join('&', queryParams);
|
||
byte[] paramsBuf = Encoding.UTF8.GetBytes(paramsStr);
|
||
|
||
string log = $"{paramsStr}\n{JsonSerializer.Serialize(Request.Headers)}";
|
||
|
||
for (int i = 0; i < 2; i++)
|
||
{
|
||
string tm = now.ToString("yyyyMMdd:HHmm");
|
||
var bufToSign = paramsBuf.Concat(Encoding.UTF8.GetBytes(tm)).ToArray();
|
||
|
||
bool verify = Verify(Request.Headers["Signature"], bufToSign, merchant.Streampay.public_key);
|
||
log += $"\nverify: {verify} | {tm} | signature: {Request.Headers["Signature"]}";
|
||
|
||
if (verify)
|
||
{
|
||
PayConfirm(IO.ReadAllText($"merchant/invoice/streampay/{transid}"), "streampay", transid);
|
||
|
||
WriteLog("streampay", log + "\nOK");
|
||
return Ok();
|
||
}
|
||
|
||
now = now.AddMinutes(-1);
|
||
}
|
||
|
||
WriteLog("streampay", log + "\nForbid");
|
||
return Forbid();
|
||
}
|
||
|
||
|
||
|
||
static string Sign(byte[] message, string privateKey)
|
||
{
|
||
var bytes = Ed25519.Sign(message, HexToBytes(privateKey));
|
||
|
||
StringBuilder hex = new StringBuilder(bytes.Length * 2);
|
||
|
||
foreach (byte b in bytes)
|
||
hex.AppendFormat("{0:x2}", b);
|
||
|
||
return hex.ToString();
|
||
}
|
||
|
||
static bool Verify(string signature, byte[] message, string publicKey)
|
||
{
|
||
try
|
||
{
|
||
return Ed25519.Verify(HexToBytes(signature), message, HexToBytes(publicKey));
|
||
}
|
||
catch { return false; }
|
||
}
|
||
|
||
static byte[] HexToBytes(string key)
|
||
{
|
||
if (key.Length % 2 != 0)
|
||
return null;
|
||
|
||
int byteCount = key.Length / 2;
|
||
byte[] hexKey = new byte[byteCount];
|
||
|
||
for (int i = 0; i < byteCount; i++)
|
||
{
|
||
string byteString = key.Substring(i * 2, 2);
|
||
byte keyValue = Convert.ToByte(byteString, 16);
|
||
hexKey[i] = keyValue;
|
||
}
|
||
|
||
return hexKey;
|
||
}
|
||
|
||
static string EndOfText(string s1, string s2, string s3, int x)
|
||
{
|
||
int n = x % 100;
|
||
if ((n > 10) && (n < 20))
|
||
return s3;
|
||
|
||
switch (x % 10)
|
||
{
|
||
case 4: return s2;
|
||
case 0:
|
||
case 5:
|
||
case 6:
|
||
case 7:
|
||
case 8:
|
||
case 9: return s3;
|
||
default: return s1;
|
||
}
|
||
}
|
||
}
|
||
}
|