Integrating custom payment gateways is essential for expanding checkout capabilities and supporting regional preferences. Shopware 6, particularly in version 6.7, provides a mature, type-safe architecture that simplifies connecting external providers while enforcing security and performance standards. This guide details how to implement payment integrations via API using Shopware's extension framework, focusing on modern development practices compatible with Shopware 6.7+.
The Payment Handler Architecture
In Shopware 6.7, custom payments are built around the PaymentMethodHandlerInterface. The architecture separates the checkout flow from gateway logic. Your handler acts as the orchestrator: it receives order data, validates context, constructs API requests to the provider, and maps the response to a result that updates the transaction entity.
Shopware 6.7 reinforces this pattern by requiring strict typing and immutable configurations where possible. The framework utilizes PSR-18 HTTP clients, ensuring your integrations are portable and testable.
Implementing the Handler in Shopware 6.7
Below is a complete example of a synchronous payment handler. This code demonstrates Dependency Injection (DI), secure request construction, and proper error handling compatible with Shopware 6.7+.
declare(strict_types=1);
namespace YourVendor\\YourShopwareExtension\\Payment\\Handler;
use Psr\\Http\\Client\\ClientInterface;
use Psr\\Http\\Message\\ResponseInterface;
use Shopware\\Core\\Checkout\\Payment\\Cart\\Order\\Entity\\OrderTransactionEntity;
use Shopware\\Core\\Checkout\\Payment\\PaymentException;
use Shopware\\Core\\Checkout\\Payment\\SalesChannel\\PaymentMethodHandlerInterface;
use Shopware\\Core\\Checkout\\Payment\\SalesChannel\\PaymentResult;
use Shopware\\Core\\Framework\\Http\\ClientResponseTrait;
use Symfony\\Contracts\\Translation\\TranslatorInterface;
use Throwable;
final class CustomGatewayPaymentMethodHandler implements PaymentMethodHandlerInterface
{
use ClientResponseTrait;
public function __construct(
private readonly ClientInterface $httpClient,
private readonly string $apiKey,
private readonly string $baseUrl,
private readonly TranslatorInterface $translator
) {}
/**
* @param array<string, mixed> $payload
*/
public function finalize(OrderTransactionEntity $transaction, array $payload): PaymentResult
{
$orderId = (string) $transaction->getOrderId();
$amount = $transaction->getAmount()->getTotalPrice();
try {
// Construct payload for gateway API
$requestPayload = [
'order_reference' => $orderId,
'amount' => $amount,
'currency' => $transaction->getOrder()->getCurrency()->getIsoCode(),
'token' => $payload['gateway_token'] ?? null,
];
// Execute API call via PSR-18 Client
/** @var ResponseInterface $response */
$response = $this->httpClient->request('POST', sprintf('%s/charges', $this->baseUrl), [
'json' => $requestPayload,
'headers' => [
'Authorization' => 'Bearer ' . $this->apiKey,
'Content-Type' => 'application/json',
'Idempotency-Key' => md5($orderId . $amount . microtime(true)), // Prevent double charges
],
]);
$data = $this->getJsonArrayFromResponse($response);
if ($data['status'] === 'approved') {
return new PaymentResult(
true,
$data['provider_transaction_id'],
[
'provider_payment_id' => $data['id'],
'raw_response' => json_encode($data),
]
);
}
// Map gateway errors to Shopware UI messages
throw PaymentException::paymentDeclined(
$orderId,
(string) $transaction->getId(),
$this->translator->trans('checkout.paymentError', ['%message%' => $data['error_message'] ?? 'Unknown error'])
);
} catch (PaymentException $e) {
throw $e; // Re-throw validation/payment exceptions directly
} catch (Throwable $e) {
// Handle connectivity or parsing errors
throw new \Exception(
$this->translator->trans('checkout.paymentServiceUnavailable'),
0,
$e
);
}
}
}
Key Implementation Details
- Dependency Injection: The handler constructor accepts the HTTP client and configuration parameters. In Shopware 6.7, using
readonlyproperties ensures immutability after instantiation, which is crucial for concurrent request safety. Configuration values like API keys should be injected via system parameters, never hardcoded. - HTTP Client: We utilize
ClientInterface, which resolves to Shopware's underlying HTTP client implementation. This allows you to override the client globally or per-handler for custom SSL configurations, proxies, or timeout limits. - Idempotency: The example includes an
Idempotency-Keyheader. When calling payment APIs, always generate a unique key per request (e.g., using order ID and timestamp) to prevent double charges if the gateway receives duplicate requests due to network retries. - Error Handling:
PaymentException::paymentDeclinedis used to communicate decline reasons back to the checkout flow. Other exceptions are wrapped to protect sensitive stack traces from leaking to the frontend while ensuring the transaction fails safely.
DTOs and Response Validation
Shopware 6.7 encourages type safety throughout the stack. When parsing API responses, avoid using raw arrays for business logic. Instead, define a Data Transfer Object (DTO) that matches the gateway's schema. Use libraries like Symfony Serializer or custom validation to ensure the response structure is correct before extracting values. This prevents runtime errors caused by API schema drift and improves code maintainability.
Asynchronous Payments and Webhooks
Many modern gateways operate asynchronously. For these flows, extend Shopware\Core\Checkout\Payment\SalesChannel\AbstractAsyncPaymentHandler. Your handler should return a response that redirects the user to the provider or returns a JSON payload containing an approval URL.
When the payment provider confirms the transaction via webhook, Shopware exposes an endpoint (typically /custom/sw-gateway-webhook) where you must validate the request signature, verify the amount, and update the order state using the Order Repository. Ensure your webhook handler is idempotent to handle duplicate notifications gracefully.
Service Registration
To activate the integration, register the service in services.xml. You must tag the handler with shopware.payment.method_handler and map it to the Payment Method ID created in the Shopware Administration.
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services">
<services>
<!-- Configuration parameters should be defined in SystemConfiguration -->
<service id="YourVendor\\YourShopwareExtension\\Payment\\Handler\\CustomGatewayPaymentMethodHandler">
<argument type="service" id="Psr\\Http\\Client\\ClientInterface"/>
<argument>%shopware.custom_gateway.api_key%</argument>
<argument>%shopware.custom_gateway.base_url%</argument>
<argument type="service" id="translator"/>
<!-- Link to the Payment Method ID defined in the Administration -->
<tag name="shopware.payment.method_handler" method-id="custom-gateway-method-id"/>
</service>
</services>
</container>
- System Configuration: Use Shopware's
SystemConfigurationclass to allow admins to input API keys and URLs in the backend. Store these secrets insw_sys_configusing encryption masks where supported. - Method ID: The
method-idattribute in the tag must exactly match thetechnical_nameor UUID of the Payment Method entity in Shopware.
Security and Compliance
Connecting to payment gateways requires strict adherence to security best practices:
- Tokenization: Never store full credit card numbers or CVVs. Rely on the gateway's tokenization flow. Store only provider IDs and transaction references.
- Logging Sanitization: Implement a PSR-3 logger middleware or custom handler that masks sensitive fields in API requests and responses before they are written to Shopware's log files.
- HTTPS Enforcement: Ensure all outbound connections use TLS 1.2 or higher. Validate certificates strictly to prevent man-in-the-middle attacks.
- Testing: Write unit tests mocking
ClientInterfaceto simulate success, decline, and network failure scenarios. Use Shopware's test infrastructure to verify that order states transition correctly and that exceptions are handled as expected.
Conclusion
Shopware 6.7 provides a powerful, secure foundation for payment integrations. By leveraging the PaymentMethodHandlerInterface, strict dependency injection, and PSR-compliant HTTP clients, you can build robust connections to any modern payment gateway. Adhering to these architectural patterns ensures your extension remains maintainable, PCI-compliant, and fully compatible with the Shopware ecosystem. Whether implementing synchronous charges or asynchronous webhook flows, a disciplined approach to API integration will deliver a seamless checkout experience for your merchants and customers.