<?php
declare(strict_types=1);
namespace BTCPayServer\WC\Helper;
use BTCPayServer\Client\ApiKey;
use BTCPayServer\Client\Invoice;
use BTCPayServer\Client\Server;
use BTCPayServer\Client\Store;
use BTCPayServer\Client\StorePaymentMethod;
use BTCPayServer\Client\Webhook;
use BTCPayServer\Result\AbstractStorePaymentMethodResult;
use BTCPayServer\Result\ServerInfo;
use BTCPayServer\WC\Admin\Notice;
class GreenfieldApiHelper {
const PM_CACHE_KEY = 'btcpay_payment_methods';
const PM_CLASS_NAME_PREFIX = 'BTCPay_GF_';
public $configured = false;
public $url;
public $apiKey;
public $storeId;
public $webhook;
// todo: need to refactor as it loads cached options if form submitted by ajax
public function __construct() {
if ($config = self::getConfig()) {
$this->url = $config['url'];
$this->apiKey = $config['api_key'];
$this->storeId = $config['store_id'];
$this->webhook = $config['webhook'];
$this->configured = true;
}
}
// todo: maybe remove static class and make GFConfig object or similar
public static function getConfig(): array {
// todo: perf: maybe add caching
$url = get_option('btcpay_gf_url');
$key = get_option('btcpay_gf_api_key');
if ($url && $key) {
return [
'url' => rtrim($url, '/'),
'api_key' => $key,
'store_id' => get_option('btcpay_gf_store_id', null),
'webhook' => get_option('btcpay_gf_webhook', null)
];
}
else {
return [];
}
}
public static function checkApiKeyWorks(?string $url = null, ?string $apiKey = null): bool {
$config = [];
if ($url && $apiKey) {
$config['url'] = $url;
$config['api_key'] = $apiKey;
} else {
$config = self::getConfig();
}
if ($config) {
$client = new Store($config['url'], $config['api_key']);
if (!empty($client->getStores())) {
return true;
} else {
Logger::debug('Could not fetch stores from BTCPay Server with the given API key.');
return false;
}
}
return false;
}
public static function getServerInfo(): ?ServerInfo {
if ($config = self::getConfig()) {
try {
$client = new Server( $config['url'], $config['api_key'] );
return $client->getInfo();
} catch (\Throwable $e) {
Logger::debug('Error fetching server info: ' . $e->getMessage(), true);
return null;
}
}
return null;
}
/**
* List supported payment methods by BTCPay Server.
*/
public static function supportedPaymentMethods(): array {
$paymentMethods = [];
// Use transients API to cache pm for a few minutes to avoid too many requests to BTCPay Server.
if ($cachedPaymentMethods = get_transient(self::PM_CACHE_KEY)) {
return $cachedPaymentMethods;
}
if ($config = self::getConfig()) {
$client = new StorePaymentMethod($config['url'], $config['api_key']);
if ($storeId = get_option('btcpay_gf_store_id')) {
try {
$pmResult = $client->getPaymentMethods($storeId);
/** @var AbstractStorePaymentMethodResult $pm */
foreach ($pmResult as $pm) {
if ($pm->isEnabled() && $pmName = $pm->getData()['paymentMethod'] ) {
// Convert - to _ and escape value for later use in gateway class generator.
$symbol = sanitize_html_class(str_replace('-', '_', $pmName));
$paymentMethods[] = [
'symbol' => $symbol,
'className' => self::PM_CLASS_NAME_PREFIX . $symbol
];
}
}
} catch (\Throwable $e) {
$exceptionPM = 'Exception loading payment methods: ' . $e->getMessage();
Logger::debug( $exceptionPM, true);
Notice::addNotice('error', $exceptionPM);
}
}
}
// Store payment methods into cache.
set_transient( self::PM_CACHE_KEY, $paymentMethods,5 * MINUTE_IN_SECONDS );
return $paymentMethods;
}
/**
* Deletes local cache of supported payment methods.
*/
public static function clearSupportedPaymentMethodsCache(): void {
delete_transient( self::PM_CACHE_KEY );
}
/**
* Returns BTCPay Server invoice url.
*/
public function getInvoiceRedirectUrl($invoiceId): ?string {
if ($this->configured) {
return $this->url . '/i/' . urlencode($invoiceId);
}
return null;
}
/**
* Check webhook signature to be a valid request.
*/
public function validWebhookRequest(string $signature, string $requestData): bool {
if ($this->configured) {
return Webhook::isIncomingWebhookRequestValid($requestData, $signature, $this->webhook['secret']);
}
return false;
}
/**
* Checks if the provided API config already exists in options table.
*/
public static function apiCredentialsExist(string $apiUrl, string $apiKey, string $storeId): bool {
if ($config = self::getConfig()) {
if (
$config['url'] === $apiUrl &&
$config['api_key'] === $apiKey &&
$config['store_id'] === $storeId
) {
return true;
}
}
return false;
}
public static function webhookIsSetup(): bool {
if ($config = self::getConfig()) {
return !empty($config['webhook']['secret']);
}
return false;
}
public static function webhookIsSetupManual(): bool {
if ($config = self::getConfig()) {
return !empty($config['webhook']['secret']) && $config['webhook']['id'] === 'manual';
}
return false;
}
/**
* Checks if a given invoice id has status of fully paid (settled) or paid late.
*/
public static function invoiceIsFullyPaid(string $invoiceId): bool {
if ($config = self::getConfig()) {
$client = new Invoice($config['url'], $config['api_key']);
try {
$invoice = $client->getInvoice($config['store_id'], $invoiceId);
return $invoice->isSettled();
} catch (\Throwable $e) {
Logger::debug('Exception while checking if invoice settled '. $invoiceId . ': ' . $e->getMessage());
}
}
return false;
}
public function apiKeyHasRefundPermission(): bool {
if ($this->configured) {
$client = new ApiKey($this->url, $this->apiKey);
try {
$apiKey = $client->getCurrent();
$apiAuth = new GreenfieldApiAuthorization( $apiKey->getData() );
return $apiAuth->hasRefundsPermission();
} catch (\Throwable $e) {
Logger::debug('Exception while checking current API key: ' . $e->getMessage());
}
}
return false;
}
public function serverSupportsRefunds(): bool {
if ($this->configured) {
$client = new Server($this->url, $this->apiKey);
try {
$serverInfo = $client->getInfo();
return !version_compare($serverInfo->getVersion(), '1.7.6', '<');
} catch (\Throwable $e) {
Logger::debug('Exception while checking current API key: ' . $e->getMessage());
}
}
return false;
}
}