Skip to content

Commit

Permalink
Idempotency key (#41)
Browse files Browse the repository at this point in the history
* ✨ Handle idempotency key for draft and booked invoice

* ✅ Update tests

* Fix styling

---------

Co-authored-by: SimonJnsson <[email protected]>
  • Loading branch information
SimonJnsson and SimonJnsson authored Nov 25, 2024
1 parent e54f8bb commit a0b63e4
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 31 deletions.
4 changes: 2 additions & 2 deletions src/Interfaces/EconomicDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ interface EconomicDriver
{
public function get(string $url, array $queryArgs = []): EconomicResponse;

public function post(string $url, array $body = []): EconomicResponse;
public function post(string $url, array $body = [], ?string $idempotencyKey = null): EconomicResponse;

public function put(string $url, array $body = []): EconomicResponse;
public function put(string $url, array $body = [], ?string $idempotencyKey = null): EconomicResponse;

public function delete(string $url): EconomicResponse;

Expand Down
4 changes: 2 additions & 2 deletions src/Resources/Invoice/BookedInvoice.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ class BookedInvoice extends Invoice
{
public ?int $bookedInvoiceNumber = null;

public static function createFromDraft(int|DraftInvoice $draft)
public static function createFromDraft(int|DraftInvoice $draft, ?string $idempotencyKey = null)
{
return static::createRequest([
'draftInvoice' => [
'draftInvoiceNumber' => is_int($draft) ? $draft : $draft->draftInvoiceNumber,
],
]);
], [], $idempotencyKey);
}
}
14 changes: 8 additions & 6 deletions src/Resources/Invoice/DraftInvoice.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ public static function create(
?float $exchangeRate = null,
?Note $notes = null,
Project|int|null $project = null,
?Reference $references = null
?Reference $references = null,
?string $idempotencyKey = null,
): ?static {
return static::createRequest(compact(
'currency',
Expand All @@ -62,7 +63,7 @@ public static function create(
'notes',
'project',
'references',
));
), [], $idempotencyKey);
}

public static function new(
Expand Down Expand Up @@ -96,7 +97,7 @@ public static function new(
)));
}

public function save(): ?static
public function save(?string $idempotencyKey = null): ?static
{
if (empty($this->draftInvoiceNumber)) {
$new = static::create(
Expand All @@ -111,7 +112,8 @@ public function save(): ?static
exchangeRate: $this->exchangeRate,
notes: $this->notes,
project: $this->project,
references: $this->references
references: $this->references,
idempotencyKey: $idempotencyKey
);

$this->populate($new->toArray());
Expand All @@ -122,8 +124,8 @@ public function save(): ?static
return $this->saveRequest();
}

public function book(): ?BookedInvoice
public function book(?string $idempotencyKey = null): ?BookedInvoice
{
return BookedInvoice::createFromDraft($this);
return BookedInvoice::createFromDraft($this, $idempotencyKey);
}
}
8 changes: 4 additions & 4 deletions src/Services/EconomicApiService.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ public static function get(string $endpoint, array $queryArgs = []): EconomicRes
return static::getDriver()->get(static::createURL($endpoint), $queryArgs);
}

public static function post(string $endpoint, array $body = []): EconomicResponse
public static function post(string $endpoint, array $body = [], ?string $idempotencyKey = null): EconomicResponse
{
return static::getDriver()->post(static::createURL($endpoint), static::castParameters($body));
return static::getDriver()->post(static::createURL($endpoint), static::castParameters($body), $idempotencyKey);
}

public static function put(string $endpoint, array $body = []): EconomicResponse
public static function put(string $endpoint, array $body = [], ?string $idempotencyKey = null): EconomicResponse
{
return static::getDriver()->put(static::createURL($endpoint), static::castParameters($body));
return static::getDriver()->put(static::createURL($endpoint), static::castParameters($body), $idempotencyKey);
}

