Skip to content

Commit

Permalink
feat: Add support for customer portal sessions
Browse files Browse the repository at this point in the history
  • Loading branch information
davidgrayston-paddle committed Dec 3, 2024
1 parent d3807a1 commit 23716aa
Show file tree
Hide file tree
Showing 15 changed files with 422 additions and 0 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

Check our main [developer changelog](https://developer.paddle.com/?utm_source=dx&utm_medium=paddle-php-sdk) for information about changes to the Paddle Billing platform, the Paddle API, and other developer tools.

## [Unreleased]

### Added

- Support for customer portal sessions, see [related changelog](https://developer.paddle.com/changelog/2024/customer-portal-sessions?utm_source=dx&utm_medium=paddle-php-sdk)
- `Client->customerPortalSessions->create()`

## [1.5.0] - 2024-11-18

### Added
Expand Down
3 changes: 3 additions & 0 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Paddle\SDK\Resources\Addresses\AddressesClient;
use Paddle\SDK\Resources\Adjustments\AdjustmentsClient;
use Paddle\SDK\Resources\Businesses\BusinessesClient;
use Paddle\SDK\Resources\CustomerPortalSessions\CustomerPortalSessionsClient;
use Paddle\SDK\Resources\Customers\CustomersClient;
use Paddle\SDK\Resources\Discounts\DiscountsClient;
use Paddle\SDK\Resources\Events\EventsClient;
Expand Down Expand Up @@ -69,6 +70,7 @@ class Client
public readonly TransactionsClient $transactions;
public readonly AdjustmentsClient $adjustments;
public readonly CustomersClient $customers;
public readonly CustomerPortalSessionsClient $customerPortalSessions;
public readonly AddressesClient $addresses;
public readonly BusinessesClient $businesses;
public readonly DiscountsClient $discounts;
Expand Down Expand Up @@ -118,6 +120,7 @@ public function __construct(
$this->adjustments = new AdjustmentsClient($this);
$this->customers = new CustomersClient($this);
$this->addresses = new AddressesClient($this);
$this->customerPortalSessions = new CustomerPortalSessionsClient($this);
$this->businesses = new BusinessesClient($this);
$this->discounts = new DiscountsClient($this);
$this->subscriptions = new SubscriptionsClient($this);
Expand Down
36 changes: 36 additions & 0 deletions src/Entities/CustomerPortalSession.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

/**
* |------
* | ! Generated code !
* | Altering this code will result in changes being overwritten |
* |-------------------------------------------------------------|.
*/

namespace Paddle\SDK\Entities;

use Paddle\SDK\Entities\CustomerPortalSession\CustomerPortalSessionUrls;
use Paddle\SDK\Notifications\Entities\Entity;

class CustomerPortalSession implements Entity
{
private function __construct(
public string $id,
public string $customerId,
public CustomerPortalSessionUrls $urls,
public \DateTimeInterface $createdAt,
) {
}

public static function from(array $data): self
{
return new self(
id: $data['id'],
customerId: $data['customer_id'],
urls: CustomerPortalSessionUrls::from($data['urls']),
createdAt: DateTime::from($data['created_at']),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

/**
* |------
* | ! Generated code !
* | Altering this code will result in changes being overwritten |
* |-------------------------------------------------------------|.
*/

namespace Paddle\SDK\Entities\CustomerPortalSession;

use Paddle\SDK\Notifications\Entities\Entity;

class CustomerPortalSessionGeneralUrl implements Entity
{
private function __construct(
public string $overview,
) {
}

public static function from(array $data): self
{
return new self(
overview: $data['overview'],
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

/**
* |------
* | ! Generated code !
* | Altering this code will result in changes being overwritten |
* |-------------------------------------------------------------|.
*/

namespace Paddle\SDK\Entities\CustomerPortalSession;

use Paddle\SDK\Notifications\Entities\Entity;

class CustomerPortalSessionSubscriptionUrl implements Entity
{
private function __construct(
public string $id,
public string $cancelSubscription,
public string $updateSubscriptionPaymentMethod,
) {
}

public static function from(array $data): self
{
return new self(
id: $data['id'],
cancelSubscription: $data['cancel_subscription'],
updateSubscriptionPaymentMethod: $data['update_subscription_payment_method'],
);
}
}
37 changes: 37 additions & 0 deletions src/Entities/CustomerPortalSession/CustomerPortalSessionUrls.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

/**
* |------
* | ! Generated code !
* | Altering this code will result in changes being overwritten |
* |-------------------------------------------------------------|.
*/

namespace Paddle\SDK\Entities\CustomerPortalSession;

use Paddle\SDK\Notifications\Entities\Entity;

class CustomerPortalSessionUrls implements Entity
{
/**
* @param CustomerPortalSessionSubscriptionUrl[] $subscriptions
*/
private function __construct(
public CustomerPortalSessionGeneralUrl $general,
public array $subscriptions,
) {
}

public static function from(array $data): self
{
return new self(
general: CustomerPortalSessionGeneralUrl::from($data['general']),
subscriptions: array_map(
fn (array $item): CustomerPortalSessionSubscriptionUrl => CustomerPortalSessionSubscriptionUrl::from($item),
$data['subscriptions'] ?? [],
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

/**
* |------
* | ! Generated code !
* | Altering this code will result in changes being overwritten |
* |-------------------------------------------------------------|.
*/

namespace Paddle\SDK\Resources\CustomerPortalSessions;

use Paddle\SDK\Client;
use Paddle\SDK\Entities\CustomerPortalSession;
use Paddle\SDK\Exceptions\ApiError;
use Paddle\SDK\Exceptions\SdkExceptions\MalformedResponse;
use Paddle\SDK\Resources\CustomerPortalSessions\Operations\CreateCustomerPortalSession;
use Paddle\SDK\ResponseParser;

class CustomerPortalSessionsClient
{
public function __construct(
private readonly Client $client,
) {
}

/**
* @throws ApiError On a generic API error
* @throws MalformedResponse If the API response was not parsable
*/
public function create(string $customerId, CreateCustomerPortalSession $createOperation): CustomerPortalSession
{
$parser = new ResponseParser(
$this->client->postRaw("/customers/{$customerId}/portal-sessions", $createOperation),
);

return CustomerPortalSession::from($parser->getData());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Paddle\SDK\Resources\CustomerPortalSessions\Operations;

use Paddle\SDK\FiltersUndefined;
use Paddle\SDK\Undefined;

class CreateCustomerPortalSession implements \JsonSerializable
{
use FiltersUndefined;

/**
* @param string[] $subscriptionIds
*/
public function __construct(
public readonly array|Undefined $subscriptionIds = new Undefined(),
) {
}

public function jsonSerialize(): array
{
return $this->filterUndefined([
'subscription_ids' => $this->subscriptionIds,
]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php

declare(strict_types=1);

namespace Paddle\SDK\Tests\Functional\Resources\CustomerPortalSessions;

use GuzzleHttp\Psr7\Response;
use Http\Mock\Client as MockClient;
use Paddle\SDK\Client;
use Paddle\SDK\Environment;
use Paddle\SDK\Options;
use Paddle\SDK\Resources\CustomerPortalSessions\Operations\CreateCustomerPortalSession;
use Paddle\SDK\Tests\Utils\ReadsFixtures;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

class CustomerPortalSessionsClientTest extends TestCase
{
use ReadsFixtures;

private MockClient $mockClient;
private Client $client;

public function setUp(): void
{
$this->mockClient = new MockClient();
$this->client = new Client(
apiKey: 'API_KEY_PLACEHOLDER',
options: new Options(Environment::SANDBOX),
httpClient: $this->mockClient);
}

/**
* @test
*
* @dataProvider createOperationsProvider
*/
public function it_uses_expected_payload_on_create(
CreateCustomerPortalSession $operation,
ResponseInterface $response,
string $expectedBody,
): void {
$this->mockClient->addResponse($response);
$this->client->customerPortalSessions->create('ctm_01h844p3h41s12zs5mn4axja51', $operation);
$request = $this->mockClient->getLastRequest();

self::assertInstanceOf(RequestInterface::class, $request);
self::assertEquals('POST', $request->getMethod());
self::assertEquals(
Environment::SANDBOX->baseUrl() . '/customers/ctm_01h844p3h41s12zs5mn4axja51/portal-sessions',
urldecode((string) $request->getUri()),
);
self::assertJsonStringEqualsJsonString($expectedBody, (string) $request->getBody());
}

public static function createOperationsProvider(): \Generator
{
yield 'Create portal session with single subscription ID' => [
new CreateCustomerPortalSession(['sub_01h04vsc0qhwtsbsxh3422wjs4']),
new Response(201, body: self::readRawJsonFixture('response/full_entity_single')),
self::readRawJsonFixture('request/create_single'),
];

yield 'Create portal session with multiple subscription IDs' => [
new CreateCustomerPortalSession(['sub_01h04vsc0qhwtsbsxh3422wjs4', 'sub_02h04vsc0qhwtsbsxh3422wjs4']),
new Response(201, body: self::readRawJsonFixture('response/full_entity_multiple')),
self::readRawJsonFixture('request/create_multiple'),
];

yield 'Create portal session with empty subscription IDs' => [
new CreateCustomerPortalSession([]),
new Response(201, body: self::readRawJsonFixture('response/full_entity_empty')),
self::readRawJsonFixture('request/create_empty'),
];

yield 'Create portal session with omitted subscription IDs' => [
new CreateCustomerPortalSession(),
new Response(201, body: self::readRawJsonFixture('response/full_entity_empty')),
'{}',
];
}

/**
* @test
*/
public function it_returns_expected_response_on_create(): void
{
$operation = new CreateCustomerPortalSession(['sub_01h04vsc0qhwtsbsxh3422wjs4', 'sub_02h04vsc0qhwtsbsxh3422wjs4']);
$response = new Response(201, body: self::readRawJsonFixture('response/full_entity_multiple'));

$this->mockClient->addResponse($response);
$portalSession = $this->client->customerPortalSessions->create('ctm_01gysfvfy7vqhpzkq8rjmrq7an', $operation);

self::assertEquals('cpls_01h4ge9r64c22exjsx0fy8b48b', $portalSession->id);
self::assertEquals('ctm_01gysfvfy7vqhpzkq8rjmrq7an', $portalSession->customerId);
self::assertEquals('2024-10-25T06:53:58+00:00', $portalSession->createdAt->format(DATE_RFC3339));

self::assertEquals(
'https://customer-portal.paddle.com/cpl_01j7zbyqs3vah3aafp4jf62qaw?action=overview&token=pga_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjdG1fMDFncm5uNHp0YTVhMW1mMDJqanplN3kyeXMiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3Mjc2NzkyMzh9._oO12IejzdKmyKTwb7BLjmiILkx4_cSyGjXraOBUI_g',
$portalSession->urls->general->overview,
);

self::assertEquals(
'sub_01h04vsc0qhwtsbsxh3422wjs4',
$portalSession->urls->subscriptions[0]->id,
);
self::assertEquals(
'https://customer-portal.paddle.com/cpl_01j7zbyqs3vah3aafp4jf62qaw?action=cancel_subscription&subscription_id=sub_01h04vsc0qhwtsbsxh3422wjs4&token=pga_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjdG1fMDFncm5uNHp0YTVhMW1mMDJqanplN3kyeXMiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3Mjc2NzkyMzh9._oO12IejzdKmyKTwb7BLjmiILkx4_cSyGjXraOBUI_g',
$portalSession->urls->subscriptions[0]->cancelSubscription,
);
self::assertEquals(
'https://customer-portal.paddle.com/cpl_01j7zbyqs3vah3aafp4jf62qaw?action=update_subscription_payment_method&subscription_id=sub_01h04vsc0qhwtsbsxh3422wjs4&token=pga_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjdG1fMDFncm5uNHp0YTVhMW1mMDJqanplN3kyeXMiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3Mjc2NzkyMzh9._oO12IejzdKmyKTwb7BLjmiILkx4_cSyGjXraOBUI_g',
$portalSession->urls->subscriptions[0]->updateSubscriptionPaymentMethod,
);

self::assertEquals(
'sub_02h04vsc0qhwtsbsxh3422wjs4',
$portalSession->urls->subscriptions[1]->id,
);
self::assertEquals(
'https://customer-portal.paddle.com/cpl_01j7zbyqs3vah3aafp4jf62qaw?action=cancel_subscription&subscription_id=sub_02h04vsc0qhwtsbsxh3422wjs4&token=pga_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjdG1fMDFncm5uNHp0YTVhMW1mMDJqanplN3kyeXMiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3Mjc2NzkyMzh9._oO12IejzdKmyKTwb7BLjmiILkx4_cSyGjXraOBUI_g',
$portalSession->urls->subscriptions[1]->cancelSubscription,
);
self::assertEquals(
'https://customer-portal.paddle.com/cpl_01j7zbyqs3vah3aafp4jf62qaw?action=update_subscription_payment_method&subscription_id=sub_02h04vsc0qhwtsbsxh3422wjs4&token=pga_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjdG1fMDFncm5uNHp0YTVhMW1mMDJqanplN3kyeXMiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3Mjc2NzkyMzh9._oO12IejzdKmyKTwb7BLjmiILkx4_cSyGjXraOBUI_g',
$portalSession->urls->subscriptions[1]->updateSubscriptionPaymentMethod,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"subscription_ids": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"subscription_ids": [
"sub_01h04vsc0qhwtsbsxh3422wjs4",
"sub_02h04vsc0qhwtsbsxh3422wjs4"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"subscription_ids": [
"sub_01h04vsc0qhwtsbsxh3422wjs4"
]
}
Loading

0 comments on commit 23716aa

Please sign in to comment.