Skip to content

Commit

Permalink
支持TRX收款
Browse files Browse the repository at this point in the history
  • Loading branch information
ZhangYiQiu committed Sep 14, 2022
1 parent 2817776 commit 7bf6bc3
Show file tree
Hide file tree
Showing 16 changed files with 589 additions and 61 deletions.
Binary file removed Plugs/dujiaoka.zip
Binary file not shown.
101 changes: 101 additions & 0 deletions Plugs/dujiaoka/app/Http/Controllers/Pay/TokenPayController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php
/**
* The file was created by Assimon.
*
* @author assimon<[email protected]>
* @copyright assimon<[email protected]>
* @link http://utf8.hk/
*/

namespace App\Http\Controllers\Pay;


use App\Exceptions\RuleValidationException;
use App\Http\Controllers\PayController;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Http\Request;

class TokenPayController extends PayController
{
public function gateway(string $payway, string $orderSN)
{
try {
// 加载网关
$this->loadGateWay($orderSN, $payway);
//构造要请求的参数数组,无需改动
$parameter = [
"ActualAmount" => (float)$this->order->actual_price,//原价
"OutOrderId" => $this->order->order_sn,
// 当前为每个用户分配一个收款地址,通过此参数识别是否为同一个用户
// 如需要每单一个地址,可将此行改为 "OrderUserKey" => $this->order->order_sn,
"OrderUserKey" => $this->order->email,
"Currency" => $this->payGateway->merchant_id,
'RedirectUrl' => route('tokenpay-return', ['order_id' => $this->order->order_sn]),
'NotifyUrl' => url($this->payGateway->pay_handleroute . '/notify_url'),
];
$client = new Client();
$response = $client->post($this->payGateway->merchant_pem, ['form_params' => $parameter]);
$body = json_decode($response->getBody()->getContents(), true);
if (!isset($body['success']) || $body['success'] != true) {
return $this->err(__('dujiaoka.prompt.abnormal_payment_channel') . $body['message']);
}
return redirect()->away($body['data']);
} catch (RuleValidationException $exception) {
} catch (GuzzleException $exception) {
return $this->err($exception->getMessage());
}
}

private function VerifySign(array $parameter, string $signKey)
{
ksort($parameter);
reset($parameter); //内部指针指向数组中的第一个元素
$sign = '';
$urls = '';
foreach ($parameter as $key => $val) {
if ($key != 'Signature') {
if ($sign != '') {
$sign .= "&";
$urls .= "&";
}
$sign .= "$key=$val"; //拼接为url参数形式
$urls .= "$key=" . urlencode($val); //拼接为url参数形式
}
}
$sign = md5($sign . $signKey);//密码追加进入开始MD5签名
return $sign;
}

public function notifyUrl(Request $request)
{
$data = $request->all();
$order = $this->orderService->detailOrderSN($data['OutOrderId']);
if (!$order) {
return 'fail';
}
$payGateway = $this->payService->detail($order->pay_id);
if (!$payGateway) {
return 'fail';
}
//合法的数据
$signature = $this->VerifySign($data, $payGateway->merchant_key);
if ($data['Signature'] != $signature) { //不合法的数据
return 'fail'; //返回失败 继续补单
} else {
//合法的数据
//业务处理
$this->orderProcessService->completedOrder($data['OutOrderId'], $data['ActualAmount'], $data['Id']);
return 'ok';
}
}

public function returnUrl(Request $request)
{
$oid = $request->get('order_id');
// 异步通知还没到就跳转了,所以这里休眠2秒
sleep(2);
return redirect(url('detail-order-sn', ['orderSN' => $oid]));
}

}
2 changes: 2 additions & 0 deletions Plugs/dujiaoka/database/add.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
INSERT INTO `pays` (`pay_name`, `pay_check`, `pay_method`, `pay_client`, `merchant_id`, `merchant_key`, `merchant_pem`, `pay_handleroute`, `is_open`, `created_at`, `updated_at`, `deleted_at`) VALUES ('USDT-TRC20', 'tokenpay-usdt-trc', 1, 3, 'USDT_TRC20', '你的API密钥', 'https://token-pay.xxx.com/CreateOrder', 'pay/tokenpay', 1, now(), now(), NULL);
INSERT INTO `pays` (`pay_name`, `pay_check`, `pay_method`, `pay_client`, `merchant_id`, `merchant_key`, `merchant_pem`, `pay_handleroute`, `is_open`, `created_at`, `updated_at`, `deleted_at`) VALUES ('TRX', 'tokenpay-trx', 1, 3, 'TRX', '你的API密钥', 'https://token-pay.xxx.com/CreateOrder', 'pay/tokenpay', 1, now(), now(), NULL);
60 changes: 60 additions & 0 deletions Plugs/dujiaoka/routes/common/pay.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php
/**
* The file was created by Assimon.
*
* @author assimon<[email protected]>
* @copyright assimon<[email protected]>
* @link http://utf8.hk/
*/
use Illuminate\Support\Facades\Route;

Route::get('pay-gateway/{handle}/{payway}/{orderSN}', 'PayController@redirectGateway');

// 支付相关
Route::group(['prefix' => 'pay', 'namespace' => 'Pay', 'middleware' => ['dujiaoka.pay_gate_way']], function () {
// 支付宝
Route::get('alipay/{payway}/{orderSN}', 'AlipayController@gateway');
Route::post('alipay/notify_url', 'AlipayController@notifyUrl');
// 微信
Route::get('wepay/{payway}/{orderSN}', 'WepayController@gateway');
Route::post('wepay/notify_url', 'WepayController@notifyUrl');
// 码支付
Route::get('mapay/{payway}/{orderSN}', 'MapayController@gateway');
Route::post('mapay/notify_url', 'MapayController@notifyUrl');
// Paysapi
Route::get('paysapi/{payway}/{orderSN}', 'PaysapiController@gateway');
Route::post('paysapi/notify_url', 'PaysapiController@notifyUrl');
Route::get('paysapi/return_url', 'PaysapiController@returnUrl')->name('paysapi-return');
// payjs
Route::get('payjs/{payway}/{orderSN}', 'PayjsController@gateway');
Route::post('payjs/notify_url', 'PayjsController@notifyUrl');
// 易支付
Route::get('yipay/{payway}/{orderSN}', 'YipayController@gateway');
Route::get('yipay/notify_url', 'YipayController@notifyUrl');
Route::get('yipay/return_url', 'YipayController@returnUrl')->name('yipay-return');
// paypal
Route::get('paypal/{payway}/{orderSN}', 'PaypalPayController@gateway');
Route::get('paypal/return_url', 'PaypalPayController@returnUrl')->name('paypal-return');
Route::any('paypal/notify_url', 'PaypalPayController@notifyUrl');
// V免签
Route::get('vpay/{payway}/{orderSN}', 'VpayController@gateway');
Route::get('vpay/notify_url', 'VpayController@notifyUrl');
Route::get('vpay/return_url', 'VpayController@returnUrl')->name('vpay-return');
// stripe
Route::get('stripe/{payway}/{oid}','StripeController@gateway');
Route::get('stripe/return_url','StripeController@returnUrl');
Route::get('stripe/check','StripeController@check');
Route::get('stripe/charge','StripeController@charge');
// Coinbase
Route::get('coinbase/{payway}/{orderSN}', 'CoinbaseController@gateway');
Route::post('coinbase/notify_url', 'CoinbaseController@notifyUrl');
// epusdt
Route::get('epusdt/{payway}/{orderSN}', 'EpusdtController@gateway');
Route::post('epusdt/notify_url', 'EpusdtController@notifyUrl');
Route::get('epusdt/return_url', 'EpusdtController@returnUrl')->name('epusdt-return');
// tokenpay
Route::get('tokenpay/{payway}/{orderSN}', 'TokenPayController@gateway');
Route::post('tokenpay/notify_url', 'TokenPayController@notifyUrl');
Route::get('tokenpay/return_url', 'TokenPayController@returnUrl')->name('tokenpay-return');

});
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
<a href="https://github.com/assimon/dujiaoka/releases/tag/1.0.0"><img src="https://img.shields.io/badge/version-1.0.0-red" alt="version 1.0.0"></a>
</p>

