Skip to content
Snippets Groups Projects
Commit 46984476 authored by Jakub Piasecki's avatar Jakub Piasecki
Browse files

Issue #3467368 by zaporylie: Add support for shipping in Checkout when...

parent 9e215272
No related branches found
Tags 1.0.0-alpha3
1 merge request!7Issue #3467368 by zaporylie: Add support for shipping in Checkout when...
Pipeline #304547 passed with warnings
<?php
namespace Drupal\vipps_mobilepay_commerce\EventSubscriber;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\commerce_order\Adjustment;
use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\commerce_payment\Entity\PaymentGatewayInterface;
use Drupal\commerce_payment\Entity\PaymentInterface;
use Drupal\commerce_price\MinorUnitsConverterInterface;
use Drupal\commerce_shipping\ShippingOrderManagerInterface;
use Drupal\profile\Entity\ProfileInterface;
use Drupal\state_machine\Event\WorkflowTransitionEvent;
use Drupal\vipps_mobilepay\VippsMobilePayManagerFactoryInterface;
use Drupal\vipps_mobilepay_commerce\Plugin\Commerce\PaymentGateway\Checkout;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use zaporylie\Vipps\Api\v3\Checkout as CheckoutSdk;
use zaporylie\Vipps\Model\Checkout\v3\BillingDetails;
use zaporylie\Vipps\Model\Checkout\v3\ShippingDetails;
/**
* Adds checkout info when order is paid with Vipps Checkout.
*/
class OrderSubscriber implements EventSubscriberInterface {
use StringTranslationTrait;
/**
* The subscriber's constructor.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entity type manager.
* @param \Psr\Log\LoggerInterface $logger
* The logger.
* @param \Drupal\vipps_mobilepay\VippsMobilePayManagerFactoryInterface $managerFactory
* The Vipps MobilePay manager factory.
* @param \Drupal\commerce_price\MinorUnitsConverterInterface $minorUnitsConverter
* The units converter.
* @param \Drupal\commerce_shipping\ShippingOrderManagerInterface|null $orderManager
* The order manager.
*/
public function __construct(
protected EntityTypeManagerInterface $entityTypeManager,
protected LoggerInterface $logger,
protected VippsMobilePayManagerFactoryInterface $managerFactory,
protected MinorUnitsConverterInterface $minorUnitsConverter,
protected ?ShippingOrderManagerInterface $orderManager = NULL,
) {}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
return [
'commerce_order.place.pre_transition' => ['onPlace'],
];
}
/**
* Adds checkout info when order is paid with Vipps Checkout.
*
* @param \Drupal\state_machine\Event\WorkflowTransitionEvent $event
* The transition event.
*/
public function onPlace(WorkflowTransitionEvent $event) {
/** @var \Drupal\commerce_order\Entity\OrderInterface $order */
$order = $event->getEntity();
if (!$this->isPaidWithCheckout($order)) {
return;
}
/** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway */
$payment_gateway = $order->get('payment_gateway')->entity;
/** @var \Drupal\vipps_mobilepay_commerce\Plugin\Commerce\PaymentGateway\Checkout $payment_gateway_plugin */
$payment_gateway_plugin = $payment_gateway->getPlugin();
$profiles = $order->collectProfiles();
/** @var \Drupal\commerce_payment\PaymentStorageInterface $paymentStorage */
$paymentStorage = $this->entityTypeManager->getStorage('commerce_payment');
$payments = $paymentStorage->loadMultipleByOrder($order);
$payments = array_filter($payments, function (PaymentInterface $payment) use ($payment_gateway) {
if ($payment_gateway->id() !== $payment->getPaymentGatewayId()) {
return FALSE;
}
if (!in_array($payment->getState()->getId(), ['authorization', 'completed'])) {
return FALSE;
}
return TRUE;
});
if (count($payments) === 1) {
$payment = reset($payments);
}
else {
$this->logger->critical('Missing payment for order %order_id', ['%order_id' => $order->id()]);
return;
}
$settings = $payment_gateway_plugin->getConfiguration();
/** @var \Drupal\vipps_mobilepay\VippsMobilepayConnectionInterface $connection */
$connection = $this->entityTypeManager->getStorage('vipps_mobilepay_connection')->load($settings['connection_profile']);
$vipps = $this->managerFactory->get($connection);
$response = (new CheckoutSdk($vipps))
->getCheckoutSession($payment->getRemoteId());
$order->setEmail($response->getBillingDetails()->getEmail());
$billing_profile = $profiles['billing'] ?? NULL;
if (!$billing_profile) {
$profile_storage = $this->entityTypeManager->getStorage('profile');
$billing_profile = $profile_storage->create([
'type' => 'customer',
'uid' => 0,
]);
}
$this->populateProfile($billing_profile, $response->getBillingDetails());
if ($billing_profile->hasField('field_phone') && $response->getBillingDetails()->getPhoneNumber()) {
$billing_profile->field_phone->setValue($response->getBillingDetails()->getPhoneNumber());
}
$billing_profile->save();
$order->setBillingProfile($billing_profile);
$shipping_profile = $profiles['shipping'] ?? NULL;
if (!$shipping_profile && isset($this->orderManager)) {
$shipping_profile = $this->orderManager->createProfile($order, []);
}
if ($shipping_profile) {
$this->populateProfile($shipping_profile, $response->getBillingDetails(),
$response->getShippingDetails());
if ($shipping_profile->hasField('field_phone') && $response->getBillingDetails()
->getPhoneNumber()) {
$shipping_profile->field_phone->setValue($response->getBillingDetails()
->getPhoneNumber());
}
$shipping_profile->save();
if ($shipping_method_id = $response->getShippingDetails()->getShippingMethodId()) {
$shipments = $this->orderManager->pack($order, $shipping_profile);
/** @var \Drupal\commerce_shipping\Entity\ShipmentInterface $shipment */
foreach ($shipments as $shipment) {
[$shipping_method_id, $service_id] = explode('--', $shipping_method_id);
$shipment->setShippingMethodId($shipping_method_id);
$shipment->setShippingService($service_id);
$shipment->setAmount($this->minorUnitsConverter->fromMinorUnits($response->getShippingDetails()->getAmount()->getValue(), $response->getShippingDetails()->getAmount()->getCurrency()));
$shipment->save();
if ($amount = $shipment->getAmount()) {
// Shipments without an amount are incomplete / unrated.
$order->addAdjustment(new Adjustment([
'type' => 'shipping',
'label' => count($shipments) == 1 ? $this->t('Shipping') : $shipment->getTitle(),
'amount' => $amount,
'source_id' => $shipment->id(),
]));
foreach ($shipment->getAdjustments() as $adjustment) {
if ($adjustment->isLocked()) {
// Locked shipment adjustments must be transferred unlocked
// so that they're cleared at the beginning of order refresh.
$adjustment = new Adjustment([
'locked' => FALSE,
] + $adjustment->toArray());
}
$order->addAdjustment($adjustment);
}
}
}
}
}
}
/**
* Returns whether the order's payment gateway is Vipps MobilePay Checkout.
*
* @param \Drupal\commerce_order\Entity\OrderInterface $order
* The order.
*
* @return bool
* TRUE if the order's payment gateway is Checkout, FALSE otherwise.
*/
protected function isPaidWithCheckout(OrderInterface $order) {
$payment_gateway = !$order->get('payment_gateway')->isEmpty() ? $order->get('payment_gateway')->entity : FALSE;
return $payment_gateway instanceof PaymentGatewayInterface && $payment_gateway->getPlugin() instanceof Checkout;
}
/**
* Populates the given profile with the given address.
*
* @param \Drupal\profile\Entity\ProfileInterface $profile
* The profile to populate.
* @param \zaporylie\Vipps\Model\Checkout\v3\BillingDetails $billingDetails
* The Vipps address.
* @param \zaporylie\Vipps\Model\Checkout\v3\ShippingDetails|null $shippingDetails
* The Vipps address.
*/
protected function populateProfile(ProfileInterface $profile, BillingDetails $billingDetails, ShippingDetails|null $shippingDetails = NULL) {
$address_properties = [
'organization',
'address_line1',
'address_line2',
'locality',
'administrative_area',
'postal_code',
'country_code',
'given_name',
'family_name',
];
foreach ($address_properties as $key) {
$value = match ($key) {
'address_line1' => ($shippingDetails ? $shippingDetails->getStreetAddress() : $billingDetails->getStreetAddress()) ?: $billingDetails->getStreetAddress(),
'locality' => ($shippingDetails ? $shippingDetails->getCity() : $billingDetails->getCity()) ?: $billingDetails->getCity(),
'postal_code' => ($shippingDetails ? $shippingDetails->getPostalCode() : $billingDetails->getPostalCode()) ?: $billingDetails->getPostalCode(),
'country_code' => ($shippingDetails ? $shippingDetails->getCountry() : $billingDetails->getCountry()) ?: $billingDetails->getCountry(),
'given_name' => ($shippingDetails ? $shippingDetails->getLastName() : $billingDetails->getLastName()) ?: $billingDetails->getLastName(),
'family_name' => ($shippingDetails ? $shippingDetails->getFirstName() : $billingDetails->getFirstName()) ?: $billingDetails->getFirstName(),
default => NULL,
};
/** @var \Drupal\address\Plugin\Field\FieldType\AddressFieldItemList $field */
$field = $profile->address;
$field_value = $field->getValue();
if ($field->first()) {
// If it's already set a value, then the values will be number indexed
// here. So instead of being that, let's make sure they are an
// associative array of properties like we want.
$field_value = $field->first()->getValue();
}
$field_value[$key] = $value;
$field->setValue($field_value);
}
}
}
<?php
namespace Drupal\vipps_mobilepay_commerce\EventSubscriber;
use Drupal\vipps_mobilepay_commerce\Event\InitiateCheckoutEvent;
use Drupal\vipps_mobilepay_commerce\Event\VippsMobilePayEvents;
use Drupal\vipps_mobilepay_commerce\Resolver\CommerceShippingMethodsResolver;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use zaporylie\Vipps\Model\Checkout\v3\Logistics;
/**
* Adds shipping rates to the Vipps MobilePay checkout.
*/
class ShippingCheckoutEventSubscriber implements EventSubscriberInterface {
/**
* The subscriber constructor.
*
* @param \Drupal\vipps_mobilepay_commerce\Resolver\CommerceShippingMethodsResolver $resolver
* The resolver.
*/
public function __construct(protected CommerceShippingMethodsResolver $resolver) {}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
return [
VippsMobilePayEvents::INITIATE_CHECKOUT => ['onShipping'],
];
}
/**
* Adds shipping rates to the request.
*
* @param \Drupal\vipps_mobilepay_commerce\Event\InitiateCheckoutEvent $event
* The checkout initiation event.
*/
public function onShipping(InitiateCheckoutEvent $event) {
$request = $event->getRequest();
$logistics = new Logistics();
$options = $this->resolver->resolve($event->getPayment()->getOrder());
$logistics->setFixedOptions($options);
$request->setLogistics($logistics);
}
}
<?php
namespace Drupal\vipps_mobilepay_commerce\Resolver;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\commerce_price\MinorUnitsConverterInterface;
use Drupal\commerce_shipping\ShipmentManagerInterface;
use Drupal\commerce_shipping\ShippingOrderManagerInterface;
use Drupal\profile\Entity\ProfileInterface;
use zaporylie\Vipps\Model\Checkout\v3\Amount;
use zaporylie\Vipps\Model\Checkout\v3\OtherLogisticsOption;
/**
* Returns the site's default remote order id.
*/
class CommerceShippingMethodsResolver {
/**
* The shipping order manager.
*
* @var \Drupal\commerce_shipping\ShippingOrderManagerInterface
*/
protected $orderManager;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The shipment manager.
*
* @var \Drupal\commerce_shipping\ShipmentManagerInterface
*/
protected $shipmentManager;
/**
* The minor units converter.
*
* @var \Drupal\commerce_price\MinorUnitsConverterInterface
*/
protected $minorUnitsConverter;
/**
* CommerceShippingMethodsResolver constructor.
*
* @param \Drupal\commerce_shipping\ShippingOrderManagerInterface $orderManager
* The Shipping Order Manager.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entity type manager.
* @param \Drupal\commerce_shipping\ShipmentManagerInterface $shipment_manager
* The shipment manager.
* @param \Drupal\commerce_price\MinorUnitsConverterInterface $minor_units_converter
* The minor units converter.
*/
public function __construct(ShippingOrderManagerInterface $orderManager, EntityTypeManagerInterface $entityTypeManager, ShipmentManagerInterface $shipment_manager, MinorUnitsConverterInterface $minor_units_converter) {
$this->orderManager = $orderManager;
$this->entityTypeManager = $entityTypeManager;
$this->shipmentManager = $shipment_manager;
$this->minorUnitsConverter = $minor_units_converter;
}
/**
* {@inheritdoc}
*/
public function resolve(OrderInterface $order) {
$profiles = $order->collectProfiles();
if (isset($profiles['shipping']) && ($profiles['shipping'] instanceof ProfileInterface)) {
$profile = $profiles['shipping'];
}
else {
$profile = $this->orderManager->createProfile($order, [
'address' => [
'country_code' => '',
'postal_code' => '',
'locality' => '',
'address_line1' => '',
'address_line2' => '',
],
]);
}
$i = 0;
/** @var \Drupal\commerce_shipping\Entity\Shipment[] $shipments */
$shipments = $this->orderManager->pack($order, $profile);
$available_shipping_methods = [];
/** @var \Drupal\commerce_shipping\Entity\ShipmentInterface $shipment */
foreach ($shipments as $shipment) {
$shipping_rates = $this->shipmentManager->calculateRates($shipment);
foreach ($shipping_rates as $shipping_rate) {
// @see \Drupal\commerce_shipping\Plugin\Field\FieldWidget\ShippingRateWidget::formElement().
$service = $shipping_rate->getService();
$amount = $shipping_rate->getAmount();
$option_id = $shipping_rate->getShippingMethodId() . '--' . $service->getId();
$option_label = $service->getLabel();
$shipping_details = new OtherLogisticsOption();
$shipping_details->setAmount((new Amount())
->setCurrency($amount->getCurrencyCode())
->setValue($this->minorUnitsConverter->toMinorUnits($amount)));
$shipping_details->setId($option_id);
$shipping_details->setPriority($i);
$shipping_details->setTitle($option_label);
$shipping_details->setBrand('OTHER');
$i++;
$available_shipping_methods[] = $shipping_details;
}
}
return $available_shipping_methods;
}
}
<?php
namespace Drupal\vipps_mobilepay_commerce;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
/**
* Registers shipping-related event subscribers when module is enabled.
*/
class VippsMobilepayCommerceServiceProvider extends ServiceProviderBase {
/**
* {@inheritdoc}
*/
public function alter(ContainerBuilder $container) {
$modules = $container->getParameter('container.modules');
if (isset($modules['commerce_shipping'])) {
$definition = $container->getDefinition('vipps_mobilepay_commerce.commerce_shipping_methods_resolver');
$definition->setAbstract(FALSE);
$container->setDefinition('vipps_mobilepay_commerce.commerce_shipping_methods_resolver', $definition);
$definition = $container->getDefinition('vipps_mobilepay_commerce.shipping_checkout_event_subscriber');
$definition->setAbstract(FALSE);
$container->setDefinition('vipps_mobilepay_commerce.shipping_checkout_event_subscriber', $definition);
}
}
}
......@@ -20,3 +20,23 @@ services:
arguments: ['@state']
tags:
- { name: config.factory.override }
vipps_mobilepay_commerce.shipping_checkout_event_subscriber:
class: Drupal\vipps_mobilepay_commerce\EventSubscriber\ShippingCheckoutEventSubscriber
arguments: [ '@vipps_mobilepay_commerce.commerce_shipping_methods_resolver' ]
abstract: true
tags:
- { name: event_subscriber }
vipps_mobilepay_commerce.order_subscriber:
class: Drupal\vipps_mobilepay_commerce\EventSubscriber\OrderSubscriber
arguments:
- '@entity_type.manager'
- '@logger.channel.vipps_mobilepay_commerce'
- '@vipps_mobilepay.manager_factory'
- '@commerce_price.minor_units_converter'
- '@?commerce_shipping.order_manager'
tags:
- { name: event_subscriber }
vipps_mobilepay_commerce.commerce_shipping_methods_resolver:
class: Drupal\vipps_mobilepay_commerce\Resolver\CommerceShippingMethodsResolver
abstract: true
arguments: ['@commerce_shipping.order_manager', '@entity_type.manager', '@commerce_shipping.shipment_manager', '@commerce_price.minor_units_converter']
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment