open-dxp / payment-provider-unzer-bundle
OpenDXP Payment Provider - Unzer (Heidelpay)
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
Type:opendxp-bundle
pkg:composer/open-dxp/payment-provider-unzer-bundle
Requires
- open-dxp/ecommerce-framework-bundle: ^1.0
- open-dxp/opendxp: ^1.0
- unzerdev/php-sdk: ^3.0 || ^1.2.0
Requires (Dev)
- phpstan/phpstan: ^2.0
- phpstan/phpstan-symfony: ^2.0
- symfony/dotenv: ^7.4
- symfony/runtime: ^7.4
README
Disclaimer
OpenDXP is a community-driven fork based on the Pimcore® Community Edition (GPLv3).
OpenDXP is independent and maintained by its community and contributors. It is not affiliated with, endorsed by, or sponsored by Pimcore GmbH.
Original credits: Pimcore GmbH
OpenDXP E-Commerce Framework Payment Provider - Unzer is based on the Pimcore® Community Edition and remains licensed under GPLv3.
Unzer Web Integration
To integrate Unzer web integration see Unzer docs and follow following steps.
The basic flow goes as following:
- Unzer gets initialized via java script and depending on activated payment methods additional form fields are injected to view template.
- User selects payment method and enters additional information if necessary (e.g. credit card information).
- Information is submitted to unzer, payment transaction is started and payment id is returned.
- Payment id is submitted back to OpenDxp and OpenDxp payment transaction is started.
- If necessary user is redirected to payment provider (e.g. Paypal).
- If user comes back from external payment site, payment state is checked server-to-server between OpenDxp and unzer and if successful order is committed and user is redirected to success page.
Installation
Install latest version with composer:
composer require open-dxp/payment-provider-unzer-bundle
Enable bundle via console:
php bin/console opendxp:bundle:enable OpenDxpPaymentProviderUnzerBundle php bin/console opendxp:bundle:install OpenDxpPaymentProviderUnzerBundle
Configuration
Setup payment provider in e-commerce framework configuration. The access keys you find in Unzer documentation (or you will get them from Unzer for production integrations).
unzer: provider_id: OpenDxp\Bundle\EcommerceFrameworkBundle\PaymentManager\Payment\Unzer profile: sandbox profiles: sandbox: publicAccessKey: s-pub-2a10gsZJ2IeiiK80Wh68qrOzu4IZse6k privateAccessKey: s-priv-2a10BF2Cq2YvAo6ALSGHc3X7F42oWAIp
Payment Information: Order payment section "Payment Informations" stores information about every payment trial by Customer.
Add additional fields in "PaymentInfo" fieldcollection, so that Order Manager stores information in Order object:

