commerce_paypal-8.x-1.0-beta11/commerce_paypal.module
commerce_paypal.module
<?php
/**
* @file
* Implements PayPal payment services for use with Drupal Commerce.
*/
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Asset\AttachedAssetsInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\TrustedRedirectResponse;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\commerce_checkout\Entity\CheckoutFlowInterface;
use Drupal\commerce_payment\Entity\PaymentGateway;
use Drupal\commerce_paypal\Plugin\Commerce\PaymentGateway\CheckoutInterface;
use Drupal\commerce_paypal\Plugin\Commerce\PaymentGateway\PayflowLinkInterface;
use Drupal\commerce_price\Calculator;
use Drupal\Core\Render\Markup;
/**
* Implements hook_theme().
*/
function commerce_paypal_theme() {
$theme = [
'commerce_paypal_checkout_custom_card_fields' => [
'variables' => [],
],
'commerce_paypal_credit_card_logos' => [
'variables' => [
'credit_cards' => [],
],
],
];
return $theme;
}
/**
* Implements hook_form_BASE_FORM_ID_alter().
*/
function commerce_paypal_form_views_form_commerce_cart_form_default_alter(&$form, FormStateInterface $form_state, $form_id) {
/** @var \Drupal\views\ViewExecutable $view */
$view = reset($form_state->getBuildInfo()['args']);
// Only add the smart payment buttons if the cart form view has order items.
if (empty($view->result)) {
return;
}
$entity_type_manager = \Drupal::entityTypeManager();
$order_id = $view->args[0];
/** @var \Drupal\commerce_order\Entity\OrderInterface $order */
$order = $entity_type_manager->getStorage('commerce_order')->load($order_id);
// Skip injecting the smart payment buttons if the order total is zero or
// negative.
if (!$order->getTotalPrice() || !$order->getTotalPrice()->isPositive()) {
return;
}
/** @var \Drupal\commerce_payment\PaymentGatewayStorageInterface $payment_gateway_storage */
$payment_gateway_storage = $entity_type_manager->getStorage('commerce_payment_gateway');
// Load the payment gateways. This fires an event for filtering the
// available gateways, and then evaluates conditions on all remaining ones.
$payment_gateways = $payment_gateway_storage->loadMultipleForOrder($order);
// Can't proceed without any payment gateways.
if (empty($payment_gateways)) {
return;
}
foreach ($payment_gateways as $payment_gateway) {
$payment_gateway_plugin = $payment_gateway->getPlugin();
if (!$payment_gateway_plugin instanceof CheckoutInterface) {
continue;
}
$config = $payment_gateway_plugin->getConfiguration();
// We only inject the Smart payment buttons on the cart page if the
// configured payment solution is "smart_payment_buttons" and if the
// "enable_on_cart" setting is TRUE.
if ($payment_gateway_plugin->getPaymentSolution() !== 'smart_payment_buttons' || !$config['enable_on_cart']) {
continue;
}
/** @var \Drupal\commerce_paypal\SmartPaymentButtonsBuilderInterface $builder */
$builder = \Drupal::service('commerce_paypal.smart_payment_buttons_builder');
$form['paypal_smart_payment_buttons'] = $builder->build($order, $payment_gateway, FALSE);
break;
}
}
/**
* Implements hook_form_BASE_FORM_ID_alter() for commerce_checkout_flow.
*/
function commerce_paypal_form_commerce_checkout_flow_alter(&$form, FormStateInterface $form_state) {
/** @var \Drupal\commerce_order\Entity\OrderInterface $order */
$order = \Drupal::routeMatch()->getParameter('commerce_order');
// Loop over the payment methods to remove potentially duplicate PayPal
// options (See http://www.drupal.org/project/commerce_paypal/issues/3154770).
if (isset($form['payment_information']['payment_method'], $form['payment_information']['#payment_options'])) {
/** @var \Drupal\commerce_payment\PaymentOption $payment_option */
$paypal_checkout_options_count = 0;
foreach ($form['payment_information']['#payment_options'] as $key => $payment_option) {
/** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway */
$payment_gateway = PaymentGateway::load($payment_option->getPaymentGatewayId());
$plugin = $payment_gateway->getPlugin();
// Skip, if the payment gateway is not a PayPal checkout gateway:
if (!($plugin instanceof CheckoutInterface)) {
continue;
}
if ($plugin->getPaymentSolution() === 'smart_payment_buttons') {
$paypal_checkout_options_count++;
// This will ensure we only keep the first PayPal checkout option found.
if ($paypal_checkout_options_count > 1 && isset($form['payment_information']['payment_method']['#options'][$key])) {
unset($form['payment_information']['payment_method']['#options'][$key]);
}
}
// The payment method might be unset. In this case, continue:
if (!isset($form['payment_information']['payment_method']['#options'][$key])) {
continue;
}
// Adjust, how the PayPal payment method is displayed:
$plugin_configuration = $plugin->getConfiguration();
$label = $form['payment_information']['payment_method']['#options'][$key];
$logo = Markup::create('<img src="https://www.paypalobjects.com/webstatic/en_US/i/buttons/pp-acceptance-small.png" alt="PayPal Acceptance" style="vertical-align: middle">');
$description = !empty($plugin_configuration['payment_selection_display']['description']) ? Markup::create('<div class="form-item__description">' . $plugin_configuration['payment_selection_display']['description']) . '</div>' : '';
$display_style = $plugin_configuration['payment_selection_display']['style'];
$form['payment_information']['payment_method']['#options'][$key] = match ($display_style) {
'label' => $label . $description,
'logo' => $logo . $description,
'label_logo' => $label . ' ' . $logo . $description,
'logo_label' => $logo . ' ' . $label . $description,
default => $label,
};
}
}
if (!in_array($form['#step_id'], ['review', 'complete'])) {
return;
}
if ($order->get('payment_gateway')->isEmpty() ||
!$order->get('payment_gateway')->entity ||
$order->get('checkout_flow')->target_id === 'paypal_checkout') {
return;
}
/** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway */
$payment_gateway = $order->get('payment_gateway')->entity;
$payment_gateway_plugin = $payment_gateway->getPlugin();
// Add fixes for Payflow Link iframe.
if ($payment_gateway_plugin instanceof PayflowLinkInterface) {
if ($payment_gateway_plugin->getConfiguration()['redirect_mode'] === 'iframe') {
$form['#attached']['library'][] = 'commerce_paypal/paypal_payflow_link_iframe_fix';
$form['#attached']['library'][] = 'commerce_paypal/paypal_payflow_link';
// Error handling for PayflowLink iframe.
if ($form['#step_id'] === 'review') {
$form['#attached']['drupalSettings']['commercePayflow'] = ['page' => 'review'];
// Don't cache form,
// otherwise following code will not work properly for anonymous user.
\Drupal::service('page_cache_kill_switch')->trigger();
// If the Payflow query variable is present, reshow the error message
// and reload the page.
$query_params = \Drupal::request()->query->all();
if (isset($query_params['payflow-page']) && $query_params['payflow-page'] === 'review') {
\Drupal::messenger()
->addMessage(t('Payment failed at the payment server. Please review your information and try again.'), 'error');
$redirect_url = Url::fromRoute('commerce_checkout.form', [
'commerce_order' => $order->id(),
'step' => 'review',
])->toString();
$redirect = new TrustedRedirectResponse($redirect_url);
$redirect->send();
}
}
}
}
elseif ($form['#step_id'] !== 'review') {
return;
}
// Inject the Smart payment buttons on the review page.
// Skip injecting the smart payment buttons if the order total is zero or
// negative.
if (!$order->getTotalPrice() || !$order->getTotalPrice()->isPositive()) {
return;
}
if (!$payment_gateway_plugin instanceof CheckoutInterface ||
$payment_gateway_plugin->getPaymentSolution() !== 'smart_payment_buttons') {
return;
}
/** @var \Drupal\commerce_paypal\SmartPaymentButtonsBuilderInterface $builder */
$builder = \Drupal::service('commerce_paypal.smart_payment_buttons_builder');
$form['paypal_smart_payment_buttons'] = $builder->build($order, $payment_gateway, TRUE);
$form['actions']['#access'] = FALSE;
// Put back the "go back" link.
if (isset($form['actions']['next']['#suffix'])) {
$form['paypal_smart_payment_buttons']['#suffix'] = $form['actions']['next']['#suffix'];
}
}
/**
* Implements hook_ENTITY_TYPE_access().
*
* Forbids the "paypal_checkout" checkout flow from being deletable.
*/
function commerce_paypal_commerce_checkout_flow_access(CheckoutFlowInterface $checkout_flow, $operation, AccountInterface $account) {
if ($checkout_flow->id() === 'paypal_checkout' && $operation === 'delete') {
return AccessResult::forbidden();
}
return AccessResult::neutral();
}
/**
* Implements hook_library_info_build().
*/
function commerce_paypal_library_info_build() {
// Only build the PayPal Credit messaging JS if a PayPal Client ID was set on
// the PayPal Credit messaging settings form.
$client_id = \Drupal::config('commerce_paypal.credit_messaging_settings')->get('client_id');
if (!$client_id) {
return [];
}
$url = sprintf('https://www.paypal.com/sdk/js?client-id=%s&components=messages', $client_id);
$libraries['credit_messaging'] = [
'header' => TRUE,
'js' => [
$url => [
'type' => 'external',
'attributes' => [
'data-partner-attribution-id' => 'CommerceGuys_Cart_SPB',
],
],
],
];
return $libraries;
}
/**
* Implements hook_form_BASE_FORM_ID_alter().
*/
function commerce_paypal_form_commerce_order_item_add_to_cart_form_alter(&$form, FormStateInterface $form_state, $form_id) {
// Check to see if PayPal Credit messaging is enabled on Add to Cart forms.
$enable_messaging = \Drupal::config('commerce_paypal.credit_messaging_settings')->get('add_to_cart');
/** @var \Drupal\commerce_order\Entity\OrderItemInterface $order_item */
$order_item = $form_state->getFormObject()->getEntity();
if (!$enable_messaging || !$order_item->getUnitPrice()) {
return;
}
// Add Credit Messaging JS to the form.
$form['#attached']['library'][] = 'commerce_paypal/credit_messaging';
$form['paypal_credit_messaging_product'] = [
'#type' => 'html_tag',
'#tag' => 'div',
'#attributes' => [
'data-pp-message' => '',
'data-pp-placement' => 'product',
'data-pp-amount' => Calculator::trim($order_item->getUnitPrice()->getNumber()),
],
'#weight' => 1,
];
}
/**
* Implements hook_js_alter().
*/
function commerce_paypal_js_alter(&$javascript, AttachedAssetsInterface $assets) {
$client_id = \Drupal::config('commerce_paypal.credit_messaging_settings')->get('client_id');
if (!$client_id) {
return;
}
/** @var \Drupal\Core\Extension\ExtensionPathResolver $extension_path_resolver */
$extension_path_resolver = \Drupal::service('extension.path.resolver');
$paypal_checkout_js = $extension_path_resolver->getPath('module', 'commerce_paypal') . '/js/paypal-checkout.js';
// The paypal-checkout JS file isn't present, no need to do anything.
if (!isset($javascript[$paypal_checkout_js])) {
return;
}
// Remove the extra JS SDK added for credit messaging library if present.
foreach ($javascript as $key => $js) {
if (str_starts_with($key, 'https://www.paypal.com/sdk/js')) {
unset($javascript[$key]);
break;
}
}
}
/**
* Implements hook_form_BASE_FORM_ID_alter().
*/
function commerce_paypal_form_commerce_payment_method_add_form_alter(&$form, FormStateInterface $form_state, $form_id) {
// The current PayPal checkout implementation doesn't support tokenization.
unset($form["payment_method"]["#options"]["new--paypal_checkout--paypal"]);
}
/**
* Returns the list of funding sources to be enabled or disabled.
*
* The full list can be found at:
* https://developer.paypal.com/sdk/js/configuration/#enable-funding.
*
* @return array
* Machine name as key and translated label as value.
*/
function commerce_paypal_get_funding_sources() {
return [
'paypal' => t('PayPal'),
'card' => t('Credit or debit card'),
'credit' => t('PayPal Credit'),
'paylater' => t('Pay Later'),
'bancontact' => t('Bancontact'),
'blik' => t('BLIK'),
'eps' => t('eps'),
'giropay' => t('giropay'),
'ideal' => t('iDEAL'),
'mercadopago' => t('Mercado Pago'),
'mybank' => t('MyBank'),
'p24' => t('Przelewy24'),
'sepa' => t('SEPA-Lastschrift'),
'sofort' => t('Sofort'),
'venmo' => t('Venmo'),
];
}
/**
* Returns the label to use for a PayPal funding source.
*
* @param string $funding_source
* The machine-name of the funding source returned by PayPal.
*
* @return string
* The label to show a customer for the funding source.
*/
function commerce_paypal_funding_source_label($funding_source) {
$funding_sources = commerce_paypal_get_funding_sources();
return $funding_sources[$funding_source] ?? '';
}
/**
* Implements hook_preprocess_HOOK().
*
* To facilitate the display of the funding source in order templates, this
* function looks for an order with a PayPal Checkout funding source set and
* adds its label to the array of available template variables.
*
* @see https://developer.paypal.com/docs/checkout/standard/customize/display-funding-source/
*/
function commerce_paypal_preprocess_commerce_order(&$variables) {
if (!empty($variables['elements']['#commerce_order'])) {
/** @var Drupal\commerce_order\Entity\OrderInterface $order */
$order = $variables['elements']['#commerce_order'];
// Check for a PayPal Checkout funding source.
$data = $order->getData('commerce_paypal_checkout', []);
// If we found a funding source, add its label to the template variables.
if (!empty($data['funding_source'])) {
$variables['order']['funding_source'] = [
'#markup' => commerce_paypal_funding_source_label($data['funding_source']),
];
}
}
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function commerce_paypal_form_commerce_order_type_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {
if (isset($form['commerce_checkout']['checkout_flow']['#options']['paypal_checkout'])) {
unset($form['commerce_checkout']['checkout_flow']['#options']['paypal_checkout']);
}
}
