Compare commits

..

16 Commits
3.x ... master

Author SHA1 Message Date
ndeet
a3004b382b
Adding new subscription routes and updating subs examples. (#142)
Some checks failed
Code Style / php-cs-fixer (push) Has been cancelled
Static analysis (Psalm) / Psalm (8.0) (push) Has been cancelled
* Adding new subscription routes and updating subs examples.

* Updating github actions due to deprecated nodejs 20.
2026-04-30 11:29:35 +02:00
ndeet
60e6be57f9
Fixes #136 by adding BTCPay >=2.0 parameter. (#141)
Some checks failed
Code Style / php-cs-fixer (push) Has been cancelled
Static analysis (Psalm) / Psalm (8.0) (push) Has been cancelled
2026-01-21 12:31:48 +01:00
Števo Bartko
7d244da111
FIX - php8.4 compatiblity - Explicit nullable param type (#133) 2026-01-21 11:47:07 +01:00
ndeet
e9b01b5266
Change hash comparison, fixes #138 (#140) 2026-01-20 22:51:56 +01:00
ndeet
db79fee3d2
Add subscriptions support. (#139)
* Add subscriptions support.
2026-01-20 22:43:02 +01:00
ndeet
3118f9e4e0
BTCPay 2.0 compatibilty for pull payments due to changed parameter name. (#132)
Some checks failed
Code Style / php-cs-fixer (push) Has been cancelled
Static analysis (Psalm) / Psalm (8.0) (push) Has been cancelled
2024-11-22 17:34:09 +01:00
ndeet
f4fac20f19
Adding filters to invoices search. (#130)
Some checks failed
Code Style / php-cs-fixer (push) Has been cancelled
Static analysis (Psalm) / Psalm (8.0) (push) Has been cancelled
* Adding filters to invoices search.
2024-11-05 21:44:23 +01:00
ndeet
28197bf65f
Merge pull request #128 from ndeet/cleanup
Some checks failed
Code Style / php-cs-fixer (push) Has been cancelled
Static analysis (Psalm) / Psalm (8.0) (push) Has been cancelled
Cleanup and adding getCurrency() for new data field.
2024-09-16 23:02:29 +02:00
ndeet
108f18b444 Cleanup and adding getCurrency() for new data field. 2024-09-16 23:01:08 +02:00
ndeet
5e2ba7e3f5 Move description parameter to end to not cause breaking change in minor release.
Some checks failed
Code Style / php-cs-fixer (push) Has been cancelled
Static analysis (Psalm) / Psalm (8.0) (push) Has been cancelled
2024-09-13 16:54:13 +02:00
ndeet
9bbb7e8ebe
BTCPay 2.0 compatibiltiy fixes in a backward compatible way. (#127)
* BTCPay 2.0 compatibiltiy fixes in a backward compatible way.

* Update GH workflow dependencies.
2024-09-12 16:54:24 +02:00
Markus Petzsch
60e7b42de2
add description field to pull payment (#125)
* add description field to pull payments
2024-06-27 15:04:02 +02:00
ndeet
c115b04157
Add refund invoice endpoint. Add getters to ApiKey result. (#123)
Some checks failed
Code Style / php-cs-fixer (push) Has been cancelled
PHP Unit Tests / phpunit (8.0, latest) (push) Has been cancelled
PHP Unit Tests / phpunit (8.1, latest) (push) Has been cancelled
Static analysis (Psalm) / Psalm (8.0) (push) Has been cancelled
* Add refund invoice endpoint. 
* Add getters to ApiKey result.
2024-04-25 11:19:49 +02:00
ndeet
385b7f6882
Adding missing stores endpoints update/delete (#122)
Some checks failed
Code Style / php-cs-fixer (push) Has been cancelled
PHP Unit Tests / phpunit (8.0, latest) (push) Has been cancelled
PHP Unit Tests / phpunit (8.1, latest) (push) Has been cancelled
Static analysis (Psalm) / Psalm (8.0) (push) Has been cancelled
* Adding missing functions of updating and deleting store; renaming examples file.
2024-04-03 10:35:46 +02:00
ndeet
b88cd1cf5c
Create onchain wallet (#120)
Some checks failed
Code Style / php-cs-fixer (push) Has been cancelled
PHP Unit Tests / phpunit (8.0, latest) (push) Has been cancelled
PHP Unit Tests / phpunit (8.1, latest) (push) Has been cancelled
Static analysis (Psalm) / Psalm (8.0) (push) Has been cancelled
* Adding createStoreOnChainWallet functionality.
2024-03-25 19:42:58 +01:00
ndeet
8b119a836a
Fix http status check on create user. (#113) 2023-06-28 22:45:39 +02:00
75 changed files with 1843 additions and 1824 deletions

View File

@ -8,7 +8,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v5
- name: Run PHP CS Fixer
uses: docker://oskarstark/php-cs-fixer-ga

View File

@ -1,52 +1,53 @@
# 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"]
# 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: 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: Install Composer dependencies
# run: composer install --no-progress --optimize-autoloader
# - 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 PHPUnit
# run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
#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']
#
#
# 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: 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: Install Composer dependencies
# run: composer install --no-progress --optimize-autoloader
#
# - 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 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
uses: actions/checkout@v5
with:
fetch-depth: "0"
fetch-depth: '0'
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
@ -27,7 +27,7 @@ jobs:
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer dependencies
uses: actions/cache@v2
uses: actions/cache@v5
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}

3
.gitignore vendored
View File

@ -4,5 +4,4 @@
*.cache
composer.lock
/tests/.env
/tests/.env.testnet
/tests/.env.mainnet
/.claude/

View File

@ -1,14 +1,11 @@
# 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
@ -17,13 +14,11 @@ 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.
@ -33,17 +28,13 @@ 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';
@ -61,25 +52,19 @@ 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,17 +28,11 @@
"friendsofphp/php-cs-fixer": "^3.0",
"vimeo/psalm": "^4.8",
"phpunit/phpunit": "^9.5",
"vlucas/phpdotenv": "^5.5",
"pestphp/pest": "^1.22"
"vlucas/phpdotenv": "^5.5"
},
"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

@ -22,3 +22,22 @@ try {
} catch (\Throwable $e) {
echo "Error: " . $e->getMessage();
}
// Get 2 invoices, skip 2
try {
echo 'Get invoices:' . PHP_EOL;
$client = new Invoice($host, $apiKey);
var_dump($client->getAllInvoices($storeId, 2, 2));
} catch (\Throwable $e) {
echo "Error: " . $e->getMessage();
}
// Get newer/equal than 2024-10-20
try {
echo 'Get invoices newer/equal than 2024-10-20:' . PHP_EOL;
$date = new DateTime('2024-10-20');
$client = new Invoice($host, $apiKey);
var_dump($client->getAllInvoicesWithFilter($storeId, null, null, null, $date->getTimestamp()));
} catch (\Throwable $e) {
echo "Error: " . $e->getMessage();
}

View File

@ -43,7 +43,7 @@ class PullPayments
$autoApproveClaims = false;
$startsAt = null;
$expiresAt = null;
$paymentMethods = ['BTC'];
$paymentMethods = ['BTC-CHAIN'];
try {
$client = new PullPayment($this->host, $this->apiKey);
@ -113,7 +113,7 @@ class PullPayments
public function approvePayout()
{
$payoutId ='';
$payoutId = '';
try {
$client = new PullPayment($this->host, $this->apiKey);
var_dump($client->approvePayout(
@ -163,7 +163,7 @@ class PullPayments
$pullPaymentId = '';
$destination = '';
$amount = PreciseNumber::parseString('0.000001');
$paymentMethod = '';
$paymentMethod = 'BTC-CHAIN';
try {
$client = new PullPayment($this->host, $this->apiKey);

View File

@ -32,6 +32,21 @@ class StoreOnChainWallets
}
}
public function createStoreOnChainWallet()
{
$cryptoCode = 'BTC';
try {
$client = new StoreOnChainWallet($this->host, $this->apiKey);
var_dump($client->createStoreOnchainWallet(
$this->storeId,
$cryptoCode
));
} catch (\Throwable $e) {
echo "Error: " . $e->getMessage();
}
}
public function getStoreOnChainWalletFeeRate()
{
$cryptoCode = 'BTC';
@ -171,3 +186,4 @@ $store = new StoreOnChainWallets();
$store->getStoreOnChainWalletTransactions();
//$store->getStoreOnChainWalletTransaction();
//$store->getStoreOnChainWalletUTXOs();
//$store->createStoreOnChainWallet();

View File

@ -8,9 +8,10 @@ use BTCPayServer\Client\Store;
$apiKey = '';
$host = ''; // e.g. https://your.btcpay-server.tld
$storeId = '';
$invoiceId = '';
$updateStoreId = '';
// Get information about store on BTCPay Server.
try {
$client = new Store($host, $apiKey);
var_dump($client->getStore($storeId));
@ -21,7 +22,17 @@ try {
// Create a new store.
try {
$client = new Store($host, $apiKey);
var_dump($client->createStore('my new store'));
$newStore = $client->createStore('New store', null, 'EUR');
var_dump($newStore);
} catch (\Throwable $e) {
echo "Error: " . $e->getMessage();
}
// Update a store.
// You need to pass all variables to make sure it does not get reset to defaults if you want to preserve them.
try {
$client = new Store($host, $apiKey);
var_dump($client->updateStore($updateStoreId, 'Store name CHANGED'));
} catch (\Throwable $e) {
echo "Error: " . $e->getMessage();
}

242
examples/subscriptions.php Normal file
View File

@ -0,0 +1,242 @@
<?php
// Include autoload file.
require __DIR__ . '/../vendor/autoload.php';
// Import Subscriptions client class.
use BTCPayServer\Client\Subscriptions;
// Fill in with your BTCPay Server data.
$apiKey = '';
$host = ''; // e.g. https://your.btcpay-server.tld
$storeId = '';
// Create the subscriptions client.
try {
$client = new Subscriptions($host, $apiKey);
echo "=== BTCPay Server Subscriptions API Examples ===\n\n";
// 1. Create a new offering
echo "1. Creating a new offering...\n";
$offering = $client->createOffering(
$storeId,
'Premium SaaS App',
'https://example.com/success',
[
'category' => 'saas',
'region' => 'us',
'version' => '1.0'
],
[
['id' => 'feature-analytics', 'description' => 'Advanced analytics dashboard'],
['id' => 'feature-support', 'description' => '24/7 priority support'],
['id' => 'feature-api', 'description' => 'Unlimited API access']
]
);
echo "Offering created with ID: " . $offering->getId() . "\n";
echo "App Name: " . $offering->getAppName() . "\n\n";
$offeringId = $offering->getId();
// 2. Create plans for the offering
echo "2. Creating plans for the offering...\n";
// Basic plan
$basicPlan = $client->createOfferingPlan(
$storeId,
$offeringId,
'Basic monthly subscription with essential features',
'USD',
7,
'Basic Plan',
true,
'1.99',
true,
null,
['tier' => 'basic'],
'Monthly',
['feature-analytics']
);
echo "Basic plan created with ID: " . $basicPlan->getId() . "\n";
// Premium plan
$premiumPlan = $client->createOfferingPlan(
$storeId,
$offeringId,
'Premium monthly subscription with all features',
'USD',
7,
'Premium Plan',
true,
'29.99',
true,
14,
['tier' => 'premium'],
'Monthly',
['feature-analytics', 'feature-support', 'feature-api']
);
echo "Premium plan created with ID: " . $premiumPlan->getId() . "\n\n";
$basicPlanId = $basicPlan->getId();
$premiumPlanId = $premiumPlan->getId();
// 2b. Update the offering
echo "2b. Updating the offering...\n";
$updatedOffering = $client->updateOffering(
$storeId,
$offeringId,
'Premium SaaS App v2',
'https://example.com/success-v2',
['category' => 'saas', 'region' => 'eu', 'version' => '2.0']
);
echo "Offering updated: " . $updatedOffering->getAppName() . "\n\n";
// 2c. Update a plan
echo "2c. Updating the basic plan...\n";
$updatedPlan = $client->updateOfferingPlan(
$storeId,
$offeringId,
$basicPlanId,
'Updated basic monthly subscription',
'USD',
7,
'Basic Plan v2',
null,
'2.99'
);
echo "Plan updated: " . $updatedPlan->getName() . " - " . $updatedPlan->getPrice() . " " . $updatedPlan->getCurrency() . "\n\n";
// 3. Get all offerings for the store
echo "3. Getting all offerings for the store...\n";
$offerings = $client->getOfferings($storeId);
foreach ($offerings->all() as $off) {
echo "- Offering: " . $off->getAppName() . " (ID: " . $off->getId() . ")\n";
foreach ($off->getPlans() as $plan) {
echo " - Plan: " . $plan->getName() . " - " . $plan->getPrice() . " " . $plan->getCurrency() . "/" . $plan->getRecurringType() . "\n";
}
}
echo "\n";
// 4. Get a specific offering
echo "4. Getting specific offering details...\n";
$specificOffering = $client->getOffering($storeId, $offeringId);
echo "Offering: " . $specificOffering->getAppName() . "\n";
echo "Success URL: " . $specificOffering->getSuccessRedirectUrl() . "\n";
echo "Number of plans: " . count($specificOffering->getPlans()) . "\n\n";
// 5. Get a specific plan
echo "5. Getting specific plan details...\n";
$specificPlan = $client->getOfferingPlan($storeId, $offeringId, $basicPlanId);
echo "Plan: " . $specificPlan->getName() . "\n";
echo "Price: " . $specificPlan->getPrice() . " " . $specificPlan->getCurrency() . "\n";
echo "Trial Days: " . $specificPlan->getTrialDays() . "\n";
echo "Features: " . implode(', ', $specificPlan->getFeatures()) . "\n\n";
// 6. Create a plan checkout session
echo "6. Creating a plan checkout session...\n";
$checkout = $client->createPlanCheckout(
$storeId,
$offeringId,
$basicPlanId,
null, // If the customer already exists on BTCPay, fill the email or other id here.
60,
'SoftMigration',
['source' => 'web'],
['campaign' => 'summer2026'],
['flow' => 'new_signup'],
false,
null, // You can override the plan price here if you want to force more credit or custom amount.
'https://example.com/welcome',
'test@example.com' // This is optional and will prefill the checkout page with the email.
);
echo "Checkout created with ID: " . $checkout->getId() . "\n";
echo "Checkout URL: " . $checkout->getUrl() . "\n";
echo "Is Trial: " . ($checkout->isTrial() ? 'Yes' : 'No') . "\n";
echo "New Subscriber: " . ($checkout->isNewSubscriber() ? 'Yes' : 'No') . "\n\n";
$checkoutId = $checkout->getId();
// 7. Get plan checkout details
echo "7. Getting plan checkout details...\n";
$checkoutDetails = $client->getPlanCheckout($checkoutId);
echo "Checkout ID: " . $checkoutDetails->getId() . "\n";
echo "Plan: " . $checkoutDetails->getPlan()->getName() . "\n";
$subscriber = $checkoutDetails->getSubscriber();
if ($subscriber && $subscriber->getCustomer()->getIdentities()) {
echo "Subscriber Email: " . ($subscriber->getCustomer()->getIdentities()['Email'] ?? 'N/A') . "\n";
}
echo "\n";
// 8. Subscriber management examples
/*
// Fill these variables with actual values to test subscriber operations
$offeringId = ''; // e.g. "offering_GFbMSBpybM6i5uEiqc"
$customerSelector = ''; // e.g. "ps_N71XxcPDnKNgNDxKHZ" or customer email
$suspensionReason = 'User requested cancellation';
if (!empty($storeId) && !empty($offeringId) && !empty($customerSelector)) {
try {
// Get subscriber details
echo "8. Getting subscriber details...\n";
$subscriber = $client->getSubscriber($storeId, $offeringId, $customerSelector);
echo "Customer ID: " . $subscriber->getCustomer()->getId() . "\n";
echo "Active: " . ($subscriber->isActive() ? 'Yes' : 'No') . "\n";
echo "Phase: " . $subscriber->getPhase() . "\n";
echo "Created: " . date('Y-m-d H:i:s', $subscriber->getCreated()) . "\n";
echo "\n";
// Suspend subscriber
if (!empty($suspensionReason)) {
echo "9. Suspending subscriber...\n";
$client->suspendSubscriber($storeId, $offeringId, $customerSelector, $suspensionReason);
echo "Subscriber suspended successfully!\n";
// Check status after suspension
$suspendedSubscriber = $client->getSubscriber($storeId, $offeringId, $customerSelector);
echo "Status after suspension: " . ($suspendedSubscriber->isActive() ? 'Active' : 'Suspended') . "\n";
echo "Suspension reason: " . ($suspendedSubscriber->getSuspensionReason() ?? 'N/A') . "\n\n";
// Update subscriber dates
echo "9b. Updating subscriber dates...\n";
$updatedSubscriber = $client->updateSubscriberDates(
$storeId,
$offeringId,
$customerSelector,
null,
time() + (30 * 24 * 60 * 60) // 30 days from now
);
echo "Subscriber expiration updated\n";
echo "Scheduled plan: " . ($updatedSubscriber->getScheduledPlan() ? $updatedSubscriber->getScheduledPlan()->getName() : 'None') . "\n";
echo "Scheduled plan activates at: " . ($updatedSubscriber->getScheduledPlanActivatesAt() ? date('Y-m-d H:i:s', $updatedSubscriber->getScheduledPlanActivatesAt()) : 'N/A') . "\n\n";
// Unsuspend subscriber
echo "10. Unsuspending subscriber...\n";
$client->unsuspendSubscriber($storeId, $offeringId, $customerSelector);
echo "Subscriber unsuspended successfully!\n";
// Check status after unsuspending
$reactivatedSubscriber = $client->getSubscriber($storeId, $offeringId, $customerSelector);
echo "Status after unsuspending: " . ($reactivatedSubscriber->isActive() ? 'Active' : 'Suspended') . "\n";
echo "Suspension reason: " . ($reactivatedSubscriber->getSuspensionReason() ?? 'N/A') . "\n\n";
}
// Delete subscriber
echo "11. Deleting subscriber...\n";
$client->deleteSubscriber($storeId, $offeringId, $customerSelector);
echo "Subscriber deleted successfully!\n\n";
} catch (\Throwable $e) {
echo "Error in subscriber management: " . $e->getMessage() . "\n";
}
} else {
echo "8. Subscriber management examples skipped - please fill in storeId, offeringId, and customerSelector variables\n";
}
*/
echo "=== Examples completed successfully! ===\n";
} catch (\Throwable $e) {
echo "Error: " . $e->getMessage() . "\n";
echo "Stack trace:\n" . $e->getTraceAsString() . "\n";
}

View File

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

View File

@ -22,7 +22,7 @@ class AbstractClient
/** @var ClientInterface */
private $httpClient;
public function __construct(string $baseUrl, string $apiKey, ClientInterface $client = null)
public function __construct(string $baseUrl, string $apiKey, ?ClientInterface $client = null)
{
$this->baseUrl = rtrim($baseUrl, '/');
$this->apiKey = $apiKey;

View File

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

View File

@ -7,6 +7,7 @@ namespace BTCPayServer\Client;
use BTCPayServer\Result\Invoice as ResultInvoice;
use BTCPayServer\Result\InvoiceList;
use BTCPayServer\Result\InvoicePaymentMethod;
use BTCPayServer\Result\PullPayment as ResultPullPayment;
use BTCPayServer\Util\PreciseNumber;
class Invoice extends AbstractClient
@ -115,19 +116,71 @@ class Invoice extends AbstractClient
}
}
public function getAllInvoices(string $storeId): InvoiceList
{
return $this->_getAllInvoicesWithFilter($storeId, null);
}
public function getInvoicesByOrderIds(string $storeId, array $orderIds): InvoiceList
{
return $this->_getAllInvoicesWithFilter($storeId, $orderIds);
}
private function _getAllInvoicesWithFilter(
public function getAllInvoices(
string $storeId,
array $filterByOrderIds = null
?int $take = null,
?int $skip = null
): InvoiceList {
return $this->getAllInvoicesWithFilter($storeId, null, null, null, null, null, $take, $skip);
}
public function getInvoicesByOrderIds(
string $storeId,
array $orderIds,
?int $take = null,
?int $skip = null
): InvoiceList {
return $this->getAllInvoicesWithFilter($storeId, $orderIds, null, null, null, null, $take, $skip);
}
public function getInvoicesByText(
string $storeId,
string $text,
?int $take = null,
?int $skip = null
): InvoiceList {
return $this->getAllInvoicesWithFilter($storeId, null, $text, null, null, null, $take, $skip);
}
public function getInvoicesByStatus(
string $storeId,
array $status,
?int $take = null,
?int $skip = null
): InvoiceList {
return $this->getAllInvoicesWithFilter($storeId, null, null, $status, null, null, $take, $skip);
}
public function getInvoicesByStartDate(
string $storeId,
int $startDate,
?int $take = null,
?int $skip = null
): InvoiceList {
return $this->getAllInvoicesWithFilter($storeId, null, null, null, $startDate, null, $take, $skip);
}
public function getInvoicesByEndDate(
string $storeId,
int $endDate,
?int $take = null,
?int $skip = null
): InvoiceList {
return $this->getAllInvoicesWithFilter($storeId, null, null, null, null, $endDate, $take, $skip);
}
/**
* @see https://docs.btcpayserver.org/API/Greenfield/v1/#operation/Invoices_GetInvoices
*/
public function getAllInvoicesWithFilter(
string $storeId,
?array $filterByOrderIds = null,
?string $filterByText = null,
?array $filterByStatus = null,
?int $filterByStartDate = null,
?int $filterByEndDate = null,
?int $take = null,
?int $skip = null
): InvoiceList {
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/invoices?';
if ($filterByOrderIds !== null) {
@ -135,6 +188,26 @@ class Invoice extends AbstractClient
$url .= 'orderId=' . urlencode($filterByOrderId) . '&';
}
}
if ($filterByText !== null) {
$url .= 'textSearch=' . urlencode($filterByText) . '&';
}
if ($filterByStatus !== null) {
foreach ($filterByStatus as $filterByStatusItem) {
$url .= 'status=' . urlencode($filterByStatusItem) . '&';
}
}
if ($filterByStartDate !== null) {
$url .= 'startDate=' . $filterByStartDate . '&';
}
if ($filterByEndDate !== null) {
$url .= 'endDate=' . $filterByEndDate . '&';
}
if ($take !== null) {
$url .= 'take=' . $take . '&';
}
if ($skip !== null) {
$url .= 'skip=' . $skip . '&';
}
// Clean URL.
$url = rtrim($url, '&');
@ -159,7 +232,8 @@ class Invoice extends AbstractClient
public function getPaymentMethods(string $storeId, string $invoiceId): array
{
$method = 'GET';
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/invoices/' . urlencode($invoiceId) . '/payment-methods';
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/invoices/'
. urlencode($invoiceId) . '/payment-methods';
$headers = $this->getRequestHeaders();
$response = $this->getHttpClient()->request($method, $url, $headers);
@ -181,11 +255,15 @@ class Invoice extends AbstractClient
}
}
/**
* Mark an invoice status.
*
* @see https://docs.btcpayserver.org/API/Greenfield/v1/#operation/Invoices_MarkInvoiceStatus
* @throws \JsonException
*/
public function markInvoiceStatus(string $storeId, string $invoiceId, string $markAs): ResultInvoice
{
$url = $this->getApiUrl() . 'stores/' . urlencode(
$storeId
) . '/invoices/' . urlencode($invoiceId) . '/status';
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/invoices/' . urlencode($invoiceId) . '/status';
$headers = $this->getRequestHeaders();
$method = 'POST';
@ -207,72 +285,36 @@ class Invoice extends AbstractClient
}
}
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(
/**
* Refund an invoice.
*
* @see https://docs.btcpayserver.org/API/Greenfield/v1/#operation/Invoices_Refund
* @throws \JsonException
*/
public function refundInvoice(
string $storeId,
string $invoiceId,
string $paymentMethod,
): bool {
$url = $this->getApiUrl() . 'stores/' . urlencode(
$storeId
) . '/invoices/' . urlencode($invoiceId) . '/payment-methods/' . urlencode($paymentMethod) . '/activate';
?string $refundVariant = 'CurrentRate',
?string $paymentMethod = 'BTC',
?string $name = null,
?string $description = null,
?float $subtractPercentage = 0.0,
?PreciseNumber $customAmount = null,
?string $customCurrency = null
): ResultPullPayment {
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/invoices/' . urlencode($invoiceId) . '/refund';
$headers = $this->getRequestHeaders();
$method = 'POST';
$body = json_encode(
[
'storeId' => $storeId,
'invoiceId' => $invoiceId,
'paymentMethod' => $paymentMethod
'name' => $name,
'description' => $description,
'paymentMethod' => $paymentMethod,
'refundVariant' => $refundVariant,
'subtractPercentage' => $subtractPercentage,
'customAmount' => $customAmount?->__toString(),
'customCurrency' => $customCurrency
],
JSON_THROW_ON_ERROR
);
@ -280,7 +322,9 @@ class Invoice extends AbstractClient
$response = $this->getHttpClient()->request($method, $url, $headers, $body);
if ($response->getStatus() === 200) {
return true;
return new ResultPullPayment(
json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR)
);
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}

View File

@ -26,7 +26,7 @@ class InvoiceCheckoutOptions
/** @var int */
protected $monitoringMinutes;
/** @var float */
/** @var int */
protected $paymentTolerance;
/** @var string */
@ -38,19 +38,15 @@ class InvoiceCheckoutOptions
/** @var string */
protected $defaultLanguage;
/** @var bool */
protected $requiresRefundEmail;
public static function create(
?string $speedPolicy,
?array $paymentMethods,
?int $expirationMinutes,
?int $monitoringMinutes,
?float $paymentTolerance,
?int $paymentTolerance,
?string $redirectURL,
?bool $redirectAutomatically,
?string $defaultLanguage,
?bool $requiresRefundEmail = false,
?string $defaultLanguage
) {
$options = new InvoiceCheckoutOptions();
$options->setSpeedPolicy($speedPolicy);
@ -60,9 +56,7 @@ class InvoiceCheckoutOptions
$options->paymentTolerance = $paymentTolerance;
$options->redirectURL = $redirectURL;
$options->redirectAutomatically = $redirectAutomatically;
$options->requiresRefundEmail = $requiresRefundEmail;
$options->defaultLanguage = $defaultLanguage;
$options->requiresRefundEmail = $requiresRefundEmail;
return $options;
}
@ -119,12 +113,12 @@ class InvoiceCheckoutOptions
return $this;
}
public function getPaymentTolerance(): ?float
public function getPaymentTolerance(): ?int
{
return $this->paymentTolerance;
}
public function setPaymentTolerance(?float $paymentTolerance): self
public function setPaymentTolerance(?int $paymentTolerance): self
{
$this->paymentTolerance = $paymentTolerance;
return $this;
@ -152,17 +146,6 @@ 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;
@ -187,7 +170,7 @@ class InvoiceCheckoutOptions
$lastIndex = strrpos($k, $separator);
if ($lastIndex !== false) {
$k = substr($k, $lastIndex +1);
$k = substr($k, $lastIndex + 1);
}
$array[$k] = $v;
}

View File

@ -138,16 +138,9 @@ 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 {
@ -160,7 +153,6 @@ 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 getLightningInvoice(
public function getInvoice(
string $cryptoCode,
string $storeId,
string $id
@ -152,33 +152,21 @@ 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 $amount = null,
?string $maxFeePercent = null,
?string $maxFeeFlat = null,
string $BOLT11
): LightningPayment {
$url = $this->getApiUrl() . 'stores/' .
urlencode($storeId) . '/lightning/' .
urlencode($cryptoCode) . '/invoices/pay';
urlencode($cryptoCode) . '/info';
$headers = $this->getRequestHeaders();
$method = 'POST';
$body = json_encode(
[
'BOLT11' => $BOLT11,
'amount' => $amount,
'maxFeePercent' => $maxFeePercent,
'maxFeeFlat' => $maxFeeFlat,
'BOLT11' => $BOLT11
],
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\PermissionMetadataList;
use BTCPayServer\Result\PermissionMetadata;
use BTCPayServer\Result\RateSourceList;
class Miscellaneous extends AbstractClient
{
public function getPermissionMetadata(): PermissionMetadataList
public function getPermissionMetadata(): PermissionMetadata
{
$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 PermissionMetadataList(
return new PermissionMetadata(
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,7 +62,9 @@ class Miscellaneous extends AbstractClient
$response = $this->getHttpClient()->request($method, $url, $headers);
if ($response->getStatus() === 200) {
return new InvoiceCheckoutHtml($response->getBody());
return new InvoiceCheckoutHTML(
json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR)
);
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}

View File

@ -17,13 +17,19 @@ class PullPayment extends AbstractClient
bool $includeArchived
): PullPaymentList {
$url = $this->getApiUrl() . 'stores/' .
urlencode($storeId) . '/pull-payments?includeArchived=' .
($includeArchived ? 'true' : 'false');
urlencode($storeId) . '/pull-payments';
$headers = $this->getRequestHeaders();
$method = 'GET';
$response = $this->getHttpClient()->request($method, $url, $headers);
$body = json_encode(
[
'includeArchived' => $includeArchived,
],
JSON_THROW_ON_ERROR
);
$response = $this->getHttpClient()->request($method, $url, $headers, $body);
if ($response->getStatus() === 200) {
return new PullPaymentList(
@ -44,7 +50,8 @@ class PullPayment extends AbstractClient
?bool $autoApproveClaims = false,
?int $startsAt,
?int $expiresAt,
array $paymentMethods
?array $paymentMethods = null,
?string $description = null
): ResultPullPayment {
$url = $this->getApiUrl() . 'stores/' .
urlencode($storeId) . '/pull-payments';
@ -55,6 +62,7 @@ class PullPayment extends AbstractClient
$body = json_encode(
[
'name' => $name,
'description' => $description,
'amount' => $amount->__toString(),
'currency' => $currency,
'period' => $period,
@ -62,7 +70,8 @@ class PullPayment extends AbstractClient
'autoApproveClaims' => $autoApproveClaims,
'startsAt' => $startsAt,
'expiresAt' => $expiresAt,
'paymentMethods' => $paymentMethods
'paymentMethods' => $paymentMethods,
'payoutMethods' => $paymentMethods
],
JSON_THROW_ON_ERROR
);
@ -235,6 +244,7 @@ class PullPayment extends AbstractClient
'destination' => $destination,
'amount' => $amount->__toString(),
'paymentMethod' => $paymentMethod,
'payoutMethodId' => $paymentMethod, // BTCPay 2.0.0 compatibilty
],
JSON_THROW_ON_ERROR
);

View File

@ -5,7 +5,6 @@ declare(strict_types=1);
namespace BTCPayServer\Client;
use BTCPayServer\Result\Store as ResultStore;
use BTCPayServer\Result\StoreList;
class Store extends AbstractClient
{
@ -21,7 +20,7 @@ class Store extends AbstractClient
int $paymentTolerance = 0,
bool $anyoneCanCreateInvoice = false,
bool $requiresRefundEmail = false,
?string $checkoutType = 'V1',
?string $checkoutType = 'V2',
?array $receipt = null,
bool $lightningAmountInSatoshi = false,
bool $lightningPrivateRouteHints = false,
@ -36,7 +35,15 @@ class Store extends AbstractClient
string $networkFeeMode = 'MultiplePaymentsOnly',
bool $payJoinEnabled = false,
bool $lazyPaymentMethods = false,
string $defaultPaymentMethod = 'BTC'
string $defaultPaymentMethod = 'BTC',
?string $supportUrl = null,
bool $archived = false,
bool $autodetectLanguage = false,
bool $showPayInWalletButton = true,
bool $showStoreHeader = true,
bool $celebratePayment = true,
bool $playSoundOnPayment = false,
?array $paymentMethodCriteria = null
): ResultStore {
$url = $this->getApiUrl() . 'stores';
$headers = $this->getRequestHeaders();
@ -46,6 +53,7 @@ class Store extends AbstractClient
[
"name" => $name,
"website" => $website,
"supportUrl" => $supportUrl,
"defaultCurrency" => $defaultCurrency,
"invoiceExpiration" => $invoiceExpiration,
"displayExpirationTimer" => $displayExpirationTimer,
@ -53,6 +61,7 @@ class Store extends AbstractClient
"speedPolicy" => $speedPolicy,
"lightningDescriptionTemplate" => $lightningDescriptionTemplate,
"paymentTolerance" => $paymentTolerance,
"archived" => $archived,
"anyoneCanCreateInvoice" => $anyoneCanCreateInvoice,
"requiresRefundEmail" => $requiresRefundEmail,
"checkoutType" => $checkoutType,
@ -69,8 +78,14 @@ class Store extends AbstractClient
"htmlTitle" => $htmlTitle,
"networkFeeMode" => $networkFeeMode,
"payJoinEnabled" => $payJoinEnabled,
"autodetectLanguage" => $autodetectLanguage,
"showPayInWalletButton" => $showPayInWalletButton,
"showStoreHeader" => $showStoreHeader,
"celebratePayment" => $celebratePayment,
"playSoundOnPayment" => $playSoundOnPayment,
"lazyPaymentMethods" => $lazyPaymentMethods,
"defaultPaymentMethod" => $defaultPaymentMethod
"defaultPaymentMethod" => $defaultPaymentMethod,
"paymentMethodCriteria" => $paymentMethodCriteria
],
JSON_THROW_ON_ERROR
);
@ -99,9 +114,118 @@ class Store extends AbstractClient
}
/**
* @return \BTCPayServer\Result\StoreList
* Update store settings. Make sure to pass all the settings, even if you don't want to change them.
*/
public function getStores(): StoreList
public function updateStore(
string $storeId,
string $name,
?string $website = null,
string $defaultCurrency = 'USD',
int $invoiceExpiration = 900,
int $displayExpirationTimer = 300,
int $monitoringExpiration = 3600,
string $speedPolicy = 'MediumSpeed',
?string $lightningDescriptionTemplate = null,
int $paymentTolerance = 0,
bool $anyoneCanCreateInvoice = false,
bool $requiresRefundEmail = false,
?string $checkoutType = 'V2',
?array $receipt = null,
bool $lightningAmountInSatoshi = false,
bool $lightningPrivateRouteHints = false,
bool $onChainWithLnInvoiceFallback = false,
bool $redirectAutomatically = false,
bool $showRecommendedFee = true,
int $recommendedFeeBlockTarget = 1,
string $defaultLang = 'en',
?string $customLogo = null,
?string $customCSS = null,
?string $htmlTitle = null,
string $networkFeeMode = 'MultiplePaymentsOnly',
bool $payJoinEnabled = false,
bool $lazyPaymentMethods = false,
string $defaultPaymentMethod = 'BTC',
?string $supportUrl = null,
bool $archived = false,
bool $autodetectLanguage = false,
bool $showPayInWalletButton = true,
bool $showStoreHeader = true,
bool $celebratePayment = true,
bool $playSoundOnPayment = false,
?array $paymentMethodCriteria = null
): ResultStore {
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId);
$headers = $this->getRequestHeaders();
$method = 'PUT';
$body = json_encode(
[
"name" => $name,
"website" => $website,
"supportUrl" => $supportUrl,
"defaultCurrency" => $defaultCurrency,
"invoiceExpiration" => $invoiceExpiration,
"displayExpirationTimer" => $displayExpirationTimer,
"monitoringExpiration" => $monitoringExpiration,
"speedPolicy" => $speedPolicy,
"lightningDescriptionTemplate" => $lightningDescriptionTemplate,
"paymentTolerance" => $paymentTolerance,
"archived" => $archived,
"anyoneCanCreateInvoice" => $anyoneCanCreateInvoice,
"requiresRefundEmail" => $requiresRefundEmail,
"checkoutType" => $checkoutType,
"receipt" => $receipt,
"lightningAmountInSatoshi" => $lightningAmountInSatoshi,
"lightningPrivateRouteHints" => $lightningPrivateRouteHints,
"onChainWithLnInvoiceFallback" => $onChainWithLnInvoiceFallback,
"redirectAutomatically" => $redirectAutomatically,
"showRecommendedFee" => $showRecommendedFee,
"recommendedFeeBlockTarget" => $recommendedFeeBlockTarget,
"defaultLang" => $defaultLang,
"customLogo" => $customLogo,
"customCSS" => $customCSS,
"htmlTitle" => $htmlTitle,
"networkFeeMode" => $networkFeeMode,
"payJoinEnabled" => $payJoinEnabled,
"autodetectLanguage" => $autodetectLanguage,
"showPayInWalletButton" => $showPayInWalletButton,
"showStoreHeader" => $showStoreHeader,
"celebratePayment" => $celebratePayment,
"playSoundOnPayment" => $playSoundOnPayment,
"lazyPaymentMethods" => $lazyPaymentMethods,
"defaultPaymentMethod" => $defaultPaymentMethod,
"paymentMethodCriteria" => $paymentMethodCriteria
],
JSON_THROW_ON_ERROR
);
$response = $this->getHttpClient()->request($method, $url, $headers, $body);
if ($response->getStatus() === 200) {
return new ResultStore(json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR));
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
public function deleteStore(string $storeId): bool
{
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId);
$headers = $this->getRequestHeaders();
$method = 'DELETE';
$response = $this->getHttpClient()->request($method, $url, $headers);
if ($response->getStatus() === 200) {
return true;
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
/**
* @return \BTCPayServer\Result\Store[]
*/
public function getStores(): array
{
$url = $this->getApiUrl() . 'stores';
$headers = $this->getRequestHeaders();
@ -109,8 +233,13 @@ class Store extends AbstractClient
$response = $this->getHttpClient()->request($method, $url, $headers);
if ($response->getStatus() === 200) {
$r = [];
$data = json_decode($response->getBody(), true);
return new StoreList($data);
foreach ($data as $item) {
$item = new ResultStore($item);
$r[] = $item;
}
return $r;
} 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
{
@ -35,6 +35,50 @@ class StoreOnChainWallet extends AbstractClient
}
}
public function createStoreOnChainWallet(
string $storeId,
string $cryptoCode,
?string $existingMnemonic = null,
?string $passphrase = null,
int $accountNumber = 0,
bool $savePrivateKeys = false,
bool $importKeysToRPC = false,
string $wordList = 'English',
int $wordCount = 12,
string $scriptPubKeyType = 'Segwit'
): ResultStoreOnChainWallet {
$url = $this->getApiUrl() . 'stores/' .
urlencode($storeId) . '/payment-methods/onchain/' .
urlencode($cryptoCode) . '/generate';
$headers = $this->getRequestHeaders();
$method = 'POST';
$body = json_encode(
[
'existingMnemonic' => $existingMnemonic,
'passphrase' => $passphrase,
'accountNumber' => $accountNumber,
'savePrivateKeys' => $savePrivateKeys,
'importKeysToRPC' => $importKeysToRPC,
'wordList' => $wordList,
'wordCount' => $wordCount,
'scriptPubKeyType' => $scriptPubKeyType
],
JSON_THROW_ON_ERROR
);
$response = $this->getHttpClient()->request($method, $url, $headers, $body);
if ($response->getStatus() === 200) {
return new ResultStoreOnChainWallet(
json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR)
);
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
public function getStoreOnChainWalletFeeRate(
string $storeId,
string $cryptoCode,
@ -241,10 +285,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 +299,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 {

View File

@ -15,9 +15,12 @@ use BTCPayServer\Result\StorePaymentMethodCollection;
*/
class StorePaymentMethod extends AbstractClient
{
public function getPaymentMethods(string $storeId): array
public function getPaymentMethods(string $storeId, bool $includeConfig = false): array
{
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/payment-methods';
if ($includeConfig) {
$url .= '?includeConfig=true';
}
$headers = $this->getRequestHeaders();
$method = 'GET';
$response = $this->getHttpClient()->request($method, $url, $headers);

View File

@ -10,6 +10,8 @@ use BTCPayServer\Result\StorePaymentMethodLightningNetwork as ResultStorePayment
* Handles a stores LightningNetwork payment methods.
*
* @see https://docs.btcpayserver.org/API/Greenfield/v1/#tag/Store-Payment-Methods-(Lightning-Network)
*
* @deprecated with BTCPay 2.0. Use \BTCPayServer\Client\StorePaymentMethod->getPaymentMethods() instead.
*/
class StorePaymentMethodLightningNetwork extends AbstractStorePaymentMethodClient
{

View File

@ -10,6 +10,8 @@ use BTCPayServer\Result\StorePaymentMethodOnChain as ResultStorePaymentMethodOnC
* Handles stores on chain payment methods.
*
* @see https://docs.btcpayserver.org/API/Greenfield/v1/#tag/Store-Payment-Methods-(On-Chain)
*
* @deprecated with BTCPay 2.0. Use \BTCPayServer\Client\StorePaymentMethod->getPaymentMethods() instead.
*/
class StorePaymentMethodOnChain extends AbstractStorePaymentMethodClient
{
@ -133,7 +135,7 @@ class StorePaymentMethodOnChain extends AbstractStorePaymentMethodClient
string $storeId,
string $cryptoCode,
string $derivationScheme,
string $accountKeyPath = null
?string $accountKeyPath = null
): array {
// todo: add offset + amount query parameters + check structure of derivationScheme etc.

View File

@ -97,7 +97,7 @@ class StoreRate extends AbstractClient
public function getRates(
string $storeId,
array $currencyPairs = null
?array $currencyPairs = null
): StoreRateList {
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/rates?';
$headers = $this->getRequestHeaders();

View File

@ -0,0 +1,493 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Client;
use BTCPayServer\Result\Credit;
use BTCPayServer\Result\Offering;
use BTCPayServer\Result\OfferingList;
use BTCPayServer\Result\OfferingPlan;
use BTCPayServer\Result\PlanCheckout;
use BTCPayServer\Result\PortalSession;
use BTCPayServer\Result\Subscriber;
/**
* Handles subscriptions operations.
*
* @see https://docs.btcpayserver.org/API/Greenfield/v1/#tag/Subscriptions
*/
class Subscriptions extends AbstractClient
{
// Offering endpoints
public function getOffering(string $storeId, string $offeringId): Offering
{
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/offerings/' . urlencode($offeringId);
$headers = $this->getRequestHeaders();
$method = 'GET';
$response = $this->getHttpClient()->request($method, $url, $headers);
if ($response->getStatus() === 200) {
return new Offering(json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR));
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
public function getOfferings(string $storeId): OfferingList
{
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/offerings';
$headers = $this->getRequestHeaders();
$method = 'GET';
$response = $this->getHttpClient()->request($method, $url, $headers);
if ($response->getStatus() === 200) {
return new OfferingList(json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR));
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
public function createOffering(
string $storeId,
?string $appName = null,
?string $successRedirectUrl = null,
?array $metadata = null,
?array $features = null
): Offering {
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/offerings';
$headers = $this->getRequestHeaders();
$method = 'POST';
$body = json_encode(
[
'appName' => $appName,
'successRedirectUrl' => $successRedirectUrl,
'metadata' => $metadata,
'features' => $features
],
JSON_THROW_ON_ERROR
);
$response = $this->getHttpClient()->request($method, $url, $headers, $body);
if ($response->getStatus() === 200 || $response->getStatus() === 201) {
return new Offering(json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR));
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
public function updateOffering(
string $storeId,
string $offeringId,
?string $appName = null,
?string $successRedirectUrl = null,
?array $metadata = null,
?array $features = null
): Offering {
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/offerings/' . urlencode($offeringId);
$headers = $this->getRequestHeaders();
$method = 'PUT';
$body = json_encode(
[
'appName' => $appName,
'successRedirectUrl' => $successRedirectUrl,
'metadata' => $metadata,
'features' => $features
],
JSON_THROW_ON_ERROR
);
$response = $this->getHttpClient()->request($method, $url, $headers, $body);
if ($response->getStatus() === 200) {
return new Offering(json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR));
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
// Plan endpoints
public function createOfferingPlan(
string $storeId,
string $offeringId,
?string $description = null,
?string $currency = null,
?int $gracePeriodDays = null,
?string $name = null,
?bool $optimisticActivation = null,
?string $price = null,
?bool $renewable = null,
?int $trialDays = null,
?array $metadata = null,
?string $recurringType = null,
?array $features = null
): OfferingPlan {
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/offerings/' . urlencode($offeringId) . '/plans';
$headers = $this->getRequestHeaders();
$method = 'POST';
$body = json_encode(
[
'description' => $description,
'currency' => $currency,
'gracePeriodDays' => $gracePeriodDays,
'name' => $name,
'optimisticActivation' => $optimisticActivation,
'price' => $price,
'renewable' => $renewable,
'trialDays' => $trialDays,
'metadata' => $metadata,
'recurringType' => $recurringType,
'features' => $features
],
JSON_THROW_ON_ERROR
);
$response = $this->getHttpClient()->request($method, $url, $headers, $body);
if ($response->getStatus() === 200 || $response->getStatus() === 201) {
return new OfferingPlan(json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR));
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
public function getOfferingPlan(string $storeId, string $offeringId, string $planId): OfferingPlan
{
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/offerings/' . urlencode($offeringId) . '/plans/' . urlencode($planId);
$headers = $this->getRequestHeaders();
$method = 'GET';
$response = $this->getHttpClient()->request($method, $url, $headers);
if ($response->getStatus() === 200) {
return new OfferingPlan(json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR));
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
public function updateOfferingPlan(
string $storeId,
string $offeringId,
string $planId,
?string $description = null,
?string $currency = null,
?int $gracePeriodDays = null,
?string $name = null,
?bool $optimisticActivation = null,
?string $price = null,
?bool $renewable = null,
?int $trialDays = null,
?array $metadata = null,
?string $recurringType = null,
?array $features = null
): OfferingPlan {
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/offerings/' . urlencode($offeringId) . '/plans/' . urlencode($planId);
$headers = $this->getRequestHeaders();
$method = 'PUT';
$body = json_encode(
[
'description' => $description,
'currency' => $currency,
'gracePeriodDays' => $gracePeriodDays,
'name' => $name,
'optimisticActivation' => $optimisticActivation,
'price' => $price,
'renewable' => $renewable,
'trialDays' => $trialDays,
'metadata' => $metadata,
'recurringType' => $recurringType,
'features' => $features
],
JSON_THROW_ON_ERROR
);
$response = $this->getHttpClient()->request($method, $url, $headers, $body);
if ($response->getStatus() === 200) {
return new OfferingPlan(json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR));
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
// Subscriber endpoints
public function getSubscriber(string $storeId, string $offeringId, string $customerSelector): Subscriber
{
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/offerings/' . urlencode($offeringId) . '/subscribers/' . urlencode($customerSelector);
$headers = $this->getRequestHeaders();
$method = 'GET';
$response = $this->getHttpClient()->request($method, $url, $headers);
if ($response->getStatus() === 200) {
return new Subscriber(json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR));
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
public function deleteSubscriber(string $storeId, string $offeringId, string $customerSelector): void
{
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/offerings/' . urlencode($offeringId) . '/subscribers/' . urlencode($customerSelector);
$headers = $this->getRequestHeaders();
$method = 'DELETE';
$response = $this->getHttpClient()->request($method, $url, $headers);
if ($response->getStatus() !== 204) {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
public function updateSubscriberDates(
string $storeId,
string $offeringId,
string $customerSelector,
?int $startDate = null,
?int $expirationDate = null
): Subscriber {
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/offerings/' . urlencode($offeringId) . '/subscribers/' . urlencode($customerSelector) . '/dates';
$headers = $this->getRequestHeaders();
$method = 'PUT';
$body = json_encode(
[
'startDate' => $startDate,
'expirationDate' => $expirationDate
],
JSON_THROW_ON_ERROR
);
$response = $this->getHttpClient()->request($method, $url, $headers, $body);
if ($response->getStatus() === 200) {
return new Subscriber(json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR));
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
public function suspendSubscriber(string $storeId, string $offeringId, string $customerSelector, string $reason): Subscriber
{
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/offerings/' . urlencode($offeringId) . '/subscribers/' . urlencode($customerSelector) . '/suspend';
$headers = $this->getRequestHeaders();
$method = 'POST';
$body = json_encode(
[
'reason' => $reason
],
JSON_THROW_ON_ERROR
);
$response = $this->getHttpClient()->request($method, $url, $headers, $body);
if ($response->getStatus() === 200) {
return new Subscriber(json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR));
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
public function unsuspendSubscriber(string $storeId, string $offeringId, string $customerSelector): Subscriber
{
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/offerings/' . urlencode($offeringId) . '/subscribers/' . urlencode($customerSelector) . '/unsuspend';
$headers = $this->getRequestHeaders();
$method = 'POST';
$response = $this->getHttpClient()->request($method, $url, $headers);
if ($response->getStatus() === 200) {
return new Subscriber(json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR));
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
// Credit endpoints
public function getCredit(string $storeId, string $offeringId, string $customerSelector, string $currency): Credit
{
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/offerings/' . urlencode($offeringId) . '/subscribers/' . urlencode($customerSelector) . '/credits/' . urlencode($currency);
$headers = $this->getRequestHeaders();
$method = 'GET';
$response = $this->getHttpClient()->request($method, $url, $headers);
if ($response->getStatus() === 200) {
return new Credit(json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR));
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
public function updateCredit(
string $storeId,
string $offeringId,
string $customerSelector,
string $currency,
?string $credit = null,
?string $charge = null,
?string $description = null,
?bool $allowOverdraft = null
): Credit {
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/offerings/' . urlencode($offeringId) . '/subscribers/' . urlencode($customerSelector) . '/credits/' . urlencode($currency);
$headers = $this->getRequestHeaders();
$method = 'POST';
$body = json_encode(
[
'credit' => $credit,
'charge' => $charge,
'description' => $description,
'allowOverdraft' => $allowOverdraft
],
JSON_THROW_ON_ERROR
);
$response = $this->getHttpClient()->request($method, $url, $headers, $body);
if ($response->getStatus() === 200) {
return new Credit(json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR));
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
// Plan checkout endpoints
public function getPlanCheckout(string $checkoutId): PlanCheckout
{
$url = $this->getApiUrl() . 'plan-checkout/' . urlencode($checkoutId);
$headers = $this->getRequestHeaders();
$method = 'GET';
$response = $this->getHttpClient()->request($method, $url, $headers);
if ($response->getStatus() === 200) {
return new PlanCheckout(json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR));
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
public function proceedPlanCheckout(string $checkoutId, ?string $email = null): PlanCheckout
{
$url = $this->getApiUrl() . 'plan-checkout/' . urlencode($checkoutId);
$headers = $this->getRequestHeaders();
$method = 'POST';
$params = [];
if ($email !== null) {
$params['email'] = $email;
}
if (!empty($params)) {
$url .= '?' . http_build_query($params);
}
$response = $this->getHttpClient()->request($method, $url, $headers);
if ($response->getStatus() === 200) {
return new PlanCheckout(json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR));
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
public function createPlanCheckout(
string $storeId,
string $offeringId,
string $planId,
?string $customerSelector = null,
?int $durationMinutes = null,
?string $onPayBehavior = null,
?array $newSubscriberMetadata = null,
?array $invoiceMetadata = null,
?array $metadata = null,
?bool $isTrial = null,
?string $creditPurchase = null,
?string $successRedirectLink = null,
?string $newSubscriberEmail = null
): PlanCheckout {
$url = $this->getApiUrl() . 'plan-checkout';
$headers = $this->getRequestHeaders();
$method = 'POST';
$body = json_encode(
[
'storeId' => $storeId,
'offeringId' => $offeringId,
'planId' => $planId,
'customerSelector' => $customerSelector,
'durationMinutes' => $durationMinutes,
'onPayBehavior' => $onPayBehavior,
'newSubscriberMetadata' => $newSubscriberMetadata,
'invoiceMetadata' => $invoiceMetadata,
'metadata' => $metadata,
'isTrial' => $isTrial,
'creditPurchase' => $creditPurchase,
'successRedirectLink' => $successRedirectLink,
'newSubscriberEmail' => $newSubscriberEmail
],
JSON_THROW_ON_ERROR
);
$response = $this->getHttpClient()->request($method, $url, $headers, $body);
if ($response->getStatus() === 200) {
return new PlanCheckout(json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR));
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
// Portal session endpoints
public function createPortalSession(
string $storeId,
string $offeringId,
string $customerSelector,
?int $durationMinutes = null
): PortalSession {
$url = $this->getApiUrl() . 'subscriber-portal';
$headers = $this->getRequestHeaders();
$method = 'POST';
$body = json_encode(
[
'storeId' => $storeId,
'offeringId' => $offeringId,
'customerSelector' => $customerSelector,
'durationMinutes' => $durationMinutes
],
JSON_THROW_ON_ERROR
);
$response = $this->getHttpClient()->request($method, $url, $headers, $body);
if ($response->getStatus() === 200) {
return new PortalSession(json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR));
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
public function getPortalSession(string $portalSessionId): PortalSession
{
$url = $this->getApiUrl() . 'subscriber-portal/' . urlencode($portalSessionId);
$headers = $this->getRequestHeaders();
$method = 'GET';
$response = $this->getHttpClient()->request($method, $url, $headers);
if ($response->getStatus() === 200) {
return new PortalSession(json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR));
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
}

View File

@ -59,7 +59,7 @@ class User extends AbstractClient
$response = $this->getHttpClient()->request($method, $url, $headers, $body);
if ($response->getStatus() === 200) {
if ($response->getStatus() === 201) {
return new ResultUser(
json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR)
);

View File

@ -238,7 +238,7 @@ class Webhook extends AbstractClient
if ($requestBody && $btcpaySigHeader) {
$expectedHeader = 'sha256=' . hash_hmac('sha256', $requestBody, $secret);
if ($expectedHeader === $btcpaySigHeader) {
if (hash_equals($expectedHeader, $btcpaySigHeader)) {
return true;
}
}

View File

@ -6,7 +6,7 @@ namespace BTCPayServer\Exception;
class BTCPayException extends \RuntimeException
{
public function __construct(string $message, int $code, \Throwable $previous = null)
public function __construct(string $message, int $code, ?\Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}

View File

@ -6,7 +6,7 @@ namespace BTCPayServer\Result;
abstract class AbstractStorePaymentMethodResult extends AbstractResult
{
public function __construct(array $data, string $paymentMethod = null)
public function __construct(array $data, ?string $paymentMethod = null)
{
// Temporary workaround until the api provides paymentMethod.
if (!isset($data['paymentMethod'])) {

View File

@ -6,4 +6,18 @@ namespace BTCPayServer\Result;
class ApiKey extends AbstractResult
{
public function getApiKey(): string
{
return $this->getData()['apiKey'];
}
public function getLabel(): string
{
return $this->getData()['label'];
}
public function getPermissions(): array
{
return $this->getData()['permissions'];
}
}

18
src/Result/Credit.php Normal file
View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Result;
class Credit extends AbstractResult
{
public function getCurrency(): string
{
return $this->getData()['currency'];
}
public function getValue(): string
{
return $this->getData()['value'];
}
}

33
src/Result/Customer.php Normal file
View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Result;
class Customer extends AbstractResult
{
public function getStoreId(): string
{
return $this->getData()['storeId'];
}
public function getId(): string
{
return $this->getData()['id'];
}
public function getExternalId(): ?string
{
return $this->getData()['externalId'] ?? null;
}
public function getIdentities(): ?array
{
return $this->getData()['identities'] ?? null;
}
public function getMetadata(): ?array
{
return $this->getData()['metadata'] ?? null;
}
}

18
src/Result/Feature.php Normal file
View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Result;
class Feature extends AbstractResult
{
public function getId(): string
{
return $this->getData()['id'];
}
public function getDescription(): string
{
return $this->getData()['description'];
}
}

View File

@ -1,14 +0,0 @@
<?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,7 +4,6 @@ declare(strict_types=1);
namespace BTCPayServer\Result;
use BTCPayServer\Client\InvoiceCheckoutOptions;
use BTCPayServer\Util\PreciseNumber;
class Invoice extends AbstractResult
@ -27,11 +26,6 @@ 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'];
@ -42,11 +36,6 @@ 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'];
@ -72,9 +61,9 @@ class Invoice extends AbstractResult
return $this->getData()['expirationTime'];
}
public function getMonitoringExpiration(): int
public function getMonitoringTime(): int
{
return $this->getData()['monitoringExpiration'];
return $this->getData()['monitoringTime'];
}
public function isArchived(): bool
@ -82,28 +71,6 @@ 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();
@ -121,11 +88,6 @@ 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

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

View File

@ -1,28 +0,0 @@
<?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

@ -59,25 +59,37 @@ class InvoicePaymentMethod extends AbstractResult
public function getNetworkFee(): string
{
$data = $this->getData();
return $data['networkFee'];
// BTCPay 2.0.0 compatibility: networkFee was renamed to paymentMethodFee.
return $data['networkFee'] ?? $data['paymentMethodFee'];
}
public function getPaymentMethod(): string
{
$data = $this->getData();
return $data['paymentMethod'];
// BTCPay 2.0.0 compatibility: paymentMethod was renamed to paymentMethodId.
return $data['paymentMethod'] ?? $data['paymentMethodId'];
}
public function getCryptoCode(): string
{
$data = $this->getData();
// For future compatibility check if cryptoCode exists.
if (isset($data['cryptoCode'])) {
return $data['cryptoCode'];
} else {
// Extract cryptoCode from paymentMethod string.
$parts = explode('-', $data['paymentMethod']);
$parts = explode('-', $this->getPaymentMethod());
return $parts[0];
}
}
/**
* New field as of BTCPay 2.0.0.
*/
public function getCurrency(): ?string
{
$data = $this->getData();
return $data['currency'] ?? null;
}
}

View File

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

66
src/Result/Offering.php Normal file
View File

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Result;
class Offering extends AbstractResult
{
public function getId(): string
{
return $this->getData()['id'];
}
public function getStoreId(): string
{
return $this->getData()['storeId'];
}
public function getAppId(): ?string
{
return $this->getData()['appId'] ?? null;
}
public function getAppName(): ?string
{
return $this->getData()['appName'] ?? null;
}
public function getSuccessRedirectUrl(): ?string
{
return $this->getData()['successRedirectUrl'] ?? null;
}
public function getMetadata(): ?array
{
return $this->getData()['metadata'] ?? null;
}
/**
* @return OfferingPlan[]
*/
public function getPlans(): array
{
$plans = [];
if (isset($this->getData()['plans']) && is_array($this->getData()['plans'])) {
foreach ($this->getData()['plans'] as $plan) {
$plans[] = new OfferingPlan($plan);
}
}
return $plans;
}
/**
* @return Feature[]
*/
public function getFeatures(): array
{
$features = [];
if (isset($this->getData()['features']) && is_array($this->getData()['features'])) {
foreach ($this->getData()['features'] as $feature) {
$features[] = new Feature($feature);
}
}
return $features;
}
}

View File

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

View File

@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Result;
class OfferingPlan extends AbstractResult
{
public function getId(): string
{
return $this->getData()['id'];
}
public function getName(): string
{
return $this->getData()['name'];
}
public function getStatus(): string
{
return $this->getData()['status'];
}
public function getPrice(): string
{
return $this->getData()['price'];
}
public function getCurrency(): string
{
return $this->getData()['currency'];
}
public function getRecurringType(): string
{
return $this->getData()['recurringType'];
}
public function getGracePeriodDays(): int
{
return $this->getData()['gracePeriodDays'];
}
public function getTrialDays(): int
{
return $this->getData()['trialDays'];
}
public function getDescription(): string
{
return $this->getData()['description'];
}
public function getMemberCount(): int
{
return $this->getData()['memberCount'];
}
public function isOptimisticActivation(): bool
{
return $this->getData()['optimisticActivation'];
}
public function isRenewable(): bool
{
return $this->getData()['renewable'];
}
/**
* @return string[]
*/
public function getFeatures(): array
{
return $this->getData()['features'] ?? [];
}
public function getMetadata(): ?array
{
return $this->getData()['metadata'] ?? null;
}
}

View File

@ -1,20 +0,0 @@
<?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;
}
}

113
src/Result/PlanCheckout.php Normal file
View File

@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Result;
class PlanCheckout extends AbstractResult
{
public function getId(): string
{
return $this->getData()['id'];
}
public function getSubscriber(): ?Subscriber
{
return isset($this->getData()['subscriber']) ? new Subscriber($this->getData()['subscriber']) : null;
}
public function getPlan(): OfferingPlan
{
return new OfferingPlan($this->getData()['plan']);
}
public function getBaseUrl(): string
{
return $this->getData()['baseUrl'];
}
public function getInvoiceId(): ?string
{
return $this->getData()['invoiceId'] ?? null;
}
public function getSuccessRedirectUrl(): ?string
{
return $this->getData()['successRedirectUrl'] ?? null;
}
public function getExpiration(): int
{
return $this->getData()['expiration'];
}
public function getRedirectUrl(): string
{
return $this->getData()['redirectUrl'];
}
public function getInvoiceMetadata(): ?array
{
return $this->getData()['invoiceMetadata'] ?? null;
}
public function getMetadata(): ?array
{
return $this->getData()['metadata'] ?? null;
}
public function isNewSubscriber(): bool
{
return $this->getData()['newSubscriber'];
}
public function isTrial(): bool
{
return $this->getData()['isTrial'];
}
public function getCreated(): int
{
return $this->getData()['created'];
}
public function isPlanStarted(): bool
{
return $this->getData()['planStarted'];
}
public function getNewSubscriberMetadata(): ?array
{
return $this->getData()['newSubscriberMetadata'] ?? null;
}
public function getRefundAmount(): ?string
{
return $this->getData()['refundAmount'] ?? null;
}
public function getCreditedByInvoice(): ?string
{
return $this->getData()['creditedByInvoice'] ?? null;
}
public function getOnPayBehavior(): ?string
{
return $this->getData()['onPayBehavior'] ?? null;
}
public function isExpired(): bool
{
return $this->getData()['isExpired'];
}
public function getUrl(): string
{
return $this->getData()['url'];
}
public function getCreditPurchase(): ?string
{
return $this->getData()['creditPurchase'] ?? null;
}
}

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Result;
class PortalSession extends AbstractResult
{
public function getId(): string
{
return $this->getData()['id'];
}
public function getBaseUrl(): string
{
return $this->getData()['baseUrl'];
}
public function getSubscriber(): Subscriber
{
return new Subscriber($this->getData()['subscriber']);
}
public function getExpiration(): ?int
{
return $this->getData()['expiration'] ?? null;
}
public function isExpired(): bool
{
return $this->getData()['isExpired'];
}
public function getUrl(): string
{
return $this->getData()['url'];
}
}

View File

@ -20,6 +20,12 @@ class PullPayment extends AbstractResult
return $data['name'];
}
public function getDescription(): string
{
$data = $this->getData();
return $data['description'];
}
public function getCurrency(): string
{
$data = $this->getData();

View File

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

View File

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

View File

@ -1,33 +0,0 @@
<?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

@ -1,20 +0,0 @@
<?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

@ -1,25 +0,0 @@
<?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,8 +4,6 @@ declare(strict_types=1);
namespace BTCPayServer\Result;
use BTCPayServer\Util\PreciseNumber;
class Store extends AbstractResult
{
public function getName(): string
@ -14,13 +12,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'];
@ -50,10 +48,10 @@ class Store extends AbstractResult
return $data['lightningDescriptionTemplate'];
}
public function getPaymentTolerance(): PreciseNumber
public function getPaymentTolerance(): int
{
$data = $this->getData();
return PreciseNumber::parseFloat($data['paymentTolerance']);
return $data['paymentTolerance'];
}
public function anyoneCanCreateInvoice(): bool
@ -104,25 +102,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'];
@ -146,7 +144,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,45 +6,39 @@ namespace BTCPayServer\Result;
class StoreEmailSettings extends AbstractResult
{
public function getServer(): ?string
public function getServer(): string
{
$data = $this->getData();
return $data['server'];
}
public function getPort(): ?int
public function getPort(): string
{
$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'];
}
}

View File

@ -1,20 +0,0 @@
<?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,10 +25,4 @@ 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,13 +4,11 @@ declare(strict_types=1);
namespace BTCPayServer\Result;
use BTCPayServer\Util\PreciseNumber;
class StoreOnChainWalletFeeRate extends AbstractResult
{
public function getFeeRate(): PreciseNumber
public function getFeeRate(): float
{
$data = $this->getData();
return PreciseNumber::parseFloat($data['feeRate']);
return $data['feeRate'];
}
}

View File

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

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 \BTCPayServer\Result\StoreOnChainWalletUTXO($storeWalletUTXO);
}
return $storeWalletUTXOs;
}
}

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 StoreOnChainWalletUtxo($storeWalletUtxo);
}
return $storeWalletUtxos;
}
}

View File

@ -13,17 +13,40 @@ class StorePaymentMethodCollection extends AbstractListResult
{
$r = [];
foreach ($this->getData() as $paymentMethod => $paymentMethodData) {
// BTCPay 2.0 compatibility: List is not a keyed array anymore so fix it here.
if (is_numeric($paymentMethod)) {
$paymentMethod = $paymentMethodData['paymentMethodId'];
// Extract the cryptoCode from the paymentMethodId. e.g. "BTC-CHAIN" -> "BTC"
$parts = explode('-', $paymentMethod);
$extractedCryptoCode = $parts[0];
}
// Consistency: Flatten the array to be consistent with the specific
// payment method endpoints.
$paymentMethodData += $paymentMethodData['data'];
unset($paymentMethodData['data']);
if (isset($paymentMethodData['data'])) {
$paymentMethodData += $paymentMethodData['data'];
unset($paymentMethodData['data']);
}
if (strpos($paymentMethod, 'LightningNetwork') !== false) {
// BTCPay 2.0 compatibility: Handle config data if exists.
if (isset($paymentMethodData['config'])) {
$paymentMethodData += $paymentMethodData['config'];
unset($paymentMethodData['config']);
}
// BTCPay 2.0 compatibility: Check for renamed LN payment method id.
if (preg_match('/(LightningNetwork|-LN$)/', $paymentMethod)) {
// Consistency: Add back the cryptoCode missing on this endpoint
// results until it is there.
if (!isset($paymentMethodData['cryptoCode'])) {
$paymentMethodData['cryptoCode'] = str_replace('-LightningNetwork', '', $paymentMethod);
}
// BTCPay 2.0 compatibility: put the extracted cryptoCode in the cryptoCode field.
if (isset($extractedCryptoCode)) {
$paymentMethodData['cryptoCode'] = $extractedCryptoCode;
}
$r[] = new StorePaymentMethodLightningNetwork($paymentMethodData, $paymentMethod);
} else {
// Consistency: Add back the cryptoCode missing on this endpoint
@ -31,6 +54,12 @@ class StorePaymentMethodCollection extends AbstractListResult
if (!isset($paymentMethodData['cryptoCode'])) {
$paymentMethodData['cryptoCode'] = $paymentMethod;
}
// BTCPay 2.0 compatibility: put the currency code in the cryptoCode field.
if (isset($extractedCryptoCode)) {
$paymentMethodData['cryptoCode'] = $extractedCryptoCode;
}
$r[] = new StorePaymentMethodOnChain($paymentMethodData, $paymentMethod);
}
}

93
src/Result/Subscriber.php Normal file
View File

@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace BTCPayServer\Result;
class Subscriber extends AbstractResult
{
public function getCreated(): int
{
return $this->getData()['created'];
}
public function getCustomer(): Customer
{
return new Customer($this->getData()['customer']);
}
public function getOffering(): Offering
{
return new Offering($this->getData()['offering']);
}
public function getPlan(): OfferingPlan
{
return new OfferingPlan($this->getData()['plan']);
}
public function getPeriodEnd(): ?int
{
return $this->getData()['periodEnd'] ?? null;
}
public function getTrialEnd(): ?int
{
return $this->getData()['trialEnd'] ?? null;
}
public function getGracePeriodEnd(): ?int
{
return $this->getData()['gracePeriodEnd'] ?? null;
}
public function isActive(): bool
{
return $this->getData()['isActive'];
}
public function isSuspended(): bool
{
return $this->getData()['isSuspended'];
}
public function getSuspensionReason(): ?string
{
return $this->getData()['suspensionReason'] ?? null;
}
public function isAutoRenew(): bool
{
return $this->getData()['autoRenew'];
}
public function getMetadata(): ?array
{
return $this->getData()['metadata'] ?? null;
}
public function getProcessingInvoiceId(): ?string
{
return $this->getData()['processingInvoiceId'] ?? null;
}
public function getNextPlan(): ?OfferingPlan
{
return isset($this->getData()['nextPlan']) ? new OfferingPlan($this->getData()['nextPlan']) : null;
}
public function getScheduledPlan(): ?OfferingPlan
{
return isset($this->getData()['scheduledPlan']) ? new OfferingPlan($this->getData()['scheduledPlan']) : null;
}
public function getScheduledPlanActivatesAt(): ?int
{
return $this->getData()['scheduledPlanActivatesAt'] ?? null;
}
public function getPhase(): string
{
return $this->getData()['phase'];
}
}

View File

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

View File

@ -1,27 +0,0 @@
<?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());
}
}

View File

@ -1,215 +0,0 @@
<?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

@ -1,197 +0,0 @@
<?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

@ -1,68 +0,0 @@
<?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

@ -1,83 +0,0 @@
<?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);
}
}

View File

@ -1,190 +0,0 @@
<?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);
}
}

View File

@ -1,49 +0,0 @@
<?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());
}
}
}

View File

@ -1,114 +0,0 @@
<?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

@ -1,198 +0,0 @@
<?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());
}
}
}

View File

@ -1,119 +0,0 @@
<?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());
}
}