Create View Template
Create view template for payment method selection. This view template
- needs to include a javascript and a css from Unzer.
- has a list of all provided payment methods and depending of the payment method additional form elements.
- includes a java script that handles data communication the data to Unzer.
- has an additional form that submits successful payment information (Unzer payment id) back to OpenDxp.
Sample template with Creditcard, Paypal and Sofort
{% do opendxp_head_link().appendStylesheet('https://static.unzer.com/v1/unzer.css') %}
{% do opendxp_head_script().appendFile('https://static.unzer.com/v1/unzer.js') %}
{% do opendxp_inline_script().appendFile(asset('static/js/payment.js')) %} {# custom payment js, see below #}
<h4 class="mb-3">{{ 'checkout.payment' | trans }}</h4>
<div class="accordion" id="paymentAccordion">
<div class="card">
<div class="card-header" id="headingCC">
<div class="display-4 mb-0">
<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#collapseCC" aria-expanded="true" aria-controls="collapseCC">
{{ 'checkout.creditcard' | trans }}
</button>
<div class="float-right mr-4">
<img style="width: 40px" src="{{ asset('static/images/mc_vrt_pos.svg') }}" />
<img style="width: 40px" src="{{ asset('static/images/visa_inc_logo.svg') }}" />
</div>
</div>
</div>
<div id="collapseCC" class="collapse show" aria-labelledby="headingCC" data-parent="#paymentAccordion">
<div class="card-body">
<!-- credit card form as from the unzer docs -->
<form id="cc-form" class="unzerUI form" novalidate>
<div class="field">
<div id="card-element-id-number" class="unzerInput">
<!-- Card number UI Element will be inserted here. -->
</div>
</div>
<div class="two fields">
<div class="field ten wide">
<div id="card-element-id-expiry" class="unzerInput">
<!-- Card expiry date UI Element will be inserted here. -->
</div>
</div>
<div class="field six wide">
<div id="card-element-id-cvc" class="unzerInput">
<!-- Card CVC UI Element will be inserted here. -->
</div>
</div>
</div>
<div class="field" id="error-holder" style="color: #9f3a38"> </div>
<div class="field">
<button id="submit-button" class="btn btn-success btn-block" type="submit">{{ 'general.creditcard.pay' | trans }}</button>
</div>
</form>
</div>
</div>
</div>
<div class="card">
<div class="card-header" id="headingPaypal">
<div class="display-4 mb-0">
<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#collapsePaypal" aria-expanded="true" aria-controls="collapsePaypal">
{{ 'checkout.paypal' | trans }}
</button>
<div class="float-right mr-4">
<img style="width: 40px" src="{{ asset('static/images/PayPal_logo.svg') }}" />
</div>
</div>
</div>
<div id="collapsePaypal" class="collapse" aria-labelledby="headingPaypal" data-parent="#paymentAccordion">
<div class="card-body">
<!-- paypal form as from the unzer docs -->
<div class="field">
<button id="js-redirect-payment-method-paypal" class="btn btn-success btn-block">{{ 'general.paypal.pay' | trans }}</button>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header" id="headingSofort">
<div class="display-4 mb-0">
<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#collapseSofort" aria-expanded="true" aria-controls="collapseSofort">
{{ 'checkout.sofort' | trans }}
</button>
<div class="float-right mr-4">
<img style="width: 40px" src="{{ asset('static/images/SOFORT_ÜBERWEISUNG_Logo.svg') }}" />
</div>
</div>
</div>
<div id="collapseSofort" class="collapse" aria-labelledby="headingSofort" data-parent="#paymentAccordion">
<div class="card-body">
<!-- redirect payment content as from the unzer docs -->
<div class="field">
<button id="js-redirect-payment-method-paypal-sofort" class="btn btn-success btn-block">{{ 'checkout.sofort.pay' | trans }}</button>
</div>
</div>
</div>
</div>
</div>
<!-- Form to submit successful payment result to OpenDxp -->
<form id="js-submit-payment-result" action="{{ path('shop-checkout-start-payment') }}">
<input type="hidden" name="paymentMethod" class="js-payment-method-hidden" value=""/>
<input type="hidden" name="paymentId" class="js-payment-id-hidden" value=""/>
</form>
Sample Javascript (payment.js) with Creditcard, Paypal and Sofort
$(document).ready(function() { let unzerInstance = new unzer(_config.accessKey, {locale: 'en-GB'}); let $errorHolder = $('#error-holder'); let Card = unzerInstance.Card(); // Rendering input fields Card.create('number', { containerId: 'card-element-id-number', onlyIframe: false }); Card.create('expiry', { containerId: 'card-element-id-expiry', onlyIframe: false }); Card.create('cvc', { containerId: 'card-element-id-cvc', onlyIframe: false }); let ccForm = document.getElementById('cc-form'); let submitPaymentResultForm = document.getElementById('js-submit-payment-result'); // General event handling let buttonDisabled = {}; let submitButton = document.getElementById('submit-button'); submitButton.disabled = true; let successHandler = function(data) { console.log('success'); data.method = data.method ? data.method : 'card'; $('.js-payment-method-hidden').val(data.method); $('.js-payment-id-hidden').val(data.id); submitPaymentResultForm.submit(); }; let errorHandler = function(error) { console.log('error'); $errorHolder.html(error.message); }; Card.addEventListener('change', function(e) { if (e.success) { buttonDisabled[e.type] = true; submitButton.disabled = false; $errorHolder.html('') } else { buttonDisabled[e.type] = false; submitButton.disabled = true; $errorHolder.html(e.error) } submitButton.disabled = !(buttonDisabled.number && buttonDisabled.expiry && buttonDisabled.cvc); }); ccForm.addEventListener('submit', function(event) { event.preventDefault(); console.log('creditcard form submit'); Card.createResource() .then(successHandler) .catch(errorHandler) }); $('#js-redirect-payment-method-paypal').on('click', function(e){ e.preventDefault(); var Paypal = unzerInstance.Paypal(); Paypal.createResource() .then(successHandler) .catch(errorHandler) }); $('#js-redirect-payment-method-paypal-sofort').on('click', function(e){ e.preventDefault(); var Sofort = unzerInstance.Sofort(); Sofort.createResource() .then(successHandler) .catch(errorHandler) }); });
- Create Controller Action for Payment Selection
The only special thing in this controller action is to get public access key out of payment provider and assign it to the template.
/** * @Route("/checkout-payment", name="shop-checkout-payment") * * @param Factory $factory * @return array */ public function checkoutPaymentAction(Factory $factory) { $cartManager = $factory->getCartManager(); $cart = $cartManager->getOrCreateCartByName('cart'); $checkoutManager = $factory->getCheckoutManager($cart); $paymentProvider = $checkoutManager->getPayment(); $accessKey = ''; if($paymentProvider instanceof Unzer) { $accessKey = $paymentProvider->getPublicAccessKey(); } return [ 'cart' => $cart, 'accessKey' => $accessKey ]; }
- Create Controller Action for Starting Payment
To this action the paymentId of Unzer is submitted after payment transaction is started successfully on client side.
Additionally an error action is defined to extract the error messages from Unzer.
/** * @Route("/checkout-start-payment", name="shop-checkout-start-payment") * * @param Request $request * @param Factory $factory * @return RedirectResponse */ public function startPaymentAction(Request $request, Factory $factory, LoggerInterface $logger) { try { $cartManager = $factory->getCartManager(); $cart = $cartManager->getOrCreateCartByName('cart'); /** @var CheckoutManagerInterface $checkoutManager */ $checkoutManager = $factory->getCheckoutManager($cart); $paymentInfo = $checkoutManager->initOrderPayment(); /** @var OnlineShopOrder $order */ $order = $paymentInfo->getObject(); $paymentConfig = new UnzerRequest(); $paymentConfig->setInternalPaymentId($paymentInfo->getInternalPaymentId()); $paymentConfig->setPaymentReference($request->get('paymentId')); $paymentConfig->setReturnUrl($this->generateUrl('shop-commit-order', ['order' => $order->getOrdernumber()], UrlGeneratorInterface::ABSOLUTE_URL)); $paymentConfig->setErrorUrl($this->generateUrl('shop-checkout-payment-error', [], UrlGeneratorInterface::ABSOLUTE_URL)); $response = $checkoutManager->startOrderPaymentWithPaymentProvider($paymentConfig); if($response instanceof UrlResponse) { return new RedirectResponse($response->getUrl()); } } catch (\Exception $e) { $this->addFlash('danger', $e->getMessage()); $logger->error($e->getMessage()); return $this->redirectToRoute('shop-checkout-payment'); } } /** * @Route("/payment-error", name = "shop-checkout-payment-error" ) */ public function paymentErrorAction(Request $request, LoggerInterface $logger) { $logger->error('payment error: ' . $request->get('merchantMessage')); if($clientMessage = $request->get('clientMessage')) { $this->addFlash('danger', $clientMessage); } return $this->redirectToRoute('shop-checkout-payment'); }
- Create Controller Action for Commit Order Finally commit order and redirect user to order success page.
/** * @Route("/payment-commit-order", name="shop-commit-order") * * @param Request $request * @param Factory $factory * @param LoggerInterface $logger * @param Translator $translator * @param SessionInterface $session * @return RedirectResponse * @throws \OpenDxp\Bundle\EcommerceFrameworkBundle\Exception\UnsupportedException */ public function commitOrderAction(Request $request, Factory $factory, LoggerInterface $logger, Translator $translator, SessionInterface $session) { $order = OnlineShopOrder::getByOrdernumber($request->query->get('order'), 1); $cartManager = $factory->getCartManager(); $cart = $cartManager->getOrCreateCartByName('cart'); /** * @var CheckoutManagerInterface $checkoutManager */ $checkoutManager = $factory->getCheckoutManager($cart); try { $order = $checkoutManager->handlePaymentResponseAndCommitOrderPayment([ 'order' => $order ]); } catch(\Exception $e) { $logger->error($e->getMessage()); } if(!$order || $order->getOrderState() !== AbstractOrder::ORDER_STATE_COMMITTED) { $this->addFlash('danger', $translator->trans('checkout.payment-failed')); return $this->redirectToRoute('shop-checkout-payment'); } if (!$session->isStarted()) { $session->start(); } $session->set("last_order_id", $order->getId()); return $this->redirectToRoute('shop-checkout-completed'); }
Important Configuration
Please make sure that serialize_precision is set to a very high value, or even better to -1 in order to prevent rounding issues with the unzer sdk.
For details also see https://docs.unzer.com/integrate/php-sdk/installation/#php-configuration
Upstream Origin & Version Transparency
This project is a fork of the Pimcore payment-provider-unzer (d76cca0 / v2.0.1), which is © Pimcore GmbH and licensed under GPLv3.
License
Licensed under the GNU General Public License v3.0 (GPLv3). For details, please see LICENSE.md.
Copyright
© Pimcore GmbH
© 2025 OpenDXP Contributors — GPLv3
Trademarks
Pimcore® is a registered trademark of Pimcore GmbH. Any use of the Pimcore® mark in this repository is purely descriptive to identify the original upstream project.
Contact
For inquiries, suggestions, or contributions, feel free to reach us at contact@opendxp.ch.
About
OpenDXP is a community-driven project initiated by DACHCOM.DIGITAL (Rheineck, Switzerland) and maintained by its community and contributors. OpenDXP is independent and not affiliated with Pimcore GmbH.
The project’s purpose is to preserve and maintain a GPLv3‑licensed codebase for community use.
It is not positioned as a competitor to products or services of Pimcore GmbH and does not purport to replace or supersede any Pimcore offering.