Introduction
Shopware 6.7 represents a significant evolution in e-commerce platform capabilities, offering developers enhanced flexibility for creating custom payment integrations. This blog post will guide you through building a complete custom payment method from scratch, leveraging the latest features and best practices available in Shopware 6.7.
Prerequisites and Setup
Before diving into implementation, ensure you have:
- Shopware 6.7+ installed
- PHP 8.1+
- Composer for dependency management
- Basic understanding of Symfony components
- Access to Shopware's admin panel
The foundation of any custom payment integration begins with creating a proper plugin structure. Start by generating your plugin skeleton:
bin/console plugin:create MyPaymentPlugin --namespace MyCompany\MyPaymentPlugin
Plugin Structure and Registration
Your plugin directory should follow this structure:
src/
├── MyPaymentPlugin.php
├── Resources/
│ ├── config/
│ │ └── services.xml
│ ├── config/
│ │ └── routes.xml
│ └── public/
└── Core/
└── Payment/
└── Method/
└── CustomPaymentMethod.php
The main plugin class acts as the entry point:
<?php declare(strict_types=1);
namespace MyCompany\MyPaymentPlugin;
use Shopware\Core\Framework\Plugin;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class MyPaymentPlugin extends Plugin
{
public function build(ContainerBuilder $container): void
{
parent::build($container);
// Custom service registration
}
}
Payment Method Implementation
Creating a custom payment method involves implementing several core interfaces. The payment method class must extend the base payment method and implement necessary interfaces:
<?php declare(strict_types=1);
namespace MyCompany\MyPaymentPlugin\Core\Payment\Method;
use Shopware\Core\Checkout\Payment\Cart\PaymentHandler\AsynchronousPaymentHandlerInterface;
use Shopware\Core\Checkout\Payment\Cart\PaymentHandler\SynchronousPaymentHandlerInterface;
use Shopware\Core\Checkout\Payment\Cart\SyncPaymentTransactionStruct;
use Shopware\Core\Checkout\Payment\Exception\SyncPaymentProcessException;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Symfony\Component\HttpFoundation\Request;
class CustomPaymentMethod implements AsynchronousPaymentHandlerInterface
{
public function pay(SyncPaymentTransactionStruct $transaction, Request $request): string
{
// Generate payment URL for redirect
$paymentUrl = $this->generatePaymentUrl($transaction);
return $paymentUrl;
}
private function generatePaymentUrl(SyncPaymentTransactionStruct $transaction): string
{
// Implementation details
return 'https://your-payment-provider.com/pay';
}
}
Service Configuration
The services.xml configuration file defines how your payment method integrates with Shopware's payment system:
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="MyCompany\MyPaymentPlugin\Core\Payment\Method\CustomPaymentMethod">
<tag name="shopware.payment.method"/>
<argument type="service" id="Shopware\Core\Checkout\Payment\Cart\PaymentTransactionStructFactoryInterface"/>
</service>
<service id="MyCompany\MyPaymentPlugin\Core\Payment\CustomPaymentService">
<argument type="service" id="Doctrine\DBAL\Connection"/>
<argument type="service" id="Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria"/>
</service>
</services>
</container>
Payment Provider Integration
For the actual payment processing, you'll need to integrate with your chosen payment provider's API. Here's a simplified example of how to handle communication:
<?php declare(strict_types=1);
namespace MyCompany\MyPaymentPlugin\Core\Payment;
use GuzzleHttp\Client;
use Psr\Http\Message\ResponseInterface;
class PaymentProviderService
{
private Client $client;
public function __construct(string $apiKey)
{
$this->client = new Client([
'headers' => [
'Authorization' => 'Bearer ' . $apiKey,
'Content-Type' => 'application/json'
]
]);
}
public function createPayment(array $paymentData): array
{
$response = $this->client->post('https://api.payment-provider.com/payments', [
'json' => $paymentData
]);
return json_decode($response->getBody()->getContents(), true);
}
public function verifyPayment(string $transactionId): bool
{
$response = $this->client->get(
"https://api.payment-provider.com/payments/{$transactionId}"
);
$data = json_decode($response->getBody()->getContents(), true);
return $data['status'] === 'completed';
}
}
Transaction Management
Proper transaction handling is crucial for payment integrations. Shopware 6.7 provides robust transaction management through its payment cart system:
<?php declare(strict_types=1);
namespace MyCompany\MyPaymentPlugin\Core\Payment;
use Shopware\Core\Checkout\Order\OrderEntity;
use Shopware\Core\Checkout\Payment\Cart\PaymentTransactionStruct;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
class TransactionManager
{
public function createTransaction(OrderEntity $order, string $paymentMethodId): PaymentTransactionStruct
{
// Create transaction record in database
$transaction = new PaymentTransactionStruct();
$transaction->setOrder($order);
$transaction->setPaymentMethodId($paymentMethodId);
return $transaction;
}
public function updateTransactionStatus(string $transactionId, string $status): void
{
// Update transaction status in database
$this->connection->update(
'payment_transaction',
['status' => $status],
['id' => $transactionId]
);
}
}
Webhook Handling
Modern payment providers rely on webhooks for real-time status updates. Implementing webhook handling requires creating an endpoint that can process asynchronous notifications:
<?php declare(strict_types=1);
namespace MyCompany\MyPaymentPlugin\Controller;
use Shopware\Core\Framework\Routing\Annotation\RouteScope;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
#[RouteScope(routes: ['api'])]
class PaymentWebhookController extends AbstractController
{
#[Route('/api/payment/webhook/{transactionId}', name: 'api.payment.webhook', methods: ['POST'])]
public function handleWebhook(Request $request, string $transactionId): Response
{
try {
$payload = json_decode($request->getContent(), true);
// Validate webhook signature
if (!$this->validateSignature($request, $payload)) {
return new Response('Invalid signature', 401);
}
// Process payment status update
$this->processPaymentStatus($transactionId, $payload['status']);
return new Response('OK');
} catch (\Exception $e) {
return new Response('Error processing webhook: ' . $e->getMessage(), 500);
}
}
private function validateSignature(Request $request, array $payload): bool
{
// Signature validation logic
return true;
}
}
Configuration and Settings
Shopware 6.7's configuration system allows you to define custom settings for your payment method:
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/platform/master/src/Core/System/SystemConfig/Schema/config.xsd">
<card>
<title>Custom Payment Settings</title>
<input-field type="text">
<name>apiKey</name>
<label>API Key</label>
<helpText>Enter your payment provider API key</helpText>
</input-field>
<input-field type="checkbox">
<name>debugMode</name>
<label>Debug Mode</label>
<value>true</value>
</input-field>
</card>
</config>
Error Handling and Logging
Robust error handling is essential for payment integrations. Implement comprehensive logging to track payment failures and issues:
<?php declare(strict_types=1);
namespace MyCompany\MyPaymentPlugin\Core\Payment;
use Psr\Log\LoggerInterface;
class PaymentErrorHandler
{
private LoggerInterface $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function handlePaymentError(string $message, array $context = []): void
{
$this->logger->error('Payment Error: ' . $message, $context);
// Additional error handling logic
// Send notification to admin
// Log to database for tracking
}
}
Testing Your Integration
Testing payment integrations requires careful consideration of various scenarios:
<?php declare(strict_types=1);
namespace MyCompany\MyPaymentPlugin\Tests;
use PHPUnit\Framework\TestCase;
use Shopware\Core\Test\Checkout\Payment\PaymentTestCase;
class CustomPaymentIntegrationTest extends PaymentTestCase
{
public function testPaymentCreation(): void
{
$this->createPaymentMethod();
$payment = $this->getPaymentMethod();
$this->assertNotNull($payment);
}
public function testTransactionProcessing(): void
{
// Test transaction creation and processing
$this->markTestIncomplete('Implement transaction processing tests');
}
}
Performance Considerations
When building payment integrations, performance optimization is crucial:
- Caching: Implement caching for frequently accessed configuration data
- Asynchronous Processing: Use Symfony's messenger component for non-blocking operations
- Database Optimization: Optimize database queries and use proper indexing
- API Rate Limiting: Respect provider API rate limits with proper queuing
Security Best Practices
Security is paramount in payment processing:
- HTTPS Only: Ensure all communication occurs over HTTPS
- Data Encryption: Encrypt sensitive data at rest
- Input Validation: Validate all inputs from payment providers
- Authentication: Implement robust authentication for webhooks
- PCI Compliance: Follow PCI DSS requirements for card data handling
Deployment and Monitoring
Deploy your payment integration with proper monitoring:
# Enable plugin
bin/console plugin:activate MyPaymentPlugin
# Clear cache
bin/console cache:clear
# Warm up cache
bin/console cache:warmup
Monitor key metrics:
- Payment success rates
- Response times
- Error frequencies
- Transaction volumes
Conclusion
Building a custom payment integration in Shopware 6.7 requires understanding of the platform's architecture, proper service configuration, and robust error handling. The modular approach allows for clean separation of concerns while leveraging Shopware's powerful payment cart system.
By following the patterns outlined in this guide, you can create reliable, scalable payment integrations that seamlessly integrate with your Shopware 6.7 store. Remember to thoroughly test your implementation and monitor performance in production environments.
The flexibility provided by Shopware 6.7's architecture makes it possible to create payment solutions tailored to specific business needs while maintaining compliance with industry standards and best practices.