Skip to content

Commit 299fe76

Browse files
committed
Implement Subscriptions
+ Created subscription related models + Integrated subscription actions into the main Client class + Added a convenience const to Env class for BitPay's datetime format + Added functional Subscription tests + Updated dependencies
1 parent 13cc6e0 commit 299fe76

File tree

11 files changed

+1632
-351
lines changed

11 files changed

+1632
-351
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,7 @@ composer.phar
1212
.phpunit.result.cache
1313
test/unit/_html
1414

15-
PrivateKey.key
15+
PrivateKey.key
16+
17+
# Local development
18+
.lando.yml

composer.lock

Lines changed: 296 additions & 349 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/BitPaySDK/Client.php

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use BitPaySDK\Client\RateClient;
1919
use BitPaySDK\Client\RefundClient;
2020
use BitPaySDK\Client\SettlementClient;
21+
use BitPaySDK\Client\SubscriptionClient;
2122
use BitPaySDK\Client\TokenClient;
2223
use BitPaySDK\Client\WalletClient;
2324
use BitPaySDK\Exceptions\BitPayApiException;
@@ -36,6 +37,7 @@
3637
use BitPaySDK\Model\Rate\Rate;
3738
use BitPaySDK\Model\Rate\Rates;
3839
use BitPaySDK\Model\Settlement\Settlement;
40+
use BitPaySDK\Model\Subscription\Subscription;
3941
use BitPaySDK\Model\Wallet\Wallet;
4042
use BitPaySDK\Util\RESTcli\RESTcli;
4143
use Exception;
@@ -576,7 +578,7 @@ public function getBill(string $billId, string $facade = Facade::MERCHANT, bool
576578
*
577579
* @see https://developer.bitpay.com/reference/retrieve-bills-by-status Retrieve Bills by Status
578580
*
579-
* @param string|null The status to filter the bills.
581+
* @param $status string|null The status to filter the bills.
580582
* @return Bill[]
581583
* @throws BitPayApiException
582584
* @throws BitPayGenericException
@@ -625,6 +627,75 @@ public function deliverBill(string $billId, string $billToken, bool $signRequest
625627
return $billClient->deliver($billId, $billToken, $signRequest);
626628
}
627629

630+
/**
631+
* Create a BitPay Subscription.
632+
*
633+
* @see https://developer.bitpay.com/reference/create-a-subscription Create a Subscription
634+
*
635+
* @param Subscription $subscription A Subscription object with request parameters defined.
636+
* @return Subscription Created Subscription object
637+
* @throws BitPayApiException
638+
* @throws BitPayGenericException
639+
*/
640+
public function createSubscription(Subscription $subscription): Subscription
641+
{
642+
$subscriptionClient = $this->getSubscriptionClient();
643+
644+
return $subscriptionClient->create($subscription);
645+
}
646+
647+
/**
648+
* Retrieve a BitPay subscription by its ID.
649+
*
650+
* @see https://developer.bitpay.com/reference/retrieve-a-subscription Retrieve a Subscription
651+
*
652+
* @param $subscriptionId string The ID of the subscription to retrieve.
653+
* @return Subscription
654+
* @throws BitPayApiException
655+
* @throws BitPayGenericException
656+
*/
657+
public function getSubscription(string $subscriptionId): Subscription
658+
{
659+
$subscriptionClient = $this->getSubscriptionClient();
660+
661+
return $subscriptionClient->get($subscriptionId);
662+
}
663+
664+
/**
665+
* Retrieve a collection of BitPay subscriptions.
666+
*
667+
* @see https://developer.bitpay.com/reference/retrieve-subscriptions-by-status Retrieve Subscriptions by Status
668+
*
669+
* @param $status string|null The status on which to filter the subscriptions.
670+
* @return Subscription[] Filtered list of Subscription objects
671+
* @throws BitPayApiException
672+
* @throws BitPayGenericException
673+
*/
674+
public function getSubscriptions(?string $status = null): array
675+
{
676+
$subscriptionClient = $this->getSubscriptionClient();
677+
678+
return $subscriptionClient->getSubscriptions($status);
679+
}
680+
681+
/**
682+
* Update a BitPay Subscription.
683+
*
684+
* @see https://developer.bitpay.com/reference/update-a-subscription Update a Subscription
685+
*
686+
* @param Subscription $subscription A Subscription object with the parameters to update defined.
687+
* @param string $subscriptionId The ID of the Subscription to update.
688+
* @return Subscription Updated Subscription object
689+
* @throws BitPayApiException
690+
* @throws BitPayGenericException
691+
*/
692+
public function updateSubscription(Subscription $subscription, string $subscriptionId): Subscription
693+
{
694+
$subscriptionClient = $this->getSubscriptionClient();
695+
696+
return $subscriptionClient->update($subscription, $subscriptionId);
697+
}
698+
628699
/**
629700
* Retrieve the exchange rate table maintained by BitPay.
630701
* @see https://bitpay.com/bitcoin-exchange-rates
@@ -1113,6 +1184,16 @@ protected function getBillClient(): BillClient
11131184
return BillClient::getInstance($this->tokenCache, $this->restCli);
11141185
}
11151186

1187+
/**
1188+
* Gets subscription client
1189+
*
1190+
* @return SubscriptionClient the subscription client
1191+
*/
1192+
protected function getSubscriptionClient(): SubscriptionClient
1193+
{
1194+
return SubscriptionClient::getInstance($this->tokenCache, $this->restCli);
1195+
}
1196+
11161197
/**
11171198
* Gets rate client
11181199
*
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<?php
2+
3+
/**
4+
* Copyright (c) 2025 BitPay
5+
**/
6+
7+
declare(strict_types=1);
8+
9+
namespace BitPaySDK\Client;
10+
11+
use BitPaySDK\Exceptions\BitPayApiException;
12+
use BitPaySDK\Exceptions\BitPayExceptionProvider;
13+
use BitPaySDK\Exceptions\BitPayGenericException;
14+
use BitPaySDK\Model\Subscription\Subscription;
15+
use BitPaySDK\Model\Facade;
16+
use BitPaySDK\Tokens;
17+
use BitPaySDK\Util\JsonMapperFactory;
18+
use BitPaySDK\Util\RESTcli\RESTcli;
19+
use Exception;
20+
21+
/**
22+
* Handles interactions with the subscriptions endpoints.
23+
*
24+
* @package BitPaySDK\Client
25+
* @author BitPay Integrations <[email protected]>
26+
* @license http://www.opensource.org/licenses/mit-license.php MIT
27+
*/
28+
class SubscriptionClient
29+
{
30+
private static ?self $instance = null;
31+
private Tokens $tokenCache;
32+
private RESTcli $restCli;
33+
34+
private function __construct(Tokens $tokenCache, RESTcli $restCli)
35+
{
36+
$this->tokenCache = $tokenCache;
37+
$this->restCli = $restCli;
38+
}
39+
40+
/**
41+
* Factory method for Subscription Client.
42+
*
43+
* @param Tokens $tokenCache
44+
* @param RESTcli $restCli
45+
* @return static
46+
*/
47+
public static function getInstance(Tokens $tokenCache, RESTcli $restCli): self
48+
{
49+
if (!self::$instance) {
50+
self::$instance = new self($tokenCache, $restCli);
51+
}
52+
53+
return self::$instance;
54+
}
55+
56+
/**
57+
* Create a BitPay Subscription.
58+
*
59+
* @param Subscription $subscription A Subscription object with request parameters defined.
60+
* @return Subscription Created Subscription object
61+
* @throws BitPayApiException
62+
* @throws BitPayGenericException
63+
*/
64+
public function create(Subscription $subscription): Subscription
65+
{
66+
$subscription->setToken($this->tokenCache->getTokenByFacade(Facade::MERCHANT));
67+
68+
$responseJson = $this->restCli->post("subscriptions", $subscription->toArray());
69+
70+
try {
71+
return $this->mapJsonToSubscriptionClass($responseJson);
72+
} catch (Exception $e) {
73+
BitPayExceptionProvider::throwDeserializeResourceException('Subscription', $e->getMessage());
74+
}
75+
}
76+
77+
/**
78+
* Retrieve a BitPay subscription by its resource ID.
79+
*
80+
* @param $subscriptionId string The id of the subscription to retrieve.
81+
* @return Subscription Retrieved Subscription object
82+
* @throws BitPayApiException
83+
* @throws BitPayGenericException
84+
*/
85+
public function get(string $subscriptionId): Subscription
86+
{
87+
$params = [];
88+
$params["token"] = $this->tokenCache->getTokenByFacade(Facade::MERCHANT);
89+
90+
$responseJson = $this->restCli->get("subscriptions/" . $subscriptionId, $params);
91+
92+
try {
93+
return $this->mapJsonToSubscriptionClass($responseJson);
94+
} catch (Exception $e) {
95+
BitPayExceptionProvider::throwDeserializeResourceException('Subscription', $e->getMessage());
96+
}
97+
}
98+
99+
/**
100+
* Retrieve a collection of BitPay subscriptions.
101+
*
102+
* @param string|null $status The status to filter the subscriptions.
103+
* @return Subscription[] Filtered list of Subscription objects
104+
* @throws BitPayApiException
105+
* @throws BitPayGenericException
106+
*/
107+
public function getSubscriptions(?string $status = null): array
108+
{
109+
$params = [];
110+
$params["token"] = $this->tokenCache->getTokenByFacade(Facade::MERCHANT);
111+
if ($status) {
112+
$params["status"] = $status;
113+
}
114+
115+
$responseJson = $this->restCli->get("subscriptions", $params);
116+
117+
try {
118+
$mapper = JsonMapperFactory::create();
119+
return $mapper->mapArray(
120+
json_decode($responseJson, true, 512, JSON_THROW_ON_ERROR),
121+
[],
122+
Subscription::class
123+
);
124+
} catch (Exception $e) {
125+
BitPayExceptionProvider::throwDeserializeResourceException('Subscription', $e->getMessage());
126+
}
127+
}
128+
129+
/**
130+
* Update a BitPay Subscription.
131+
*
132+
* @param Subscription $subscription A Subscription object with the parameters to update defined.
133+
* @param string $subscriptionId The ID of the Subscription to update.
134+
* @return Subscription Updated Subscription object
135+
* @throws BitPayApiException
136+
* @throws BitPayGenericException
137+
*/
138+
public function update(Subscription $subscription, string $subscriptionId): Subscription
139+
{
140+
$subscriptionToken = $this->get($subscription->getId())->getToken();
141+
$subscription->setToken($subscriptionToken);
142+
143+
$responseJson = $this->restCli->update("subscriptions/" . $subscriptionId, $subscription->toArray());
144+
145+
try {
146+
$mapper = JsonMapperFactory::create();
147+
148+
return $mapper->map(
149+
json_decode($responseJson, true, 512, JSON_THROW_ON_ERROR),
150+
$subscription
151+
);
152+
} catch (Exception $e) {
153+
BitPayExceptionProvider::throwDeserializeResourceException('Subscription', $e->getMessage());
154+
}
155+
}
156+
157+
/**
158+
* @param string|null $responseJson
159+
* @return Subscription
160+
* @throws \JsonException
161+
* @throws \JsonMapper_Exception
162+
*/
163+
private function mapJsonToSubscriptionClass(?string $responseJson): Subscription
164+
{
165+
$mapper = JsonMapperFactory::create();
166+
167+
return $mapper->map(
168+
json_decode($responseJson, true, 512, JSON_THROW_ON_ERROR),
169+
new Subscription()
170+
);
171+
}
172+
}

src/BitPaySDK/Env.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ interface Env
2020
public const BITPAY_PLUGIN_INFO = "BitPay_PHP_Client_v9.2.0";
2121
public const BITPAY_API_FRAME = "std";
2222
public const BITPAY_API_FRAME_VERSION = "1.0.0";
23+
public const BITPAY_DATETIME_FORMAT = 'Y-m-d\TH:i:s\Z';
2324
}

0 commit comments

Comments
 (0)