pimcore / payment-provider-mpay24-seamless
Pimcore 支付提供者 - MPay24 Seamless
v1.0.4
2024-04-29 11:51 UTC
Requires
- mpay24/mpay24-php: ^4.2
- pimcore/pimcore: ^10.0.0
Requires (Dev)
- phpstan/phpstan: ^1.9
README
官方 MPay24 文档
需求
- mpay24/mpay24-php": "^4.2"
安装
使用 Composer 安装最新版本
composer require pimcore/payment-provider-mpay24-seamless
通过控制台或 Pimcore 后端中的扩展管理器启用扩展
php bin/console pimcore:bundle:enable PimcorePaymentProviderMpay24SeamlessBundle php bin/console pimcore:bundle:install PimcorePaymentProviderMpay24SeamlessBundle
配置
支付管理器负责实现不同的支付提供者,以便将其集成到框架中。
有关支付管理器的更多信息,请参阅支付管理器文档。
在 pimcore_ecommerce_config.payment_manager
配置部分中配置支付提供者
pimcore_ecommerce_config: payment_manager: payment_manager_id: Pimcore\Bundle\EcommerceFrameworkBundle\PaymentManager\PaymentManager providers: mpay24: provider_id: Pimcore\Bundle\EcommerceFrameworkBundle\PaymentManager\Payment\Mpay24Seamless profile: testsystem profiles: _defaults: #paypal_activate_item_level: true partial: Shared/Includes/Shop/Payment/paymentMethods.html.php payment_methods: cc: paypal: sofort: invoice: testsystem: merchant_id: 95387 password: 7&jcQ%v6RB testSystem: true debugMode: true live: merchant_id: todo password: todo testSystem: false debugMode: false
实现
CheckoutController.php (渲染支付表单的动作)
...
//important: if payment is active, then keep payment state and do not allow parallel payment!
if ($this->checkoutManager->hasActivePayment() && $this->cart->isCartReadOnly()) {
return $this->redirectToRoute('app_shop_cart_list');
}
$paymentInfo = $this->checkoutManager->startOrderPayment();
$payment = $this->checkoutManager->getPayment();
$paymentFormAsString =
$payment->initPayment(
$this->cart->getPriceCalculator()->getGrandTotal(),
[
'request' => $request,
'paymentInfo' => $paymentInfo
]
);
$order = $this->checkoutManager->getOrder();
$order->setOrderState("");//important to unlock order payment, as order can be locked
$order->save(['versionInfo' => 'Clear state, because payment is not locked yet.']);
$this->view->paymentFormAsString = $paymentFormAsString;
在 order.html.php 视图的某个位置
...
<section>
<h2>Select Payment:</h2>
<?=$paymentFormAsString;?>
</section>
...
paymentMethods.html.php 视图 (在 ecommerce.yml 中链接)
<?php
/**
* Partial for payment methods + form generation, called + configured in Mpay24Seamless Provider
* @var $tokenizer
* @var string[] $paymentMethods
* @var string $selectedMethod
*/
// injected $selectedMethod if needed
$selectedMethod = isset($selectedMethod) ? $selectedMethod : 'cc';
?>
<form action="<?=$this->prettyUrl([],'app_shop_payment_start');?>" method="post" class="js-payment-methods-form js-cart-sidebar-update">
<input name="token" type="hidden" value="<?php echo $tokenizer->getToken(); ?>"/>
<? foreach ($paymentMethods as $method => $methodConfig):?>
<div class="custom-radio">
<label>
<input class="custom-radio__input js-payment-method-select" type="radio" name="type" value="<?=strtoupper($method);?>"
data-method="<?=$method;?>"
required="required" <?=$method == $selectedMethod ? 'checked="checked"' : "";?>>
<span class="custom-radio__box"></span>
<span class="custom-radio__text"><?=$this->t('shop.payment.methods.'.$method);?></span>
</label>
</div>
<? if ($method == 'cc'): ?>
<!-- @todo frontend optimise please -->
<div class="js-payment-method-attachment" id="payment-method-attachement-<?=$method;?>" style="<?=$selectedMethod == $method ? '' : 'display:none';?>">
<iframe src="<?php echo $tokenizer->getLocation(); ?>" frameBorder="0"></iframe>
</div>
<?endif;?>
<? endforeach;?>
<input type="submit" href="<?=$this->prettyUrl([], 'app_shop_checkout_start');?>" class="js-payment-submit btn btn-success btn-block mt-4"
value="<?=$this->t('shop.checkout.order.execute-payment');?>"/>
</form>
示例 PaymentController.php (负责支付动作)
<?php
namespace AppBundle\Controller\Shop;
use AppBundle\Controller\AbstractController;
use AppBundle\Ecommerce\OrderManager\OrderManager;
use AppBundle\Service;
use AppBundle\TraitAware\TraitLoggerAware;
use Pimcore\Bundle\EcommerceFrameworkBundle\Factory;
use Pimcore\Bundle\EcommerceFrameworkBundle\Model\AbstractOrder;
use Pimcore\Bundle\EcommerceFrameworkBundle\PaymentManager\Payment\Mpay24Seamless;
use Pimcore\Model\Element\Note;
use Pimcore\Tool;
use Pimcore\Translation\Translator;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Routing\Annotation\Route;
class PaymentController extends AbstractController\AbstractFrontendController
{
use TraitLoggerAware; //initializes getLogger() functionality
/**
* @Route("/{_prefix}/checkout/payment/start", requirements={"_prefix" = "\w\w\/?[\w\-]*"})
* @param Request $request
* @return RedirectResponse|\Symfony\Component\HttpFoundation\Response
* @throws \Exception
* @throws \Pimcore\Bundle\EcommerceFrameworkBundle\Exception\UnsupportedException
*/
public function startAction(Request $request, RouterInterface $router)
{
$serviceShop = \Pimcore::getContainer()->get(Service\Shop::class);
$checkoutManager = Factory::getInstance()->getCheckoutManager(
$serviceShop->getCart()
);
/** @var Mpay24Seamless $payment */
$payment = $checkoutManager->getPayment();
$paymentInfo = $checkoutManager->startOrderPayment();
$paymentType = $request->request->getString('type');
if ($request->isMethod('post')) {
if ($request->isMethod('post') && $paymentType == 'INVOICE') {
$factory = \Pimcore\Bundle\EcommerceFrameworkBundle\Factory::getInstance();
/** @var OrderManager $orderManager */
$orderManager = $factory->getOrderManager();
$order = $orderManager->getOrCreateOrderFromCart($serviceShop->getCart());
$factory->getCommitOrderProcessor()->commitOrder($order);
return $this->redirectToCheckoutSuccessPage($order);
} else {
$baseUrl = Tool::getHostUrl();
$paymentParams = [
'request' => $request,
'order' => $checkoutManager->getOrder(),
'paymentInfo' => $checkoutManager->getOrder()->getPaymentInfo()->get(0),
'successURL' => $baseUrl . $router->generate('app_shop_payment_response', ['type' => 'success', 'elementsclientauth'=>'disabled']),
'errorURL' => $baseUrl . $router->generate('app_shop_payment_response', ['type' => 'error', 'elementsclientauth'=>'disabled']),
'confirmationURL' => $baseUrl . $router->generate('app_shop_payment_response', ['type' => 'confirmation', 'elementsclientauth'=>'disabled']),
];
//either redirect to paypal, etc. or to internal (error) URL
list($redirectUrl, $errorText) = $payment->getInitPaymentRedirectUrl($paymentParams);
if ($errorText) {
$this->addFlash('error', [$errorText]);
//save note for debugging
$order = $checkoutManager->getOrder();
$note = new Note();
$note->setElement($order);
$note->setType("user_payment_denied");
$note->setTitle($errorText);
$note->setUser(0);
$note->save();
return $this->redirectToRoute('app_shop_checkout_start');
}
return new RedirectResponse($redirectUrl);
}
}
}
private function redirectToCheckoutSuccessPage(AbstractOrder $order) {
$factory = \Pimcore\Bundle\EcommerceFrameworkBundle\Factory::getInstance();
$orderManager = $factory->getOrderManager();
$encryptedOrderNumber = $orderManager->getEncryptedOrderNumber($order);
return $this->redirectToRoute('app_shop_checkout_success', ['o' => $encryptedOrderNumber]);
}
/**
* @Route("/{_prefix}/checkout/payment/mpay-response/{type}", requirements={"_prefix" = "\w\w\/?[\w\-]*"})
* @param Request $request
* @return RedirectResponse|\Symfony\Component\HttpFoundation\Response
* @throws \Exception
*/
public function responseAction(Request $request, Translator $translator)
{
$serviceShop = \Pimcore::getContainer()->get(Service\Shop::class);
$checkoutManager = Factory::getInstance()->getCheckoutManager(
$serviceShop->getCart()
);
$type = $request->request->getString('type');
if ($type == 'confirmation') {
//@see https://docs.mpay24.com/docs/backend2backend-integration
$this->getLogger()->info('Mpay24 called confirmation URL.');
$response = new Response();
$response->setContent("OK");
$response->headers->set('Content-Type', 'text/plain');
return $response;
}
//currently errors are also handled via commit processor and result in cancel-statements.
//Thus, the order remains uncommitted.
$commitOrderProcessor = Factory::getInstance()->getCommitOrderProcessor();
/** @var Mpay24Seamless $payment */
$payment = $checkoutManager->getPayment();
try {
$order = $commitOrderProcessor->handlePaymentResponseAndCommitOrderPayment(
$request->query->all(),$payment
);
if ($order->getOrderState() == AbstractOrder::ORDER_STATE_COMMITTED) {
return $this->redirectToCheckoutSuccessPage($order);
}
} catch (\Exception $e) {
$this->getLogger()->error(sprintf('Exception in payment Controller: %s', $e->getMessage()));
}
$this->addFlash('error', [$translator->trans('shop.payment.error-or-cancelled')]);
return $this->redirectToRoute('app_shop_checkout_start');
}
}