Skip to content

Commit

Permalink
PHP-67: Add support for custom idempotency keys (#42)
Browse files Browse the repository at this point in the history
Co-authored-by: Lee Boynton <[email protected]>
  • Loading branch information
lighe and lboynton authored Nov 20, 2023
1 parent 6f77bfe commit 72c579e
Show file tree
Hide file tree
Showing 30 changed files with 674 additions and 177 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.3.0] - 2023-11-17

### Added

- Support for custom idempotency keys

## [1.2.0] - 2023-05-02

### Added
Expand Down
43 changes: 41 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@
10. [Merchant accounts](#merchant-accounts)
11. [Account identifiers](#account-identifiers)
12. [Receiving webhook notifications](#webhooks)
13. [Custom API calls](#custom-api-calls)
14. [Error Handling](#error-handling)
13. [Custom idempotency keys](#idempotency)
14. [Custom API calls](#custom-api-calls)
15. [Error Handling](#error-handling)

<a name="why"></a>

Expand Down Expand Up @@ -1041,6 +1042,44 @@ if ($accountIdentifier instanceof BbanDetailsInterface) {
}
```

<a name="idempotency"></a>

# Custom idempotency keys

By default, the client will generate and manage idempotency keys for you. However, there are cases when you might want
to set your own idempotency keys and you can do this by using the `requestOptions` setter when creating a resource.

```php
// Create a RequestOptionsInterface instance and set your custom idempotency key
$requestOptions = $client->requestOptions()->idempotencyKey('my-custom-idempotency-key');

// Creating a payment with a custom idempotency key
$client->payment()
->paymentMethod($method)
->amountInMinor(10)
->currency('GBP')
->user($user)
->requestOptions($requestOptions)
->create();

// Creating a refund with a custom idempotency key
$client->refund()
->payment($paymentId)
->amountInMinor(1)
->reference('My reference')
->requestOptions($requestOptions)
->create();

// Creating a payout with a custom idempotency key
$client->payout()
->amountInMinor(1)
->currency(Currencies::GBP)
->merchantAccountId($accountId)
->beneficiary($payoutBeneficiary)
->requestOptions($requestOptions)
->create();
```

<a name="custom-api-calls"></a>

# Custom API calls
Expand Down
2 changes: 2 additions & 0 deletions config/bindings.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,6 @@
Interfaces\Webhook\PaymentMethod\MandatePaymentMethodInterface::class => Entities\Webhook\PaymentMethod\MandatePaymentMethod::class,
Interfaces\Webhook\Beneficiary\BusinessAccountBeneficiaryInterface::class => Entities\Webhook\Beneficiary\BusinessAccountBeneficiary::class,
Interfaces\Webhook\Beneficiary\PaymentSourceBeneficiaryInterface::class => Entities\Webhook\Beneficiary\PaymentSourceBeneficiary::class,

Interfaces\RequestOptionsInterface::class => Entities\RequestOptions::class,
];
24 changes: 21 additions & 3 deletions src/Entities/Payment/PaymentRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use TrueLayer\Interfaces\Payment\PaymentCreatedInterface;
use TrueLayer\Interfaces\Payment\PaymentRequestInterface;
use TrueLayer\Interfaces\PaymentMethod\PaymentMethodInterface;
use TrueLayer\Interfaces\RequestOptionsInterface;
use TrueLayer\Interfaces\UserInterface;
use TrueLayer\Traits\ProvidesApiFactory;
use TrueLayer\Validation\ValidType;
Expand Down Expand Up @@ -47,6 +48,11 @@ final class PaymentRequest extends Entity implements PaymentRequestInterface, Ha
*/
protected array $metadata;

/**
* @var RequestOptionsInterface|null
*/
protected ?RequestOptionsInterface $requestOptions = null;

/**
* @var string[]
*/
Expand Down Expand Up @@ -141,18 +147,30 @@ public function user(UserInterface $user): PaymentRequestInterface
}

/**
* @throws SignerException
* @param RequestOptionsInterface $requestOptions
* @return $this
*/
public function requestOptions(RequestOptionsInterface $requestOptions): PaymentRequestInterface
{
$this->requestOptions = $requestOptions;

return $this;
}

/**
* @return PaymentCreatedInterface
* @throws ApiRequestJsonSerializationException
* @throws ApiResponseUnsuccessfulException
* @throws InvalidArgumentException
* @throws ValidationException
*
* @return PaymentCreatedInterface
* @throws SignerException
*/
public function create(): PaymentCreatedInterface
{
$data = $this->getApiFactory()->paymentsApi()->create(
$this->validate()->toArray()
$this->validate()->toArray(),
$this->requestOptions
);

return $this->make(PaymentCreatedInterface::class, $data);
Expand Down
27 changes: 23 additions & 4 deletions src/Entities/Payment/Refund/RefundRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use TrueLayer\Interfaces\Payment\PaymentRetrievedInterface;
use TrueLayer\Interfaces\Payment\RefundCreatedInterface;
use TrueLayer\Interfaces\Payment\RefundRequestInterface;
use TrueLayer\Interfaces\RequestOptionsInterface;
use TrueLayer\Services\Util\PaymentId;
use TrueLayer\Traits\ProvidesApiFactory;

Expand All @@ -37,6 +38,11 @@ final class RefundRequest extends Entity implements RefundRequestInterface, HasA
*/
protected string $reference;

/**
* @var RequestOptionsInterface|null
*/
protected ?RequestOptionsInterface $requestOptions = null;

/**
* @var string[]
*/
Expand All @@ -61,9 +67,9 @@ protected function rules(): array
/**
* @param string|PaymentRetrievedInterface|PaymentCreatedInterface $payment
*
* @return RefundRequestInterface
* @throws InvalidArgumentException
*
* @return RefundRequestInterface
*/
public function payment($payment): RefundRequestInterface
{
Expand Down Expand Up @@ -97,19 +103,32 @@ public function reference(string $reference): RefundRequestInterface
}

/**
* @throws SignerException
* @param RequestOptionsInterface $requestOptions
* @return RefundRequestInterface
*/
public function requestOptions(RequestOptionsInterface $requestOptions): RefundRequestInterface
{
$this->requestOptions = $requestOptions;

return $this;
}


/**
* @return RefundCreatedInterface
* @throws ApiRequestJsonSerializationException
* @throws ApiResponseUnsuccessfulException
* @throws InvalidArgumentException
* @throws ValidationException
*
* @return RefundCreatedInterface
* @throws SignerException
*/
public function create(): RefundCreatedInterface
{
$data = $this->getApiFactory()->paymentsApi()->createRefund(
$this->paymentId,
$this->validate()->toArray()
$this->validate()->toArray(),
$this->requestOptions
);

return $this->make(RefundCreatedInterface::class, $data);
Expand Down
24 changes: 21 additions & 3 deletions src/Entities/Payout/PayoutRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use TrueLayer\Interfaces\Payout\PayoutBeneficiaryInterface;
use TrueLayer\Interfaces\Payout\PayoutCreatedInterface;
use TrueLayer\Interfaces\Payout\PayoutRequestInterface;
use TrueLayer\Interfaces\RequestOptionsInterface;
use TrueLayer\Traits\ProvidesApiFactory;
use TrueLayer\Validation\ValidType;

Expand Down Expand Up @@ -43,6 +44,11 @@ final class PayoutRequest extends Entity implements PayoutRequestInterface, HasA
*/
protected PayoutBeneficiaryInterface $beneficiary;

/**
* @var RequestOptionsInterface|null
*/
protected ?RequestOptionsInterface $requestOptions = null;

/**
* @var string[]
*/
Expand Down Expand Up @@ -122,18 +128,30 @@ public function beneficiary(PayoutBeneficiaryInterface $beneficiary): PayoutRequ
}

/**
* @throws ValidationException
* @param RequestOptionsInterface $requestOptions
* @return $this
*/
public function requestOptions(RequestOptionsInterface $requestOptions): PayoutRequestInterface
{
$this->requestOptions = $requestOptions;

return $this;
}

/**
* @return PayoutCreatedInterface
* @throws SignerException
* @throws ApiRequestJsonSerializationException
* @throws ApiResponseUnsuccessfulException
* @throws InvalidArgumentException
*
* @return PayoutCreatedInterface
* @throws ValidationException
*/
public function create(): PayoutCreatedInterface
{
$data = $this->getApiFactory()->payoutsApi()->create(
$this->validate()->toArray()
$this->validate()->toArray(),
$this->requestOptions
);

return $this->make(PayoutCreatedInterface::class, $data);
Expand Down
49 changes: 49 additions & 0 deletions src/Entities/RequestOptions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace TrueLayer\Entities;

use TrueLayer\Interfaces\RequestOptionsInterface;

final class RequestOptions extends Entity implements RequestOptionsInterface
{
/**
* @var string
*/
protected string $idempotencyKey;

/**
* @var string[]
*/
protected array $arrayFields = [
'idempotency_key',
];

/**
* @var string[]
*/
protected array $rules = [
'idempotency_key' => 'nullable|string',
];

/**
* @param string $idempotencyKey
*
* @return RequestOptionsInterface
*/
public function idempotencyKey(string $idempotencyKey): RequestOptionsInterface
{
$this->idempotencyKey = $idempotencyKey;

return $this;
}

/**
* @return string|null
*/
public function getIdempotencyKey(): ?string
{
return $this->idempotencyKey ?? null;
}
}
Loading

0 comments on commit 72c579e

Please sign in to comment.