Skip to content
Merged
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
27 changes: 27 additions & 0 deletions docs/Subscriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,33 @@ echo $subscription->currentPeriodEnd; // When it ends



---

## Resume a subscription

`POST /v1/subscriptions/:id/resume`



Reverse a pending cancellation while the subscription is still on its grace period. Once a subscription has fully ended it cannot be resumed.




```php
$subscription = $vatly->subscriptions->resume('subscription_abc123');

echo $subscription->status; // 'active'
```

If you already have a `Subscription` resource instance:

```php
$subscription->resume();
```



---

## Subscription statuses
Expand Down
38 changes: 37 additions & 1 deletion openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2373,7 +2373,8 @@ paths:
- **Immediate:** Subscription ends immediately when `immediately=true` is specified

**Notes:**
- Canceled subscriptions cannot be reactivated through the API
- Subscriptions on grace period can be reactivated via `POST /subscriptions/{subscriptionId}/resume`
- Subscriptions that have fully ended cannot be reactivated
- Customers retain access until `renewedUntil` date (unless canceled immediately)
- No refunds are issued automatically; use the Refunds API if needed
tags:
Expand Down Expand Up @@ -2405,6 +2406,41 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
/subscriptions/{subscriptionId}/resume:
post:
operationId: resumeSubscription
summary: Resume a subscription
description: |
Reverses a pending cancellation while the subscription is still on its grace
period (i.e. before `renewedUntil`). The subscription returns to `active` status
and will continue renewing on its existing schedule.

**Notes:**
- Only subscriptions in the `on_grace_period` status can be resumed
- Subscriptions that have fully ended cannot be resumed
tags:
- Subscriptions
parameters:
- $ref: '#/components/parameters/subscriptionId'
responses:
'200':
description: Subscription resumed
content:
application/json:
schema:
$ref: '#/components/schemas/Subscription'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
description: Subscription cannot be resumed (not on grace period, already ended, or canceled)
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/subscriptions/{subscriptionId}/billing-update-link:
post:
operationId: createSubscriptionBillingUpdateLink
Expand Down
29 changes: 29 additions & 0 deletions src/API/Endpoints/SubscriptionEndpoint.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Vatly\API\Resources\BaseResourcePage;
use Vatly\API\Resources\Customer;
use Vatly\API\Resources\Links\PaginationLinks;
use Vatly\API\Resources\ResourceFactory;
use Vatly\API\Resources\Subscription;
use Vatly\API\Resources\SubscriptionCollection;
use Vatly\API\Types\Link;
Expand Down Expand Up @@ -110,6 +111,34 @@ public function cancel(string $subscriptionId, array $data = []): ?BaseResource
return $this->rest_delete($subscriptionId, $data);
}

/**
* Resume a subscription that is currently on grace period.
*
* Reverses a pending cancellation while the subscription is still active
* (i.e. before `renewedUntil`). Once a subscription has fully ended it
* cannot be resumed.
*
* @throws ApiException
*/
public function resume(string $subscriptionId, array $data = []): ?BaseResource
{
$this->validateSubscriptionId($subscriptionId);

$resource = "{$this->getResourcePath()}/" . urlencode($subscriptionId) . "/resume";

$result = $this->client->performHttpCall(
self::REST_CREATE,
$resource,
$this->parseRequestBody($data),
);

if ($result === null) {
return null;
}

return ResourceFactory::createResourceFromApiResult($result, $this->getResourceObject());
}

/**
* @param string $subscriptionId
* @return void
Expand Down
8 changes: 8 additions & 0 deletions src/API/Resources/Subscription.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ public function cancel(array $data = []): ?BaseResource
return $this->apiClient->subscriptions->cancel($this->id, $data);
}

/**
* @throws ApiException
*/
public function resume(array $data = []): ?BaseResource
{
return $this->apiClient->subscriptions->resume($this->id, $data);
}

public function isCanceled(): bool
{
return $this->status === SubscriptionStatus::CANCELED;
Expand Down
31 changes: 31 additions & 0 deletions tests/Endpoints/SubscriptionEndpointTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,37 @@ public function can_cancel_subscription()
);
}

/** @test */
public function can_resume_subscription()
{
/** @var Subscription $subscription */
$subscription = ResourceFactory::createResourceFromApiResult((object) $this->subscriptionDemoData('subscription_123', SubscriptionStatus::ON_GRACE_PERIOD), new Subscription($this->client));

$this->httpClient->setSendReturnObjectFromArray($this->subscriptionDemoData('subscription_123', SubscriptionStatus::ACTIVE));
$resumed = $subscription->resume();

$this->assertWasSentOnly(
VatlyApiClient::HTTP_POST,
self::API_ENDPOINT_URL.'/subscriptions/subscription_123/resume',
[],
null
);

$this->assertInstanceOf(Subscription::class, $resumed);
$this->assertEquals(SubscriptionStatus::ACTIVE, $resumed->status);
}

/** @test */
public function can_resume_subscription_with_idempotency_key()
{
$this->httpClient->setSendReturnObjectFromArray($this->subscriptionDemoData('subscription_123'));
$this->client->setIdempotencyKey('my-resume-idempotency-key');
$this->client->subscriptions->resume('subscription_123');

$headers = $this->httpClient->lastSentHeaders();
$this->assertEquals('my-resume-idempotency-key', $headers['Idempotency-Key']);
}

/** @test */
public function can_update_billing_details()
{
Expand Down