## TokenPay - USDT-TRC20 动态收款地址解决方案
## TokenPay - `USDT-TRC20``TRX` 动态收款地址解决方案

>一款开源USDT-TRC20动态收款地址的解决方案
>一款开源`USDT-TRC20``TRX`动态收款地址的解决方案
## 项目简介
- `TokenPay`是一个由`C#语言`编写的私有化部署`USDT-TRC20`收款解决方案。
- `TokenPay`是一个由`C#语言`编写的私有化部署`USDT-TRC20``TRX`收款解决方案。
- 本项目不依赖任何外部资源,无需另外部署`数据库`,采用轻量化的`sqlite`,也无需`redis`
- 任意项目都可以对接,轻松实现`USDT-TRC20`收款!😊 😊 😊
- 任意项目都可以对接,轻松实现`USDT-TRC20``TRX`收款!😊 😊 😊
- `TokenPay` 遵守 [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) 开源协议!

## 项目特点
Expand All @@ -39,7 +39,7 @@ TokenPay

## 设计实现
`TokenPay`的实现方式与其他项目原理类似,都是通过`TronGrid`提供的api节点,
轮询有订单的钱包地址的`USDT`代币入账事件,将入账金额,与数据库的订单金额进行对比,若一致,则视为订单完成
轮询有订单的钱包地址的`USDT`代币、`TRX`入账事件,将入账金额,与数据库的订单金额进行对比,若一致,则视为订单完成
```
简单的原理:
0.服务器定时同步交易所最新汇率
Expand Down
8 changes: 5 additions & 3 deletions Wiki/appsettings.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@
"DB": "Data Source=|DataDirectory|TokenPay.db; Pooling=true;Min Pool Size=1"
},
"TRON-PRO-API-KEY": "xxxxxx-xxxx-xxxx-xxxxxxxxxxxx", // 此处申请 https://www.trongrid.io/dashboard/keys
"Rate": 0, //汇率 设置0将使用自动汇率
"Rate": { //汇率 设置0将使用自动汇率
"USDT": 0,
"TRX": 0
},
"ExpireTime": 3600, //单位秒
"UseDynamicAddress": true, //是否使用动态地址,设为false时,与EPUSDT表现类似
//"USDT-TRC20-Address": [ "Txxxxxx1", "Txxxxxx2" ], // UseDynamicAddress设为false时在此配置收款地址
//"TRON-Address": [ "Txxxxxx1", "Txxxxxx2" ], // UseDynamicAddress设为false时在此配置收款地址
"OnlyConfirmed": true, //默认仅查询已确认的数据,如果想要回调更快,可以设置为false
"NotifyTimeOut": 3, //异步通知超时时间
"NotifyKey": "666666" //异步通知密钥,请务必修改此密钥为随机字符串,脸滚键盘即可!!!
}

```
142 changes: 142 additions & 0 deletions src/TokenPay/BgServices/OrderCheckTRXService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
using Flurl;
using Flurl.Http;
using FreeSql;
using TokenPay.Domains;
using TokenPay.Extensions;
using TokenPay.Models.TronModel;

namespace TokenPay.BgServices
{
public class OrderCheckTRXService : BaseScheduledService
{
private readonly ILogger<OrderCheckTRXService> _logger;
private readonly IConfiguration _configuration;
private readonly IHostEnvironment env;
private readonly IServiceProvider _serviceProvider;

public OrderCheckTRXService(ILogger<OrderCheckTRXService> logger,
IConfiguration configuration,
IHostEnvironment env,
IServiceProvider serviceProvider) : base("TRX订单检测", TimeSpan.FromSeconds(3), logger)
{
_logger = logger;
this._configuration = configuration;
this.env = env;
_serviceProvider = serviceProvider;
}

protected override async Task ExecuteAsync()
{
using IServiceScope scope = _serviceProvider.CreateScope();
var _repository = scope.ServiceProvider.GetRequiredService<IBaseRepository<TokenOrders>>();

var Address = await _repository
.Where(x => x.Status == OrderStatus.Pending)
.Where(x => x.Currency == Currency.TRX)
.ToListAsync(x => x.ToAddress);
if (Address.Count > 0)
_logger.LogInformation("待检测地址数:{c}", Address.Count);
var BaseUrl = "https://api.trongrid.io";
if (!env.IsProduction())
{
BaseUrl = "https://api.shasta.trongrid.io";
}
var OnlyConfirmed = _configuration.GetValue("OnlyConfirmed", true);
var start = DateTime.Now.AddMinutes(-120);
foreach (var address in Address)
{
//查询此地址待支付订单
var orders = await _repository
.Where(x => x.Status == OrderStatus.Pending)
.Where(x => x.Currency == Currency.TRX)
.Where(x => x.ToAddress == address)
.OrderBy(x => x.CreateTime)
.ToListAsync();
if (!orders.Any())
{
continue;
}
var query = new Dictionary<string, object>();
if (OnlyConfirmed)
{
query.Add("only_confirmed", true);
}
else
{
query.Add("only_unconfirmed", true);
}
query.Add("only_to", true);
query.Add("limit", 50);
query.Add("min_timestamp", start.ToUnixTimeStamp());
var req = BaseUrl
.AppendPathSegment($"v1/accounts/{address}/transactions")
.WithTimeout(15);
if (!env.IsProduction())
req = req.WithHeader("TRON-PRO-API-KEY", _configuration.GetValue("TRON-PRO-API-KEY", ""));
var result = await req
.GetJsonAsync<BaseResponse<TrxTransaction>>();

if (result.Success && result.Data?.Count > 0)
{
foreach (var item in result.Data)
{
//没有需要匹配的订单了
if (!orders.Any())
{
break;
}
//此交易已被其他订单使用
if (await _repository.Select.AnyAsync(x => x.BlockTransactionId == item.TxID))
{
continue;
}
var raw = item.RawData.Contract.FirstOrDefault(x => x.Type == "TransferContract")?.Parameter?.Value;
if (raw == null || raw.AssetName != null)
{
continue;
}
var order = orders.Where(x => x.Amount == raw.RealAmount && x.ToAddress == raw.ToAddressBase58 && x.CreateTime < item.BlockTimestamp.ToDateTime())
.OrderByDescending(x=>x.CreateTime)//优先付最后一单
.FirstOrDefault();
if (order != null)
{
order.FromAddress = raw.OwnerAddress;
order.BlockTransactionId = item.TxID;
order.Status = OrderStatus.Paid;
order.PayTime = DateTime.Now;
await _repository.UpdateAsync(order);
orders.Remove(order);
}
}
}
}
}


private async Task<bool> Notify(TokenOrders order)
{
if (!string.IsNullOrEmpty(order.NotifyUrl))
{
try
{
var result = await order.NotifyUrl.PostJsonAsync(order);
var message = await result.GetStringAsync();
if (result.StatusCode == 200)
{
_logger.LogInformation("订单异步通知成功!\n{msg}", message);
return true;
}
else
{
_logger.LogInformation("订单异步通知失败:{msg}", message);
}
}
catch (Exception e)
{
_logger.LogInformation("订单异步通知失败:{msg}", e.Message);
}
}
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@

namespace TokenPay.BgServices
{
public class OrderCheckService : BaseScheduledService
public class OrderCheckUSDTService : BaseScheduledService
{
private readonly ILogger<OrderCheckService> _logger;
private readonly ILogger<OrderCheckUSDTService> _logger;
private readonly IConfiguration _configuration;
private readonly IHostEnvironment env;
private readonly IServiceProvider _serviceProvider;

public OrderCheckService(ILogger<OrderCheckService> logger,
public OrderCheckUSDTService(ILogger<OrderCheckUSDTService> logger,
IConfiguration configuration,
IHostEnvironment env,
IServiceProvider serviceProvider) : base("订单检测", TimeSpan.FromSeconds(3), logger)
IServiceProvider serviceProvider) : base("USDT订单检测", TimeSpan.FromSeconds(3), logger)
{
_logger = logger;
this._configuration = configuration;
Expand Down
Loading

0 comments on commit 7bf6bc3

Please sign in to comment.