Compare commits

...

11 Commits
master ... 3.x

Author SHA1 Message Date
utxo.one
81e1b17f10
Store Chain Wallet Tests + Getters (#119)
Some checks failed
Code Style / php-cs-fixer (push) Has been cancelled
Static analysis (Psalm) / Psalm (8.0) (push) Has been cancelled
* Store Chain Wallet Tests + Getters
2023-08-01 17:05:39 +02:00
utxo.one
c6876af1d0
StoreEmail Tests & Client Fixes (#117)
* StoreEmail Tests & Client Fixes

* remove phpunit tests
2023-07-04 18:30:21 +02:00
utxo.one
75451c6ba3
StoreClient Tests + Result Updates (#116)
* Store Tests
* removed unused variable
* remove commented methods
2023-06-26 22:07:49 +02:00
utxo.one
3e6ab646fa
ServerInfo Test + Result Getters (#115)
* ServerSyncList Class, PreciseNumber fix
2023-06-24 23:33:17 +02:00
utxo.one
ff667252d8
3.x Pull Payment Test Fix (#114)
* remove duplicate function, fix pull payment test
* cs fix
2023-06-24 22:39:09 +02:00
utxo.one
a3f4a60400 Dev 3.x merge new prs (#111)
* Get v3 up to date with latest PRs on v2
2023-05-07 00:21:11 +02:00
utxo.one
90c6ff52d3 Pull Payments Test (#92) 2023-05-07 00:18:16 +02:00
utxo.one
bb8e7e8bf0 Notification Tests (#90)
* Notification Tests
2023-05-07 00:18:16 +02:00
utxo.one
5d19edcff8 Miscellaneous Client Test Suite (#88)
* Miscellaneous Client Test Suite
* InvoiceCheckoutHtml Result Class + Test
2023-05-07 00:18:08 +02:00
utxo.one
261bb459a3 LightningStore Client Test Suite (#86)
* LightningStore Client Test Suite
* Renames getInvoice to getLightningInvoice
* Adds amount, maxFeePercent and maxFeeFlat to payLightningInvoice
2023-05-07 00:16:59 +02:00
utxo.one
158ab833f4 Complete Invoice Client/Result Classes & Test Suite (#82)
* Extending Invoice Client and Result classes

* Health and Invoice Tests

* Removed isPaid

* Move requiresRefundEmail to bottom with default value
2023-05-07 00:16:59 +02:00
46 changed files with 1791 additions and 184 deletions

View File

@ -1,53 +1,52 @@
name: PHP Unit Tests
env:
BTCPAY_HOST: ${{ secrets.BTCPAY_HOST }}
BTCPAY_API_KEY: ${{ secrets.BTCPAY_API_KEY }}
BTCPAY_STORE_ID: ${{ secrets.BTCPAY_STORE_ID }}
BTCPAY_NODE_URI: ${{ secrets.BTCPAY_NODE_URI }}
on: [ push, pull_request ]
# name: PHP Unit Tests
# env:
# BTCPAY_HOST: ${{ secrets.BTCPAY_HOST }}
# BTCPAY_API_KEY: ${{ secrets.BTCPAY_API_KEY }}
# BTCPAY_STORE_ID: ${{ secrets.BTCPAY_STORE_ID }}
# BTCPAY_NODE_URI: ${{ secrets.BTCPAY_NODE_URI }}
# on: [push, pull_request]
jobs:
phpunit:
runs-on: ubuntu-latest
strategy:
matrix:
php-versions: ['8.0', '8.1']
phpunit-versions: ['latest']
# jobs:
# phpunit:
# runs-on: ubuntu-latest
# strategy:
# matrix:
# php-versions: ["8.0", "8.1"]
# phpunit-versions: ["latest"]
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: '0'
# steps:
# - name: Checkout
# uses: actions/checkout@v3
# with:
# fetch-depth: "0"
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
tools: composer:v2, phpunit:${{ matrix.phpunit-versions }}
extensions: curl, json, mbstring, bcmath
coverage: xdebug #optional
# - name: Setup PHP, with composer and extensions
# uses: shivammathur/setup-php@v2
# with:
# php-version: ${{ matrix.php-versions }}
# tools: composer:v2, phpunit:${{ matrix.phpunit-versions }}
# extensions: curl, json, mbstring, bcmath
# coverage: xdebug #optional
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
# - name: Get composer cache directory
# id: composer-cache
# run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
# - name: Cache composer dependencies
# uses: actions/cache@v2
# with:
# path: ${{ steps.composer-cache.outputs.dir }}
# key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
# restore-keys: ${{ runner.os }}-composer-
- name: Install Composer dependencies
run: composer install --no-progress --optimize-autoloader
# - name: Install Composer dependencies
# run: composer install --no-progress --optimize-autoloader
- name: Test with phpunit
run: vendor/bin/phpunit --coverage-text
# - name: Test with phpunit
# run: vendor/bin/phpunit --coverage-text
- name: Setup problem matchers for PHP
run: echo "::add-matcher::${{ runner.tool_cache }}/php.json"
# - name: Setup problem matchers for PHP
# run: echo "::add-matcher::${{ runner.tool_cache }}/php.json"
- name: Setup problem matchers for PHPUnit
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
# - name: Setup problem matchers for PHPUnit
# run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"

View File

@ -9,12 +9,12 @@ jobs:
strategy:
fail-fast: false
matrix:
php-versions: ['8.0']
php-versions: ["8.0"]
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: '0'
fetch-depth: "0"
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2

4
.gitignore vendored
View File

@ -3,4 +3,6 @@
.php-cs-fixer.cache
*.cache
composer.lock
/tests/.env
/tests/.env
/tests/.env.testnet
/tests/.env.mainnet

View File

@ -1,11 +1,14 @@
# BTCPay Server Greenfield API PHP client library
This library makes it easier to integrate BTCPay Server in your PHP application.
## Approach
This library takes an opinionated approach to Greenfield API with the aim of making your developer life as easy and convenient as possible.
For this reason, we have decided to structure arguments a bit differently, but still allow full and advanced use cases.
The general reasoning behind the arguments an API client takes are in this order:
- First the required parameters => method arguments with NULL not allowed
- Recommended parameters => method arguments with NULL as default
- Optional parameters => arguments with NULL as default
@ -14,11 +17,13 @@ The general reasoning behind the arguments an API client takes are in this order
Methods that return a Unix timestamp always end with `Timestamp` like `getReceivedTimestamp()` to avoid format and timezone confusion. These are always in seconds (not milliseconds).
## Features
- No external dependencies. You can just drop this code in your project using composer or without composer.
- Requires PHP 8.0 and up. End-of-life'd versions will not be actively supported.
- All calls needed for eCommerce are included, but there are more we still need to add.
## TODO
- convert examples to tests
- Getters and setters
- Expand beyond the eCommerce related API calls and make this library 100% complete.
@ -28,13 +33,17 @@ Methods that return a Unix timestamp always end with `Timestamp` like `getReceiv
```
composer require btcpayserver/btcpayserver-greenfield-php
```
If you use some framework or other project you likely are ready to go. If you start from scratch make sure to include Composer autoloader.
```
require __DIR__ . '/../vendor/autoload.php';
```
## How to use without composer (not recommended)
In the `src` directory we have a custom `autoload.php` which you can require and avoid using composer if needed.
```
// Require the autoload file.
require __DIR__ . '/../src/autoload.php';
@ -52,19 +61,25 @@ try {
```
## Best practices
- Always use an API key with as little permissions as possible.
- If you only interact with specific stores, use an API key that is limited to that store or those stores only.
- When processing an incoming webhook, always load the data fresh using the API as the data may be stale or changed in the meantime. Webhook payloads can be resent on error, so you could be seeing outdated information. By loading the data fresh, you are also protecting yourself from possibly spoofed (fake) requests.
- When processing an incoming webhook, always load the data fresh using the API as the data may be stale or changed in the meantime. Webhook payloads can be resent on error, so you could be seeing outdated information. By loading the data fresh, you are also protecting yourself from possibly spoofed (fake) requests.
## FAQ
### Where to get the API key from?
The API keys for Greenfield API are *not* on the store level "Access Tokens" anymore. You need to go to your account profile: "My Settings" (user profile icon) -> "API Keys" instead. You can even redirect the users to generate the API keys there.
The API keys for Greenfield API are _not_ on the store level "Access Tokens" anymore. You need to go to your account profile: "My Settings" (user profile icon) -> "API Keys" instead. You can even redirect the users to generate the API keys there.
## Contribute
We run static analyzer [Psalm](https://psalm.dev/) and [PHP-CS-fixer](https://github.com/FriendsOfPhp/PHP-CS-Fixer) for codestyle when you open a pull-request. Please check if there are any errors and fix them accordingly.
### Codestyle
We use PSR-12 code style to ensure proper formatting and spacing. You can test and format your code using composer commands. Before doing a PR you can run `composer cs-check` and `composer cs-fix` which will run php-cs-fixer.
### Greenfield API coverage
Currently implemented functionality is tracked in [this sheet](https://docs.google.com/spreadsheets/d/1A1tMWYHGVkFWRgqfkW9GSGBRjzKZzsu5XMIW1NLs-xg/edit#gid=0) and will be updated sporadically. Check to see which areas still need work in case you want to contribute.

View File

@ -28,11 +28,17 @@
"friendsofphp/php-cs-fixer": "^3.0",
"vimeo/psalm": "^4.8",
"phpunit/phpunit": "^9.5",
"vlucas/phpdotenv": "^5.5"
"vlucas/phpdotenv": "^5.5",
"pestphp/pest": "^1.22"
},
"scripts": {
"cs-check": [ "vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php --allow-risky=yes --using-cache=no --verbose --dry-run" ],
"cs-fix": [ "vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php --allow-risky=yes --using-cache=no" ],
"psalm": [ "vendor/bin/psalm" ]
},
"config": {
"allow-plugins": {
"pestphp/pest-plugin": true
}
}
}

View File

@ -49,7 +49,7 @@ class Users
}
}
public function deleteUser($userId)
public function deleteUser(string $userId)
{
try {
$client = new User($this->host, $this->apiKey);
@ -59,7 +59,7 @@ class Users
}
}
public function setUserLock($userId, $toggle)
public function setUserLock(string $userId, bool $toggle)
{
try {
$client = new User($this->host, $this->apiKey);

View File

@ -4,9 +4,11 @@ declare(strict_types=1);
namespace BTCPayServer\Client;
use BTCPayServer\Result\Health as ResultHealth;
class Health extends AbstractClient
{
public function getHealthStatus(): bool
public function getHealthStatus(): ResultHealth
{
$url = $this->getApiUrl() . 'health';
$headers = $this->getRequestHeaders();
@ -15,7 +17,9 @@ class Health extends AbstractClient
$response = $this->getHttpClient()->request($method, $url, $headers);
if ($response->getStatus() === 200) {
return true;
return new ResultHealth(
json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR)
);
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}

View File

@ -206,4 +206,83 @@ class Invoice extends AbstractClient
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
public function archiveInvoice(string $storeId, string $invoiceId): bool
{
$url = $this->getApiUrl() . 'stores/' . urlencode(
$storeId
) . '/invoices/' . urlencode($invoiceId);
$headers = $this->getRequestHeaders();
$method = 'DELETE';
$body = json_encode(
[
'storeId' => $storeId,
'invoiceId' => $invoiceId
],
JSON_THROW_ON_ERROR
);
$response = $this->getHttpClient()->request($method, $url, $headers, $body);
if ($response->getStatus() === 200) {
return true;
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
public function unarchiveInvoice(string $storeId, string $invoiceId): bool
{
$url = $this->getApiUrl() . 'stores/' . urlencode(
$storeId
) . '/invoices/' . urlencode($invoiceId) . '/unarchive';
$headers = $this->getRequestHeaders();
$method = 'POST';
$body = json_encode(
[
'storeId' => $storeId,
'invoiceId' => $invoiceId
],
JSON_THROW_ON_ERROR
);
$response = $this->getHttpClient()->request($method, $url, $headers, $body);
if ($response->getStatus() === 200) {
return true;
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
public function activatePaymentMethod(
string $storeId,
string $invoiceId,
string $paymentMethod,
): bool {
$url = $this->getApiUrl() . 'stores/' . urlencode(
$storeId
) . '/invoices/' . urlencode($invoiceId) . '/payment-methods/' . urlencode($paymentMethod) . '/activate';
$headers = $this->getRequestHeaders();
$method = 'POST';
$body = json_encode(
[
'storeId' => $storeId,
'invoiceId' => $invoiceId,
'paymentMethod' => $paymentMethod
],
JSON_THROW_ON_ERROR
);
$response = $this->getHttpClient()->request($method, $url, $headers, $body);
if ($response->getStatus() === 200) {
return true;
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
}

View File

@ -26,7 +26,7 @@ class InvoiceCheckoutOptions
/** @var int */
protected $monitoringMinutes;
/** @var int */
/** @var float */
protected $paymentTolerance;
/** @var string */
@ -38,15 +38,19 @@ class InvoiceCheckoutOptions
/** @var string */
protected $defaultLanguage;
/** @var bool */
protected $requiresRefundEmail;
public static function create(
?string $speedPolicy,
?array $paymentMethods,
?int $expirationMinutes,
?int $monitoringMinutes,
?int $paymentTolerance,
?float $paymentTolerance,
?string $redirectURL,
?bool $redirectAutomatically,
?string $defaultLanguage
?string $defaultLanguage,
?bool $requiresRefundEmail = false,
) {
$options = new InvoiceCheckoutOptions();
$options->setSpeedPolicy($speedPolicy);
@ -56,7 +60,9 @@ class InvoiceCheckoutOptions
$options->paymentTolerance = $paymentTolerance;
$options->redirectURL = $redirectURL;
$options->redirectAutomatically = $redirectAutomatically;
$options->requiresRefundEmail = $requiresRefundEmail;
$options->defaultLanguage = $defaultLanguage;
$options->requiresRefundEmail = $requiresRefundEmail;
return $options;
}
@ -113,12 +119,12 @@ class InvoiceCheckoutOptions
return $this;
}
public function getPaymentTolerance(): ?int
public function getPaymentTolerance(): ?float
{
return $this->paymentTolerance;
}
public function setPaymentTolerance(?int $paymentTolerance): self
public function setPaymentTolerance(?float $paymentTolerance): self
{
$this->paymentTolerance = $paymentTolerance;
return $this;
@ -146,6 +152,17 @@ class InvoiceCheckoutOptions
return $this;
}
public function isRequiresRefundEmail(): ?bool
{
return $this->requiresRefundEmail;
}
public function setRequiresRefundEmail(?bool $requiresRefundEmail): self
{
$this->requiresRefundEmail = $requiresRefundEmail;
return $this;
}
public function getDefaultLanguage(): ?string
{
return $this->defaultLanguage;

View File

@ -138,9 +138,16 @@ class LightningInternalNode extends AbstractClient
}
}
/**
* Amount wrapped in a string, represented in a millistatoshi string.
* (1000 millisatoshi = 1 satoshi.
*
* @param string $amount
*/
public function payLightningInvoice(
string $cryptoCode,
string $BOLT11,
?string $amount,
?string $maxFeePercent,
?string $maxFeeFlat
): LightningPayment {
@ -153,6 +160,7 @@ class LightningInternalNode extends AbstractClient
$body = json_encode(
[
'BOLT11' => $BOLT11,
'amount' => $amount,
'maxFeePercent' => $maxFeePercent,
'maxFeeFlat' => $maxFeeFlat,
],

View File

@ -129,7 +129,7 @@ class LightningStore extends AbstractClient
}
}
public function getInvoice(
public function getLightningInvoice(
string $cryptoCode,
string $storeId,
string $id
@ -152,21 +152,33 @@ class LightningStore extends AbstractClient
}
}
/**
* Amount wrapped in a string, represented in a millistatoshi string.
* (1000 millisatoshi = 1 satoshi.
*
* @param string $amount
*/
public function payLightningInvoice(
string $cryptoCode,
string $storeId,
string $BOLT11
string $BOLT11,
?string $amount = null,
?string $maxFeePercent = null,
?string $maxFeeFlat = null,
): LightningPayment {
$url = $this->getApiUrl() . 'stores/' .
urlencode($storeId) . '/lightning/' .
urlencode($cryptoCode) . '/info';
urlencode($cryptoCode) . '/invoices/pay';
$headers = $this->getRequestHeaders();
$method = 'POST';
$body = json_encode(
[
'BOLT11' => $BOLT11
'BOLT11' => $BOLT11,
'amount' => $amount,
'maxFeePercent' => $maxFeePercent,
'maxFeeFlat' => $maxFeeFlat,
],
JSON_THROW_ON_ERROR
);

View File

@ -4,14 +4,14 @@ declare(strict_types=1);
namespace BTCPayServer\Client;
use BTCPayServer\Result\InvoiceCheckoutHTML;
use BTCPayServer\Result\InvoiceCheckoutHtml;
use BTCPayServer\Result\LanguageCodeList;
use BTCPayServer\Result\PermissionMetadata;
use BTCPayServer\Result\PermissionMetadataList;
use BTCPayServer\Result\RateSourceList;
class Miscellaneous extends AbstractClient
{
public function getPermissionMetadata(): PermissionMetadata
public function getPermissionMetadata(): PermissionMetadataList
{
$url = $this->getBaseUrl() . '/misc/permissions';
$headers = $this->getRequestHeaders();
@ -20,7 +20,7 @@ class Miscellaneous extends AbstractClient
$response = $this->getHttpClient()->request($method, $url, $headers);
if ($response->getStatus() === 200) {
return new PermissionMetadata(
return new PermissionMetadataList(
json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR)
);
} else {
@ -48,7 +48,7 @@ class Miscellaneous extends AbstractClient
public function getInvoiceCheckout(
string $invoiceId,
?string $lang
): InvoiceCheckoutHTML {
): InvoiceCheckoutHtml {
$url = $this->getBaseUrl() . '/i/' . urlencode($invoiceId);
//set language query parameter if passed
@ -62,9 +62,7 @@ class Miscellaneous extends AbstractClient
$response = $this->getHttpClient()->request($method, $url, $headers);
if ($response->getStatus() === 200) {
return new InvoiceCheckoutHTML(
json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR)
);
return new InvoiceCheckoutHtml($response->getBody());
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}

View File

@ -17,19 +17,13 @@ class PullPayment extends AbstractClient
bool $includeArchived
): PullPaymentList {
$url = $this->getApiUrl() . 'stores/' .
urlencode($storeId) . '/pull-payments';
urlencode($storeId) . '/pull-payments?includeArchived=' .
($includeArchived ? 'true' : 'false');
$headers = $this->getRequestHeaders();
$method = 'GET';
$body = json_encode(
[
'includeArchived' => $includeArchived,
],
JSON_THROW_ON_ERROR
);
$response = $this->getHttpClient()->request($method, $url, $headers, $body);
$response = $this->getHttpClient()->request($method, $url, $headers);
if ($response->getStatus() === 200) {
return new PullPaymentList(

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace BTCPayServer\Client;
use BTCPayServer\Result\Store as ResultStore;
use BTCPayServer\Result\StoreList;
class Store extends AbstractClient
{
@ -98,9 +99,9 @@ class Store extends AbstractClient
}
/**
* @return \BTCPayServer\Result\Store[]
* @return \BTCPayServer\Result\StoreList
*/
public function getStores(): array
public function getStores(): StoreList
{
$url = $this->getApiUrl() . 'stores';
$headers = $this->getRequestHeaders();
@ -108,13 +109,8 @@ class Store extends AbstractClient
$response = $this->getHttpClient()->request($method, $url, $headers);
if ($response->getStatus() === 200) {
$r = [];
$data = json_decode($response->getBody(), true);
foreach ($data as $item) {
$item = new ResultStore($item);
$r[] = $item;
}
return $r;
return new StoreList($data);
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}

View File

@ -9,7 +9,7 @@ use BTCPayServer\Result\StoreOnChainWalletAddress;
use BTCPayServer\Result\StoreOnChainWalletFeeRate;
use BTCPayServer\Result\StoreOnChainWalletTransaction;
use BTCPayServer\Result\StoreOnChainWalletTransactionList;
use BTCPayServer\Result\StoreOnChainWalletUTXOList;
use BTCPayServer\Result\StoreOnChainWalletUtxoList;
class StoreOnChainWallet extends AbstractClient
{
@ -241,10 +241,10 @@ class StoreOnChainWallet extends AbstractClient
}
}
public function getStoreOnChainWalletUTXOs(
public function getStoreOnChainWalletUtxos(
string $storeId,
string $cryptoCode
): StoreOnChainWalletUTXOList {
): StoreOnChainWalletUtxoList {
$url = $this->getApiUrl() . 'stores/' .
urlencode($storeId) . '/payment-methods' . '/OnChain' . '/' .
urlencode($cryptoCode) . '/wallet' . '/utxos';
@ -255,7 +255,7 @@ class StoreOnChainWallet extends AbstractClient
$response = $this->getHttpClient()->request($method, $url, $headers);
if ($response->getStatus() === 200) {
return new StoreOnChainWalletUTXOList(
return new StoreOnChainWalletUtxoList(
json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR)
);
} else {

14
src/Result/Health.php Normal file
View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Result;
class Health extends AbstractResult
{
public function isSyncronized(): bool
{
$data = $this->getData();
return $data['synchronized'];
}
}

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace BTCPayServer\Result;
use BTCPayServer\Client\InvoiceCheckoutOptions;
use BTCPayServer\Util\PreciseNumber;
class Invoice extends AbstractResult
@ -26,6 +27,11 @@ class Invoice extends AbstractResult
public const ADDITIONAL_STATUS_PAID_LATE = 'PaidLate';
public function getMetaData(): array
{
return $this->getData()['metadata'];
}
public function getId(): string
{
return $this->getData()['id'];
@ -36,6 +42,11 @@ class Invoice extends AbstractResult
return PreciseNumber::parseString($this->getData()['amount']);
}
public function getStoreId(): string
{
return $this->getData()['storeId'];
}
public function getCurrency(): string
{
return $this->getData()['currency'];
@ -61,9 +72,9 @@ class Invoice extends AbstractResult
return $this->getData()['expirationTime'];
}
public function getMonitoringTime(): int
public function getMonitoringExpiration(): int
{
return $this->getData()['monitoringTime'];
return $this->getData()['monitoringExpiration'];
}
public function isArchived(): bool
@ -71,6 +82,28 @@ class Invoice extends AbstractResult
return $this->getData()['archived'];
}
public function getCheckoutOptions(): InvoiceCheckoutOptions
{
$options = new InvoiceCheckoutOptions();
$options->setSpeedPolicy($this->getData()['checkout']['speedPolicy']);
$options->setPaymentMethods($this->getData()['checkout']['paymentMethods']);
$options->setExpirationMinutes($this->getData()['checkout']['expirationMinutes']);
$options->setMonitoringMinutes($this->getData()['checkout']['monitoringMinutes']);
$options->setPaymentTolerance($this->getData()['checkout']['paymentTolerance']);
$options->setRedirectURL($this->getData()['checkout']['redirectURL']);
$options->setRedirectAutomatically($this->getData()['checkout']['redirectAutomatically']);
$options->setRequiresRefundEmail($this->getData()['checkout']['requiresRefundEmail']);
$options->setDefaultLanguage($this->getData()['checkout']['defaultLanguage']);
return $options;
}
public function isPaid(): bool
{
$data = $this->getData();
return $data['status'] === self::STATUS_SETTLED || $data['additionalStatus'] === self::ADDITIONAL_STATUS_PAID_PARTIAL;
}
public function isNew(): bool
{
$data = $this->getData();
@ -88,6 +121,11 @@ class Invoice extends AbstractResult
return $this->getData()['status'];
}
public function getAdditionalStatus(): string
{
return $this->getData()['additionalStatus'];
}
public function isExpired(): bool
{
$data = $this->getData();

View File

@ -1,9 +0,0 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Result;
class InvoiceCheckoutHTML extends AbstractResult
{
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Result;
class InvoiceCheckoutHtml
{
/**
* @var string
*/
private $html;
public function __construct(string $html)
{
$this->html = $html;
}
public function getHtml(): string
{
return $this->html;
}
public function __toString()
{
return $this->html;
}
}

View File

@ -7,13 +7,13 @@ namespace BTCPayServer\Result;
class LanguageCodeList extends AbstractListResult
{
/**
* @return \BTCPayServer\Result\LanguageCode[]
* @return LanguageCode[]
*/
public function all(): array
{
$languageCodes = [];
foreach ($this->getData() as $languageCode) {
$languageCodes[] = new \BTCPayServer\Result\LanguageCode($languageCode);
$languageCodes[] = new LanguageCode($languageCode);
}
return $languageCodes;
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Result;
class PermissionMetadataList extends AbstractListResult
{
/**
* @return PermissionMetadata[]
*/
public function all(): array
{
$permissionMetadataList = [];
foreach ($this->getData() as $permissionMetadata) {
$permissionMetadataList[] = new PermissionMetadata($permissionMetadata);
}
return $permissionMetadataList;
}
}

View File

@ -10,61 +10,53 @@ class PullPaymentPayout extends AbstractResult
{
public function getId(): string
{
$data = $this->getData();
return $data['id'];
return $this->getData()['id'];
}
public function getRevision(): int
{
$data = $this->getData();
return $data['revision'];
return $this->getData()['revision'];
}
public function getPullPaymentId(): string
{
$data = $this->getData();
return $data['pullPaymentId'];
return $this->getData()['pullPaymentId'];
}
public function getDate(): string
public function getDate(): int
{
$data = $this->getData();
return $data['date'];
return $this->getData()['date'];
}
public function getDestination(): string
{
$data = $this->getData();
return $data['destination'];
return $this->getData()['destination'];
}
public function getAmount(): PreciseNumber
{
$data = $this->getData();
return PreciseNumber::parseString($data['amount']);
return PreciseNumber::parseString($this->getData()['amount']);
}
public function getPaymentMethod(): string
{
$data = $this->getData();
return $data['paymentMethod'];
return $this->getData()['paymentMethod'];
}
public function getCryptoCode(): string
{
$data = $this->getData();
return $data['cryptoCode'];
return $this->getData()['cryptoCode'];
}
public function getPaymentMethodAmount(): PreciseNumber
public function getPaymentMethodAmount(): ?PreciseNumber
{
$data = $this->getData();
return PreciseNumber::parseString($data['paymentMethodAmount']);
return (isset($this->getData()['paymentMethodAmount']))
? PreciseNumber::parseString($this->getData()['paymentMethodAmount'])
: null;
}
public function getState(): string
{
$data = $this->getData();
return $data['state'];
return $this->getData()['state'];
}
}

View File

@ -32,5 +32,8 @@ class ServerInfo extends AbstractResult
return $this->getData()['supportedPaymentMethods'];
}
// TODO add "syncStatus" structure
public function getSyncStatus(): ServerSyncStatusList
{
return new ServerSyncStatusList($this->getData()['syncStatus']);
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Result;
class ServerSyncStatus extends AbstractResult
{
public function getChainHeight(): int
{
return $this->getData()['chainHeight'];
}
public function getSyncHeight(): int
{
return $this->getData()['syncHeight'];
}
public function getNodeInformation(): ServerSyncStatusNodeInformation
{
return new ServerSyncStatusNodeInformation($this->getData()['nodeInformation']);
}
public function getCryptoCode(): string
{
return $this->getData()['cryptoCode'];
}
public function isAvailable(): bool
{
return $this->getData()['available'];
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Result;
class ServerSyncStatusList extends AbstractResult
{
/**
* @return \BTCPayServer\Result\ServerSyncStatus[]
*/
public function all(): array
{
$serverSyncStatuses = [];
foreach ($this->getData() as $serverSyncStatus) {
$serverSyncStatuses[] = new \BTCPayServer\Result\ServerSyncStatus($serverSyncStatus);
}
return $serverSyncStatuses;
}
}

View File

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Result;
use BTCPayServer\Util\PreciseNumber;
class ServerSyncStatusNodeInformation extends AbstractResult
{
public function getHeaders(): int
{
return $this->getData()['headers'];
}
public function getBlocks(): int
{
return $this->getData()['blocks'];
}
public function getVerificationProgress(): PreciseNumber
{
return PreciseNumber::parseFloat($this->getData()['verificationProgress']);
}
}

View File

@ -4,6 +4,8 @@ declare(strict_types=1);
namespace BTCPayServer\Result;
use BTCPayServer\Util\PreciseNumber;
class Store extends AbstractResult
{
public function getName(): string
@ -12,13 +14,13 @@ class Store extends AbstractResult
return $data['name'];
}
public function getWebsite(): string
public function getWebsite(): ?string
{
$data = $this->getData();
return $data['website'];
}
public function getDefaultCurrency(): string
public function getDefaultCurrency(): ?string
{
$data = $this->getData();
return $data['defaultCurrency'];
@ -48,10 +50,10 @@ class Store extends AbstractResult
return $data['lightningDescriptionTemplate'];
}
public function getPaymentTolerance(): int
public function getPaymentTolerance(): PreciseNumber
{
$data = $this->getData();
return $data['paymentTolerance'];
return PreciseNumber::parseFloat($data['paymentTolerance']);
}
public function anyoneCanCreateInvoice(): bool
@ -102,25 +104,25 @@ class Store extends AbstractResult
return $data['recommendedFeeBlockTarget'];
}
public function getDefaultLang(): string
public function getDefaultLang(): ?string
{
$data = $this->getData();
return $data['defaultLang'];
}
public function getCustomLogo(): string
public function getCustomLogo(): ?string
{
$data = $this->getData();
return $data['customLogo'];
}
public function getCustomCSS(): string
public function getCustomCSS(): ?string
{
$data = $this->getData();
return $data['customCSS'];
}
public function getHtmlTitle(): string
public function getHtmlTitle(): ?string
{
$data = $this->getData();
return $data['htmlTitle'];
@ -144,7 +146,7 @@ class Store extends AbstractResult
return $data['lazyPaymentMethods'];
}
public function getDefaultPaymentMethod(): string
public function getDefaultPaymentMethod(): ?string
{
$data = $this->getData();
return $data['defaultPaymentMethod'];

View File

@ -6,39 +6,45 @@ namespace BTCPayServer\Result;
class StoreEmailSettings extends AbstractResult
{
public function getServer(): string
public function getServer(): ?string
{
$data = $this->getData();
return $data['server'];
}
public function getPort(): string
public function getPort(): ?int
{
$data = $this->getData();
return $data['port'];
}
public function getUsername(): string
public function getUsername(): ?string
{
$data = $this->getData();
return $data['login'];
}
public function getPassword(): string
public function getPassword(): ?string
{
$data = $this->getData();
return $data['password'];
}
public function getFromEmail(): string
public function getFromEmail(): ?string
{
$data = $this->getData();
return $data['from'];
}
public function getFromName(): string
public function getFromName(): ?string
{
$data = $this->getData();
return $data['fromDisplay'];
}
public function getDisableCertificateCheck(): ?bool
{
$data = $this->getData();
return $data['disableCertificateCheck'];
}
}

20
src/Result/StoreList.php Normal file
View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Result;
class StoreList extends AbstractListResult
{
/**
* @return Store[]
*/
public function all(): array
{
$stores = [];
foreach ($this->getData() as $store) {
$stores[] = new Store($store);
}
return $stores;
}
}

View File

@ -25,4 +25,10 @@ class StoreOnChainWallet extends AbstractResult
$data = $this->getData();
return PreciseNumber::parseString($data['confirmedBalance']);
}
public function getLabel(): string
{
$data = $this->getData();
return $data['label'];
}
}

View File

@ -4,11 +4,13 @@ declare(strict_types=1);
namespace BTCPayServer\Result;
use BTCPayServer\Util\PreciseNumber;
class StoreOnChainWalletFeeRate extends AbstractResult
{
public function getFeeRate(): float
public function getFeeRate(): PreciseNumber
{
$data = $this->getData();
return $data['feeRate'];
return PreciseNumber::parseFloat($data['feeRate']);
}
}

View File

@ -4,50 +4,61 @@ declare(strict_types=1);
namespace BTCPayServer\Result;
use BTCPayServer\Util\PreciseNumber;
class StoreOnChainWalletTransaction extends AbstractResult
{
public function getDestinations(): StoreOnChainWalletTransactionDestinationList
public function getTransactionHash(): string
{
$data = $this->getData();
return new StoreOnChainWalletTransactionDestinationList($data['destinations']);
return $data['transactionHash'];
}
public function getFeeRate(): StoreOnChainWalletFeeRate
public function getComment(): string
{
$data = $this->getData();
return new StoreOnChainWalletFeeRate($data['feeRate']);
return $data['comment'];
}
public function proceedWithPayjoin(): bool
public function getAmount(): PreciseNumber
{
$data = $this->getData();
return $data['proceedWithPayjoin'];
return new PreciseNumber($data['amount']);
}
public function proceedWithBroadcast(): bool
public function getLabels(): array
{
$data = $this->getData();
return $data['proceedWithBroadcast'];
return $data['labels'];
}
public function noChange(): bool
public function getBlockHash(): ?string
{
$data = $this->getData();
return $data['noChange'];
return $data['blockHash'];
}
public function rbf(): bool
public function getBlockHeight(): ?int
{
$data = $this->getData();
return $data['rbf'];
return $data['blockHeight'];
}
/**
* @return array strings
*/
public function selectedInputs(): array
public function getConfirmations(): int
{
$data = $this->getData();
return $data['selectedInputs'];
return $data['confirmations'];
}
public function getTimestamp(): int
{
$data = $this->getData();
return $data['timestamp'];
}
public function getStatus(): string
{
$data = $this->getData();
return $data['status'];
}
}

View File

@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Result;
class StoreOnChainWalletUTXOList extends AbstractListResult
{
/**
* @return \BTCPayServer\Result\StoreOnChainWalletUTXO[]
*/
public function all(): array
{
$storeWalletUTXOs = [];
foreach ($this->getData() as $storeWalletUTXO) {
$storeWalletUTXOs[] = new \BTCPayServer\Result\StoreOnChainWalletUTXO($storeWalletUTXO);
}
return $storeWalletUTXOs;
}
}

View File

@ -6,7 +6,7 @@ namespace BTCPayServer\Result;
use BTCPayServer\Util\PreciseNumber;
class StoreOnChainWalletUTXO extends AbstractResult
class StoreOnChainWalletUtxo extends AbstractResult
{
public function getComment(): string
{

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Result;
class StoreOnChainWalletUtxoList extends AbstractListResult
{
/**
* @return \BTCPayServer\Result\StoreOnChainWalletUtxo[]
*/
public function all(): array
{
$storeWalletUtxos = [];
foreach ($this->getData() as $storeWalletUtxo) {
$storeWalletUtxos[] = new StoreOnChainWalletUtxo($storeWalletUtxo);
}
return $storeWalletUtxos;
}
}

View File

@ -32,7 +32,7 @@ class BaseTest extends TestCase
$this->storeId = $_ENV['BTCPAY_STORE_ID'];
}
public function testThatAllTheVariablesAreSet(): void
public function testItSetsAllTheEnvironmentVariables(): void
{
$this->assertIsString($this->apiKey);
$this->assertIsString($this->host);
@ -44,4 +44,10 @@ class BaseTest extends TestCase
$this->assertNotEmpty($this->storeId);
$this->assertNotEmpty($this->nodeUri);
}
public function dd($var): void
{
var_dump($var);
die();
}
}

27
tests/HealthTest.php Normal file
View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Tests;
use BTCPayServer\Client\Health;
use BTCPayServer\Result\Health as ResultHealth;
final class HealthTest extends BaseTest
{
public function setUp(): void
{
parent::setUp();
}
/** @group getHealthStatus */
public function testItCanGetHealthStatus(): void
{
$healthClient = new Health($this->host, $this->apiKey);
$health = $healthClient->getHealthStatus();
$this->assertInstanceOf(ResultHealth::class, $health);
$this->assertIsBool($health->isSyncronized());
}
}

215
tests/InvoiceTest.php Normal file
View File

@ -0,0 +1,215 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Tests;
use BTCPayServer\Client\Invoice;
use BTCPayServer\Client\InvoiceCheckoutOptions;
use BTCPayServer\Result\Invoice as ResultInvoice;
use BTCPayServer\Result\InvoicePaymentMethod;
use BTCPayServer\Util\PreciseNumber;
final class InvoiceTest extends BaseTest
{
private ResultInvoice $resultInvoice;
private Invoice $invoiceClient;
private InvoiceCheckoutOptions $invoiceCheckoutOptions;
public function setUp(): void
{
parent::setUp();
$this->invoiceClient = new Invoice($this->host, $this->apiKey);
$this->invoiceCheckoutOptions = new InvoiceCheckoutOptions();
$this->invoiceCheckoutOptions->setRedirectAutomatically(true);
$this->invoiceCheckoutOptions->setRedirectURL('https://example.com/redirect');
$this->invoiceCheckoutOptions->setDefaultLanguage('EN');
$this->invoiceCheckoutOptions->setPaymentMethods(['BTC-LightningNetwork']);
$this->invoiceCheckoutOptions->setRequiresRefundEmail(true);
$this->resultInvoice = $this->invoiceClient->createInvoice(
storeId: $this->storeId,
currency: 'SATS',
amount: PreciseNumber::parseString('1000'),
orderId: '1234',
buyerEmail: 'testing@btcpayserver.org',
metaData: [
'user_id' => '123456789',
],
checkoutOptions: $this->invoiceCheckoutOptions,
);
}
/** @group createInvoice */
public function testItCanCreateAnInvoiceAndSetCheckoutOptions(): void
{
$this->assertInvoiceGettersAreSet($this->resultInvoice);
}
/** @group getInvoice */
public function testItCanGetAnInvoiceWithCheckoutOptions(): void
{
$invoice = $this->invoiceClient->getInvoice(
storeId: $this->storeId,
invoiceId: $this->resultInvoice->getId()
);
$this->assertInvoiceGettersAreSet($invoice);
}
private function assertInvoiceGettersAreSet(ResultInvoice $invoice): void
{
$this->assertInstanceOf(ResultInvoice::class, $invoice);
$this->assertIsString($invoice->getId());
$this->assertIsString($invoice->getStoreId());
$this->assertIsString($invoice->getCurrency());
$this->assertIsString($invoice->getType());
$this->assertIsString($invoice->getCheckoutLink());
$this->assertIsInt($invoice->getCreatedTime());
$this->assertIsInt($invoice->getExpirationTime());
$this->assertIsInt($invoice->getMonitoringExpiration());
$this->assertisString($invoice->getAdditionalStatus());
$this->assertIsString($invoice->getStatus());
$this->assertIsArray($invoice->getMetaData());
// Checkout Options Assertions
$this->assertInstanceOf(InvoiceCheckoutOptions::class, $invoice->getCheckoutOptions());
$this->assertIsArray($invoice->getCheckoutOptions()->getPaymentMethods());
// Assert that RedirectURL is either null or string
if ($invoice->getCheckoutOptions()->getRedirectURL() !== null) {
$this->assertIsString($invoice->getCheckoutOptions()->getRedirectURL());
} else {
$this->assertNull($invoice->getCheckoutOptions()->getRedirectURL());
}
$this->assertIsInt($invoice->getCheckoutOptions()->getExpirationMinutes());
$this->assertIsInt($invoice->getCheckoutOptions()->getMonitoringMinutes());
$this->assertIsFloat($invoice->getCheckoutOptions()->getPaymentTolerance());
$this->assertIsBool($invoice->getCheckoutOptions()->isRedirectAutomatically());
// Assert that DefaultLanguage is either null or bool
if ($invoice->getCheckoutOptions()->getDefaultLanguage() !== null) {
$this->assertIsString($invoice->getCheckoutOptions()->getDefaultLanguage());
} else {
$this->assertNull($invoice->getCheckoutOptions()->getDefaultLanguage());
}
}
public function testItCanGetInvoicePaymentMethodsAndGetters(): void
{
$paymentMethods = $this->invoiceClient->getPaymentMethods(
storeId: $this->storeId,
invoiceId: $this->resultInvoice->getId()
);
$this->assertIsArray($paymentMethods);
foreach ($paymentMethods as $paymentMethod) {
$this->assertInstanceOf(InvoicePaymentMethod::class, $paymentMethod);
$this->assertIsArray($paymentMethod->getPayments());
$this->assertIsString($paymentMethod->getPaymentMethod());
$this->assertIsString($paymentMethod->getDestination());
$this->assertIsString($paymentMethod->getRate());
$this->assertIsString($paymentMethod->getPaymentMethodPaid());
$this->assertIsString($paymentMethod->getTotalPaid());
$this->assertIsString($paymentMethod->getDue());
$this->assertIsString($paymentMethod->getAmount());
$this->assertIsString($paymentMethod->getNetworkFee());
$this->assertIsString($paymentMethod->getCryptoCode());
}
}
public function testItCanUpdateAnInvoice(): void
{
$invoice = $this->invoiceClient->updateInvoice(
storeId: $this->storeId,
invoiceId: $this->resultInvoice->getId(),
metaData: [
'user_id' => '987654321',
],
);
$this->assertIsArray($invoice->getMetaData());
$this->assertEquals('987654321', $invoice->getMetaData()['user_id']);
$this->assertInvoiceGettersAreSet($invoice);
}
public function testItCanMarkInvoiceStatus(): void
{
$invoice = $this->invoiceClient->markInvoiceStatus(
storeId: $this->storeId,
invoiceId: $this->resultInvoice->getId(),
markAs: 'Invalid'
);
$this->assertInvoiceGettersAreSet($invoice);
$this->assertEquals('Invalid', $invoice->getStatus());
$invoice = $this->invoiceClient->markInvoiceStatus(
storeId: $this->storeId,
invoiceId: $this->resultInvoice->getId(),
markAs: 'Settled'
);
$this->assertEquals('Settled', $invoice->getStatus());
}
public function testItCanArchiveAndUnarchiveAnInvoice(): void
{
$archiveInvoice = $this->invoiceClient->archiveInvoice(
storeId: $this->storeId,
invoiceId: $this->resultInvoice->getId()
);
$this->assertTrue($archiveInvoice);
// Get the invoice again as a new object with hydrated properties.
$invoice = $this->invoiceClient->getInvoice(
storeId: $this->storeId,
invoiceId: $this->resultInvoice->getId()
);
$this->assertTrue($invoice->isArchived());
$unarchiveInvoice = $this->invoiceClient->unarchiveInvoice(
storeId: $this->storeId,
invoiceId: $this->resultInvoice->getId()
);
$this->assertTrue($unarchiveInvoice);
// Get the invoice again as a new object with hydrated properties.
$invoice = $this->invoiceClient->getInvoice(
storeId: $this->storeId,
invoiceId: $this->resultInvoice->getId()
);
$this->assertFalse($invoice->isArchived());
}
public function testItCanActivatePaymentMethod(): void
{
$activatePaymentMethod = $this->invoiceClient->activatePaymentMethod(
storeId: $this->storeId,
invoiceId: $this->resultInvoice->getId(),
paymentMethod: 'BTC-LightningNetwork'
);
$this->assertTrue($activatePaymentMethod);
}
public function tearDown(): void
{
$achivedInvoice = $this->invoiceClient->archiveInvoice(
storeId: $this->storeId,
invoiceId: $this->resultInvoice->getId()
);
$this->assertIsBool($achivedInvoice);
}
}

View File

@ -0,0 +1,197 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Tests;
use BTCPayServer\Client\LightningStore;
use BTCPayServer\Result\LightningChannel;
use BTCPayServer\Result\LightningChannelList;
use BTCPayServer\Result\LightningInvoice;
use BTCPayServer\Result\LightningPayment;
use BTCPayServer\Util\PreciseNumber;
use Exception;
final class LightningStoreTest extends BaseTest
{
protected LightningStore $lightningStoreClient;
public function setUp(): void
{
parent::setUp();
$this->lightningStoreClient = new LightningStore($this->host, $this->apiKey);
}
/** @group createLightningInvoice */
public function testItCanCreateALightningInvoiceAndReturnsLightningInvoiceObject(): void
{
$lightningInvoice = $this->lightningStoreClient->createLightningInvoice(
cryptoCode: 'BTC',
storeId: $this->storeId,
amount: '100000', // milisats
expiry: 111111,
description: 'Test invoice description',
);
$this->assertInstanceOf(LightningInvoice::class, $lightningInvoice);
$this->lightningInvoice = $lightningInvoice;
$this->assertIsString($lightningInvoice->getId());
$this->assertIsString($lightningInvoice->getStatus());
$this->assertIsString($lightningInvoice->getBolt11());
// If the lightning invoice is paid, assert the paid at is an int
if ($lightningInvoice->getPaidAt()) {
$this->assertIsInt($lightningInvoice->getPaidAt());
}
$this->assertIsInt($lightningInvoice->getExpiresAt());
$this->assertInstanceOf(PreciseNumber::class, $lightningInvoice->getAmount());
// If the lightning invoice is paid, assert the amount received is a PreciseNumber
if ($lightningInvoice->getAmountReceived()) {
$this->assertInstanceOf(PreciseNumber::class, $lightningInvoice->getAmountReceived());
}
}
/** @group payLightningInvoice */
public function testItReceivesLightningPaymentObjectAfterPayingLightningInvoiceWithAllGetters(): void
{
$this->markTestSkipped('Requires a new invoice on each test run');
$bolt11 = '';
$lightningPayment = $this->lightningStoreClient->payLightningInvoice(
cryptoCode: 'BTC',
storeId: $this->storeId,
BOLT11: $bolt11,
maxFeePercent: 0.1,
maxFeeFlat: 100,
);
$this->assertInstanceOf(LightningPayment::class, $lightningPayment);
// There is a bug in Greenfield API that is returning null values on everything except total and fee amounts.
// Uncomment these lines when the bug is fixed.
// https://github.com/btcpayserver/btcpayserver/issues/4229
// $this->assertIsString($lightningPayment->getId());
// $this->assertIsString($lightningPayment->getStatus());
// $this->assertIsString($lightningPayment->getBolt11());
// $this->assertIsString($lightningPayment->getPaymentHash());
// $this->assertIsString($lightningPayment->getPreimage());
// $this->assertIsInt($lightningPayment->getCreatedAt());
// $this->assertInstanceOf(PreciseNumber::class, $lightningPayment->getTotalAmount());
// $this->assertInstanceOf(PreciseNumber::class, $lightningPayment->getFeeAmount());
}
/** @group connectToLightningNode */
public function testItCanConnectToALightningNodeAndReturnsLightningNodeConnectionObject(): void
{
$this->markTestSkipped('This test is skipped because I always get 503.');
try {
$lightningNodeConnection = $this->lightningStoreClient->connectToLightningNode(
cryptoCode: 'BTC',
storeId: $this->storeId,
nodeURI: $this->nodeUri,
);
$this->assertInstanceOf(LightningNodeConnection::class, $lightningNodeConnection);
} catch (Exception $e) {
die($e->getMessage());
}
}
/** @group getNodeInformation */
public function testItCanGetNodeInformationAndReturnsLightningNodeInformationObject(): void
{
$lightningNodeInformation = $this->lightningStoreClient->getNodeInformation(
cryptoCode: 'BTC',
storeId: $this->storeId,
);
$this->assertInstanceOf(\BTCPayServer\Result\LightningNode::class, $lightningNodeInformation);
$this->assertIsArray($lightningNodeInformation->getNodeURIs());
$this->assertIsInt($lightningNodeInformation->getBlockHeight());
$this->assertIsString($lightningNodeInformation->getAlias());
$this->assertIsString($lightningNodeInformation->getColor());
$this->assertIsString($lightningNodeInformation->getVersion());
$this->assertIsInt($lightningNodeInformation->getPeersCount());
$this->assertIsInt($lightningNodeInformation->getPendingChannelsCount());
$this->assertIsInt($lightningNodeInformation->getActiveChannelsCount());
$this->assertIsInt($lightningNodeInformation->getInactiveChannelsCount());
}
/** @group getChannels */
public function testItCanGetChannelsAndReturnsLightningChannelListObject(): void
{
$lightningChannels = $this->lightningStoreClient->getChannels(
cryptoCode: 'BTC',
storeId: $this->storeId,
);
$this->assertInstanceOf(LightningChannelList::class, $lightningChannels);
$this->assertIsArray($lightningChannels->all());
foreach ($lightningChannels->all() as $channel) {
$this->assertInstanceOf(LightningChannel::class, $channel);
$this->assertIsString($channel->getRemoteNode());
$this->assertIsString($channel->getChannelPoint());
$this->assertIsString($channel->getCapacity());
$this->assertIsString($channel->getLocalBalance());
$this->assertIsBool($channel->isActive());
$this->assertIsBool($channel->isPublic());
}
}
/** @group getDepositAddress */
public function testItCanGetANewDepositAddress(): void
{
$depositAddress = $this->lightningStoreClient->getDepositAddress(
cryptoCode: 'BTC',
storeId: $this->storeId,
);
$this->assertIsString($depositAddress);
}
/** @group getLightningInvoice */
public function testItCanGetAnInvoiceAndReturnsLightningInvoiceObject(): void
{
$getLightningInvoice = $this->lightningStoreClient->createLightningInvoice(
cryptoCode: 'BTC',
storeId: $this->storeId,
amount: '100000', // milisats
expiry: 111111,
description: 'Test invoice description',
);
$lightningInvoice = $this->lightningStoreClient->getLightningInvoice(
'BTC',
$this->storeId,
$getLightningInvoice->getId(),
);
$this->assertInstanceOf(LightningInvoice::class, $lightningInvoice);
$this->assertIsString($lightningInvoice->getId());
$this->assertIsString($lightningInvoice->getStatus());
$this->assertIsString($lightningInvoice->getBolt11());
// If the invoice get Paid at is not null, assert it's int
if ($lightningInvoice->getPaidAt() !== null) {
$this->assertIsInt($lightningInvoice->getPaidAt());
}
$this->assertIsInt($lightningInvoice->getExpiresAt());
$this->assertInstanceOf(PreciseNumber::class, $lightningInvoice->getAmount());
// If the invoice get Paid amount is not null, assert it's PreciseNumber
if ($lightningInvoice->getAmountReceived() !== null) {
$this->assertInstanceOf(PreciseNumber::class, $lightningInvoice->getAmountReceived());
}
}
}

View File

@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Tests;
use BTCPayServer\Client\Invoice;
use BTCPayServer\Client\Miscellaneous;
use BTCPayServer\Result\InvoiceCheckoutHtml;
use BTCPayServer\Result\LanguageCode;
use BTCPayServer\Result\LanguageCodeList;
use BTCPayServer\Result\PermissionMetadata;
use BTCPayServer\Result\PermissionMetadataList;
use BTCPayServer\Util\PreciseNumber;
final class MiscellaneousTest extends BaseTest
{
private Miscellaneous $miscellaneousClient;
public function setUp(): void
{
parent::setUp();
$this->miscellaneousClient = new Miscellaneous($this->host, $this->apiKey);
}
public function testItCanGetPermissionMetadata(): void
{
$result = $this->miscellaneousClient->getPermissionMetadata();
$this->assertInstanceOf(PermissionMetadataList::class, $result);
foreach ($result->all() as $permissionMetadata) {
$this->assertInstanceOf(PermissionMetadata::class, $permissionMetadata);
$this->assertIsString($permissionMetadata->getName());
$this->assertIsArray($permissionMetadata->getIncluded());
}
}
public function testItCanGetLanguageCodes(): void
{
$result = $this->miscellaneousClient->getLanguageCodes();
$this->assertInstanceOf(LanguageCodeList::class, $result);
foreach ($result->all() as $languageCode) {
$this->assertInstanceOf(LanguageCode::class, $languageCode);
$this->assertIsString($languageCode->getCode());
$this->assertIsString($languageCode->getCurrentLanguage());
}
}
public function testItCanGetInvoiceCheckout(): void
{
$invoiceClient = new Invoice($this->host, $this->apiKey);
$invoice = $invoiceClient->createInvoice(
storeId: $this->storeId,
currency: 'SATS',
amount: PreciseNumber::parseString('1000'),
);
$result = $this->miscellaneousClient->getInvoiceCheckout($invoice->getId(), null);
$this->assertInstanceOf(InvoiceCheckoutHtml::class, $result);
$this->assertStringContainsString('<!DOCTYPE html>', $result->getHtml());
}
}

View File

@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Tests;
use BTCPayServer\Client\Notification;
use BTCPayServer\Result\NotificationList;
final class NotificationTest extends BaseTest
{
public function setUp(): void
{
parent::setUp();
$this->notificationClient = new Notification($this->host, $this->apiKey);
}
/** @group getNotifications */
public function testItCanGetNotifications(): void
{
$notifications = $this->notificationClient->getNotifications();
$this->assertInstanceOf(NotificationList::class, $notifications);
foreach ($notifications->all() as $notification) {
$this->assertIsString($notification->getId());
$this->assertIsString($notification->getBody());
$this->assertIsString($notification->getLink());
$this->assertIsInt($notification->getCreatedTime());
$this->assertIsBool($notification->isSeen());
}
}
/** @group getNotification */
public function testItCanGetNotification(): void
{
$notifications = $this->notificationClient->getNotifications();
if (empty($notifications->all())) {
$this->markTestSkipped('No notifications found');
}
$notification = $this->notificationClient->getNotification($notifications->all()[0]->getId());
$this->assertIsString($notification->getId());
$this->assertIsString($notification->getBody());
$this->assertIsString($notification->getLink());
$this->assertIsInt($notification->getCreatedTime());
$this->assertIsBool($notification->isSeen());
}
/** @group updateNotification */
public function testItCanUpdateNotification(): void
{
$notifications = $this->notificationClient->getNotifications();
if (empty($notifications->all())) {
$this->markTestSkipped('No notifications found');
}
$notification = $this->notificationClient->updateNotification($notifications->all()[0]->getId(), true);
$this->assertIsString($notification->getId());
$this->assertIsString($notification->getBody());
$this->assertIsString($notification->getLink());
$this->assertIsInt($notification->getCreatedTime());
$this->assertTrue($notification->isSeen());
}
/** @group removeNotification */
public function testItCanRemoveNotification(): void
{
$notifications = $this->notificationClient->getNotifications();
if (empty($notifications->all())) {
$this->markTestSkipped('No notifications found');
}
$notification = $this->notificationClient->removeNotification($notifications->all()[0]->getId());
$this->assertTrue($notification);
}
}

190
tests/PullPaymentTest.php Normal file
View File

@ -0,0 +1,190 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Tests;
use BTCPayServer\Client\PullPayment;
use BTCPayServer\Result\PullPayment as ResultPullPayment;
use BTCPayServer\Result\PullPaymentList;
use BTCPayServer\Result\PullPaymentPayout;
use BTCPayServer\Util\PreciseNumber;
final class PullPaymentTest extends BaseTest
{
public PullPayment $pullPaymentClient;
public function setUp(): void
{
parent::setUp();
$this->pullPaymentClient = new PullPayment($this->host, $this->apiKey);
}
/** @group createPullPayment */
public function testItCanCreatePullPayment(): void
{
$pullPayment = $this->pullPaymentClient->createPullPayment(
storeId: $this->storeId,
name: 'Test Pull Payment',
amount: PreciseNumber::parseString('100'),
currency: 'SATS',
period: 30,
BOLT11Expiration: 30,
autoApproveClaims: false,
startsAt: time(),
expiresAt: time() + 30,
paymentMethods: ['BTC-LightningNetwork']
);
$this->assertInstanceOf(ResultPullPayment::class, $pullPayment);
$this->assertNotEmpty($pullPayment->getId());
$this->assertEquals('Test Pull Payment', $pullPayment->getName());
$this->assertEquals('SATS', $pullPayment->getCurrency());
$this->assertEquals('100', $pullPayment->getAmount());
$this->assertEquals(30, $pullPayment->getPeriod());
$this->assertEquals(30, $pullPayment->getBOLT11Expiration());
$this->assertFalse($pullPayment->isArchived());
$this->assertNotEmpty($pullPayment->getViewLink());
}
/** @group getStorePullPayments */
public function testGetStorePullPayments(): void
{
$pullPayments = $this->pullPaymentClient->getStorePullPayments($this->storeId, false);
$this->assertInstanceOf(PullPaymentList::class, $pullPayments);
$this->assertNotEmpty($pullPayments->all());
$this->assertIsArray($pullPayments->all());
foreach ($pullPayments->all() as $pullPayment) {
$this->assertInstanceOf(ResultPullPayment::class, $pullPayment);
$this->assertIsString($pullPayment->getId());
$this->assertIsString($pullPayment->getName());
$this->assertIsString($pullPayment->getCurrency());
$this->assertInstanceOf(PreciseNumber::class, $pullPayment->getAmount());
$this->assertIsInt($pullPayment->getPeriod());
$this->assertIsInt($pullPayment->getBOLT11Expiration());
$this->assertIsBool($pullPayment->isArchived());
$this->assertIsString($pullPayment->getViewLink());
}
}
/** @group getPullPayment */
public function testGetPullPayment(): void
{
$pullPayments = $this->pullPaymentClient->getStorePullPayments($this->storeId, false);
$pullPayment = $this->pullPaymentClient->getPullPayment($pullPayments->all()[0]->getId());
$this->assertInstanceOf(ResultPullPayment::class, $pullPayment);
$this->assertIsString($pullPayment->getId());
$this->assertIsString($pullPayment->getName());
$this->assertIsString($pullPayment->getCurrency());
$this->assertInstanceOf(PreciseNumber::class, $pullPayment->getAmount());
$this->assertIsInt($pullPayment->getPeriod());
$this->assertIsInt($pullPayment->getBOLT11Expiration());
$this->assertIsBool($pullPayment->isArchived());
$this->assertIsString($pullPayment->getViewLink());
}
/** @group archivePullPayment */
public function testArchivePullPayment(): void
{
// Grab the first unarchived pull payment and archive it.
$pullPayment = $this->pullPaymentClient->getStorePullPayments($this->storeId, false)->all()[0];
$this->pullPaymentClient->archivePullPayment($this->storeId, $pullPayment->getId());
// Get All Pull Payments, including archived, as an array.
$allPullPayments = $this->pullPaymentClient->getStorePullPayments($this->storeId, true)->all();
// Find the the archived pull payment by id.
$archivedPullPayment = array_filter($allPullPayments, function ($pullPayment) {
return $pullPayment->getId() === $pullPayment->getId();
});
// Assert that the pull payment is archived.
$this->assertTrue($archivedPullPayment[0]->isArchived());
}
/** @group payouts */
public function testItCanGetAllPayoutMethods(): void
{
// Create a pull payment.
$pullPayment = $this->pullPaymentClient->createPullPayment(
storeId: $this->storeId,
name: 'Test Pull Payment',
amount: PreciseNumber::parseFloat(0.00001),
currency: 'BTC',
period: 1,
BOLT11Expiration: 1,
autoApproveClaims: false,
startsAt: time(),
expiresAt: time() + 100,
paymentMethods: ['BTC-LightningNetwork']
);
$lightningClient = new \BTCPayServer\Client\LightningInternalNode($this->host, $this->apiKey);
$lightningInvoice = $lightningClient->createLightningInvoice(
'BTC',
'1000000', // milisats
111111,
'Test invoice description',
);
$bolt11 = $lightningInvoice["BOLT11"];
// Create a payout associated with the pull payment.
$payout = $this->pullPaymentClient->createPayout(
pullPaymentId: $pullPayment->getId(),
destination: $bolt11,
amount: PreciseNumber::parseFloat(0.00001),
paymentMethod: 'BTC-LightningNetwork'
);
$this->assertInstanceOf(PullPaymentPayout::class, $payout);
$this->assertIsString($payout->getId());
$this->assertIsInt($payout->getRevision());
$this->assertIsString($payout->getPullPaymentId());
$this->assertIsInt($payout->getDate());
$this->assertIsString($payout->getDestination());
$this->assertInstanceOf(PreciseNumber::class, $payout->getAmount());
$this->assertIsString($payout->getPaymentMethod());
$this->assertIsString($payout->getCryptoCode());
$this->assertIsString($payout->getState());
$this->assertEquals('AwaitingApproval', $payout->getState());
// If PaymentMethodAmount is not null, assert it's an int.
if ($payout->getPaymentMethodAmount() !== null) {
$this->assertIsInt($payout->getPaymentMethodAmount());
}
// Test that we can get the payout.
$getPayout = $this->pullPaymentClient->getPayout($pullPayment->getId(), $payout->getId());
// Assert that the payout is the same as the one we created.
$this->assertEquals($payout, $getPayout);
// Approve the payout.
$approve = $this->pullPaymentClient->approvePayout(
storeId: $this->storeId,
payoutId: $payout->getId(),
revision: $payout->getRevision(),
rateRule: null,
);
$this->assertInstanceOf(PullPaymentPayout::class, $approve);
$this->assertEquals('AwaitingPayment', $approve->getState());
// Mark the Payout as Paid.
$paid = $this->pullPaymentClient->markPayoutAsPaid(
storeId: $this->storeId,
payoutId: $payout->getId(),
);
$this->assertTrue($paid);
// Archive the new pull payment.
$archive = $this->pullPaymentClient->archivePullPayment($this->storeId, $pullPayment->getId());
$this->assertTrue($archive);
}
}

49
tests/ServerTest.php Normal file
View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Tests;
use BTCPayServer\Client\Server;
use BTCPayServer\Result\ServerInfo;
use BTCPayServer\Result\ServerSyncStatusList;
use BTCPayServer\Result\ServerSyncStatusNodeInformation;
use BTCPayServer\Util\PreciseNumber;
final class ServerTest extends BaseTest
{
public Server $serverClient;
public function setUp(): void
{
parent::setUp();
$this->serverClient = new Server($this->host, $this->apiKey);
}
/** @group getServerInfo */
public function testItGetsServerInfoAndAllGetters(): void
{
$serverInfo = $this->serverClient->getInfo();
$this->assertInstanceOf(ServerInfo::class, $serverInfo);
$this->assertIsString($serverInfo->getVersion());
$this->assertIsString($serverInfo->getOnionUrl());
$this->assertIsBool($serverInfo->isFullySynced());
$this->assertIsArray($serverInfo->getSupportedPaymentMethods());
$this->assertInstanceOf(ServerSyncStatusList::class, $serverInfo->getSyncStatus());
foreach ($serverInfo->getSyncStatus()->all() as $serverSyncStatus) {
$this->assertIsInt($serverSyncStatus->getChainHeight());
$this->assertIsInt($serverSyncStatus->getSyncHeight());
$this->assertIsString($serverSyncStatus->getCryptoCode());
$this->assertIsBool($serverSyncStatus->isAvailable());
$this->assertInstanceOf(ServerSyncStatusNodeInformation::class, $serverSyncStatus->getNodeInformation());
$this->assertIsInt($serverSyncStatus->getNodeInformation()->getHeaders());
$this->assertIsInt($serverSyncStatus->getNodeInformation()->getBlocks());
$this->assertInstanceOf(PreciseNumber::class, $serverSyncStatus->getNodeInformation()->getVerificationProgress());
}
}
}

114
tests/StoreEmailTest.php Normal file
View File

@ -0,0 +1,114 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Tests;
use BTCPayServer\Client\Store;
use BTCPayServer\Client\StoreEmail;
use BTCPayServer\Result\Store as ResultStore;
use BTCPayServer\Result\StoreEmailSettings;
final class StoreEmailTest extends BaseTest
{
public Store $storeClient;
public ResultStore $store;
public StoreEmail $storeEmailClient;
public function setUp(): void
{
parent::setUp();
$this->storeClient = new Store($this->host, $this->apiKey);
$this->store = $this->storeClient->createStore(
name: 'Test Store',
website: 'https://example.com',
defaultCurrency: 'USD',
invoiceExpiration: 900,
displayExpirationTimer: 300,
monitoringExpiration: 3600,
speedPolicy: 'MediumSpeed',
lightningDescriptionTemplate: null,
paymentTolerance: 0,
anyoneCanCreateInvoice: false,
requiresRefundEmail: false,
checkoutType: 'V1',
receipt: null,
lightningAmountInSatoshi: false,
lightningPrivateRouteHints: false,
onChainWithLnInvoiceFallback: false,
redirectAutomatically: false,
showRecommendedFee: true,
recommendedFeeBlockTarget: 1,
defaultLang: 'en',
customLogo: 'https://test.com',
customCSS: 'auto: 100px;',
htmlTitle: 'the best store ever',
networkFeeMode: 'MultiplePaymentsOnly',
payJoinEnabled: false,
lazyPaymentMethods: false,
defaultPaymentMethod: 'BTC'
);
$this->storeEmailClient = new StoreEmail($this->host, $this->apiKey);
}
/** @group getSettings */
public function testItCanGetEmailSettings(): void
{
$storeEmailSettings = $this->storeEmailClient->getSettings($this->store->getId());
$this->assertEmailSettingsGettersAreSet($storeEmailSettings);
}
/** @group updateSettings */
public function testItCanUpdateEmailSettings(): void
{
$storeEmailSettings = $this->storeEmailClient->updateSettings(
$this->store->getId(),
server: 'smtp.example.com',
port: 587,
username: 'tester',
password: 'password',
fromEmail: 'tester@btcpayserver.org',
fromName: 'Tester',
disableCertificateCheck: false,
);
$this->assertEmailSettingsGettersAreSet($storeEmailSettings);
}
/** @group sendMail */
public function testItCanSendMail(): void
{
$email = $this->storeEmailClient->sendMail(
storeId: $this->store->getId(),
email: 'testing@btcpayserver.org',
subject: 'Test Email',
body: 'This is a test email',
);
$this->assertTrue($email);
}
private function assertEmailSettingsGettersAreSet($storeEmailSettings): void
{
$this->assertInstanceOf(StoreEmailSettings::class, $storeEmailSettings);
if ($storeEmailSettings->getServer()) {
$this->assertIsString($storeEmailSettings->getServer());
}
if ($storeEmailSettings->getPort()) {
$this->assertIsInt($storeEmailSettings->getPort());
}
if ($storeEmailSettings->getUsername()) {
$this->assertIsString($storeEmailSettings->getUsername());
}
if ($storeEmailSettings->getPassword()) {
$this->assertIsString($storeEmailSettings->getPassword());
}
if ($storeEmailSettings->getFromEmail()) {
$this->assertIsString($storeEmailSettings->getFromEmail());
}
// @TODO: Re-enable when bug is fixed - https://github.com/btcpayserver/btcpayserver/issues/5139
// if ($storeEmailSettings->getFromName()) $this->assertIsString($storeEmailSettings->getFromName());
}
}

View File

@ -0,0 +1,198 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Tests;
use BTCPayServer\Client\Store;
use BTCPayServer\Client\StoreOnChainWallet;
use BTCPayServer\Result\StoreOnChainWallet as ResultStoreOnChainWallet;
use BTCPayServer\Result\StoreOnChainWalletAddress;
use BTCPayServer\Result\StoreOnChainWalletFeeRate;
use BTCPayServer\Result\StoreOnChainWalletTransaction;
use BTCPayServer\Result\StoreOnChainWalletTransactionList;
use BTCPayServer\Result\StoreOnChainWalletUtxo;
use BTCPayServer\Result\StoreOnChainWalletUtxoList;
use BTCPayServer\Util\PreciseNumber;
final class StoreOnChainWalletTest extends BaseTest
{
public Store $storeClient;
public StoreOnChainWallet $storeOnChainWalletClient;
public function setUp(): void
{
parent::setUp();
$this->storeClient = new Store($this->host, $this->apiKey);
$this->storeOnChainWalletClient = new StoreOnChainWallet($this->host, $this->apiKey);
}
/** @group getStoreOnChainWalletOverview */
public function testItCanGetStoreOnChainWalletOverview(): void
{
//$this->markTestIncomplete('BTC doesnt have any derivation scheme set');
$overview = $this->storeOnChainWalletClient->getStoreOnChainWalletOverview(
$this->storeId,
'BTC'
);
$this->assertInstanceOf(ResultStoreOnChainWallet::class, $overview);
$this->assertInstanceOf(PreciseNumber::class, $overview->getBalance());
$this->assertInstanceOf(PreciseNumber::class, $overview->getUnconfirmedBalance());
$this->assertInstanceOf(PreciseNumber::class, $overview->getConfirmedBalance());
$this->assertIsString($overview->getLabel());
}
/** @group getStoreOnChainWalletFeeRate */
public function testItCanGetStoreOnChainWalletFeeRate(): void
{
$feeRate = $this->storeOnChainWalletClient->getStoreOnChainWalletFeeRate(
$this->storeId,
'BTC'
);
$this->assertInstanceOf(StoreOnChainWalletFeeRate::class, $feeRate);
$this->assertInstanceOf(PreciseNumber::class, $feeRate->getFeeRate());
}
/** @group getStoreOnChainWalletAddress */
public function testItCanGetStoreOnChainWalletAddress(): void
{
$address = $this->storeOnChainWalletClient->getStoreOnChainWalletAddress(
$this->storeId,
'BTC'
);
$this->assertInstanceOf(StoreOnChainWalletAddress::class, $address);
$this->assertIsString($address->getAddress());
$this->assertIsString($address->getKeyPath());
$this->assertIsString($address->getPaymentLink());
}
/** @group unReserveLastStoreOnChainWalletAddress */
public function testItCanunReserveLastStoreOnChainWalletAddress(): void
{
$address = $this->storeOnChainWalletClient->unReserveLastStoreOnChainWalletAddress(
$this->storeId,
'BTC'
);
$this->assertIsBool($address);
}
/** @group getStoreOnChainWalletTransactions */
public function testItCanGetStoreOnChainWalletTransactions(): void
{
$transactions = $this->storeOnChainWalletClient->getStoreOnChainWalletTransactions(
$this->storeId,
'BTC'
);
$this->assertInstanceOf(StoreOnChainWalletTransactionList::class, $transactions);
$this->assertIsArray($transactions->all());
foreach ($transactions->all() as $transaction) {
$this->assertInstanceOf(StoreOnChainWalletTransaction::class, $transaction);
$this->assertIsString($transaction->getTransactionHash());
$this->assertIsString($transaction->getComment());
$this->assertIsArray($transaction->getLabels());
$this->assertIsInt($transaction->getConfirmations());
$this->assertIsInt($transaction->getTimestamp());
$this->assertIsString($transaction->getStatus());
}
}
/** @group createStoreOnChainWalletTransaction */
public function testItCanCreateGetUpdateStoreOnChainWalletTransaction(): void
{
$destination =
[
'destination' => 'tb1q2yy5gxpdlsr40xjvy7v6x4gjxr5y8t428nqppa',
'amount' => "0.00001",
'subtractFromAmount' => true,
];
$transaction = $this->storeOnChainWalletClient->createStoreOnChainWalletTransaction(
$this->storeId,
'BTC',
[$destination],
2.0,
false,
true,
false,
);
$this->assertInstanceOf(StoreOnChainWalletTransaction::class, $transaction);
$this->assertIsString($transaction->getTransactionHash());
$this->assertIsString($transaction->getComment());
$this->assertIsArray($transaction->getLabels());
$this->assertIsInt($transaction->getConfirmations());
$this->assertIsInt($transaction->getTimestamp());
$this->assertIsString($transaction->getStatus());
if ($transaction->getBlockHash() !== null) {
$this->assertIsString($transaction->getBlockHash());
}
if ($transaction->getBlockHeight() !== null) {
$this->assertIsInt($transaction->getBlockHeight());
}
$getTransaction = $this->storeOnChainWalletClient->getStoreOnChainWalletTransaction(
$this->storeId,
'BTC',
$transaction->getTransactionHash(),
);
$this->assertInstanceOf(StoreOnChainWalletTransaction::class, $getTransaction);
$this->assertIsString($getTransaction->getTransactionHash());
$this->assertIsString($getTransaction->getComment());
$this->assertIsArray($getTransaction->getLabels());
$this->assertIsInt($getTransaction->getConfirmations());
$this->assertIsInt($getTransaction->getTimestamp());
$this->assertIsString($getTransaction->getStatus());
$updatedTransaction = $this->storeOnChainWalletClient->updateStoreOnChainWalletTransaction(
$this->storeId,
'BTC',
$transaction->getTransactionHash(),
'test comment',
['test label'],
);
$this->assertInstanceOf(StoreOnChainWalletTransaction::class, $updatedTransaction);
$this->assertIsString($updatedTransaction->getTransactionHash());
$this->assertIsString($updatedTransaction->getComment());
$this->assertIsArray($updatedTransaction->getLabels());
$this->assertIsInt($updatedTransaction->getConfirmations());
$this->assertIsInt($updatedTransaction->getTimestamp());
$this->assertIsString($updatedTransaction->getStatus());
$this->assertEquals('test comment', $updatedTransaction->getComment());
}
/** @group getStoreOnChainWalletUtxos */
public function testItCanGetStoreOnChainWalletUtxos(): void
{
$utxos = $this->storeOnChainWalletClient->getStoreOnChainWalletUtxos(
$this->storeId,
'BTC'
);
$this->assertInstanceOf(StoreOnChainWalletUtxoList::class, $utxos);
foreach($utxos as $utxo) {
$this->assertInstanceOf(StoreOnChainWalletUtxo::class, $utxo);
$this->assertIsString($utxo->getComment());
$this->assertIsString($utxo->getAmount());
$this->assertIsString($utxo->getOutpoint());
$this->assertIsString($utxo->getLink());
$this->assertIsArray($utxo->getLabels());
$this->assertIsInt($utxo->getTimestamp());
$this->assertIsString($utxo->getKeyPath());
$this->assertIsString($utxo->getAddress());
$this->assertIsInt($utxo->getConfirmations());
}
}
}

119
tests/StoreTest.php Normal file
View File

@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Tests;
use BTCPayServer\Client\Store;
use BTCPayServer\Result\Store as ResultStore;
use BTCPayServer\Result\StoreList;
use BTCPayServer\Util\PreciseNumber;
final class StoreTest extends BaseTest
{
public Store $storeClient;
public ResultStore $store;
public function setUp(): void
{
parent::setUp();
$this->storeClient = new Store($this->host, $this->apiKey);
$this->store = $this->storeClient->createStore(
name: 'Test Store',
website: 'https://example.com',
defaultCurrency: 'USD',
invoiceExpiration: 900,
displayExpirationTimer: 300,
monitoringExpiration: 3600,
speedPolicy: 'MediumSpeed',
lightningDescriptionTemplate: null,
paymentTolerance: 0,
anyoneCanCreateInvoice: false,
requiresRefundEmail: false,
checkoutType: 'V1',
receipt: null,
lightningAmountInSatoshi: false,
lightningPrivateRouteHints: false,
onChainWithLnInvoiceFallback: false,
redirectAutomatically: false,
showRecommendedFee: true,
recommendedFeeBlockTarget: 1,
defaultLang: 'en',
customLogo: 'https://test.com',
customCSS: 'auto: 100px;',
htmlTitle: 'the best store ever',
networkFeeMode: 'MultiplePaymentsOnly',
payJoinEnabled: false,
lazyPaymentMethods: false,
defaultPaymentMethod: 'BTC'
);
}
/** @group createStore */
public function testItCanCreateAStore(): void
{
$this->assertStoreGettersAreSet($this->store);
}
/** @group getStores */
public function testItCanGetAllStores(): void
{
$stores = $this->storeClient->getStores();
$this->assertInstanceOf(StoreList::class, $stores);
foreach ($stores->all() as $store) {
$this->assertStoreGettersAreSet($store);
}
}
/** @group getStore */
public function testItCanGetAnIndividualStore(): void
{
$store = $this->storeClient->getStore($this->store->getId());
$this->assertStoreGettersAreSet($store);
}
private function assertStoreGettersAreSet(ResultStore $store): void
{
$this->assertInstanceOf(ResultStore::class, $store);
$this->assertIsString($store->getName());
$this->assertIsInt($store->getInvoiceExpiration());
$this->assertIsInt($store->getMonitoringExpiration());
$this->assertIsString($store->getSpeedPolicy());
$this->assertIsString($store->getLightningDescriptionTemplate());
$this->assertInstanceOf(PreciseNumber::class, $store->getPaymentTolerance());
$this->assertIsBool($store->anyoneCanCreateInvoice());
$this->assertIsBool($store->requiresRefundEmail());
$this->assertIsBool($store->lightningAmountInSatoshi());
$this->assertIsBool($store->lightningPrivateRouteHints());
$this->assertIsBool($store->onChainWithLnInvoiceFallback());
$this->assertIsBool($store->redirectAutomatically());
$this->assertIsBool($store->showRecommendedFee());
$this->assertIsInt($store->getRecommendedFeeBlockTarget());
if ($store->getCustomLogo() !== null) {
$this->assertIsString($store->getCustomLogo());
}
if ($store->getCustomCSS() !== null) {
$this->assertIsString($store->getCustomCSS());
}
if ($store->getHtmlTitle() !== null) {
$this->assertIsString($store->getHtmlTitle());
}
if ($store->getWebsite() !== null) {
$this->assertIsString($store->getWebsite());
}
if ($store->getDefaultPaymentMethod() !== null) {
$this->assertIsString($store->getDefaultPaymentMethod());
}
$this->assertIsString($store->getNetworkFeeMode());
$this->assertIsBool($store->payJoinEnabled());
$this->assertIsBool($store->lazyPaymentMethods());
$this->assertIsString($store->getId());
}
}