diff --git a/.gitignore b/.gitignore index aa9bf3d2..dcdaa3a6 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,7 @@ composer.phar .phpunit.result.cache test/unit/_html -PrivateKey.key \ No newline at end of file +PrivateKey.key + +# Local development +.lando.yml \ No newline at end of file diff --git a/composer.json b/composer.json index 08c4163d..f007bd8c 100644 --- a/composer.json +++ b/composer.json @@ -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": [ { diff --git a/src/BitPaySDK/Client.php b/src/BitPaySDK/Client.php index cb91d1e2..4fb9403e 100644 --- a/src/BitPaySDK/Client.php +++ b/src/BitPaySDK/Client.php @@ -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; @@ -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; @@ -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 @@ -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 @@ -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 * diff --git a/src/BitPaySDK/Client/SubscriptionClient.php b/src/BitPaySDK/Client/SubscriptionClient.php new file mode 100644 index 00000000..5588d33d --- /dev/null +++ b/src/BitPaySDK/Client/SubscriptionClient.php @@ -0,0 +1,172 @@ + + * @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() + ); + } +} diff --git a/src/BitPaySDK/Env.php b/src/BitPaySDK/Env.php index 2c1a9e09..7e8d3d40 100644 --- a/src/BitPaySDK/Env.php +++ b/src/BitPaySDK/Env.php @@ -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'; } diff --git a/src/BitPaySDK/Model/Settlement/Settlement.php b/src/BitPaySDK/Model/Settlement/Settlement.php index 43d0204b..9c95ce0d 100644 --- a/src/BitPaySDK/Model/Settlement/Settlement.php +++ b/src/BitPaySDK/Model/Settlement/Settlement.php @@ -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 { @@ -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 { diff --git a/src/BitPaySDK/Model/Settlement/SettlementStatus.php b/src/BitPaySDK/Model/Settlement/SettlementStatus.php new file mode 100644 index 00000000..e848653a --- /dev/null +++ b/src/BitPaySDK/Model/Settlement/SettlementStatus.php @@ -0,0 +1,29 @@ + + * @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 + * @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"; +} diff --git a/src/BitPaySDK/Model/Subscription/BillData.php b/src/BitPaySDK/Model/Subscription/BillData.php new file mode 100644 index 00000000..74611b73 --- /dev/null +++ b/src/BitPaySDK/Model/Subscription/BillData.php @@ -0,0 +1,485 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + */ +class BillData +{ + protected ?bool $emailBill = null; + protected ?array $cc = null; + protected ?string $number = null; + protected ?string $currency = null; + protected ?string $name = null; + protected ?string $address1 = null; + protected ?string $address2 = null; + protected ?string $city = null; + protected ?string $state = null; + protected ?string $zip = null; + protected ?string $country = null; + protected ?string $email = null; + protected ?string $phone = null; + protected ?string $dueDate = null; + protected ?bool $passProcessingFee = null; + protected array $items = []; + + /** + * Constructor, create a minimal request BillData object. + * + * @param string|null $number Recurring bill identifier, specified by merchant + * @param string|null $currency The three digit currency code used to compute the billData's amount. + * @param string|null $email The email address of the receiver for this billData. + * @param string|\DateTime|null $dueDate Date and time at which a bill is due, ISO-8601 format yyyy-mm-ddThh:mm:ssZ + * (UTC). + * @param array|null $items The list of line items to add to this billData. + */ + public function __construct( + ?string $number = null, + ?string $currency = null, + ?string $email = null, + string|\DateTime|null $dueDate = null, + ?array $items = null + ) { + $this->number = $number; + $this->currency = $currency ?: Currency::USD; + $this->email = $email; + $this->setEmailBill(); + + if (!$dueDate || is_a($dueDate, \DateTime::class)) { + $dueDate = ($dueDate ? new \DateTime('now') : $dueDate)->format(Env::BITPAY_DATETIME_FORMAT); + } + $this->dueDate = $dueDate; + + if (!$items) { + $items = []; + } + $this->setItems($items); + } + + /** + * Gets billData emailBill + * + * If set to `true`, BitPay will automatically issue recurring bills to the `email` address provided once the + * status of the subscription is set to `active`. + * + * @return bool|null + */ + public function getEmailBill(): ?bool + { + return $this->emailBill; + } + + /** + * Sets billData's emailBill + * + * If set to `true`, BitPay will automatically issue recurring bills to the `email` address provided once the + * status of the subscription is set to `active`. + * + * @param bool|null $emailBill + * @return void + */ + public function setEmailBill(?bool $emailBill = true): void + { + $this->emailBill = $emailBill; + } + + /** + * Gets items from billData + * + * @return Item[] + */ + public function getItems(): array + { + return $this->items; + } + + /** + * Sets BillData's items + * + * @param Item[] $items List of line items + */ + public function setItems(array $items): void + { + $itemsArray = []; + + foreach ($items as $item) { + if ($item instanceof Item) { + $itemsArray[] = $item; + } else { + $itemsArray[] = Item::createFromArray((array)$item); + } + } + + $this->items = $itemsArray; + } + + /** + * Gets items as array from billData + * + * @return array|null items as array from billData + */ + public function getItemsAsArray(): ?array + { + $items = []; + + foreach ($this->items as $item) { + $items[] = $item->toArray(); + } + + return $items; + } + + /** + * Get billData data as array + * + * @return array billData data as array + */ + public function toArray(): array + { + $elements = [ + 'emailBill' => $this->emailBill, + 'cc' => $this->getCc(), + 'number' => $this->getNumber(), + 'currency' => $this->getCurrency(), + 'name' => $this->getName(), + 'address1' => $this->getAddress1(), + 'address2' => $this->getAddress2(), + 'city' => $this->getCity(), + 'state' => $this->getState(), + 'zip' => $this->getZip(), + 'country' => $this->getCountry(), + 'email' => $this->getEmail(), + 'phone' => $this->getPhone(), + 'dueDate' => $this->getDueDate(), + 'passProcessingFee' => $this->getPassProcessingFee(), + 'items' => $this->getItemsAsArray(), + ]; + + foreach ($elements as $key => $value) { + if (empty($value)) { + unset($elements[$key]); + } + } + + return $elements; + } + + /** + * Gets BillData cc + * + * Email addresses to which a copy of the billData must be sent + * + * @return array|null the cc + */ + public function getCc(): ?array + { + return $this->cc; + } + + /** + * Sets BillData's cc + * + * Email addresses to which a copy of the billData must be sent + * + * @param array $cc Email addresses to which a copy of the billData must be sent + */ + public function setCc(array $cc): void + { + $this->cc = $cc; + } + + /** + * Gets billData number + * + * BillData identifier, specified by merchant + * + * @return string|null the number + */ + public function getNumber(): ?string + { + return $this->number; + } + + /** + * Sets BillData's number + * + * BillData identifier, specified by merchant + * + * @param string $number BillData identifier, specified by merchant + */ + public function setNumber(string $number): void + { + $this->number = $number; + } + + /** + * Gets billData currency + * + * ISO 4217 3-character currency code. This is the currency associated with the price field + * + * @return string|null the billData currency + */ + public function getCurrency(): ?string + { + return $this->currency; + } + + /** + * Sets BillData's currency + * + * ISO 4217 3-character currency code. This is the currency associated with the price field + * + * @param string $currency 3-character currency code + * @throws BitPayValidationException + */ + public function setCurrency(string $currency): void + { + if (!Currency::isValid($currency)) { + BitPayExceptionProvider::throwInvalidCurrencyException($currency); + } + + $this->currency = $currency; + } + + /** + * Gets BillData recipient's name + * + * @return string|null the name + */ + public function getName(): ?string + { + return $this->name; + } + + /** + * Sets BillData recipient's name + * + * @param string $name BillData recipient's name + */ + public function setName(string $name): void + { + $this->name = $name; + } + + /** + * Gets BillData recipient's address + * + * @return string|null the address1 + */ + public function getAddress1(): ?string + { + return $this->address1; + } + + /** + * Sets BillData recipient's address + * + * @param string $address1 BillData recipient's address + */ + public function setAddress1(string $address1): void + { + $this->address1 = $address1; + } + + /** + * Gets BillData recipient's address + * + * @return string|null the address2 + */ + public function getAddress2(): ?string + { + return $this->address2; + } + + /** + * Sets BillData recipient's address + * + * @param string $address2 BillData recipient's address + */ + public function setAddress2(string $address2): void + { + $this->address2 = $address2; + } + + /** + * Gets BillData recipient's city + * + * @return string|null the city + */ + public function getCity(): ?string + { + return $this->city; + } + + /** + * Sets BillData recipient's city + * + * @param string $city BillData recipient's city + */ + public function setCity(string $city): void + { + $this->city = $city; + } + + /** + * Gets BillData recipient's state or province + * + * @return string|null the state + */ + public function getState(): ?string + { + return $this->state; + } + + /** + * Sets BillData recipient's state or province + * + * @param string $state BillData recipient's state or province + */ + public function setState(string $state): void + { + $this->state = $state; + } + + /** + * Gets BillData recipient's ZIP code + * + * @return string|null the zip + */ + public function getZip(): ?string + { + return $this->zip; + } + + /** + * Sets BillData recipient's ZIP code + * + * @param string $zip BillData recipient's ZIP code + */ + public function setZip(string $zip): void + { + $this->zip = $zip; + } + + /** + * Gets BillData recipient's country + * + * @return string|null the country + */ + public function getCountry(): ?string + { + return $this->country; + } + + /** + * Sets BillData recipient's country + * + * @param string $country BillData recipient's country + */ + public function setCountry(string $country): void + { + $this->country = $country; + } + + /** + * Gets billData email + * + * @return string|null the email + */ + public function getEmail(): ?string + { + return $this->email; + } + + /** + * Sets BillData's email + * + * @param string $email BillData's email + */ + public function setEmail(string $email): void + { + $this->email = $email; + } + + /** + * Gets BillData recipient's phone number + * + * @return string|null the phone + */ + public function getPhone(): ?string + { + return $this->phone; + } + + /** + * Sets BillData recipient's phone number + * + * @param string $phone BillData recipient's phone number + */ + public function setPhone(string $phone): void + { + $this->phone = $phone; + } + + /** + * Gets BillData due date + * + * Date and time at which a billData is due, ISO-8601 format yyyy-mm-ddThh:mm:ssZ. (UTC) + * + * @return string|null the number + * @see Env::BITPAY_DATETIME_FORMAT + */ + public function getDueDate(): ?string + { + return $this->dueDate; + } + + /** + * Sets BillData's due date + * + * Date and time at which a billData is due, ISO-8601 format yyyy-mm-ddThh:mm:ssZ. (UTC) + * + * @param string $dueDate Date and time at which a billData is due + * @see Env::BITPAY_DATETIME_FORMAT + */ + public function setDueDate(string $dueDate): void + { + $this->dueDate = $dueDate; + } + + /** + * Gets billData pass processing fee + * + * @return bool|null the pass processing fee + */ + public function getPassProcessingFee(): ?bool + { + return $this->passProcessingFee; + } + + /** + * Sets BillData's pass processing fee + * + * If set to `true`, BitPay's processing fee will be included in the amount charged on the invoice + * + * @param bool $passProcessingFee BillData's pass processing fee + */ + public function setPassProcessingFee(bool $passProcessingFee): void + { + $this->passProcessingFee = $passProcessingFee; + } +} diff --git a/src/BitPaySDK/Model/Subscription/Item.php b/src/BitPaySDK/Model/Subscription/Item.php new file mode 100644 index 00000000..3432fb4a --- /dev/null +++ b/src/BitPaySDK/Model/Subscription/Item.php @@ -0,0 +1,153 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + */ + +namespace BitPaySDK\Model\Subscription; + +/** + * @see https://developer.bitpay.com/reference/bills REST API Bills + * + * @package BitPaySDK\Model\Subscription + * @author BitPay Integrations + * @license http://www.opensource.org/licenses/mit-license.php MIT + */ + +#[\AllowDynamicProperties] +class Item +{ + protected ?string $id = null; + protected ?string $description = null; + protected ?float $price = null; + protected ?int $quantity = null; + + public function __construct() + { + } + + /** + * Gets id. + * + * @return string|null the id + */ + public function getId(): ?string + { + return $this->id; + } + + /** + * Sets id. + * + * @param string $id the id + */ + public function setId(string $id): void + { + $this->id = $id; + } + + /** + * Gets Line item description + * + * @return string|null the description + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * Sets Line item description + * + * @param string $description the description + */ + public function setDescription(string $description): void + { + $this->description = $description; + } + + /** + * Gets Line item unit price for the corresponding currency + * + * @return float|null the price + */ + public function getPrice(): ?float + { + return $this->price; + } + + /** + * Sets Line item unit price for the corresponding currency + * + * @param float $price the price + */ + public function setPrice(float $price): void + { + $this->price = $price; + } + + /** + * Gets Line item number of units + * + * @return int|null the quantity + */ + public function getQuantity(): ?int + { + return $this->quantity; + } + + /** + * Sets Line item number of units + * + * @param int $quantity the quantity + */ + public function setQuantity(int $quantity): void + { + $this->quantity = $quantity; + } + + /** + * @param array $item List of line items + * @return Item + */ + public static function createFromArray(array $item): Item + { + $instance = new self(); + + foreach ($item as $key => $value) { + $instance->{$key} = $value; + } + + return $instance; + } + + /** + * Gets Item data as array + * + * @return array item data as array + */ + public function toArray(): array + { + $elements = [ + 'id' => $this->getId(), + 'description' => $this->getDescription(), + 'price' => $this->getPrice(), + 'quantity' => $this->getQuantity(), + ]; + + foreach ($elements as $key => $value) { + if (empty($value)) { + unset($elements[$key]); + } + } + + return $elements; + } +} diff --git a/src/BitPaySDK/Model/Subscription/Subscription.php b/src/BitPaySDK/Model/Subscription/Subscription.php new file mode 100644 index 00000000..80454ebc --- /dev/null +++ b/src/BitPaySDK/Model/Subscription/Subscription.php @@ -0,0 +1,270 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + */ +class Subscription +{ + protected ?string $id = null; + protected ?string $status = null; + protected BillData $billData; + protected ?string $merchant = null; + protected ?string $schedule = null; + protected ?string $nextDelivery = null; + protected ?string $createdDate = null; + protected ?string $token = null; + + /** + * Constructor, create a minimal request Subscription object. + * + * @param BillData|null $billData Object containing the recurring billing information. + * @param string|null $schedule Schedule of recurring billing due dates + */ + public function __construct(?BillData $billData = null, ?string $schedule = SubscriptionSchedule::MONTHLY) + { + $this->billData = $billData ?: new BillData(); + $this->schedule = $schedule; + } + + /** + * Get subscription data as array + * + * @return array subscription data as array + */ + public function toArray(): array + { + $elements = [ + 'id' => $this->getId(), + 'status' => $this->getStatus(), + 'billData' => $this->getBillData()->toArray(), + 'merchant' => $this->getMerchant(), + 'schedule' => $this->getSchedule(), + 'nextDelivery' => $this->getNextDelivery(), + 'createdDate' => $this->getCreatedDate(), + 'token' => $this->getToken(), + ]; + + foreach ($elements as $key => $value) { + if (empty($value)) { + unset($elements[$key]); + } + } + + return $elements; + } + + /** + * Get Subscription id + * + * Subscription resource id + * + * @return string|null the id + */ + public function getId(): ?string + { + return $this->id; + } + + /** + * Set Subscription id + * + * Subscription resource id + * + * @param string $id Subscription resource id + */ + public function setId(string $id): void + { + $this->id = $id; + } + + /** + * Get Subscription status + * + * @return string|null the status + * @see SubscriptionStatus + * + */ + public function getStatus(): ?string + { + return $this->status; + } + + /** + * Set Subscription status + * + * @param string $status Subscription's status + * @see SubscriptionStatus + * + */ + public function setStatus(string $status): void + { + $this->status = $status; + } + + /** + * Get Subscription billData + * + * Object containing the recurring billing information + * + * @return BillData + * @see BillData + */ + public function getBillData(): BillData + { + return $this->billData; + } + + /** + * Set Subscription billData + * + * @param BillData $billData Object containing the recurring billing information. + * @return void + * @see BillData + */ + public function setBillData(BillData $billData): void + { + $this->billData = $billData; + } + + /** + * Get Subscription merchant + * + * Internal identifier for BitPay, this field can be ignored by the merchants. + * + * @return string|null the merchant + */ + public function getMerchant(): ?string + { + return $this->merchant; + } + + /** + * Set Subscription merchant + * + * Internal identifier for BitPay, this field can be ignored by the merchants. + * + * @param string $merchant Internal identifier for BitPay + */ + public function setMerchant(string $merchant): void + { + $this->merchant = $merchant; + } + + /** + * Get Subscription created date + * + * Date and time of Subscription creation, ISO-8601 format yyyy-mm-ddThh:mm:ssZ. (UTC) + * + * @return string|null the created date + * @see Env::BITPAY_DATETIME_FORMAT + */ + public function getCreatedDate(): ?string + { + return $this->createdDate; + } + + /** + * Set Subscription created date + * + * Date and time of Subscription creation, ISO-8601 format yyyy-mm-ddThh:mm:ssZ. (UTC) + * + * @param string $createdDate Subscription's created date + * @see Env::BITPAY_DATETIME_FORMAT + */ + public function setCreatedDate(string $createdDate): void + { + $this->createdDate = $createdDate; + } + + /** + * Gets token + * + * API token for subscription resource. This token is actually derived from the API token used to create the + * subscription and is tied to the specific resource id created. + * + * @return string|null the token + */ + public function getToken(): ?string + { + return $this->token; + } + + /** + * Set Subscription token + * + * API token for subscription resource. This token is actually derived from the API token used to create the + * subscription and is tied to the specific resource id created. + * + * @param string $token API token for subscription resource + */ + public function setToken(string $token): void + { + $this->token = $token; + } + + /** + * Get Subscription schedule + * + * @return string|null + * @see SubscriptionSchedule + * + */ + public function getSchedule(): ?string + { + return $this->schedule; + } + + /** + * Set Subscription schedule + * + * @param string $schedule + * @return void + * @see SubscriptionSchedule + * + */ + public function setSchedule(string $schedule): void + { + $this->schedule = $schedule; + } + + /** + * Get Subscription's next delivery date + * + * Default is current date & time, ISO-8601 format yyyy-mm-ddThh:mm:ssZ (UTC). Current or past date indicates that + * the bill can be delivered immediately. BitPay may modify the hh:mm:ss values in order to distribute deliveries + * evenly throughout the day. + * + * @return string|null Subscription's next delivery date + */ + public function getNextDelivery(): ?string + { + return $this->nextDelivery; + } + + /** + * Set Subscription's next delivery date + * + * Default is current date & time, ISO-8601 format yyyy-mm-ddThh:mm:ssZ (UTC). Current or past date indicates that + * the bill can be delivered immediately. BitPay may modify the hh:mm:ss values in order to distribute deliveries + * evenly throughout the day. + * + * @param string $nextDelivery Subscription's next delivery date + * @return void + */ + public function setNextDelivery(string $nextDelivery): void + { + $this->nextDelivery = $nextDelivery; + } +} diff --git a/src/BitPaySDK/Model/Subscription/SubscriptionSchedule.php b/src/BitPaySDK/Model/Subscription/SubscriptionSchedule.php new file mode 100644 index 00000000..92c75a18 --- /dev/null +++ b/src/BitPaySDK/Model/Subscription/SubscriptionSchedule.php @@ -0,0 +1,46 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + */ + +namespace BitPaySDK\Model\Subscription; + +/** + * Schedule of repeat bill due dates. Can be `weekly`, `monthly`, `quarterly`, `yearly`, or a simple cron expression + * specifying seconds, minutes, hours, day of month, month, and day of week. BitPay maintains the difference between + * the due date and the delivery date in all subsequent, automatically-generated bills. + * + * +-------------- second (0 - 59) + * + * | +------------ minute (0 - 59) + * + * | | +---------- hour (0 - 23) + * + * | | | +-------- day of month (1 - 31) + * + * | | | | +------ month (1 - 12) + * + * | | | | | +---- day of week (0 - 6) (Sunday=0 or 7) + * + * | | | | | | + * + * \* * * * * Cron expression + * + * @package BitPaySDK\Model\Subscription + * @author BitPay Integrations + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @see https://developer.bitpay.com/reference/subscriptions Subscriptions + */ +interface SubscriptionSchedule +{ + public const WEEKLY = "weekly"; + public const MONTHLY = "monthly"; + public const QUARTERLY = "quarterly"; + public const YEARLY = "yearly"; +} diff --git a/src/BitPaySDK/Model/Subscription/SubscriptionStatus.php b/src/BitPaySDK/Model/Subscription/SubscriptionStatus.php new file mode 100644 index 00000000..5c08977f --- /dev/null +++ b/src/BitPaySDK/Model/Subscription/SubscriptionStatus.php @@ -0,0 +1,28 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + */ + +namespace BitPaySDK\Model\Subscription; + +/** + * Subscription object status. Can be `draft`, `active` or `cancelled`. + * Subscriptions in `active` state will create new Bills on the `nextDelivery` date. + * + * @package BitPaySDK\Model\Subscription + * @author BitPay Integrations + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @see https://developer.bitpay.com/reference/subscriptions Subscriptions + */ +interface SubscriptionStatus +{ + public const DRAFT = "draft"; + public const ACTIVE = "active"; + public const CANCELLED = "cancelled"; +} diff --git a/test/functional/BitPaySDK/SubscriptionClientTest.php b/test/functional/BitPaySDK/SubscriptionClientTest.php new file mode 100644 index 00000000..2b2d7dba --- /dev/null +++ b/test/functional/BitPaySDK/SubscriptionClientTest.php @@ -0,0 +1,99 @@ +getSubscriptionExample(); + $subscription = $this->client->createSubscription($subscription); + + // Subscription tests + self::assertEquals(SubscriptionStatus::DRAFT, $subscription->getStatus()); + self::assertEquals(SubscriptionSchedule::MONTHLY, $subscription->getSchedule()); + + // BillData tests + self::assertEquals(3.0, $subscription->getBillData()->getItems()[0]->getPrice()); + self::assertEquals(2, $subscription->getBillData()->getItems()[0]->getQuantity()); + self::assertEquals(1, $subscription->getBillData()->getItems()[1]->getQuantity()); + self::assertEquals(7.0, $subscription->getBillData()->getItems()[1]->getPrice()); + self::assertEquals("Test Item 1", $subscription->getBillData()->getItems()[0]->getDescription()); + self::assertEquals("Test Item 2", $subscription->getBillData()->getItems()[1]->getDescription()); + self::assertEquals(Currency::USD, $subscription->getBillData()->getCurrency()); + } + + public function testGetSubscription(): void + { + $subscription = $this->getSubscriptionExample(); + $subscription = $this->client->createSubscription($subscription); + $subscription = $this->client->getSubscription($subscription->getId()); + + self::assertEquals(SubscriptionStatus::DRAFT, $subscription->getStatus()); + self::assertCount(2, $subscription->getBillData()->getItems()); + self::assertEquals(Currency::USD, $subscription->getBillData()->getCurrency()); + self::assertEquals('billData1234-ABCD', $subscription->getBillData()->getNumber()); + self::assertEquals('john.doe@example.com', $subscription->getBillData()->getEmail()); + } + + public function testGetSubscriptions(): void + { + $subscriptions = $this->client->getSubscriptions(); + + self::assertNotNull($subscriptions); + self::assertIsArray($subscriptions); + $isCount = count($subscriptions) > 0; + self::assertTrue($isCount); + } + + public function testUpdateSubscription(): void + { + $subscription = $this->getSubscriptionExample(); + $subscription = $this->client->createSubscription($subscription); + $subscription = $this->client->getSubscription($subscription->getId()); + + $bill = $subscription->getBillData(); + $bill->setEmail("jane.doe@example.com"); + $subscription->setBillData($bill); + + $subscription = $this->client->updateSubscription($subscription, $subscription->getId()); + + self::assertEquals("jane.doe@example.com", $subscription->getBillData()->getEmail()); + } + + private function getSubscriptionExample(): Subscription + { + return new Subscription($this->getBillDataExample()); + } + + private function getBillDataExample(): BillData + { + $items = []; + $item = new Item(); + $item->setPrice(3.0); + $item->setQuantity(2); + $item->setDescription("Test Item 1"); + $items[] = $item; + + $item = new Item(); + $item->setPrice(7.0); + $item->setQuantity(1); + $item->setDescription("Test Item 2"); + $items[] = $item; + + return new BillData("billData1234-ABCD", Currency::USD, "john.doe@example.com", null, $items); + } +}