From d87a3f8fa11d3bcb3fed8880d082aceb13a37997 Mon Sep 17 00:00:00 2001 From: KristofersOzolinsMagebit Date: Tue, 11 Aug 2026 14:11:32 +0300 Subject: [PATCH 1/2] fix: accept a set of ACP API versions and ignore unknown fields The pinned 2025-10-01 is not a released version and the strict comparison rejected every conformant client before any handler ran. --- Api/Data/Response/ErrorResponseInterface.php | 19 +++++++++++ .../CompleteCheckoutSessionRequest.php | 9 ++--- .../Request/CreateCheckoutSessionRequest.php | 9 ++--- Model/Data/Request/DelegatePaymentRequest.php | 7 ++-- .../Request/UpdateCheckoutSessionRequest.php | 9 ++--- Model/Data/Response/ErrorResponse.php | 22 +++++++++++++ Service/ComplianceService.php | 33 ++++++++++++++++--- 7 files changed, 89 insertions(+), 19 deletions(-) diff --git a/Api/Data/Response/ErrorResponseInterface.php b/Api/Data/Response/ErrorResponseInterface.php index ac04966..9e2796f 100644 --- a/Api/Data/Response/ErrorResponseInterface.php +++ b/Api/Data/Response/ErrorResponseInterface.php @@ -23,6 +23,10 @@ interface ErrorResponseInterface public const CODE_INVALID_CARD = 'invalid_card'; public const CODE_DUPLICATE_REQUEST = 'duplicate_request'; public const CODE_IDEMPOTENCY_CONFLICT = 'idempotency_conflict'; + public const CODE_MISSING_API_VERSION = 'missing_api_version'; + public const CODE_UNSUPPORTED_API_VERSION = 'unsupported_api_version'; + + public const KEY_SUPPORTED_VERSIONS = 'supported_versions'; /** * Get type @@ -83,4 +87,19 @@ public function getParam(): ?string; * @return $this */ public function setParam(?string $param): self; + + /** + * Get the API versions this module accepts, newest first + * + * @return string[]|null + */ + public function getSupportedVersions(): ?array; + + /** + * Set the API versions this module accepts, newest first + * + * @param string[]|null $supportedVersions + * @return $this + */ + public function setSupportedVersions(?array $supportedVersions): self; } diff --git a/Model/Data/Request/CompleteCheckoutSessionRequest.php b/Model/Data/Request/CompleteCheckoutSessionRequest.php index 2037457..8c99b56 100644 --- a/Model/Data/Request/CompleteCheckoutSessionRequest.php +++ b/Model/Data/Request/CompleteCheckoutSessionRequest.php @@ -73,6 +73,7 @@ public function getPaymentData(): PaymentDataInterface public static function loadValidatorMetadata(ClassMetadata $metadata): void { // Validate raw data array directly per OpenAI Agentic Checkout Spec + // allowExtraFields stays true: ACP adds optional fields between releases; ignore, never reject. $metadata->addGetterConstraint('rawData', new Assert\Collection([ 'fields' => [ 'buyer' => new Assert\Optional([ @@ -91,7 +92,7 @@ public static function loadValidatorMetadata(ClassMetadata $metadata): void ]), 'phone_number' => new Assert\Optional(), ], - 'allowExtraFields' => false, + 'allowExtraFields' => true, ]), ]), 'payment_data' => new Assert\Required([ @@ -142,15 +143,15 @@ public static function loadValidatorMetadata(ClassMetadata $metadata): void new Assert\Length(max: 20), ]), ], - 'allowExtraFields' => false, + 'allowExtraFields' => true, ]), ]), ], - 'allowExtraFields' => false, + 'allowExtraFields' => true, ]), ]), ], - 'allowExtraFields' => false, + 'allowExtraFields' => true, 'allowMissingFields' => false, ])); } diff --git a/Model/Data/Request/CreateCheckoutSessionRequest.php b/Model/Data/Request/CreateCheckoutSessionRequest.php index 73b5acf..c52f6ff 100644 --- a/Model/Data/Request/CreateCheckoutSessionRequest.php +++ b/Model/Data/Request/CreateCheckoutSessionRequest.php @@ -78,6 +78,7 @@ public function getBuyer(): ?BuyerInterface public static function loadValidatorMetadata(ClassMetadata $metadata): void { // Validate raw data array directly per OpenAI Agentic Checkout Spec + // allowExtraFields stays true: ACP adds optional fields between releases; ignore, never reject. $metadata->addGetterConstraint('rawData', new Assert\Collection([ 'fields' => [ 'buyer' => new Assert\Optional([ @@ -96,7 +97,7 @@ public static function loadValidatorMetadata(ClassMetadata $metadata): void ]), 'phone_number' => new Assert\Optional(), ], - 'allowExtraFields' => false, + 'allowExtraFields' => true, ]), ]), 'items' => new Assert\Required([ @@ -115,7 +116,7 @@ public static function loadValidatorMetadata(ClassMetadata $metadata): void new Assert\GreaterThan(0, message: 'Quantity must be greater than 0'), ]), ], - 'allowExtraFields' => false, + 'allowExtraFields' => true, ]), ]), ]), @@ -152,11 +153,11 @@ public static function loadValidatorMetadata(ClassMetadata $metadata): void new Assert\Length(max: 20), ]), ], - 'allowExtraFields' => false, + 'allowExtraFields' => true, ]), ]), ], - 'allowExtraFields' => false, + 'allowExtraFields' => true, 'allowMissingFields' => false, ])); } diff --git a/Model/Data/Request/DelegatePaymentRequest.php b/Model/Data/Request/DelegatePaymentRequest.php index 6f7e5a0..61d7322 100644 --- a/Model/Data/Request/DelegatePaymentRequest.php +++ b/Model/Data/Request/DelegatePaymentRequest.php @@ -138,6 +138,7 @@ public function setMetadata(array $metadata): DelegatePaymentRequestInterface */ public static function loadValidatorMetadata(ClassMetadata $metadata): void { + // allowExtraFields stays true: ACP adds optional fields between releases; ignore, never reject. $metadata->addGetterConstraint('rawData', new Assert\Collection([ 'fields' => [ 'payment_method' => new Assert\Required([ @@ -224,7 +225,7 @@ public static function loadValidatorMetadata(ClassMetadata $metadata): void ), ]), ], - 'allowExtraFields' => false, + 'allowExtraFields' => true, ]), ]), 'billing_address' => new Assert\Optional([ @@ -257,7 +258,7 @@ public static function loadValidatorMetadata(ClassMetadata $metadata): void new Assert\Length(max: 20), ]), ], - 'allowExtraFields' => false, + 'allowExtraFields' => true, ]), ]), 'risk_signals' => new Assert\Required([ @@ -278,7 +279,7 @@ public static function loadValidatorMetadata(ClassMetadata $metadata): void new Assert\Choice(['blocked', 'manual_review', 'authorized'], message: 'Action must be "blocked", "manual_review", or "authorized"'), ]), ], - 'allowExtraFields' => false, + 'allowExtraFields' => true, ]), ]), ]), diff --git a/Model/Data/Request/UpdateCheckoutSessionRequest.php b/Model/Data/Request/UpdateCheckoutSessionRequest.php index 714d0e7..1e4ff4d 100644 --- a/Model/Data/Request/UpdateCheckoutSessionRequest.php +++ b/Model/Data/Request/UpdateCheckoutSessionRequest.php @@ -86,6 +86,7 @@ public function getFulfillmentOptionId(): ?string public static function loadValidatorMetadata(ClassMetadata $metadata): void { // Validate raw data array directly per OpenAI Agentic Checkout Spec + // allowExtraFields stays true: ACP adds optional fields between releases; ignore, never reject. $metadata->addGetterConstraint('rawData', new Assert\Collection([ 'fields' => [ 'buyer' => new Assert\Optional([ @@ -104,7 +105,7 @@ public static function loadValidatorMetadata(ClassMetadata $metadata): void ]), 'phone_number' => new Assert\Optional(), ], - 'allowExtraFields' => false, + 'allowExtraFields' => true, ]), ]), 'items' => new Assert\Optional([ @@ -121,7 +122,7 @@ public static function loadValidatorMetadata(ClassMetadata $metadata): void new Assert\GreaterThan(0, message: 'Quantity must be greater than 0'), ]), ], - 'allowExtraFields' => false, + 'allowExtraFields' => true, ]), ]), ]), @@ -158,12 +159,12 @@ public static function loadValidatorMetadata(ClassMetadata $metadata): void new Assert\Length(max: 20), ]), ], - 'allowExtraFields' => false, + 'allowExtraFields' => true, ]), ]), 'fulfillment_option_id' => new Assert\Optional(), ], - 'allowExtraFields' => false, + 'allowExtraFields' => true, 'allowMissingFields' => true, ])); } diff --git a/Model/Data/Response/ErrorResponse.php b/Model/Data/Response/ErrorResponse.php index 2efb1bd..5ff65ef 100644 --- a/Model/Data/Response/ErrorResponse.php +++ b/Model/Data/Response/ErrorResponse.php @@ -83,4 +83,26 @@ public function setParam(?string $param): ErrorResponseInterface { return $this->setData('param', $param); } + + /** + * @inheritDoc + */ + public function getSupportedVersions(): ?array + { + $data = $this->getData(ErrorResponseInterface::KEY_SUPPORTED_VERSIONS); + + if (!is_array($data)) { + return null; + } + + return array_values(array_filter($data, 'is_string')); + } + + /** + * @inheritDoc + */ + public function setSupportedVersions(?array $supportedVersions): ErrorResponseInterface + { + return $this->setData(ErrorResponseInterface::KEY_SUPPORTED_VERSIONS, $supportedVersions); + } } diff --git a/Service/ComplianceService.php b/Service/ComplianceService.php index 66eeba2..72d83f5 100644 --- a/Service/ComplianceService.php +++ b/Service/ComplianceService.php @@ -23,7 +23,16 @@ class ComplianceService { - public const API_VERSION = '2025-10-01'; + /** Newest first — returned to clients as a preference list. */ + public const SUPPORTED_API_VERSIONS = [ + '2026-04-17', + '2026-01-30', + '2025-12-12', + '2025-09-29', + ]; + + /** The version we emit, not the newest we accept. */ + public const API_VERSION = '2025-09-29'; /** * @param ErrorResponseInterfaceFactory $errorResponseFactory @@ -47,7 +56,16 @@ public function __construct( */ public function validateApiVersion(Http $request): bool { - return $request->getHeader('API-Version') === self::API_VERSION; + return in_array($this->getRequestedApiVersion($request), self::SUPPORTED_API_VERSIONS, true); + } + + /** + * @param Http $request + * @return string + */ + public function getRequestedApiVersion(Http $request): string + { + return trim((string) $request->getHeader('API-Version')); } /** @@ -72,10 +90,17 @@ public function validateApiToken(Http $request): bool public function validateRequest(Http $request): ?ErrorResponseInterface { if (!$this->validateApiVersion($request)) { + $requestedVersion = $this->getRequestedApiVersion($request); + return $this->errorResponseFactory->create(['data' => [ 'type' => ErrorResponseInterface::TYPE_INVALID_REQUEST, - 'code' => 'invalid_api_version', - 'message' => 'Invalid API version', + 'code' => $requestedVersion === '' + ? 'missing_api_version' + : 'unsupported_api_version', + 'message' => $requestedVersion === '' + ? 'The API-Version header is required.' + : 'The requested API version is not supported.', + ErrorResponseInterface::KEY_SUPPORTED_VERSIONS => self::SUPPORTED_API_VERSIONS, ]]); } From 87c6c00a6dd2311a78bab3cf561f75aa230c2290 Mon Sep 17 00:00:00 2001 From: KristofersOzolinsMagebit Date: Tue, 11 Aug 2026 14:27:45 +0300 Subject: [PATCH 2/2] chore: removed notify prs workflow --- .github/workflows/notify-issues-prs.yml | 157 ------------------------ 1 file changed, 157 deletions(-) delete mode 100644 .github/workflows/notify-issues-prs.yml diff --git a/.github/workflows/notify-issues-prs.yml b/.github/workflows/notify-issues-prs.yml deleted file mode 100644 index 5a6841d..0000000 --- a/.github/workflows/notify-issues-prs.yml +++ /dev/null @@ -1,157 +0,0 @@ -name: Notify on Issues and PRs - -on: - issues: - types: [opened] - pull_request: - types: [opened] - -jobs: - notify: - runs-on: ubuntu-latest - - steps: - - name: Send Slack Notification for Issue - if: github.event_name == 'issues' - uses: slackapi/slack-github-action@v1.26.0 - with: - payload: | - { - "text": "🆕 New Issue Created", - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "🆕 New Issue Created" - } - }, - { - "type": "section", - "fields": [ - { - "type": "mrkdwn", - "text": "*Repository:*\n${{ github.repository }}" - }, - { - "type": "mrkdwn", - "text": "*Issue:*\n<${{ github.event.issue.html_url }}|#${{ github.event.issue.number }} ${{ github.event.issue.title }}>" - }, - { - "type": "mrkdwn", - "text": "*Author:*\n<${{ github.event.issue.user.html_url }}|${{ github.event.issue.user.login }}>" - }, - { - "type": "mrkdwn", - "text": "*Labels:*\n${{ join(github.event.issue.labels.*.name, ', ') || 'None' }}" - } - ] - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*Description:*\n${{ github.event.issue.body }}" - } - }, - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": "View Issue" - }, - "url": "${{ github.event.issue.html_url }}" - } - ] - } - ] - } - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK - - - name: Send Slack Notification for Pull Request - if: github.event_name == 'pull_request' - uses: slackapi/slack-github-action@v1.26.0 - with: - payload: | - { - "text": "🔀 New Pull Request Created", - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "🔀 New Pull Request Created" - } - }, - { - "type": "section", - "fields": [ - { - "type": "mrkdwn", - "text": "*Repository:*\n${{ github.repository }}" - }, - { - "type": "mrkdwn", - "text": "*Pull Request:*\n<${{ github.event.pull_request.html_url }}|#${{ github.event.pull_request.number }} ${{ github.event.pull_request.title }}>" - }, - { - "type": "mrkdwn", - "text": "*Author:*\n<${{ github.event.pull_request.user.html_url }}|${{ github.event.pull_request.user.login }}>" - }, - { - "type": "mrkdwn", - "text": "*Branch:*\n`${{ github.event.pull_request.head.ref }}` → `${{ github.event.pull_request.base.ref }}`" - } - ] - }, - { - "type": "section", - "fields": [ - { - "type": "mrkdwn", - "text": "*Status:*\n${{ github.event.pull_request.draft && '📝 Draft' || '✅ Ready for Review' }}" - }, - { - "type": "mrkdwn", - "text": "*Labels:*\n${{ join(github.event.pull_request.labels.*.name, ', ') || 'None' }}" - } - ] - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*Description:*\n${{ github.event.pull_request.body }}" - } - }, - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": "View Pull Request" - }, - "url": "${{ github.event.pull_request.html_url }}" - }, - { - "type": "button", - "text": { - "type": "plain_text", - "text": "View Changes" - }, - "url": "${{ github.event.pull_request.html_url }}/files" - } - ] - } - ] - } - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK