pimcore / payment-provider-unzer
Pimcore 支付服务提供者 - Unzer (Heidelpay)
v2.0.1
2024-04-29 11:54 UTC
Requires
- pimcore/ecommerce-framework-bundle: ^1.0
- pimcore/pimcore: ^11.0
- unzerdev/php-sdk: ^3.0 || ^1.2.0
Requires (Dev)
- phpstan/phpstan: ^1.9
Conflicts
- pimcore/pimcore: v11.0.0-ALPHA1 || v11.0.0-ALPHA2
README
Unzer 网页集成
要集成 Unzer 网页集成,请参阅 Unzer 文档 并按照以下步骤操作。
基本流程如下:
- Unzer 通过 JavaScript 初始化,并根据激活的支付方式,将额外的表单字段注入到视图模板中。
- 用户选择支付方式,并根据需要输入额外信息(例如信用卡信息)。
- 信息提交给 Unzer,开始支付交易并返回支付 ID。
- 支付 ID 返回到 Pimcore,并开始 Pimcore 支付交易。
- 如果需要,用户将被重定向到支付服务提供商(例如 PayPal)。
- 如果用户从外部支付站点返回,Pimcore 和 Unzer 之间将服务器到服务器地检查支付状态,如果成功,则提交订单并将用户重定向到成功页面。
安装
使用 composer 安装最新版本
composer require pimcore/payment-provider-unzer
通过控制台或 Pimcore 后端的扩展管理器启用扩展
php bin/console pimcore:bundle:enable PimcorePaymentProviderUnzerBundle php bin/console pimcore:bundle:install PimcorePaymentProviderUnzerBundle
配置
在电子商务框架配置中设置支付服务提供商。您可以在 Unzer 文档中找到访问密钥(或者您将获得用于生产集成的访问密钥)。
unzer: provider_id: Pimcore\Bundle\EcommerceFrameworkBundle\PaymentManager\Payment\Unzer profile: sandbox profiles: sandbox: publicAccessKey: s-pub-2a10gsZJ2IeiiK80Wh68qrOzu4IZse6k privateAccessKey: s-priv-2a10BF2Cq2YvAo6ALSGHc3X7F42oWAIp
支付信息:订单支付部分“支付信息”存储客户每次支付尝试的信息。
在“PaymentInfo”字段集中添加额外字段,以便订单管理器在订单对象中存储信息:
创建视图模板
创建支付方法选择的视图模板。此视图模板
- 需要包含 Unzer 的 JavaScript 和 CSS。
- 包含所有提供的支付方法列表,并根据支付方法包含额外的表单元素。
- 包含处理与 Unzer 数据通信的 JavaScript。
- 包含一个额外的表单,用于提交成功的支付信息(Unzer 支付 ID)返回到 Pimcore。
信用卡、PayPal 和 Sofort 的示例模板
{% do pimcore_head_link().appendStylesheet('https://static.unzer.com/v1/unzer.css') %} {% do pimcore_head_script().appendFile('https://static.unzer.com/v1/unzer.js') %} {% do pimcore_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 Pimcore --> <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>
信用卡、PayPal 和 Sofort 的示例 JavaScript(payment.js)
$(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) }); });
- 创建支付选择控制器操作
在此控制器操作中,唯一特殊的地方是从支付服务提供商获取公共访问密钥并将其分配给模板。
/** * @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 ]; }
- 创建开始支付控制器操作
在此操作中,在客户端成功开始支付交易后,提交 Unzer 的支付 ID。
另外还定义了一个错误操作,用于从 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'); }
- 创建提交订单控制器操作 最后提交订单并将用户重定向到订单成功页面。
/** * @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 \Pimcore\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'); }
重要配置
请确保将 serialize_precision
设置为非常高的值,或者更好的是设置为 -1
,以防止与 Unzer SDK 的舍入问题。有关详细信息,也请参阅 https://docs.unzer.com/integrate/php-sdk/installation/#php-configuration