public static function delete(string $endpoint): EconomicResponse
Expand Down
8 changes: 6 additions & 2 deletions src/Traits/Resources/Creatable.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@ trait Creatable
/**
* @param static|array $args
*/
public static function createRequest($args, array $endpointReferences = []): ?static
public static function createRequest($args, array $endpointReferences = [], ?string $idempotencyKey = null): ?static
{
// TODO: add validation method to check if required properties are set and primary key is not set - throw exception if not

$args = static::resolveArgs($args, true); // We need to convert objects to an array to avoid issues with the API

$response = EconomicApiService::post(static::getEndpoint(Create::class, ...$endpointReferences), $args);
$response = EconomicApiService::post(
static::getEndpoint(Create::class, ...$endpointReferences),
$args,
$idempotencyKey
);

if ($response->getStatusCode() !== 201) {
EconomicLoggerService::error('Economic API Service returned a non 201 status code when creating a resource',
Expand Down
10 changes: 7 additions & 3 deletions src/Traits/Resources/Updatable.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,22 @@ trait Updatable
{
use EndpointResolvable;

public function save(): static
public function save(?string $idempotencyKey = null): static
{
// TODO: add validation method to check if required properties are set and primary key is set - throw exception if not

$response = EconomicApiService::put(static::getEndpoint(Update::class, $this), $this->toArray(true));
$response = EconomicApiService::put(
static::getEndpoint(Update::class, $this),
$this->toArray(true),
$idempotencyKey
);

if ($response->getStatusCode() !== 200 && $response->getStatusCode() !== 201) { // 201 is for created, 200 is for updated -> both are valid here
EconomicLoggerService::error('Economic API Service returned a non 200 or 201 status code when updating a resource', [
'status_code' => $response->getStatusCode(),
'response_body' => $response->getBody(),
'resource' => static::class,
'args' => $this->toArray(true),
'idempotency_key' => $idempotencyKey,
]);

throw new Exception('Economic API Service returned a non 200 status code when updating a resource');
Expand Down
4 changes: 2 additions & 2 deletions tests/DummyEconomicDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ public function get(string $url, array $queryArgs = []): EconomicResponse
ray('GET', func_get_args());
}

public function post(string $url, array $body = []): EconomicResponse
public function post(string $url, array $body = [], ?string $idempotencyKey = null): EconomicResponse
{
// TODO: Implement post() method.
ray('POST', func_get_args());
}

public function put(string $url, array $body = []): EconomicResponse
public function put(string $url, array $body = [], ?string $idempotencyKey = null): EconomicResponse
{
// TODO: Implement put() method.
ray('PUT', func_get_args());
Expand Down
6 changes: 4 additions & 2 deletions tests/Unit/CustomerContactTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
it('creates a customer contact', function () {
$this->driver->expects()->post(
'https://restapi.e-conomic.com/customers/1/contacts',
fixture('Customers/Contacts/create-request')
fixture('Customers/Contacts/create-request'),
null
)->andReturn(new EconomicResponse(201, fixture('Customers/Contacts/create-response')));

$contact = Contact::create(
Expand All @@ -60,7 +61,8 @@
it('updates a customer contact', function () {
$this->driver->expects()->put(
'https://restapi.e-conomic.com/customers/1/contacts/140',
fixture('Customers/Contacts/update-request')
fixture('Customers/Contacts/update-request'),
null
)->andReturn(new EconomicResponse(200, fixture('Customers/Contacts/update-response')));

$contact = new Contact([
Expand Down
9 changes: 6 additions & 3 deletions tests/Unit/CustomerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
it('creates customer', function () {
$this->driver->expects()->post(
'https://restapi.e-conomic.com/customers',
fixture('Customers/create-request')
fixture('Customers/create-request'),
null
)
->once()
->andReturn(new EconomicResponse(201, fixture('Customers/create-response')));
Expand Down Expand Up @@ -131,7 +132,8 @@
it('filters null values', function () {
$this->driver->expects()->post(
'https://restapi.e-conomic.com/customers',
fixture('Customers/create-request')
fixture('Customers/create-request'),
null
)
->once()
->andReturn(new EconomicResponse(201, fixture('Customers/create-response')));
Expand All @@ -149,7 +151,8 @@
it('does not filter falsy values', function () {
$this->driver->expects()->post(
'https://restapi.e-conomic.com/customers',
fixture('Customers/create-request-with-falsy-values')
fixture('Customers/create-request-with-falsy-values'),
null
)
->once()
->andReturn(new EconomicResponse(201, fixture('Customers/create-request')));
Expand Down
80 changes: 76 additions & 4 deletions tests/Unit/InvoiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@
it('creates a draft invoice using create', function () {
$this->driver->expects()->post()->with(
'https://restapi.e-conomic.com/invoices/drafts',
fixture('Invoices/draft/create-request')
fixture('Invoices/draft/create-request'),
null
)->andReturn(new EconomicResponse(201, fixture('Invoices/draft/create-response')));

$invoice = DraftInvoice::create(
Expand Down Expand Up @@ -109,7 +110,8 @@
it('creates a draft invoice using new and save', function () {
$this->driver->expects()->post()->with(
'https://restapi.e-conomic.com/invoices/drafts',
fixture('Invoices/draft/create-request')
fixture('Invoices/draft/create-request'),
null
)->andReturn(new EconomicResponse(201, fixture('Invoices/draft/create-response')));

$invoice = DraftInvoice::new(
Expand Down Expand Up @@ -150,7 +152,8 @@
it('creates a draft invoice with full customer and currency object', function () {
$this->driver->expects()->post()->with(
'https://restapi.e-conomic.com/invoices/drafts',
fixture('Invoices/draft/create-request')
fixture('Invoices/draft/create-request'),
null
)->andReturn(new EconomicResponse(201, fixture('Invoices/draft/create-response')));

$invoice = DraftInvoice::new(
Expand Down Expand Up @@ -204,7 +207,8 @@
it('books draft invoice', function () {
$this->driver->expects()->post(
'https://restapi.e-conomic.com/invoices/booked',
fixture('Invoices/draft/book-request')
fixture('Invoices/draft/book-request'),
null
)
->once()
->andReturn(new EconomicResponse(
Expand Down Expand Up @@ -300,3 +304,71 @@
->textLine1->toBe('Text line 1')
->textLine2->toBe('Text line 2');
});

it('can set idempotency key when creating a draft', function () {
$this->driver->expects()->post()->with(
'https://restapi.e-conomic.com/invoices/drafts',
fixture('Invoices/draft/create-request'),
'test-idempotency-key'
)->andReturn(new EconomicResponse(201, fixture('Invoices/draft/create-response')));

$invoice = DraftInvoice::new(
new Currency([
'code' => 'DKK',
'isoNumber' => 'DKK',
'name' => 'Danish Krone',
]),
Customer::new(
name: 'John Doe',
customerNumber: 1,
currency: 'DKK',
email: '[email protected]',
address: 'Test Street 1',
vatZone: 1,
customerGroup: 1,
paymentTerms: 1,
),
new DateTime('2024-02-13T12:20:18+00:00'),
14,
1,
Recipient::new(
'John Doe',
new VatZone(1),
),
notes: Note::new(
heading: 'Heading',
textLine1: 'Text line 1',
textLine2: 'Text line 2'
)
);

$invoice->addLine(
ProductLine::new(
description: 'T-shirt - Size L',
product: new Product([
'productNumber' => 1,
]),
quantity: 1,
unitNetPrice: 500
)
);

$invoice->save('test-idempotency-key');
});

it('books draft invoice with idempotency key', function () {
$this->driver->expects()->post(
'https://restapi.e-conomic.com/invoices/booked',
fixture('Invoices/draft/book-request'),
'test-idempotency-key'
)
->once()
->andReturn(new EconomicResponse(
201,
fixture('Invoices/draft/book-response')
));

$invoice = new DraftInvoice(424);

$bookedInvoice = $invoice->book('test-idempotency-key');
});
2 changes: 1 addition & 1 deletion tests/Unit/ProductTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@

it('creates a product', function () {
$this->driver->expects()->post()
->with('https://restapi.e-conomic.com/products', fixture('Products/create-request'))
->with('https://restapi.e-conomic.com/products', fixture('Products/create-request'), null)
->andReturn(new EconomicResponse(201, fixture('Products/create-response')));

$product = Product::create('Product 1', 1, 'p-1', barCode: '1234567890', costPrice: 100.0, recommendedPrice: 150.0, salesPrice: 199.95, description: 'test', unit: 1);
Expand Down

0 comments on commit a0b63e4

Please sign in to comment.