Compare commits

...

1 Commits

Author SHA1 Message Date
ndeet
a3004b382b
Adding new subscription routes and updating subs examples. (#142)
Some checks failed
Code Style / php-cs-fixer (push) Has been cancelled
Static analysis (Psalm) / Psalm (8.0) (push) Has been cancelled
* Adding new subscription routes and updating subs examples.

* Updating github actions due to deprecated nodejs 20.
2026-04-30 11:29:35 +02:00
6 changed files with 176 additions and 4 deletions

View File

@ -8,7 +8,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Run PHP CS Fixer
uses: docker://oskarstark/php-cs-fixer-ga

View File

@ -12,7 +12,7 @@ jobs:
php-versions: ['8.0']
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: '0'
@ -27,7 +27,7 @@ jobs:
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer dependencies
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}

3
.gitignore vendored
View File

@ -3,4 +3,5 @@
.php-cs-fixer.cache
*.cache
composer.lock
/tests/.env
/tests/.env
/.claude/

View File

@ -81,6 +81,32 @@ try {
$basicPlanId = $basicPlan->getId();
$premiumPlanId = $premiumPlan->getId();
// 2b. Update the offering
echo "2b. Updating the offering...\n";
$updatedOffering = $client->updateOffering(
$storeId,
$offeringId,
'Premium SaaS App v2',
'https://example.com/success-v2',
['category' => 'saas', 'region' => 'eu', 'version' => '2.0']
);
echo "Offering updated: " . $updatedOffering->getAppName() . "\n\n";
// 2c. Update a plan
echo "2c. Updating the basic plan...\n";
$updatedPlan = $client->updateOfferingPlan(
$storeId,
$offeringId,
$basicPlanId,
'Updated basic monthly subscription',
'USD',
7,
'Basic Plan v2',
null,
'2.99'
);
echo "Plan updated: " . $updatedPlan->getName() . " - " . $updatedPlan->getPrice() . " " . $updatedPlan->getCurrency() . "\n\n";
// 3. Get all offerings for the store
echo "3. Getting all offerings for the store...\n";
$offerings = $client->getOfferings($storeId);
@ -171,6 +197,19 @@ try {
echo "Status after suspension: " . ($suspendedSubscriber->isActive() ? 'Active' : 'Suspended') . "\n";
echo "Suspension reason: " . ($suspendedSubscriber->getSuspensionReason() ?? 'N/A') . "\n\n";
// Update subscriber dates
echo "9b. Updating subscriber dates...\n";
$updatedSubscriber = $client->updateSubscriberDates(
$storeId,
$offeringId,
$customerSelector,
null,
time() + (30 * 24 * 60 * 60) // 30 days from now
);
echo "Subscriber expiration updated\n";
echo "Scheduled plan: " . ($updatedSubscriber->getScheduledPlan() ? $updatedSubscriber->getScheduledPlan()->getName() : 'None') . "\n";
echo "Scheduled plan activates at: " . ($updatedSubscriber->getScheduledPlanActivatesAt() ? date('Y-m-d H:i:s', $updatedSubscriber->getScheduledPlanActivatesAt()) : 'N/A') . "\n\n";
// Unsuspend subscriber
echo "10. Unsuspending subscriber...\n";
$client->unsuspendSubscriber($storeId, $offeringId, $customerSelector);
@ -182,6 +221,11 @@ try {
echo "Suspension reason: " . ($reactivatedSubscriber->getSuspensionReason() ?? 'N/A') . "\n\n";
}
// Delete subscriber
echo "11. Deleting subscriber...\n";
$client->deleteSubscriber($storeId, $offeringId, $customerSelector);
echo "Subscriber deleted successfully!\n\n";
} catch (\Throwable $e) {
echo "Error in subscriber management: " . $e->getMessage() . "\n";
}

View File

@ -79,6 +79,37 @@ class Subscriptions extends AbstractClient
}
}
public function updateOffering(
string $storeId,
string $offeringId,
?string $appName = null,
?string $successRedirectUrl = null,
?array $metadata = null,
?array $features = null
): Offering {
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/offerings/' . urlencode($offeringId);
$headers = $this->getRequestHeaders();
$method = 'PUT';
$body = json_encode(
[
'appName' => $appName,
'successRedirectUrl' => $successRedirectUrl,
'metadata' => $metadata,
'features' => $features
],
JSON_THROW_ON_ERROR
);
$response = $this->getHttpClient()->request($method, $url, $headers, $body);
if ($response->getStatus() === 200) {
return new Offering(json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR));
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
// Plan endpoints
public function createOfferingPlan(
@ -140,6 +171,52 @@ class Subscriptions extends AbstractClient
}
}
public function updateOfferingPlan(
string $storeId,
string $offeringId,
string $planId,
?string $description = null,
?string $currency = null,
?int $gracePeriodDays = null,
?string $name = null,
?bool $optimisticActivation = null,
?string $price = null,
?bool $renewable = null,
?int $trialDays = null,
?array $metadata = null,
?string $recurringType = null,
?array $features = null
): OfferingPlan {
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/offerings/' . urlencode($offeringId) . '/plans/' . urlencode($planId);
$headers = $this->getRequestHeaders();
$method = 'PUT';
$body = json_encode(
[
'description' => $description,
'currency' => $currency,
'gracePeriodDays' => $gracePeriodDays,
'name' => $name,
'optimisticActivation' => $optimisticActivation,
'price' => $price,
'renewable' => $renewable,
'trialDays' => $trialDays,
'metadata' => $metadata,
'recurringType' => $recurringType,
'features' => $features
],
JSON_THROW_ON_ERROR
);
$response = $this->getHttpClient()->request($method, $url, $headers, $body);
if ($response->getStatus() === 200) {
return new OfferingPlan(json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR));
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
// Subscriber endpoints
public function getSubscriber(string $storeId, string $offeringId, string $customerSelector): Subscriber
@ -156,6 +233,46 @@ class Subscriptions extends AbstractClient
}
}
public function deleteSubscriber(string $storeId, string $offeringId, string $customerSelector): void
{
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/offerings/' . urlencode($offeringId) . '/subscribers/' . urlencode($customerSelector);
$headers = $this->getRequestHeaders();
$method = 'DELETE';
$response = $this->getHttpClient()->request($method, $url, $headers);
if ($response->getStatus() !== 204) {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
public function updateSubscriberDates(
string $storeId,
string $offeringId,
string $customerSelector,
?int $startDate = null,
?int $expirationDate = null
): Subscriber {
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/offerings/' . urlencode($offeringId) . '/subscribers/' . urlencode($customerSelector) . '/dates';
$headers = $this->getRequestHeaders();
$method = 'PUT';
$body = json_encode(
[
'startDate' => $startDate,
'expirationDate' => $expirationDate
],
JSON_THROW_ON_ERROR
);
$response = $this->getHttpClient()->request($method, $url, $headers, $body);
if ($response->getStatus() === 200) {
return new Subscriber(json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR));
} else {
throw $this->getExceptionByStatusCode($method, $url, $response);
}
}
public function suspendSubscriber(string $storeId, string $offeringId, string $customerSelector, string $reason): Subscriber
{
$url = $this->getApiUrl() . 'stores/' . urlencode($storeId) . '/offerings/' . urlencode($offeringId) . '/subscribers/' . urlencode($customerSelector) . '/suspend';

View File

@ -76,6 +76,16 @@ class Subscriber extends AbstractResult
return isset($this->getData()['nextPlan']) ? new OfferingPlan($this->getData()['nextPlan']) : null;
}
public function getScheduledPlan(): ?OfferingPlan
{
return isset($this->getData()['scheduledPlan']) ? new OfferingPlan($this->getData()['scheduledPlan']) : null;
}
public function getScheduledPlanActivatesAt(): ?int
{
return $this->getData()['scheduledPlanActivatesAt'] ?? null;
}
public function getPhase(): string
{
return $this->getData()['phase'];