feat: add API Gateway, RapidMail and Shopify integrations, update WireGuard configs, add Redis override and architecture docs
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail\ApiRequests;
|
||||
|
||||
use App\Framework\ApiGateway\ApiRequest;
|
||||
use App\Framework\ApiGateway\HasPayload;
|
||||
use App\Framework\ApiGateway\ValueObjects\ApiEndpoint;
|
||||
use App\Framework\Core\ValueObjects\Duration;
|
||||
use App\Framework\Http\Headers;
|
||||
use App\Framework\Http\Method as HttpMethod;
|
||||
use App\Framework\Http\Url\Url;
|
||||
use App\Framework\Retry\ExponentialBackoffStrategy;
|
||||
use App\Framework\Retry\RetryStrategy;
|
||||
use App\Infrastructure\Api\RapidMail\RapidMailConfig;
|
||||
|
||||
/**
|
||||
* Domain-specific API request for creating recipients in RapidMail
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* $request = new CreateRecipientApiRequest(
|
||||
* config: $rapidMailConfig,
|
||||
* recipientData: [
|
||||
* 'email' => 'user@example.com',
|
||||
* 'firstname' => 'John',
|
||||
* 'lastname' => 'Doe'
|
||||
* ]
|
||||
* );
|
||||
*
|
||||
* $response = $apiGateway->send($request);
|
||||
*/
|
||||
final readonly class CreateRecipientApiRequest implements ApiRequest, HasPayload
|
||||
{
|
||||
public function __construct(
|
||||
private RapidMailConfig $config,
|
||||
private array $recipientData
|
||||
) {
|
||||
}
|
||||
|
||||
public function getEndpoint(): ApiEndpoint
|
||||
{
|
||||
$url = rtrim($this->config->baseUrl, '/') . '/recipients';
|
||||
|
||||
return ApiEndpoint::fromUrl(Url::parse($url));
|
||||
}
|
||||
|
||||
public function getMethod(): HttpMethod
|
||||
{
|
||||
return HttpMethod::POST;
|
||||
}
|
||||
|
||||
public function getPayload(): array
|
||||
{
|
||||
return $this->recipientData;
|
||||
}
|
||||
|
||||
public function getTimeout(): Duration
|
||||
{
|
||||
return Duration::fromSeconds((int) $this->config->timeout);
|
||||
}
|
||||
|
||||
public function getRetryStrategy(): ?RetryStrategy
|
||||
{
|
||||
// Exponential backoff: 1s, 2s, 4s
|
||||
return new ExponentialBackoffStrategy(
|
||||
maxAttempts: 3,
|
||||
baseDelaySeconds: 1,
|
||||
maxDelaySeconds: 10
|
||||
);
|
||||
}
|
||||
|
||||
public function getHeaders(): Headers
|
||||
{
|
||||
// Basic Auth
|
||||
$credentials = base64_encode("{$this->config->username}:{$this->config->password}");
|
||||
|
||||
return new Headers([
|
||||
'Authorization' => "Basic {$credentials}",
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
]);
|
||||
}
|
||||
|
||||
public function getRequestName(): string
|
||||
{
|
||||
return 'rapidmail.create_recipient';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail\ApiRequests;
|
||||
|
||||
use App\Framework\ApiGateway\ApiRequest;
|
||||
use App\Framework\ApiGateway\ValueObjects\ApiEndpoint;
|
||||
use App\Framework\Core\ValueObjects\Duration;
|
||||
use App\Framework\Http\Headers;
|
||||
use App\Framework\Http\Method as HttpMethod;
|
||||
use App\Framework\Http\Url\Url;
|
||||
use App\Framework\Retry\ExponentialBackoffStrategy;
|
||||
use App\Framework\Retry\RetryStrategy;
|
||||
use App\Infrastructure\Api\RapidMail\RapidMailConfig;
|
||||
|
||||
/**
|
||||
* Domain-specific API request for deleting a recipient from RapidMail
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* $request = new DeleteRecipientApiRequest(
|
||||
* config: $rapidMailConfig,
|
||||
* recipientId: '12345'
|
||||
* );
|
||||
*
|
||||
* $response = $apiGateway->send($request);
|
||||
*/
|
||||
final readonly class DeleteRecipientApiRequest implements ApiRequest
|
||||
{
|
||||
public function __construct(
|
||||
private RapidMailConfig $config,
|
||||
private string $recipientId
|
||||
) {
|
||||
}
|
||||
|
||||
public function getEndpoint(): ApiEndpoint
|
||||
{
|
||||
$url = rtrim($this->config->baseUrl, '/') . '/recipients/' . $this->recipientId;
|
||||
|
||||
return ApiEndpoint::fromUrl(Url::parse($url));
|
||||
}
|
||||
|
||||
public function getMethod(): HttpMethod
|
||||
{
|
||||
return HttpMethod::DELETE;
|
||||
}
|
||||
|
||||
public function getTimeout(): Duration
|
||||
{
|
||||
return Duration::fromSeconds((int) $this->config->timeout);
|
||||
}
|
||||
|
||||
public function getRetryStrategy(): ?RetryStrategy
|
||||
{
|
||||
// Exponential backoff: 1s, 2s, 4s
|
||||
return new ExponentialBackoffStrategy(
|
||||
maxAttempts: 3,
|
||||
baseDelaySeconds: 1,
|
||||
maxDelaySeconds: 10
|
||||
);
|
||||
}
|
||||
|
||||
public function getHeaders(): Headers
|
||||
{
|
||||
// Basic Auth
|
||||
$credentials = base64_encode("{$this->config->username}:{$this->config->password}");
|
||||
|
||||
return new Headers([
|
||||
'Authorization' => "Basic {$credentials}",
|
||||
'Accept' => 'application/json',
|
||||
]);
|
||||
}
|
||||
|
||||
public function getRequestName(): string
|
||||
{
|
||||
return 'rapidmail.delete_recipient';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail\ApiRequests;
|
||||
|
||||
use App\Framework\ApiGateway\ApiRequest;
|
||||
use App\Framework\ApiGateway\ValueObjects\ApiEndpoint;
|
||||
use App\Framework\Core\ValueObjects\Duration;
|
||||
use App\Framework\Http\Headers;
|
||||
use App\Framework\Http\Method as HttpMethod;
|
||||
use App\Framework\Http\Url\Url;
|
||||
use App\Framework\Retry\ExponentialBackoffStrategy;
|
||||
use App\Framework\Retry\RetryStrategy;
|
||||
use App\Infrastructure\Api\RapidMail\RapidMailConfig;
|
||||
|
||||
/**
|
||||
* Domain-specific API request for retrieving a recipient from RapidMail
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* $request = new GetRecipientApiRequest(
|
||||
* config: $rapidMailConfig,
|
||||
* recipientId: '12345'
|
||||
* );
|
||||
*
|
||||
* $response = $apiGateway->send($request);
|
||||
*/
|
||||
final readonly class GetRecipientApiRequest implements ApiRequest
|
||||
{
|
||||
public function __construct(
|
||||
private RapidMailConfig $config,
|
||||
private string $recipientId
|
||||
) {
|
||||
}
|
||||
|
||||
public function getEndpoint(): ApiEndpoint
|
||||
{
|
||||
$url = rtrim($this->config->baseUrl, '/') . '/recipients/' . $this->recipientId;
|
||||
|
||||
return ApiEndpoint::fromUrl(Url::parse($url));
|
||||
}
|
||||
|
||||
public function getMethod(): HttpMethod
|
||||
{
|
||||
return HttpMethod::GET;
|
||||
}
|
||||
|
||||
public function getTimeout(): Duration
|
||||
{
|
||||
return Duration::fromSeconds((int) $this->config->timeout);
|
||||
}
|
||||
|
||||
public function getRetryStrategy(): ?RetryStrategy
|
||||
{
|
||||
// Exponential backoff: 1s, 2s, 4s
|
||||
return new ExponentialBackoffStrategy(
|
||||
maxAttempts: 3,
|
||||
baseDelaySeconds: 1,
|
||||
maxDelaySeconds: 10
|
||||
);
|
||||
}
|
||||
|
||||
public function getHeaders(): Headers
|
||||
{
|
||||
// Basic Auth
|
||||
$credentials = base64_encode("{$this->config->username}:{$this->config->password}");
|
||||
|
||||
return new Headers([
|
||||
'Authorization' => "Basic {$credentials}",
|
||||
'Accept' => 'application/json',
|
||||
]);
|
||||
}
|
||||
|
||||
public function getRequestName(): string
|
||||
{
|
||||
return 'rapidmail.get_recipient';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail\ApiRequests;
|
||||
|
||||
use App\Framework\ApiGateway\ApiRequest;
|
||||
use App\Framework\ApiGateway\ValueObjects\ApiEndpoint;
|
||||
use App\Framework\Core\ValueObjects\Duration;
|
||||
use App\Framework\Http\Headers;
|
||||
use App\Framework\Http\Method as HttpMethod;
|
||||
use App\Framework\Http\Url\Url;
|
||||
use App\Framework\Retry\ExponentialBackoffStrategy;
|
||||
use App\Framework\Retry\RetryStrategy;
|
||||
use App\Infrastructure\Api\RapidMail\RapidMailConfig;
|
||||
|
||||
/**
|
||||
* Domain-specific API request for searching recipients in RapidMail
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* $request = new SearchRecipientsApiRequest(
|
||||
* config: $rapidMailConfig,
|
||||
* filter: ['email' => 'user@example.com'],
|
||||
* page: 1,
|
||||
* perPage: 50
|
||||
* );
|
||||
*
|
||||
* $response = $apiGateway->send($request);
|
||||
*/
|
||||
final readonly class SearchRecipientsApiRequest implements ApiRequest
|
||||
{
|
||||
public function __construct(
|
||||
private RapidMailConfig $config,
|
||||
private array $filter = [],
|
||||
private int $page = 1,
|
||||
private int $perPage = 50
|
||||
) {
|
||||
}
|
||||
|
||||
public function getEndpoint(): ApiEndpoint
|
||||
{
|
||||
$baseUrl = rtrim($this->config->baseUrl, '/') . '/recipients';
|
||||
|
||||
// Build query parameters
|
||||
$queryParams = [
|
||||
'page' => (string) $this->page,
|
||||
'per_page' => (string) $this->perPage,
|
||||
];
|
||||
|
||||
// Merge filter parameters
|
||||
if (!empty($this->filter)) {
|
||||
foreach ($this->filter as $key => $value) {
|
||||
$queryParams[$key] = is_array($value) ? json_encode($value) : (string) $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Build query string
|
||||
$queryString = http_build_query($queryParams);
|
||||
|
||||
// Create URL with query parameters
|
||||
$url = Url::parse($baseUrl)->withQuery($queryString);
|
||||
|
||||
return ApiEndpoint::fromUrl($url);
|
||||
}
|
||||
|
||||
public function getMethod(): HttpMethod
|
||||
{
|
||||
return HttpMethod::GET;
|
||||
}
|
||||
|
||||
public function getTimeout(): Duration
|
||||
{
|
||||
return Duration::fromSeconds((int) $this->config->timeout);
|
||||
}
|
||||
|
||||
public function getRetryStrategy(): ?RetryStrategy
|
||||
{
|
||||
// Exponential backoff: 1s, 2s, 4s
|
||||
return new ExponentialBackoffStrategy(
|
||||
maxAttempts: 3,
|
||||
baseDelaySeconds: 1,
|
||||
maxDelaySeconds: 10
|
||||
);
|
||||
}
|
||||
|
||||
public function getHeaders(): Headers
|
||||
{
|
||||
// Basic Auth
|
||||
$credentials = base64_encode("{$this->config->username}:{$this->config->password}");
|
||||
|
||||
return new Headers([
|
||||
'Authorization' => "Basic {$credentials}",
|
||||
'Accept' => 'application/json',
|
||||
]);
|
||||
}
|
||||
|
||||
public function getRequestName(): string
|
||||
{
|
||||
return 'rapidmail.search_recipients';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail\ApiRequests;
|
||||
|
||||
use App\Framework\ApiGateway\ApiRequest;
|
||||
use App\Framework\ApiGateway\HasPayload;
|
||||
use App\Framework\ApiGateway\ValueObjects\ApiEndpoint;
|
||||
use App\Framework\Core\ValueObjects\Duration;
|
||||
use App\Framework\Http\Headers;
|
||||
use App\Framework\Http\Method as HttpMethod;
|
||||
use App\Framework\Http\Url\Url;
|
||||
use App\Framework\Retry\ExponentialBackoffStrategy;
|
||||
use App\Framework\Retry\RetryStrategy;
|
||||
use App\Infrastructure\Api\RapidMail\RapidMailConfig;
|
||||
|
||||
/**
|
||||
* Domain-specific API request for updating a recipient in RapidMail
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* $request = new UpdateRecipientApiRequest(
|
||||
* config: $rapidMailConfig,
|
||||
* recipientId: '12345',
|
||||
* recipientData: [
|
||||
* 'firstname' => 'Jane',
|
||||
* 'lastname' => 'Doe'
|
||||
* ]
|
||||
* );
|
||||
*
|
||||
* $response = $apiGateway->send($request);
|
||||
*/
|
||||
final readonly class UpdateRecipientApiRequest implements ApiRequest, HasPayload
|
||||
{
|
||||
public function __construct(
|
||||
private RapidMailConfig $config,
|
||||
private string $recipientId,
|
||||
private array $recipientData
|
||||
) {
|
||||
}
|
||||
|
||||
public function getEndpoint(): ApiEndpoint
|
||||
{
|
||||
$url = rtrim($this->config->baseUrl, '/') . '/recipients/' . $this->recipientId;
|
||||
|
||||
return ApiEndpoint::fromUrl(Url::parse($url));
|
||||
}
|
||||
|
||||
public function getMethod(): HttpMethod
|
||||
{
|
||||
return HttpMethod::PATCH;
|
||||
}
|
||||
|
||||
public function getPayload(): array
|
||||
{
|
||||
return $this->recipientData;
|
||||
}
|
||||
|
||||
public function getTimeout(): Duration
|
||||
{
|
||||
return Duration::fromSeconds((int) $this->config->timeout);
|
||||
}
|
||||
|
||||
public function getRetryStrategy(): ?RetryStrategy
|
||||
{
|
||||
// Exponential backoff: 1s, 2s, 4s
|
||||
return new ExponentialBackoffStrategy(
|
||||
maxAttempts: 3,
|
||||
baseDelaySeconds: 1,
|
||||
maxDelaySeconds: 10
|
||||
);
|
||||
}
|
||||
|
||||
public function getHeaders(): Headers
|
||||
{
|
||||
// Basic Auth
|
||||
$credentials = base64_encode("{$this->config->username}:{$this->config->password}");
|
||||
|
||||
return new Headers([
|
||||
'Authorization' => "Basic {$credentials}",
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
]);
|
||||
}
|
||||
|
||||
public function getRequestName(): string
|
||||
{
|
||||
return 'rapidmail.update_recipient';
|
||||
}
|
||||
}
|
||||
86
src/Infrastructure/Api/RapidMail/SendEmailApiRequest.php
Normal file
86
src/Infrastructure/Api/RapidMail/SendEmailApiRequest.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail;
|
||||
|
||||
use App\Framework\ApiGateway\ApiRequest;
|
||||
use App\Framework\ApiGateway\ValueObjects\ApiEndpoint;
|
||||
use App\Framework\Core\ValueObjects\Duration;
|
||||
use App\Framework\Http\Headers;
|
||||
use App\Framework\Http\Method as HttpMethod;
|
||||
use App\Framework\Http\Url\Url;
|
||||
use App\Framework\Retry\RetryStrategy;
|
||||
use App\Infrastructure\Api\RapidMail\ValueObjects\{EmailAddress, EmailTemplate, RapidMailApiKey};
|
||||
|
||||
/**
|
||||
* Domain-specific API request for sending emails via RapidMail
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* $request = new SendEmailApiRequest(
|
||||
* apiKey: $apiKey,
|
||||
* to: EmailAddress::fromString('user@example.com'),
|
||||
* template: EmailTemplate::fromString('welcome'),
|
||||
* data: ['name' => 'John Doe']
|
||||
* );
|
||||
*
|
||||
* $response = $apiGateway->send($request);
|
||||
*/
|
||||
final readonly class SendEmailApiRequest implements ApiRequest
|
||||
{
|
||||
public function __construct(
|
||||
private RapidMailApiKey $apiKey,
|
||||
private EmailAddress $to,
|
||||
private EmailTemplate $template,
|
||||
private array $data = [],
|
||||
private ?RetryStrategy $retryStrategy = null
|
||||
) {
|
||||
}
|
||||
|
||||
public function getEndpoint(): ApiEndpoint
|
||||
{
|
||||
return ApiEndpoint::fromUrl(
|
||||
Url::parse('https://api.rapidmail.com/v1/send')
|
||||
);
|
||||
}
|
||||
|
||||
public function getMethod(): HttpMethod
|
||||
{
|
||||
return HttpMethod::POST;
|
||||
}
|
||||
|
||||
public function getPayload(): array
|
||||
{
|
||||
return [
|
||||
'to' => $this->to->value,
|
||||
'template' => $this->template->value,
|
||||
'data' => $this->data,
|
||||
];
|
||||
}
|
||||
|
||||
public function getTimeout(): Duration
|
||||
{
|
||||
// Email sending can take a bit longer
|
||||
return Duration::fromSeconds(15);
|
||||
}
|
||||
|
||||
public function getRetryStrategy(): ?RetryStrategy
|
||||
{
|
||||
return $this->retryStrategy;
|
||||
}
|
||||
|
||||
public function getHeaders(): Headers
|
||||
{
|
||||
return new Headers([
|
||||
'Authorization' => "Bearer {$this->apiKey->value}",
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
]);
|
||||
}
|
||||
|
||||
public function getRequestName(): string
|
||||
{
|
||||
return 'rapidmail.send_email';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail\ValueObjects;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Value object for email address
|
||||
*/
|
||||
final readonly class EmailAddress
|
||||
{
|
||||
public function __construct(
|
||||
public string $value
|
||||
) {
|
||||
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
|
||||
throw new InvalidArgumentException("Invalid email address: {$value}");
|
||||
}
|
||||
}
|
||||
|
||||
public static function fromString(string $value): self
|
||||
{
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail\ValueObjects;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Value object for email template identifier
|
||||
*/
|
||||
final readonly class EmailTemplate
|
||||
{
|
||||
public function __construct(
|
||||
public string $value
|
||||
) {
|
||||
if (empty($value)) {
|
||||
throw new InvalidArgumentException('Email template identifier cannot be empty');
|
||||
}
|
||||
|
||||
if (!preg_match('/^[a-z0-9_-]+$/', $value)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Email template identifier must contain only lowercase letters, numbers, hyphens and underscores'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static function fromString(string $value): self
|
||||
{
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\RapidMail\ValueObjects;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Value object for RapidMail API key
|
||||
*/
|
||||
final readonly class RapidMailApiKey
|
||||
{
|
||||
public function __construct(
|
||||
public string $value
|
||||
) {
|
||||
if (empty($value)) {
|
||||
throw new InvalidArgumentException('RapidMail API key cannot be empty');
|
||||
}
|
||||
|
||||
if (strlen($value) < 32) {
|
||||
throw new InvalidArgumentException('RapidMail API key appears invalid (too short)');
|
||||
}
|
||||
}
|
||||
|
||||
public static function fromString(string $value): self
|
||||
{
|
||||
return new self($value);
|
||||
}
|
||||
}
|
||||
89
src/Infrastructure/Api/Shopify/CreateOrderApiRequest.php
Normal file
89
src/Infrastructure/Api/Shopify/CreateOrderApiRequest.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\Shopify;
|
||||
|
||||
use App\Framework\ApiGateway\ApiRequest;
|
||||
use App\Framework\ApiGateway\ValueObjects\ApiEndpoint;
|
||||
use App\Framework\Core\ValueObjects\Duration;
|
||||
use App\Framework\Http\Headers;
|
||||
use App\Framework\Http\Method as HttpMethod;
|
||||
use App\Framework\Http\Url\Url;
|
||||
use App\Framework\Retry\RetryStrategy;
|
||||
use App\Infrastructure\Api\Shopify\ValueObjects\{ShopifyApiKey, ShopifyStore};
|
||||
|
||||
/**
|
||||
* Domain-specific API request for creating orders in Shopify
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* $request = new CreateOrderApiRequest(
|
||||
* apiKey: $apiKey,
|
||||
* store: ShopifyStore::fromString('mystore'),
|
||||
* orderData: [
|
||||
* 'line_items' => [
|
||||
* ['variant_id' => 123, 'quantity' => 2]
|
||||
* ],
|
||||
* 'customer' => ['email' => 'customer@example.com']
|
||||
* ]
|
||||
* );
|
||||
*
|
||||
* $response = $apiGateway->send($request);
|
||||
*/
|
||||
final readonly class CreateOrderApiRequest implements ApiRequest
|
||||
{
|
||||
public function __construct(
|
||||
private ShopifyApiKey $apiKey,
|
||||
private ShopifyStore $store,
|
||||
private array $orderData,
|
||||
private ?RetryStrategy $retryStrategy = null
|
||||
) {
|
||||
}
|
||||
|
||||
public function getEndpoint(): ApiEndpoint
|
||||
{
|
||||
$shopUrl = "https://{$this->store->value}.myshopify.com/admin/api/2024-01/orders.json";
|
||||
|
||||
return ApiEndpoint::fromUrl(
|
||||
Url::parse($shopUrl)
|
||||
);
|
||||
}
|
||||
|
||||
public function getMethod(): HttpMethod
|
||||
{
|
||||
return HttpMethod::POST;
|
||||
}
|
||||
|
||||
public function getPayload(): array
|
||||
{
|
||||
return [
|
||||
'order' => $this->orderData,
|
||||
];
|
||||
}
|
||||
|
||||
public function getTimeout(): Duration
|
||||
{
|
||||
// Order creation should be quick
|
||||
return Duration::fromSeconds(10);
|
||||
}
|
||||
|
||||
public function getRetryStrategy(): ?RetryStrategy
|
||||
{
|
||||
return $this->retryStrategy;
|
||||
}
|
||||
|
||||
public function getHeaders(): Headers
|
||||
{
|
||||
return new Headers([
|
||||
'X-Shopify-Access-Token' => $this->apiKey->value,
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
]);
|
||||
}
|
||||
|
||||
public function getRequestName(): string
|
||||
{
|
||||
return 'shopify.create_order';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\Shopify\ValueObjects;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Value object for Shopify API access token
|
||||
*/
|
||||
final readonly class ShopifyApiKey
|
||||
{
|
||||
public function __construct(
|
||||
public string $value
|
||||
) {
|
||||
if (empty($value)) {
|
||||
throw new InvalidArgumentException('Shopify API key cannot be empty');
|
||||
}
|
||||
|
||||
// Shopify access tokens are typically 32+ characters
|
||||
if (strlen($value) < 32) {
|
||||
throw new InvalidArgumentException('Shopify API key appears invalid (too short)');
|
||||
}
|
||||
}
|
||||
|
||||
public static function fromString(string $value): self
|
||||
{
|
||||
return new self($value);
|
||||
}
|
||||
}
|
||||
51
src/Infrastructure/Api/Shopify/ValueObjects/ShopifyStore.php
Normal file
51
src/Infrastructure/Api/Shopify/ValueObjects/ShopifyStore.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Api\Shopify\ValueObjects;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Value object for Shopify store identifier
|
||||
*/
|
||||
final readonly class ShopifyStore
|
||||
{
|
||||
public function __construct(
|
||||
public string $value
|
||||
) {
|
||||
if (empty($value)) {
|
||||
throw new InvalidArgumentException('Shopify store identifier cannot be empty');
|
||||
}
|
||||
|
||||
// Shopify store names are lowercase alphanumeric with hyphens
|
||||
if (!preg_match('/^[a-z0-9][a-z0-9-]*[a-z0-9]$/', $value)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Shopify store identifier must be lowercase alphanumeric with hyphens'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static function fromString(string $value): self
|
||||
{
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full Shopify URL for this store
|
||||
*/
|
||||
public function getShopifyUrl(): string
|
||||
{
|
||||
return "https://{$this->value}.myshopify.com";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user