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:

  1. Caching: Implement caching for frequently accessed configuration data
  2. Asynchronous Processing: Use Symfony's messenger component for non-blocking operations
  3. Database Optimization: Optimize database queries and use proper indexing
  4. API Rate Limiting: Respect provider API rate limits with proper queuing

Security Best Practices

Security is paramount in payment processing:

  1. HTTPS Only: Ensure all communication occurs over HTTPS
  2. Data Encryption: Encrypt sensitive data at rest
  3. Input Validation: Validate all inputs from payment providers
  4. Authentication: Implement robust authentication for webhooks
  5. 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.