Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Subscriptions #364

Open
wants to merge 7 commits into
base: 9.2.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ composer.phar
.phpunit.result.cache
test/unit/_html

PrivateKey.key
PrivateKey.key

# Local development
.lando.yml
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"guzzlehttp/guzzle": "^7.0",
"symfony/yaml": "^5.0 || ^6.0 || ^7.0",
"netresearch/jsonmapper": "^5.0",
"symfony/console": "^4.4 || ^5.4 || ^6.0"
"symfony/console": "^4.4 || ^5.4 || ^6.0 || ^7.0"
},
"authors": [
{
Expand Down
83 changes: 82 additions & 1 deletion src/BitPaySDK/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use BitPaySDK\Client\RateClient;
use BitPaySDK\Client\RefundClient;
use BitPaySDK\Client\SettlementClient;
use BitPaySDK\Client\SubscriptionClient;
use BitPaySDK\Client\TokenClient;
use BitPaySDK\Client\WalletClient;
use BitPaySDK\Exceptions\BitPayApiException;
Expand All @@ -36,6 +37,7 @@
use BitPaySDK\Model\Rate\Rate;
use BitPaySDK\Model\Rate\Rates;
use BitPaySDK\Model\Settlement\Settlement;
use BitPaySDK\Model\Subscription\Subscription;
use BitPaySDK\Model\Wallet\Wallet;
use BitPaySDK\Util\RESTcli\RESTcli;
use Exception;
Expand Down Expand Up @@ -576,7 +578,7 @@ public function getBill(string $billId, string $facade = Facade::MERCHANT, bool
*
* @see https://developer.bitpay.com/reference/retrieve-bills-by-status Retrieve Bills by Status
*
* @param string|null The status to filter the bills.
* @param $status string|null The status to filter the bills.
* @return Bill[]
* @throws BitPayApiException
* @throws BitPayGenericException
Expand Down Expand Up @@ -625,6 +627,75 @@ public function deliverBill(string $billId, string $billToken, bool $signRequest
return $billClient->deliver($billId, $billToken, $signRequest);
}

/**
* Create a BitPay Subscription.
*
* @see https://developer.bitpay.com/reference/create-a-subscription Create a Subscription
*
* @param Subscription $subscription A Subscription object with request parameters defined.
* @return Subscription Created Subscription object
* @throws BitPayApiException
* @throws BitPayGenericException
*/
public function createSubscription(Subscription $subscription): Subscription
{
$subscriptionClient = $this->getSubscriptionClient();

return $subscriptionClient->create($subscription);
}

/**
* Retrieve a BitPay subscription by its ID.
*
* @see https://developer.bitpay.com/reference/retrieve-a-subscription Retrieve a Subscription
*
* @param $subscriptionId string The ID of the subscription to retrieve.
* @return Subscription
* @throws BitPayApiException
* @throws BitPayGenericException
*/
public function getSubscription(string $subscriptionId): Subscription
{
$subscriptionClient = $this->getSubscriptionClient();

return $subscriptionClient->get($subscriptionId);
}

/**
* Retrieve a collection of BitPay subscriptions.
*
* @see https://developer.bitpay.com/reference/retrieve-subscriptions-by-status Retrieve Subscriptions by Status
*
* @param $status string|null The status on which to filter the subscriptions.
* @return Subscription[] Filtered list of Subscription objects
* @throws BitPayApiException
* @throws BitPayGenericException
*/
public function getSubscriptions(?string $status = null): array
{
$subscriptionClient = $this->getSubscriptionClient();

return $subscriptionClient->getSubscriptions($status);
}

/**
* Update a BitPay Subscription.
*
* @see https://developer.bitpay.com/reference/update-a-subscription Update a Subscription
*
* @param Subscription $subscription A Subscription object with the parameters to update defined.
* @param string $subscriptionId The ID of the Subscription to update.
* @return Subscription Updated Subscription object
* @throws BitPayApiException
* @throws BitPayGenericException
*/
public function updateSubscription(Subscription $subscription, string $subscriptionId): Subscription
{
$subscriptionClient = $this->getSubscriptionClient();

return $subscriptionClient->update($subscription, $subscriptionId);
}

/**
* Retrieve the exchange rate table maintained by BitPay.
* @see https://bitpay.com/bitcoin-exchange-rates
Expand Down Expand Up @@ -1113,6 +1184,16 @@ protected function getBillClient(): BillClient
return BillClient::getInstance($this->tokenCache, $this->restCli);
}

/**
* Gets subscription client
*
* @return SubscriptionClient the subscription client
*/
protected function getSubscriptionClient(): SubscriptionClient
{
return SubscriptionClient::getInstance($this->tokenCache, $this->restCli);
}

/**
* Gets rate client
*
Expand Down
172 changes: 172 additions & 0 deletions src/BitPaySDK/Client/SubscriptionClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
<?php

/**
* Copyright (c) 2025 BitPay
**/

declare(strict_types=1);

namespace BitPaySDK\Client;

use BitPaySDK\Exceptions\BitPayApiException;
use BitPaySDK\Exceptions\BitPayExceptionProvider;
use BitPaySDK\Exceptions\BitPayGenericException;
use BitPaySDK\Model\Subscription\Subscription;
use BitPaySDK\Model\Facade;
use BitPaySDK\Tokens;
use BitPaySDK\Util\JsonMapperFactory;
use BitPaySDK\Util\RESTcli\RESTcli;
use Exception;

/**
* Handles interactions with the subscriptions endpoints.
*
* @package BitPaySDK\Client
* @author BitPay Integrations <[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
*/
class SubscriptionClient
{
private static ?self $instance = null;
private Tokens $tokenCache;
private RESTcli $restCli;

private function __construct(Tokens $tokenCache, RESTcli $restCli)
{
$this->tokenCache = $tokenCache;
$this->restCli = $restCli;
}

/**
* Factory method for Subscription Client.
*
* @param Tokens $tokenCache
* @param RESTcli $restCli
* @return static
*/
public static function getInstance(Tokens $tokenCache, RESTcli $restCli): self
{
if (!self::$instance) {
self::$instance = new self($tokenCache, $restCli);
}

return self::$instance;
}

/**
* Create a BitPay Subscription.
*
* @param Subscription $subscription A Subscription object with request parameters defined.
* @return Subscription Created Subscription object
* @throws BitPayApiException
* @throws BitPayGenericException
*/
public function create(Subscription $subscription): Subscription
{
$subscription->setToken($this->tokenCache->getTokenByFacade(Facade::MERCHANT));

$responseJson = $this->restCli->post("subscriptions", $subscription->toArray());

try {
return $this->mapJsonToSubscriptionClass($responseJson);
} catch (Exception $e) {
BitPayExceptionProvider::throwDeserializeResourceException('Subscription', $e->getMessage());
}
}

/**
* Retrieve a BitPay subscription by its resource ID.
*
* @param $subscriptionId string The id of the subscription to retrieve.
* @return Subscription Retrieved Subscription object
* @throws BitPayApiException
* @throws BitPayGenericException
*/
public function get(string $subscriptionId): Subscription
{
$params = [];
$params["token"] = $this->tokenCache->getTokenByFacade(Facade::MERCHANT);

$responseJson = $this->restCli->get("subscriptions/" . $subscriptionId, $params);

try {
return $this->mapJsonToSubscriptionClass($responseJson);
} catch (Exception $e) {
BitPayExceptionProvider::throwDeserializeResourceException('Subscription', $e->getMessage());
}
}

/**
* Retrieve a collection of BitPay subscriptions.
*
* @param string|null $status The status to filter the subscriptions.
* @return Subscription[] Filtered list of Subscription objects
* @throws BitPayApiException
* @throws BitPayGenericException
*/
public function getSubscriptions(?string $status = null): array
{
$params = [];
$params["token"] = $this->tokenCache->getTokenByFacade(Facade::MERCHANT);
if ($status) {
$params["status"] = $status;
}

$responseJson = $this->restCli->get("subscriptions", $params);

try {
$mapper = JsonMapperFactory::create();
return $mapper->mapArray(
json_decode($responseJson, true, 512, JSON_THROW_ON_ERROR),
[],
Subscription::class
);
} catch (Exception $e) {
BitPayExceptionProvider::throwDeserializeResourceException('Subscription', $e->getMessage());
}
}

/**
* Update a BitPay Subscription.
*
* @param Subscription $subscription A Subscription object with the parameters to update defined.
* @param string $subscriptionId The ID of the Subscription to update.
* @return Subscription Updated Subscription object
* @throws BitPayApiException
* @throws BitPayGenericException
*/
public function update(Subscription $subscription, string $subscriptionId): Subscription
{
$subscriptionToken = $this->get($subscription->getId())->getToken();
$subscription->setToken($subscriptionToken);

$responseJson = $this->restCli->update("subscriptions/" . $subscriptionId, $subscription->toArray());

try {
$mapper = JsonMapperFactory::create();

return $mapper->map(
json_decode($responseJson, true, 512, JSON_THROW_ON_ERROR),
$subscription
);
} catch (Exception $e) {
BitPayExceptionProvider::throwDeserializeResourceException('Subscription', $e->getMessage());
}
}

/**
* @param string|null $responseJson
* @return Subscription
* @throws \JsonException
* @throws \JsonMapper_Exception
*/
private function mapJsonToSubscriptionClass(?string $responseJson): Subscription
{
$mapper = JsonMapperFactory::create();

return $mapper->map(
json_decode($responseJson, true, 512, JSON_THROW_ON_ERROR),
new Subscription()
);
}
}
1 change: 1 addition & 0 deletions src/BitPaySDK/Env.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ interface Env
public const BITPAY_PLUGIN_INFO = "BitPay_PHP_Client_v9.2.0";
public const BITPAY_API_FRAME = "std";
public const BITPAY_API_FRAME_VERSION = "1.0.0";
public const BITPAY_DATETIME_FORMAT = 'Y-m-d\TH:i:s\Z';
}
2 changes: 2 additions & 0 deletions src/BitPaySDK/Model/Settlement/Settlement.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ public function setPayoutInfo(PayoutInfo $payoutInfo): void
* Gets Status of the settlement. Possible statuses are "new", "processing", "rejected" and "completed".
*
* @return string|null
* @see SettlementStatus
*/
public function getStatus(): ?string
{
Expand All @@ -151,6 +152,7 @@ public function getStatus(): ?string
* Sets Status of the settlement. Possible statuses are "new", "processing", "rejected" and "completed".
*
* @param string $status
* @see SettlementStatus
*/
public function setStatus(string $status): void
{
Expand Down
29 changes: 29 additions & 0 deletions src/BitPaySDK/Model/Settlement/SettlementStatus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

/**
* Copyright (c) 2025 BitPay
**/

/**
* @author BitPay Integrations <[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
*/

namespace BitPaySDK\Model\Settlement;

/**
* Status of the settlement.
* Possible statuses are "new", "processing", "rejected" and "completed".
*
* @package BitPaySDK\Model\Settlement
* @author BitPay Integrations <[email protected]>
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @see https://developer.bitpay.com/reference/settlements Settlements
*/
interface SettlementStatus
{
public const NEW = "new";
public const PROCESSING = "processing";
public const REJECTED = "rejected";
public const COMPLETED = "completed";
}
Loading
Loading