Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
d0eee21
feat: paypal-commerce-ai-route-scopes (#299)
lernhart Jul 16, 2025
e696f7b
feat: paypal commerce agent sales channel (#305)
lernhart Aug 4, 2025
b29f964
feat: paypal resolver (#324)
En0Ma1259 Aug 21, 2025
7ad9a74
feat: PayPal data transformer (#325)
En0Ma1259 Aug 21, 2025
284a9da
feat: route response (#316)
En0Ma1259 Aug 25, 2025
5c2c3ba
Merge remote-tracking branch 'origin/trunk' into feature/paypal-comme…
En0Ma1259 Aug 25, 2025
5f2225f
feat: delete old billing address (#341)
En0Ma1259 Aug 27, 2025
9ffd988
feat: added price validation (#347)
En0Ma1259 Aug 28, 2025
ca5c54c
feat: convert cart errors (#349)
En0Ma1259 Sep 1, 2025
0206052
fix: JWT token handling according to PayPal (#359)
lernhart Sep 3, 2025
5dbd94b
Merge branch 'trunk' into feature/paypal-commerce-agent
En0Ma1259 Sep 8, 2025
290a8de
Merge branch 'trunk' into feature/paypal-commerce-agent
En0Ma1259 Sep 8, 2025
6331ad1
feat: webhook for Agentic Commerce (#362)
En0Ma1259 Sep 9, 2025
d9ea76c
feat: added tests+ (#368)
En0Ma1259 Sep 15, 2025
36b3822
Merge branch 'trunk' into feature/paypal-commerce-agent
En0Ma1259 Sep 16, 2025
bc937b1
Unit tests (#378)
En0Ma1259 Sep 19, 2025
d1e17f8
feat: save paypal webhook status (#367)
En0Ma1259 Sep 29, 2025
5f04631
Merge branch 'trunk' into feature/paypal-commerce-agent
En0Ma1259 Sep 29, 2025
c24649a
feat: load favicon (#386)
En0Ma1259 Sep 30, 2025
8d93d18
fix: change signed with (#389)
En0Ma1259 Oct 1, 2025
7866697
feat: paypal commerce agent checkout (#333)
lernhart Oct 7, 2025
3f84dec
Merge branch 'trunk' into feature/paypal-commerce-agent
En0Ma1259 Oct 10, 2025
e4b7124
Merge branch 'trunk' into feature/paypal-commerce-agent
En0Ma1259 Oct 15, 2025
94da79d
feat: change shipping method (#398)
En0Ma1259 Oct 16, 2025
73218eb
feat: create coupons (#413)
En0Ma1259 Oct 22, 2025
1d84c21
feat: route tests (#417)
En0Ma1259 Oct 23, 2025
f1eb416
feat: subscriber tests (#422)
En0Ma1259 Oct 24, 2025
8e33a2b
Merge remote-tracking branch 'origin/trunk' into feature/paypal-comme…
En0Ma1259 Oct 24, 2025
615f232
wrong method name
En0Ma1259 Oct 29, 2025
76a0b91
change namespace
En0Ma1259 Oct 29, 2025
724de3b
small changes
En0Ma1259 Nov 5, 2025
dea3423
Merge remote-tracking branch 'origin/trunk' into feature/paypal-comme…
En0Ma1259 Nov 6, 2025
95c94ba
Merge branch 'trunk' into feature/paypal-commerce-agent
En0Ma1259 Nov 7, 2025
d140c6b
Merge branch 'trunk' into feature/paypal-commerce-agent
En0Ma1259 Dec 16, 2025
7f0d155
Merge branch 'trunk' into feature/paypal-commerce-agent
En0Ma1259 Jan 7, 2026
0cde9b9
feat: Added country state (#518)
En0Ma1259 Jan 13, 2026
6e59d06
fix: external id validation
lernhart Jan 21, 2026
f4c0196
Merge branch 'trunk' into feature/paypal-commerce-agent
En0Ma1259 Jan 23, 2026
8e0a064
fix phpstan errors
En0Ma1259 Jan 23, 2026
ef206a1
fix: public key mismatch
lernhart Jan 23, 2026
d4fa672
Merge remote-tracking branch 'origin/feature/paypal-commerce-agent' i…
lernhart Jan 23, 2026
97fe950
fix: bearer auth header + update cart route scope
lernhart Jan 26, 2026
c9499e2
fix: remove available shipping methods which do not have calculated p…
lernhart Jan 27, 2026
42105a9
fix: route scope + shipping address response format
lernhart Jan 28, 2026
0b2afca
Merge branch 'trunk' into feature/paypal-commerce-agent
En0Ma1259 Feb 2, 2026
1f820a0
feat: external id contraint (#528)
En0Ma1259 Feb 4, 2026
39c1bdc
Merge branch 'trunk' into feature/paypal-commerce-agent
En0Ma1259 Feb 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
'general_phpdoc_annotation_remove' => ['annotations' => ['copyright', 'category']],
'linebreak_after_opening_tag' => false,
'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'],
'method_chaining_indentation' => true,
'multiline_comment_opening_closing' => true,
'multiline_whitespace_before_semicolons' => true,
'native_function_invocation' => ['scope' => 'namespaced', 'strict' => false, 'exclude' => ['ini_get']],
Expand Down
6 changes: 5 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
],
"require": {
"shopware/core": "~6.7.0@dev",
"shopware/paypal-sdk": "^1.6.2"
"shopware/paypal-sdk": "dev-feat/paypal-commerce-agent"
},
"extra": {
"shopware-plugin-class": "Swag\\PayPal\\SwagPayPal",
Expand Down Expand Up @@ -42,6 +42,10 @@
"npm run unit-setup --prefix ../../../src/Administration/Resources/app/administration",
"npm run unit-watch --prefix src/Resources/app/administration"
],
"init:testdb": [
"@putenv FORCE_INSTALL=true",
"@phpunit --group=none --testsuite migration,unit,integration,devops"
],
"lint": [
"@ecs-fix",
"@phpstan",
Expand Down
24 changes: 23 additions & 1 deletion phpstan-6.7.0.0.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ parameters:
ignoreErrors:
- message: '#.* method_exists\(\) with Shopware\\Core\\Checkout\\Order\\OrderEntity .*#'
identifier: function.impossibleType
path: tests/Checkout/Document/Zugferd/ZugferdSubscriberTest.php
paths:
- tests/Checkout/Document/Zugferd/ZugferdSubscriberTest.php
- tests/AgentCommerce/SalesChannel/CheckoutRouteTest.php

- identifier: symplify.noDynamicName
path: tests/Pos/Mock/MessageBusMock.php
Expand All @@ -41,6 +43,26 @@ parameters:
- tests/Storefront/Controller/ApplePayControllerTest.php
- tests/Storefront/Data/CheckoutSubscriberTest.php

- message: '#^Class Shopware\\Core\\Checkout\\Payment\\Cart\\Error\\PaymentMethodBlockedError constructor invoked with 3 parameters, 1\-2 required\.$#'
identifier: arguments.count
count: 1
path: tests/AgentCommerce/Validation/ValidationIssuesTest.php

- message: '#^Class Shopware\\Core\\Checkout\\Shipping\\Cart\\Error\\ShippingMethodBlockedError constructor invoked with 3 parameters, 1 required\.$#'
identifier: arguments.count
count: 1
path: tests/AgentCommerce/Validation/ValidationIssuesTest.php

- message: '#^Class Shopware\\Storefront\\Checkout\\Cart\\Error\\PaymentMethodChangedError constructor invoked with 5 parameters, 2 required\.$#'
identifier: arguments.count
count: 1
path: tests/AgentCommerce/Validation/ValidationIssuesTest.php

- message: '#^Class Shopware\\Storefront\\Checkout\\Cart\\Error\\ShippingMethodChangedError constructor invoked with 5 parameters, 2 required\.$#'
identifier: arguments.count
count: 1
path: tests/AgentCommerce/Validation/ValidationIssuesTest.php

- identifier: arguments.count
message: '#.*PaymentMethodBlockedError.*#'
paths:
Expand Down
27 changes: 27 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,30 @@ parameters:
identifier: typePerfect.noMixedMethodCaller
count: 1
path: tests/Reporting/ScheduledTask/TurnoverReportingTaskHandlerTest.php

-
message: '#^Parameter \$contextService of method Swag\\PayPal\\AgentCommerce\\.*\:\:__construct\(\) has typehint with internal interface Shopware\\Core\\System\\SalesChannel\\Context\\SalesChannelContextServiceInterface\.$#'
identifier: parameter.internalInterface
paths:
- src/AgentCommerce/Util/FaviconLoader.php
- src/AgentCommerce/Routing/AgentRequestContextResolver.php
- src/AgentCommerce/SalesChannel/CreateCartRoute.php
- src/AgentCommerce/SalesChannel/UpdateCartRoute.php

-
message: '#^Property \$contextService references internal interface Shopware\\Core\\System\\SalesChannel\\Context\\SalesChannelContextServiceInterface in its type\.$#'
identifier: property.internalInterface
paths:
- src/AgentCommerce/Util/FaviconLoader.php
- src/AgentCommerce/Routing/AgentRequestContextResolver.php
- src/AgentCommerce/SalesChannel/AbstractAgentCommerceRoute.php
- src/AgentCommerce/SalesChannel/CreateCartRoute.php
- src/AgentCommerce/SalesChannel/UpdateCartRoute.php

-
message: '#^Call to method get\(\) of internal interface Shopware\\Core\\System\\SalesChannel\\Context\\SalesChannelContextServiceInterface from outside its root namespace Shopware\.$#'
identifier: method.internalInterface
paths:
- src/AgentCommerce/Util/FaviconLoader.php
- src/AgentCommerce/Routing/AgentRequestContextResolver.php
- src/AgentCommerce/SalesChannel/AbstractAgentCommerceRoute.php
49 changes: 48 additions & 1 deletion phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,35 @@ parameters:
identifier: attribute.internalClass
message: '#.*Shopware\\Core\\Framework\\Log\\Package#'

- # Ignore property type referencing internal Shopware\PayPalSDK classes
identifier: property.internalClass
message: '#.*Shopware\\PayPalSDK\\.*#'

- # Ignore instantiation of internal Shopware\PayPalSDK classes
identifier: new.internalClass
message: '#.*Shopware\\PayPalSDK\\.*#'

- # Ignore parameter typehint with internal Shopware\PayPalSDK classes
identifier: parameter.internalClass
message: '#.*Shopware\\PayPalSDK\\.*#'

- # Ignore return typehint with internal Shopware\PayPalSDK classes
identifier: return.internalClass
message: '#.*Shopware\\PayPalSDK\\.*#'

- # Ignore method class with internal Shopware\PayPalSDK classes
identifier: method.internalClass
message: '#.*Shopware\\PayPalSDK\\.*#'

- # Ignore class constant with internal Shopware\PayPalSDK classes
identifier: classConstant.internalClass
message: '#.*Shopware\\PayPalSDK\\.*#'

- # Has to be fixed upstream, but we can ignore it for now
message: '#Call to deprecated method __construct\(\) of class Shopware\\Core\\Framework\\DataAbstractionLayer\\EntityDefinition#'
identifier: method.deprecated
path: 'tests/*'

- # 6.8 deprecation
identifier: method.deprecated
message: '#__construct.*Shopware\\Core\\Framework\\DataAbstractionLayer\\EntityDefinition.*#'
Expand All @@ -105,12 +134,13 @@ parameters:

- # 6.8 deprecation - Parameter order still has to be changed
identifier: method.deprecated
message: '#Call to deprecated method __construct\(\) of class .*\\PaymentMethodBlockedError#'
message: '#Call to deprecated method __construct\(\) of class .*\\(PaymentMethodBlockedError|ShippingMethodBlockedError|PaymentMethodChangedError|ShippingMethodChangedError)#'
paths:
- src/Checkout/Cart/Validation/CartValidator.php
- tests/Checkout/PUI/PUISubscriberTest.php
- tests/Checkout/SalesChannel/MethodEligibilityRouteTest.php
- tests/Storefront/Data/CheckoutSubscriberTest.php
- tests/AgentCommerce/Validation/ValidationIssuesTest.php

- # 6.8 deprecation - Will be removed
message: '#Shopware\\Commercial\\Subscription\\Checkout\\Cart\\Recurring\\SubscriptionRecurringDataStruct#'
Expand All @@ -120,6 +150,11 @@ parameters:
- tests/Checkout/Method/ACDCHandlerTest.php
- tests/Checkout/Method/VenmoHandlerTest.php

- # 6.8 deprecation - new parameter addressId will be required
identifier: method.deprecated
message: '#Call to deprecated method __construct\(\) of class .*\\(AddressValidationError|BillingAddressBlockedError)#'
path: tests/AgentCommerce/Validation/ValidationIssuesTest.php

- # 6.8 deprecation - reason:exception-change - Will throw \Shopware\Core\Framework\Util\UtilException instead of \Doctrine\DBAL\Exception\TableNotFoundException
identifier: method.deprecated
message: '#Call to deprecated method columnExists\(\) of class .*\\MigrationStep#'
Expand All @@ -128,6 +163,18 @@ parameters:
- src/Migration/Migration1675420139AddManagerDataToRun.php
- src/Migration/Migration1626082072AddStatusAndMessageCountToRun.php

- # 6.8 deprecation - reason:return-type-change - will use "strong" return type `mixed`
identifier: method.deprecated
message: '#Call to deprecated method getPayloadValue\(\) of class .*\\LineItem#'
paths:
- src/AgentCommerce/Util/PayPalCartTransformer.php
- src/AgentCommerce/Validation/ValidationIssues.php

- # 6.8 deprecation - Use the "defaults" property instead
identifier: method.deprecated
message: '#Call to deprecated method getDefaults\(\) of class Symfony\\Component\\Routing\\Attribute\\Route#'
path: tests/AgentCommerce/SalesChannel/DefaultRouteScopeTest.php

rules:
# Shopware core rules
- Shopware\Core\DevOps\StaticAnalyze\PHPStan\Rules\Internal\InternalClassRule
Expand Down
172 changes: 172 additions & 0 deletions src/AgentCommerce/Exception/AgentException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
<?php declare(strict_types=1);
/*
* (c) shopware AG <info@shopware.com>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Swag\PayPal\AgentCommerce\Exception;

use Shopware\Core\Framework\Log\Package;
use Shopware\PayPalSDK\Struct\AgenticCommerce\V1\AgentErrorDetail;
use Shopware\PayPalSDK\Struct\AgenticCommerce\V1\AgentErrorDetailCollection;
use Symfony\Component\HttpFoundation\Response;

#[Package('checkout')]
class AgentException extends AgentHttpException
{
public const INVALID_REQUEST = 'INVALID_REQUEST';
public const INVALID_CART_ID = 'INVALID_CART_ID';
public const CART_NOT_FOUND = 'CART_NOT_FOUND';
public const INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR';
public const SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE';
public const PAYMENT_PROCESSOR_UNAVAILABLE = 'PAYMENT_PROCESSOR_UNAVAILABLE';
public const PAYMENT_CAPTURE_FAILED = 'PAYMENT_CAPTURE_FAILED';
public const INVENTORY_SYSTEM_ERROR = 'INVENTORY_SYSTEM_ERROR';
public const ORDER_SYSTEM_ERROR = 'ORDER_SYSTEM_ERROR';

public static function requiredFieldsMissing(string ...$fields): self
{
$message = 'Required field \'{{ fields }}\' is missing';
$parameters = ['fields' => implode(', ', $fields)];
$details = new AgentErrorDetailCollection();

foreach ($fields as $field) {
$detail = (new AgentErrorDetail());
$detail->setField($field);
$detail->setIssue('MISSING_REQUIRED_FIELD');
$detail->setDescription(\sprintf('The field \'%s\' is required and cannot be empty', $field));

$details->add($detail);
}

return new self(
Response::HTTP_BAD_REQUEST,
self::INVALID_REQUEST,
$message,
$parameters,
$details
);
}

public static function requiredFieldInvalid(string $field, string $reason): self
{
$message = 'Required field \'{{ field }}\' is invalid: \'{{ reason }}\'';
$parameters = ['field' => $field, 'reason' => $reason];

$detail = new AgentErrorDetail();
$detail->setField($field);
$detail->setIssue('MISSING_REQUIRED_FIELD');
$detail->setDescription(\sprintf('The field \'%s\' is invalid: %s', $field, $reason));

return new self(
Response::HTTP_BAD_REQUEST,
self::INVALID_REQUEST,
$message,
$parameters,
new AgentErrorDetailCollection([$detail])
);
}

public static function invalidJSONFormat(): self
{
return new self(
Response::HTTP_BAD_REQUEST,
self::INVALID_REQUEST,
'Request body contains invalid JSON'
);
}

public static function unauthorized(string $message, ?\Throwable $previous = null): self
{
return new self(
Response::HTTP_UNAUTHORIZED,
self::INVALID_REQUEST,
$message,
previous: $previous,
);
}

public static function invalidCartId(): self
{
return new self(
Response::HTTP_BAD_REQUEST,
self::INVALID_CART_ID,
'Cart ID format is invalid. Expected format: CART-[a-zA-Z0-9]{32}'
);
}

public static function cartNotFound(string $token): self
{
return new self(
Response::HTTP_NOT_FOUND,
self::CART_NOT_FOUND,
'Cart with ID \'{{ token }}\' does not exist',
['token' => $token]
);
}

public static function databaseConnectionFailure(): self
{
return new self(
Response::HTTP_INTERNAL_SERVER_ERROR,
self::INTERNAL_SERVER_ERROR,
'A temporary system error occurred. Please try again later.'
);
}

public static function externalServiceFailure(): self
{
return new self(
Response::HTTP_INTERNAL_SERVER_ERROR,
self::SERVICE_UNAVAILABLE,
'The payment processor is currently unavailable. Please try again later.'
);
}

public static function paymentProcessorUnavailable(): self
{
return new self(
Response::HTTP_INTERNAL_SERVER_ERROR,
self::PAYMENT_PROCESSOR_UNAVAILABLE,
'Payment processing is temporarily unavailable'
);
}

public static function paymentCaptureFailed(string $message): self
{
$detail = (new AgentErrorDetail());
$detail->setField('payment_method');
$detail->setIssue('CAPTURE_FAILED');
$detail->setDescription($message);

$details = new AgentErrorDetailCollection([$detail]);

return new self(
Response::HTTP_INTERNAL_SERVER_ERROR,
self::PAYMENT_CAPTURE_FAILED,
'Unable to capture payment at this time',
[],
$details
);
}

public static function inventorySystemError(): self
{
return new self(
Response::HTTP_INTERNAL_SERVER_ERROR,
self::INVENTORY_SYSTEM_ERROR,
'Unable to reserve inventory for checkout'
);
}

public static function orderSystemError(?\Throwable $previous = null): self
{
return new self(
Response::HTTP_INTERNAL_SERVER_ERROR,
self::ORDER_SYSTEM_ERROR,
'Order could not be created due to system error',
previous: $previous,
);
}
}
32 changes: 32 additions & 0 deletions src/AgentCommerce/Exception/AgentHttpException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php declare(strict_types=1);
/*
* (c) shopware AG <info@shopware.com>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Swag\PayPal\AgentCommerce\Exception;

use Shopware\Core\Framework\HttpException;
use Shopware\Core\Framework\Log\Package;
use Shopware\PayPalSDK\Struct\AgenticCommerce\V1\AgentErrorDetailCollection;

#[Package('checkout')]
abstract class AgentHttpException extends HttpException
{
public function __construct(
int $statusCode,
string $errorCode,
string $message,
array $parameters = [],
protected AgentErrorDetailCollection $details = new AgentErrorDetailCollection(),
?\Throwable $previous = null
) {
parent::__construct($statusCode, $errorCode, $message, $parameters, $previous);
}

public function getDetails(): AgentErrorDetailCollection
{
return $this->details;
}
}
Loading
Loading