2023-11-17 14:44:01 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
2025-07-26 18:49:58 +08:00
|
|
|
|
namespace Plugin\CoinPayments;
|
2024-04-10 00:51:03 +08:00
|
|
|
|
|
2025-07-26 18:49:58 +08:00
|
|
|
|
use App\Services\Plugin\AbstractPlugin;
|
2025-01-21 14:57:54 +08:00
|
|
|
|
use App\Contracts\PaymentInterface;
|
2023-12-04 20:40:49 +08:00
|
|
|
|
use App\Exceptions\ApiException;
|
2023-11-17 14:44:01 +08:00
|
|
|
|
|
2025-07-26 18:49:58 +08:00
|
|
|
|
class Plugin extends AbstractPlugin implements PaymentInterface
|
2024-04-10 00:51:03 +08:00
|
|
|
|
{
|
2025-07-26 18:49:58 +08:00
|
|
|
|
public function boot(): void
|
2024-04-10 00:51:03 +08:00
|
|
|
|
{
|
2025-07-26 18:49:58 +08:00
|
|
|
|
$this->filter('available_payment_methods', function($methods) {
|
|
|
|
|
|
if ($this->getConfig('enabled', true)) {
|
|
|
|
|
|
$methods['CoinPayments'] = [
|
|
|
|
|
|
'name' => $this->getConfig('display_name', 'CoinPayments'),
|
|
|
|
|
|
'icon' => $this->getConfig('icon', '💰'),
|
|
|
|
|
|
'plugin_code' => $this->getPluginCode(),
|
|
|
|
|
|
'type' => 'plugin'
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
return $methods;
|
|
|
|
|
|
});
|
2023-11-17 14:44:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-01-21 14:57:54 +08:00
|
|
|
|
public function form(): array
|
2023-11-17 14:44:01 +08:00
|
|
|
|
{
|
|
|
|
|
|
return [
|
|
|
|
|
|
'coinpayments_merchant_id' => [
|
|
|
|
|
|
'label' => 'Merchant ID',
|
2025-07-26 18:49:58 +08:00
|
|
|
|
'type' => 'string',
|
|
|
|
|
|
'required' => true,
|
|
|
|
|
|
'description' => '商户 ID,填写您在 Account Settings 中得到的 ID'
|
2023-11-17 14:44:01 +08:00
|
|
|
|
],
|
|
|
|
|
|
'coinpayments_ipn_secret' => [
|
|
|
|
|
|
'label' => 'IPN Secret',
|
2025-07-26 18:49:58 +08:00
|
|
|
|
'type' => 'string',
|
|
|
|
|
|
'required' => true,
|
|
|
|
|
|
'description' => '通知密钥,填写您在 Merchant Settings 中自行设置的值'
|
2023-11-17 14:44:01 +08:00
|
|
|
|
],
|
|
|
|
|
|
'coinpayments_currency' => [
|
|
|
|
|
|
'label' => '货币代码',
|
2025-07-26 18:49:58 +08:00
|
|
|
|
'type' => 'string',
|
|
|
|
|
|
'required' => true,
|
|
|
|
|
|
'description' => '填写您的货币代码(大写),建议与 Merchant Settings 中的值相同'
|
2023-11-17 14:44:01 +08:00
|
|
|
|
]
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-01-21 14:57:54 +08:00
|
|
|
|
public function pay($order): array
|
2023-11-17 14:44:01 +08:00
|
|
|
|
{
|
|
|
|
|
|
$parseUrl = parse_url($order['return_url']);
|
|
|
|
|
|
$port = isset($parseUrl['port']) ? ":{$parseUrl['port']}" : '';
|
|
|
|
|
|
$successUrl = "{$parseUrl['scheme']}://{$parseUrl['host']}{$port}";
|
|
|
|
|
|
|
|
|
|
|
|
$params = [
|
|
|
|
|
|
'cmd' => '_pay_simple',
|
|
|
|
|
|
'reset' => 1,
|
2025-07-26 18:49:58 +08:00
|
|
|
|
'merchant' => $this->getConfig('coinpayments_merchant_id'),
|
2023-11-17 14:44:01 +08:00
|
|
|
|
'item_name' => $order['trade_no'],
|
|
|
|
|
|
'item_number' => $order['trade_no'],
|
|
|
|
|
|
'want_shipping' => 0,
|
2025-07-26 18:49:58 +08:00
|
|
|
|
'currency' => $this->getConfig('coinpayments_currency'),
|
2023-11-17 14:44:01 +08:00
|
|
|
|
'amountf' => sprintf('%.2f', $order['total_amount'] / 100),
|
|
|
|
|
|
'success_url' => $successUrl,
|
|
|
|
|
|
'cancel_url' => $order['return_url'],
|
|
|
|
|
|
'ipn_url' => $order['notify_url']
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
$params_string = http_build_query($params);
|
|
|
|
|
|
|
|
|
|
|
|
return [
|
2025-07-26 18:49:58 +08:00
|
|
|
|
'type' => 1,
|
2025-01-21 14:57:54 +08:00
|
|
|
|
'data' => 'https://www.coinpayments.net/index.php?' . $params_string
|
2023-11-17 14:44:01 +08:00
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-26 18:49:58 +08:00
|
|
|
|
public function notify($params): array|string
|
2023-11-17 14:44:01 +08:00
|
|
|
|
{
|
2025-07-26 18:49:58 +08:00
|
|
|
|
if (!isset($params['merchant']) || $params['merchant'] != trim($this->getConfig('coinpayments_merchant_id'))) {
|
2023-12-07 04:01:32 +08:00
|
|
|
|
throw new ApiException('No or incorrect Merchant ID passed');
|
2023-11-17 14:44:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$headers = getallheaders();
|
|
|
|
|
|
|
|
|
|
|
|
ksort($params);
|
|
|
|
|
|
reset($params);
|
|
|
|
|
|
$request = stripslashes(http_build_query($params));
|
|
|
|
|
|
|
|
|
|
|
|
$headerName = 'Hmac';
|
|
|
|
|
|
$signHeader = isset($headers[$headerName]) ? $headers[$headerName] : '';
|
|
|
|
|
|
|
2025-07-26 18:49:58 +08:00
|
|
|
|
$hmac = hash_hmac("sha512", $request, trim($this->getConfig('coinpayments_ipn_secret')));
|
2023-11-17 14:44:01 +08:00
|
|
|
|
|
|
|
|
|
|
if (!hash_equals($hmac, $signHeader)) {
|
2023-12-07 04:01:32 +08:00
|
|
|
|
throw new ApiException('HMAC signature does not match', 400);
|
2023-11-17 14:44:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$status = $params['status'];
|
|
|
|
|
|
if ($status >= 100 || $status == 2) {
|
|
|
|
|
|
return [
|
|
|
|
|
|
'trade_no' => $params['item_number'],
|
|
|
|
|
|
'callback_no' => $params['txn_id'],
|
|
|
|
|
|
'custom_result' => 'IPN OK'
|
|
|
|
|
|
];
|
|
|
|
|
|
} else if ($status < 0) {
|
2023-12-07 04:01:32 +08:00
|
|
|
|
throw new ApiException('Payment Timed Out or Error');
|
2023-11-17 14:44:01 +08:00
|
|
|
|
} else {
|
2025-07-26 18:49:58 +08:00
|
|
|
|
return 'IPN OK: pending';
|
2023-11-17 14:44:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-26 18:49:58 +08:00
|
|
|
|
}
|