Merge pull request #4 from anikeen-com/feature/backdated-checkout

checkout(startsAt:) for services that are already running
This commit is contained in:
envoyr authored and GitHub committed 2026-10-25 19:04:57 +01:00
commit 0fce38f48a
4 files changed
+24 -3

No files matched your search

+1
View File
@@ -111,6 +111,7 @@ $subscription = $subscriptions->create([
]); ]);
$subscription->checkout(); // bill the first period $subscription->checkout(); // bill the first period
$subscription->checkout(startsAt: $domain->registered_at); // already running: bill the period that started then
$subscription->pause(); // expires when the paid period ends $subscription->pause(); // expires when the paid period ends
$subscription->resume(); // expired subscriptions start a new period $subscription->resume(); // expired subscriptions start a new period
$subscription->revoke(refund: false); // cancel $subscription->revoke(refund: false); // cancel
+1
View File
@@ -111,6 +111,7 @@ $subscription = $subscriptions->create([
]); ]);
$subscription->checkout(); // bill the first period $subscription->checkout(); // bill the first period
$subscription->checkout(startsAt: $domain->registered_at); // already running: bill the period that started then
$subscription->pause(); // expires when the paid period ends $subscription->pause(); // expires when the paid period ends
$subscription->resume(); // expired subscriptions start a new period $subscription->resume(); // expired subscriptions start a new period
$subscription->revoke(refund: false); // cancel $subscription->revoke(refund: false); // cancel
+11 -3
View File
@@ -4,6 +4,7 @@ namespace Anikeen\Id\Resources;
use Anikeen\Id\Exceptions\ApiException; use Anikeen\Id\Exceptions\ApiException;
use Anikeen\Id\Support\AppRequest; use Anikeen\Id\Support\AppRequest;
use DateTimeInterface;
use JsonSerializable; use JsonSerializable;
/** /**
@@ -58,11 +59,18 @@ class AppSubscription implements JsonSerializable
/** /**
* Activate the subscription and bill its first period. * Activate the subscription and bill its first period.
* *
* @throws ApiException 409 "already_active", "billing_address_missing", … * $startsAt (in the past) books a service that is already running, e.g. a domain registered
* earlier: the first period runs from $startsAt, is billed now and renews when it ends.
*
* @throws ApiException 409 "already_active", "billing_address_missing", "period_already_ended", …
*/ */
public function checkout(): self public function checkout(DateTimeInterface|string|null $startsAt = null): self
{ {
return $this->send('PUT', '/checkout'); if ($startsAt instanceof DateTimeInterface) {
$startsAt = $startsAt->format(DateTimeInterface::ATOM);
}
return $this->send('PUT', '/checkout', $startsAt === null ? null : ['starts_at' => $startsAt]);
} }
/** /**
+11
View File
@@ -76,6 +76,17 @@ class AppSubscriptionsTest extends TestCase
$this->assertSame(['refund' => true], $this->sentJson(4)); $this->assertSame(['refund' => true], $this->sentJson(4));
} }
public function test_a_running_service_is_checked_out_from_its_start(): void
{
$this->respond(201, $this->subscription());
$this->respond(200, $this->subscription(['active' => true]));
$subscription = $this->owner()->appSubscriptions()->create(['name' => '.de TLD', 'unit' => 'year', 'price' => '5.19']);
$subscription->checkout(new \DateTimeImmutable('2026-05-19 11:33:58', new \DateTimeZone('UTC')));
$this->assertSame(['starts_at' => '2026-05-19T11:33:58+00:00'], $this->sentJson(1));
}
public function test_errors_carry_status_and_error_code(): void public function test_errors_carry_status_and_error_code(): void
{ {
$this->respond(409, ['message' => 'Product subscription has already been cancelled.', 'error' => 'already_cancelled']); $this->respond(409, ['message' => 'Product subscription has already been cancelled.', 'error' => 'already_cancelled']);