From c217afc50cdcabc99d53b6acfa18a6a067e2c1ab Mon Sep 17 00:00:00 2001 From: Jonathan Sacksick Date: Thu, 14 Aug 2025 11:56:50 +0300 Subject: [PATCH 1/8] Issue #3541307 by jsacksick: Gitlab CI integration. --- .gitlab-ci.yml | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..2a56057 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,52 @@ +################ +# DrupalCI GitLabCI template +# +# Gitlab-ci.yml to replicate DrupalCI testing for Contrib +# +# With thanks to: +# * The GitLab Acceleration Initiative participants +# * DrupalSpoons +################ + +################ +# Guidelines +# +# This template is designed to give any Contrib maintainer everything they need to test, without requiring modification. It is also designed to keep up to date with Core Development automatically through the use of include files that can be centrally maintained. +# +# However, you can modify this template if you have additional needs for your project. +################ + +################ +# Includes +# +# Additional configuration can be provided through includes. +# One advantage of include files is that if they are updated upstream, the changes affect all pipelines using that include. +# +# Includes can be overridden by re-declaring anything provided in an include, here in gitlab-ci.yml +# https://docs.gitlab.com/ee/ci/yaml/includes.html#override-included-configuration-values +################ + +include: + ################ + # DrupalCI includes: + # As long as you include this, any future includes added by the Drupal Association will be accessible to your pipelines automatically. + # View these include files at https://git.drupalcode.org/project/gitlab_templates/ + ################ + - project: $_GITLAB_TEMPLATES_REPO + ref: $_GITLAB_TEMPLATES_REF + file: + - '/includes/include.drupalci.main.yml' + # EXPERIMENTAL: For Drupal 7, remove the above line and uncomment the below. + # - '/includes/include.drupalci.main-d7.yml' + - '/includes/include.drupalci.variables.yml' + - '/includes/include.drupalci.workflows.yml' + +################ +# Pipeline configuration variables +# +# These are the variables provided to the Run Pipeline form that a user may want to override. +# +# Docs at https://git.drupalcode.org/project/gitlab_templates/-/blob/1.0.x/includes/include.drupalci.variables.yml +################ +# variables: +# SKIP_ESLINT: '1' -- GitLab From 9e38f3871dabdbba32dd6f29b0b58147e6218ed7 Mon Sep 17 00:00:00 2001 From: Jonathan Sacksick Date: Thu, 14 Aug 2025 12:48:31 +0300 Subject: [PATCH 2/8] Fix several phpcs & phpstan issues. --- composer.json | 3 +- .../PaymentGateway/CyberSourceSahc.php | 113 ++++++------------ src/Plugin/Commerce/PaymentGateway/Flex.php | 77 ++---------- 3 files changed, 54 insertions(+), 139 deletions(-) diff --git a/composer.json b/composer.json index 5504e93..d6718b4 100644 --- a/composer.json +++ b/composer.json @@ -5,6 +5,7 @@ "homepage": "https://www.drupal.org/project/commerce_cybersource", "require": { "drupal/commerce": "^2.20 || ^3", - "centarro/cybersource-rest-client-php": "dev-drupal-compatibility" + "centarro/cybersource-rest-client-php": "dev-drupal-compatibility", + "php": ">=8.0" } } diff --git a/src/Plugin/Commerce/PaymentGateway/CyberSourceSahc.php b/src/Plugin/Commerce/PaymentGateway/CyberSourceSahc.php index 6c811ad..cd633e9 100644 --- a/src/Plugin/Commerce/PaymentGateway/CyberSourceSahc.php +++ b/src/Plugin/Commerce/PaymentGateway/CyberSourceSahc.php @@ -6,15 +6,10 @@ use Drupal\commerce_order\Entity\OrderInterface; use Drupal\commerce_payment\Entity\PaymentInterface; use Drupal\commerce_payment\Exception\HardDeclineException; use Drupal\commerce_payment\Exception\PaymentGatewayException; -use Drupal\commerce_payment\PaymentMethodTypeManager; -use Drupal\commerce_payment\PaymentTypeManager; use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OffsitePaymentGatewayBase; use Drupal\commerce_price\Price; -use Drupal\Component\Datetime\TimeInterface; use Drupal\Component\Utility\Html; -use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; -use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; @@ -67,14 +62,14 @@ class CyberSourceSahc extends OffsitePaymentGatewayBase { */ public function defaultConfiguration() { return [ - 'merchant_id' => '', - 'profile_id' => '', - 'access_key' => '', - 'secret_key' => '', - 'transaction_type' => self::COMMERCE_CYBERSOURCE_SAHC_TRANSACTION_SALE_CREATE_TOKEN, - 'locale' => 'en-US', - 'log_api_calls' => 0, - ] + parent::defaultConfiguration(); + 'merchant_id' => '', + 'profile_id' => '', + 'access_key' => '', + 'secret_key' => '', + 'transaction_type' => self::COMMERCE_CYBERSOURCE_SAHC_TRANSACTION_SALE_CREATE_TOKEN, + 'locale' => 'en-US', + 'log_api_calls' => 0, + ] + parent::defaultConfiguration(); } /** @@ -250,7 +245,7 @@ class CyberSourceSahc extends OffsitePaymentGatewayBase { $this->logger->notice('Data received from Cybersource: %data', ['%data' => print_r($all_received_parameters, 1)]); } - if (!$parameter_bag->has('req_reference_number') || $parameter_bag->has('req_reference_number') && $parameter_bag->get('req_reference_number') != $order->id()) { + if (!$parameter_bag->has('req_reference_number') || $parameter_bag->get('req_reference_number') != $order->id()) { if ($this->configuration['log_api_calls']) { $this->logger->notice('Invalid reference number.'); } @@ -258,10 +253,10 @@ class CyberSourceSahc extends OffsitePaymentGatewayBase { } if ($parameter_bag->has('req_transaction_uuid')) { - /** @var \Drupal\commerce_payment\Entity\PaymentInterface $commerce_payment */ $commerce_payment = $this->entityTypeManager->getStorage('commerce_payment') ->loadByProperties(['uuid' => $parameter_bag->get('req_transaction_uuid')]); $commerce_payment = array_shift($commerce_payment); + /** @var \Drupal\commerce_payment\Entity\PaymentInterface $commerce_payment */ if (empty($commerce_payment) || $commerce_payment->getOrderId() != $order->id()) { if ($this->configuration['log_api_calls']) { $this->logger->notice('Invalid transaction UUID.'); @@ -431,65 +426,35 @@ class CyberSourceSahc extends OffsitePaymentGatewayBase { * {@inheritdoc} */ function buildAvsResponseCodeLabel($avs_response_code, $card_type) { - switch ($avs_response_code) { - case 'A': - return $this->t("Street address matches, but five-digit and nine-digit postal codes do not match."); - case'B': - return $this->t("Street address matches, but postal code is not verified."); - case'C': - return $this->t("Street address and postal code do not match."); - case'D': - case'M': - case'N': - return $this->t("Street address and postal code match."); - case'E': - return $this->t("AVS data is invalid or AVS is not allowed for this card type."); - case'F': - return $this->t("Card member's name does not match, but billing postal code matches."); - case'H': - return $this->t("Card member's name does not match, but street address and postal code match."); - case'I': - return $this->t("Address not verified."); - case'J': - case'Q': - return $this->t("Card member's name, billing address, and postal code match."); - case'K': - return $this->t("Card member's name matches, but billing address and billing postal code do not match."); - case'L': - return $this->t("Card member's name and billing postal code match, but billing address does not match."); - case'O': - return $this->t("Card member's name and billing address match, but billing postal code does not match."); - case'P': - return $this->t("Postal code matches, but street address not verified."); - case'R': - return $this->t("System unavailable."); - case'S': - return $this->t("U.S.-issuing bank does not support AVS."); - case'T': - return $this->t("Card member's name does not match, but street address matches."); - case'U': - return $this->t("Your bank does not support non-U.S. AVS or is otherwise not functioning properly."); - case'V': - return $this->t("Card member's name, billing address, and billing postal code match."); - case'W': - return $this->t("Street address does not match, but nine-digit postal code matches."); - case'X': - return $this->t("Street address and nine-digit postal code match."); - case'Y': - return $this->t("Street address and five-digit postal code match."); - case'Z': - return $this->t("Street address does not match, but five-digit postal code matches."); - case'1': - return $this->t("AVS is not supported for this processor or card type."); - case'2': - return $this->t("The processor returned an unrecognized value for the AVS response."); - case'3': - return $this->t("Address is confirmed. Returned only for PayPal Express Checkout."); - case'4': - return $this->t("Address is not confirmed. Returned only for PayPal Express Checkout."); - default: - return parent::buildAvsResponseCodeLabel($avs_response_code, $card_type); - } + return match ($avs_response_code) { + 'A' => $this->t("Street address matches, but five-digit and nine-digit postal codes do not match."), + 'B' => $this->t("Street address matches, but postal code is not verified."), + 'C' => $this->t("Street address and postal code do not match."), + 'D', 'M', 'N' => $this->t("Street address and postal code match."), + 'E' => $this->t("AVS data is invalid or AVS is not allowed for this card type."), + 'F' => $this->t("Card member's name does not match, but billing postal code matches."), + 'H' => $this->t("Card member's name does not match, but street address and postal code match."), + 'I' => $this->t("Address not verified."), + 'J', 'Q' => $this->t("Card member's name, billing address, and postal code match."), + 'K' => $this->t("Card member's name matches, but billing address and billing postal code do not match."), + 'L' => $this->t("Card member's name and billing postal code match, but billing address does not match."), + 'O' => $this->t("Card member's name and billing address match, but billing postal code does not match."), + 'P' => $this->t("Postal code matches, but street address not verified."), + 'R' => $this->t("System unavailable."), + 'S' => $this->t("U.S.-issuing bank does not support AVS."), + 'T' => $this->t("Card member's name does not match, but street address matches."), + 'U' => $this->t("Your bank does not support non-U.S. AVS or is otherwise not functioning properly."), + 'V' => $this->t("Card member's name, billing address, and billing postal code match."), + 'W' => $this->t("Street address does not match, but nine-digit postal code matches."), + 'X' => $this->t("Street address and nine-digit postal code match."), + 'Y' => $this->t("Street address and five-digit postal code match."), + 'Z' => $this->t("Street address does not match, but five-digit postal code matches."), + '1' => $this->t("AVS is not supported for this processor or card type."), + '2' => $this->t("The processor returned an unrecognized value for the AVS response."), + '3' => $this->t("Address is confirmed. Returned only for PayPal Express Checkout."), + '4' => $this->t("Address is not confirmed. Returned only for PayPal Express Checkout."), + default => parent::buildAvsResponseCodeLabel($avs_response_code, $card_type), + }; } /** diff --git a/src/Plugin/Commerce/PaymentGateway/Flex.php b/src/Plugin/Commerce/PaymentGateway/Flex.php index e22e315..5e136d2 100644 --- a/src/Plugin/Commerce/PaymentGateway/Flex.php +++ b/src/Plugin/Commerce/PaymentGateway/Flex.php @@ -33,17 +33,11 @@ use Drupal\commerce_payment\Entity\PaymentMethodInterface; use Drupal\commerce_payment\Exception\DeclineException; use Drupal\commerce_payment\Exception\InvalidRequestException; use Drupal\commerce_payment\Exception\PaymentGatewayException; -use Drupal\commerce_payment\PaymentMethodTypeManager; -use Drupal\commerce_payment\PaymentTypeManager; use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OnsitePaymentGatewayBase; use Drupal\commerce_price\Price; -use Drupal\Component\Datetime\TimeInterface; use Drupal\Component\Serialization\Json; -use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; -use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\HttpFoundation\RequestStack; /** * Provides the CyberSource Flex payment gateway. @@ -104,66 +98,26 @@ class Flex extends OnsitePaymentGatewayBase implements FlexInterface { protected $capture; /** - * Constructs a new PaymentGatewayBase object. - * - * @param array $configuration - * A configuration array containing information about the plugin instance. - * @param string $plugin_id - * The plugin_id for the plugin instance. - * @param mixed $plugin_definition - * The plugin implementation definition. - * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager - * The entity type manager. - * @param \Drupal\commerce_payment\PaymentTypeManager $payment_type_manager - * The payment type manager. - * @param \Drupal\commerce_payment\PaymentMethodTypeManager $payment_method_type_manager - * The payment method type manager. - * @param \Drupal\Component\Datetime\TimeInterface $time - * The time. - * @param \Psr\Log\LoggerInterface $logger - * Logger. - * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack - * The request stack. + * {@inheritdoc} */ - public function __construct( - array $configuration, - $plugin_id, - $plugin_definition, - EntityTypeManagerInterface $entity_type_manager, - PaymentTypeManager $payment_type_manager, - PaymentMethodTypeManager $payment_method_type_manager, - TimeInterface $time, - LoggerInterface $logger, - RequestStack $request_stack - ) { - parent::__construct( - $configuration, - $plugin_id, - $plugin_definition, - $entity_type_manager, - $payment_type_manager, - $payment_method_type_manager, - $time - ); - - $this->entityTypeManager = $entity_type_manager; - $this->logger = $logger; - $this->requestStack = $request_stack; + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); - // Call CyberSource API with provided plugin configuration. + $instance->logger = $container->get('logger.channel.commerce_cybersource'); + $instance->requestStack = $container->get('request_stack'); if ($configuration) { // Check if payment method is in capture mode. - switch ($this->configuration['transaction_type']) { + switch ($instance->configuration['transaction_type']) { case self::COMMERCE_CYBERSOURCE_FLEX_TRANSACTION_AUTH_AND_CAPTURE: - $this->capture = TRUE; + $instance->capture = TRUE; break; default: - $this->capture = FALSE; + $instance->capture = FALSE; break; } - if ($this->getMode() == 'test') { + if ($instance->getMode() == 'test') { $host = self::COMMERCE_CYBERSOURCE_FLEX_TEST_API_SERVER; } else { @@ -173,22 +127,17 @@ class Flex extends OnsitePaymentGatewayBase implements FlexInterface { $config = new Configuration(); $merchantConfig = new MerchantConfiguration(); - $merchantConfig->setMerchantID($this->configuration['merchant_id']); - $merchantConfig->setApiKeyID($this->configuration['key_serial_number']); - $merchantConfig->setSecretKey($this->configuration['key_shared_secret']); + $merchantConfig->setMerchantID($instance->configuration['merchant_id']); + $merchantConfig->setApiKeyID($instance->configuration['key_serial_number']); + $merchantConfig->setSecretKey($instance->configuration['key_shared_secret']); $merchantConfig->setHost($host); $merchantConfig->setAuthenticationType('HTTP_SIGNATURE'); $config->setHost($host); // CyberSource API request. - $this->api = new ApiClient($config, $merchantConfig); + $instance->api = new ApiClient($config, $merchantConfig); } - } - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { return new static( $configuration, $plugin_id, -- GitLab From 515fef873f38267484a3e81410e832e2f4792eb4 Mon Sep 17 00:00:00 2001 From: Jonathan Sacksick Date: Thu, 14 Aug 2025 12:50:10 +0300 Subject: [PATCH 3/8] Fix Flex::create(). --- src/Plugin/Commerce/PaymentGateway/Flex.php | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/Plugin/Commerce/PaymentGateway/Flex.php b/src/Plugin/Commerce/PaymentGateway/Flex.php index 5e136d2..b5c14b0 100644 --- a/src/Plugin/Commerce/PaymentGateway/Flex.php +++ b/src/Plugin/Commerce/PaymentGateway/Flex.php @@ -138,17 +138,7 @@ class Flex extends OnsitePaymentGatewayBase implements FlexInterface { $instance->api = new ApiClient($config, $merchantConfig); } - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('entity_type.manager'), - $container->get('plugin.manager.commerce_payment_type'), - $container->get('plugin.manager.commerce_payment_method_type'), - $container->get('datetime.time'), - $container->get('logger.channel.commerce_cybersource'), - $container->get('request_stack') - ); + return $instance; } /** -- GitLab From 4ab62eb9359a2c2eee73be1d33c164f509433916 Mon Sep 17 00:00:00 2001 From: Jonathan Sacksick Date: Thu, 14 Aug 2025 13:21:07 +0300 Subject: [PATCH 4/8] Fix additional phpcs issues. --- .../PaymentGateway/CyberSourceSahc.php | 3 +++ src/Plugin/Commerce/PaymentGateway/Flex.php | 20 ++++++++++++------- src/PluginForm/CyberSourceSahcForm.php | 18 +++++++++++------ 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/Plugin/Commerce/PaymentGateway/CyberSourceSahc.php b/src/Plugin/Commerce/PaymentGateway/CyberSourceSahc.php index cd633e9..c7755ab 100644 --- a/src/Plugin/Commerce/PaymentGateway/CyberSourceSahc.php +++ b/src/Plugin/Commerce/PaymentGateway/CyberSourceSahc.php @@ -154,6 +154,7 @@ class CyberSourceSahc extends OffsitePaymentGatewayBase { * Returns options for 'Locale' field on configuration form. * * @return array + * The locale options. */ public function localeOptions() { return [ @@ -221,6 +222,7 @@ class CyberSourceSahc extends OffsitePaymentGatewayBase { * An array of various strings to implode into a signature string. * * @return string + * The base64-encoded SHA256 hash of an array using a secret key. */ public function signData($data_to_sign) { $pairs = []; @@ -292,6 +294,7 @@ class CyberSourceSahc extends OffsitePaymentGatewayBase { * All received parameters from CyberSource. * * @return bool + * Whether the response is valid. */ public function validateResponse(OrderInterface $order, array $all_received_parameters) { if (!empty($this->configuration['secret_key'])) { diff --git a/src/Plugin/Commerce/PaymentGateway/Flex.php b/src/Plugin/Commerce/PaymentGateway/Flex.php index b5c14b0..b08d1aa 100644 --- a/src/Plugin/Commerce/PaymentGateway/Flex.php +++ b/src/Plugin/Commerce/PaymentGateway/Flex.php @@ -231,7 +231,7 @@ class Flex extends OnsitePaymentGatewayBase implements FlexInterface { } // Decode the token to extract the card number. - list($header, $payload, $signature) = explode('.', $payment_details['token']); + [$header, $payload, $signature] = explode('.', $payment_details['token']); $payload = Json::decode(base64_decode($payload)); $payment_information = $payload['content']['paymentInformation']; @@ -247,7 +247,7 @@ class Flex extends OnsitePaymentGatewayBase implements FlexInterface { } // Set the masked card number and expiration month and year. - $payment_method->card_number = substr($payment_information['card']['number']['maskedValue'], -4);; + $payment_method->card_number = substr($payment_information['card']['number']['maskedValue'], -4); $payment_method->card_exp_month = $payment_information['card']['expirationMonth']['value']; $payment_method->card_exp_year = $payment_information['card']['expirationYear']['value']; @@ -398,13 +398,19 @@ class Flex extends OnsitePaymentGatewayBase implements FlexInterface { // Log the API response if enabled. if ($this->configuration['log_api_calls']) { - $this->logger->notice('Flex::createPayment() API response - @status:
@object
', ['@status' => $result[0]->getStatus() ?? 'UNKNOWN', '@object' => $result[0]->__toString()]); + $this->logger->notice('Flex::createPayment() API response - @status:
@object
', [ + '@status' => $result[0]->getStatus() ?? 'UNKNOWN', + '@object' => $result[0]->__toString(), + ]); } } catch (ApiException $e) { // Log the API error message if enabled. if ($this->configuration['log_api_calls']) { - $this->logger->notice('Flex::createPayment() API error - @reason:
@body
', ['@reason' => $e->getResponseBody()->reason ?? 'UNKNOWN', '@body' => print_r($e->getResponseBody(), TRUE)]); + $this->logger->notice('Flex::createPayment() API error - @reason:
@body
', [ + '@reason' => $e->getResponseBody()->reason ?? 'UNKNOWN', + '@body' => print_r($e->getResponseBody(), TRUE), + ]); } throw new InvalidRequestException($e->getMessage(), $e->getCode()); @@ -653,7 +659,7 @@ class Flex extends OnsitePaymentGatewayBase implements FlexInterface { // Log the API request if enabled. if ($this->configuration['log_api_calls']) { $this->logger->notice('Flex::generateKey() API request:
@object
', [ - '@object' => $flex_request->__toString() + '@object' => $flex_request->__toString(), ]); } @@ -662,7 +668,7 @@ class Flex extends OnsitePaymentGatewayBase implements FlexInterface { // Log the API response if enabled. if ($this->configuration['log_api_calls']) { $this->logger->notice('Flex::generateKey() API response:
@object
', [ - '@object' => print_r($response, TRUE) + '@object' => print_r($response, TRUE), ]); } } @@ -671,7 +677,7 @@ class Flex extends OnsitePaymentGatewayBase implements FlexInterface { if ($this->configuration['log_api_calls']) { $this->logger->notice('Flex::generateKey() API error - @reason:
@body
', [ '@reason' => $e->getResponseBody()->reason ?? 'UNKNOWN', - '@body' => print_r($e->getResponseBody(), TRUE) + '@body' => print_r($e->getResponseBody(), TRUE), ]); } diff --git a/src/PluginForm/CyberSourceSahcForm.php b/src/PluginForm/CyberSourceSahcForm.php index 56db699..297202b 100644 --- a/src/PluginForm/CyberSourceSahcForm.php +++ b/src/PluginForm/CyberSourceSahcForm.php @@ -12,6 +12,9 @@ use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\RequestStack; +/** + * Provides an offsite payment form for Cybersource SAHC. + */ class CyberSourceSahcForm extends BasePaymentOffsiteForm implements ContainerInjectionInterface { /** @@ -22,6 +25,8 @@ class CyberSourceSahcForm extends BasePaymentOffsiteForm implements ContainerInj protected $currentUser; /** + * The request stack. + * * @var \Symfony\Component\HttpFoundation\RequestStack */ protected $requestStack; @@ -141,8 +146,8 @@ class CyberSourceSahcForm extends BasePaymentOffsiteForm implements ContainerInj /* * Required. - * Date and time signature was generated. Must be in UTC date & time format. - * Used by CyberSource to detect duplicate transaction attempts. + * Date and time signature was generated. Must be in UTC date and time + * format. Used by CyberSource to detect duplicate transaction attempts. */ $data['signed_date_time'] = gmdate("Y-m-d\TH:i:s\Z"); @@ -153,7 +158,7 @@ class CyberSourceSahcForm extends BasePaymentOffsiteForm implements ContainerInj Drupal doesn't add a form_token for Anonymous on the redirect form. Telling CyberSource that we're sending it, then not sending it, causes CyberSource to throw an access denied mystery error. - @link https://drupal.org/node/2121409 Checkout as anonymous doesn't work @endlink + @see https://drupal.org/node/2121409. */ $data['unsigned_field_names'] = 'form_build_id,form_id'; } @@ -207,7 +212,7 @@ class CyberSourceSahcForm extends BasePaymentOffsiteForm implements ContainerInj if (!empty($billing_address['locality'])) { $data['bill_to_address_city'] = $billing_address['locality']; } - // Optional. @link https://drupal.org/node/2112947 UK optional state @endlink + // Optional. @see https://drupal.org/node/2112947. if (!empty($billing_address['administrative_area'])) { $data['bill_to_address_state'] = $billing_address['administrative_area']; } @@ -232,8 +237,9 @@ class CyberSourceSahcForm extends BasePaymentOffsiteForm implements ContainerInj $line_item = $line_items[$i]; $item_unit_price = $line_item->getUnitPrice()->getNumber(); if (!is_null($item_unit_price)) { - // Handle common line item data, as long as the amount is greater than zero. Cybersource does not currently allow - // sending of negative amount line items. + // Handle common line item data, as long as the amount is greater than + // zero. Cybersource does not currently allow sending of negative + // amount line items. if ($item_unit_price >= 0) { $send_line_items++; $data['item_' . $i . '_unit_price'] = number_format($item_unit_price, 2, '.', ''); -- GitLab From c60641254d89297bbc3d1e3769a5a4c06a83685c Mon Sep 17 00:00:00 2001 From: Jonathan Sacksick Date: Thu, 14 Aug 2025 13:26:55 +0300 Subject: [PATCH 5/8] Fix numerous phpcs & phpstan issues. --- .../PaymentGateway/CyberSourceSahc.php | 3 +-- src/Plugin/Commerce/PaymentGateway/Flex.php | 20 +++++++++++++------ src/PluginForm/CyberSourceSahcForm.php | 8 ++++---- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/Plugin/Commerce/PaymentGateway/CyberSourceSahc.php b/src/Plugin/Commerce/PaymentGateway/CyberSourceSahc.php index c7755ab..928b73d 100644 --- a/src/Plugin/Commerce/PaymentGateway/CyberSourceSahc.php +++ b/src/Plugin/Commerce/PaymentGateway/CyberSourceSahc.php @@ -419,7 +419,6 @@ class CyberSourceSahc extends OffsitePaymentGatewayBase { ]); } throw new PaymentGatewayException('Payment failed at the payment server. Please review your information and try again. If issues persist please contact your issuing bank.'); - break; } $commerce_payment->save(); @@ -428,7 +427,7 @@ class CyberSourceSahc extends OffsitePaymentGatewayBase { /** * {@inheritdoc} */ - function buildAvsResponseCodeLabel($avs_response_code, $card_type) { + public function buildAvsResponseCodeLabel($avs_response_code, $card_type) { return match ($avs_response_code) { 'A' => $this->t("Street address matches, but five-digit and nine-digit postal codes do not match."), 'B' => $this->t("Street address matches, but postal code is not verified."), diff --git a/src/Plugin/Commerce/PaymentGateway/Flex.php b/src/Plugin/Commerce/PaymentGateway/Flex.php index b08d1aa..5bbec83 100644 --- a/src/Plugin/Commerce/PaymentGateway/Flex.php +++ b/src/Plugin/Commerce/PaymentGateway/Flex.php @@ -256,7 +256,7 @@ class Flex extends OnsitePaymentGatewayBase implements FlexInterface { // Set the payment method to expire in 14 minutes to provide some buffer // for timing out the token and sending the customer back in checkout.. - $payment_method->setExpiresTime(\Drupal::time()->getRequestTime() + 840); + $payment_method->setExpiresTime($this->time->getRequestTime() + 840); // Make this non-reusable for now so it does not appear in the accounts // interface for stored payment methods. @@ -477,7 +477,7 @@ class Flex extends OnsitePaymentGatewayBase implements FlexInterface { * @throws \CyberSource\ApiException * @throws \Drupal\Core\Entity\EntityStorageException */ - public function capturePayment(PaymentInterface $payment, Price $amount = NULL) { + public function capturePayment(PaymentInterface $payment, ?Price $amount = NULL) { $this->assertPaymentState($payment, ['authorization']); // If not specified, capture the entire amount. $amount = $amount ?: $payment->getAmount(); @@ -506,20 +506,28 @@ class Flex extends OnsitePaymentGatewayBase implements FlexInterface { try { // Log the API request if enabled. if ($this->configuration['log_api_calls']) { - $this->logger->notice('Flex::capturePayment() API request:
@object
', ['@object' => $request_obj->__toString()]); + $this->logger->notice('Flex::capturePayment() API request:
@object
', [ + '@object' => $request_obj->__toString(), + ]); } $result = $capture_api->capturePayment($request_obj, $remote_id); // Log the API response if enabled. if ($this->configuration['log_api_calls']) { - $this->logger->notice('Flex::capturePayment() API response - @status:
@object
', ['@status' => $result[0]->getStatus() ?? 'UNKNOWN', '@object' => $result[0]->__toString()]); + $this->logger->notice('Flex::capturePayment() API response - @status:
@object
', [ + '@status' => $result[0]->getStatus() ?? 'UNKNOWN', + '@object' => $result[0]->__toString(), + ]); } } catch (ApiException $e) { // Log the API error message if enabled. if ($this->configuration['log_api_calls']) { - $this->logger->notice('Flex::capturePayment() API error - @reason:
@body
', ['@reason' => $e->getResponseBody()->reason ?? 'UNKNOWN', '@body' => print_r($e->getResponseBody(), TRUE)]); + $this->logger->notice('Flex::capturePayment() API error - @reason:
@body
', [ + '@reason' => $e->getResponseBody()->reason ?? 'UNKNOWN', + '@body' => print_r($e->getResponseBody(), TRUE)], + ); } throw new InvalidRequestException($e->getMessage(), $e->getCode()); @@ -533,7 +541,7 @@ class Flex extends OnsitePaymentGatewayBase implements FlexInterface { /** * {@inheritdoc} */ - public function refundPayment(PaymentInterface $payment, Price $amount = NULL) { + public function refundPayment(PaymentInterface $payment, ?Price $amount = NULL) { $this->assertPaymentState($payment, ['completed', 'partially_refunded']); // If not specified, refund the entire amount. $amount = $amount ?: $payment->getAmount(); diff --git a/src/PluginForm/CyberSourceSahcForm.php b/src/PluginForm/CyberSourceSahcForm.php index 297202b..7ca1939 100644 --- a/src/PluginForm/CyberSourceSahcForm.php +++ b/src/PluginForm/CyberSourceSahcForm.php @@ -159,7 +159,7 @@ class CyberSourceSahcForm extends BasePaymentOffsiteForm implements ContainerInj Telling CyberSource that we're sending it, then not sending it, causes CyberSource to throw an access denied mystery error. @see https://drupal.org/node/2121409. - */ + */ $data['unsigned_field_names'] = 'form_build_id,form_id'; } else { @@ -233,7 +233,7 @@ class CyberSourceSahcForm extends BasePaymentOffsiteForm implements ContainerInj $send_line_items = 0; $line_items = $order->getItems(); for ($i = 0, $j = count($line_items); $i < $j && $i < 200; $i++) { - /** @var $line_item \Drupal\commerce_order\Entity\OrderItemInterface */ + /** @var \Drupal\commerce_order\Entity\OrderItemInterface $line_item */ $line_item = $line_items[$i]; $item_unit_price = $line_item->getUnitPrice()->getNumber(); if (!is_null($item_unit_price)) { @@ -273,7 +273,7 @@ class CyberSourceSahcForm extends BasePaymentOffsiteForm implements ContainerInj /* All fields should be added to $form by now so they can be signed. The signed_field_names is a required field and should list itself. - */ + */ $data['signed_field_names'] = ''; $signed_field_names_list = array_keys($data); $form['signed_field_names'] = [ @@ -286,7 +286,7 @@ class CyberSourceSahcForm extends BasePaymentOffsiteForm implements ContainerInj /* Add the signature field of signed name value pairs. Generated as SHA256 base64 using the secret key. - */ + */ $form['signature'] = [ '#type' => 'hidden', '#value' => $payment_gateway_plugin->signData($data), -- GitLab From b15c5f9a51d104119e1872eae2c3eff759762547 Mon Sep 17 00:00:00 2001 From: Jonathan Sacksick Date: Thu, 14 Aug 2025 13:30:58 +0300 Subject: [PATCH 6/8] Fix stylelint and phpcs issues. --- css/token-form.css | 6 +++-- src/Plugin/Commerce/PaymentGateway/Flex.php | 28 ++++++++++----------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/css/token-form.css b/css/token-form.css index 3f4b4dd..7048436 100644 --- a/css/token-form.css +++ b/css/token-form.css @@ -1,13 +1,15 @@ #cybersource-card-number, #cybersource-card-cvv { + width: 15em; + height: 2em; border: 1px solid #767676; border-radius: 1px; - height: 2em; - width: 15em; } + #cybersource-card-cvv { width: 5em; } + .cybersource-field { height: 100%; } diff --git a/src/Plugin/Commerce/PaymentGateway/Flex.php b/src/Plugin/Commerce/PaymentGateway/Flex.php index 5bbec83..fee62b7 100644 --- a/src/Plugin/Commerce/PaymentGateway/Flex.php +++ b/src/Plugin/Commerce/PaymentGateway/Flex.php @@ -107,15 +107,7 @@ class Flex extends OnsitePaymentGatewayBase implements FlexInterface { $instance->requestStack = $container->get('request_stack'); if ($configuration) { // Check if payment method is in capture mode. - switch ($instance->configuration['transaction_type']) { - case self::COMMERCE_CYBERSOURCE_FLEX_TRANSACTION_AUTH_AND_CAPTURE: - $instance->capture = TRUE; - break; - - default: - $instance->capture = FALSE; - break; - } + $instance->capture = $instance->configuration['transaction_type'] === self::COMMERCE_CYBERSOURCE_FLEX_TRANSACTION_AUTH_AND_CAPTURE; if ($instance->getMode() == 'test') { $host = self::COMMERCE_CYBERSOURCE_FLEX_TEST_API_SERVER; @@ -526,8 +518,8 @@ class Flex extends OnsitePaymentGatewayBase implements FlexInterface { if ($this->configuration['log_api_calls']) { $this->logger->notice('Flex::capturePayment() API error - @reason:
@body
', [ '@reason' => $e->getResponseBody()->reason ?? 'UNKNOWN', - '@body' => print_r($e->getResponseBody(), TRUE)], - ); + '@body' => print_r($e->getResponseBody(), TRUE), + ]); } throw new InvalidRequestException($e->getMessage(), $e->getCode()); @@ -571,20 +563,28 @@ class Flex extends OnsitePaymentGatewayBase implements FlexInterface { try { // Log the API request if enabled. if ($this->configuration['log_api_calls']) { - $this->logger->notice('Flex::refundPayment() API request:
@object
', ['@object' => $request_obj->__toString()]); + $this->logger->notice('Flex::refundPayment() API request:
@object
', [ + '@object' => $request_obj->__toString(), + ]); } $result = $refund_api->refundPayment($request_obj, $remote_id); // Log the API response if enabled. if ($this->configuration['log_api_calls']) { - $this->logger->notice('Flex::refundPayment() API response - @status:
@object
', ['@status' => $result[0]->getStatus() ?? 'UNKNOWN', '@object' => $result[0]->__toString()]); + $this->logger->notice('Flex::refundPayment() API response - @status:
@object
', [ + '@status' => $result[0]->getStatus() ?? 'UNKNOWN', + '@object' => $result[0]->__toString(), + ]); } } catch (ApiException $e) { // Log the API error message if enabled. if ($this->configuration['log_api_calls']) { - $this->logger->notice('Flex::refundPayment() API error - @reason:
@body
', ['@reason' => $e->getResponseBody()->reason ?? 'UNKNOWN', '@body' => print_r($e->getResponseBody(), TRUE)]); + $this->logger->notice('Flex::refundPayment() API error - @reason:
@body
', [ + '@reason' => $e->getResponseBody()->reason ?? 'UNKNOWN', + '@body' => print_r($e->getResponseBody(), TRUE), + ]); } throw new InvalidRequestException($e->getMessage(), $e->getCode()); -- GitLab From aa5db3744b89b16045cfb870485d2054e91052fd Mon Sep 17 00:00:00 2001 From: Jonathan Sacksick Date: Thu, 14 Aug 2025 13:37:38 +0300 Subject: [PATCH 7/8] Attempt to fix CSPELL issues. --- .gitlab-ci.yml | 3 +++ src/Plugin/Commerce/PaymentGateway/Flex.php | 10 ++++++++-- src/PluginForm/CyberSourceSahcForm.php | 4 ++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2a56057..4a28c3e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -50,3 +50,6 @@ include: ################ # variables: # SKIP_ESLINT: '1' +variables: + _CSPELL_IGNORE_PATHS: '"composer.json","README.md"' + _CSPELL_WORDS: "apitest,bahasa,cartesbancaires,dinersclub,maco,microfrom,paymentsidcaptures,paymentsidrefunds,paymentsidreversals,sahc,serverside" diff --git a/src/Plugin/Commerce/PaymentGateway/Flex.php b/src/Plugin/Commerce/PaymentGateway/Flex.php index fee62b7..5c7ad9f 100644 --- a/src/Plugin/Commerce/PaymentGateway/Flex.php +++ b/src/Plugin/Commerce/PaymentGateway/Flex.php @@ -632,13 +632,19 @@ class Flex extends OnsitePaymentGatewayBase implements FlexInterface { // Log the API response if enabled. if ($this->configuration['log_api_calls']) { - $this->logger->notice('Flex::voidPayment() API response - @status:
@object
', ['@status' => $result[0]->getStatus() ?? 'UNKNOWN', '@object' => $result[0]->__toString()]); + $this->logger->notice('Flex::voidPayment() API response - @status:
@object
', [ + '@status' => $result[0]->getStatus() ?? 'UNKNOWN', + '@object' => $result[0]->__toString(), + ]); } } catch (ApiException $e) { // Log the API error message if enabled. if ($this->configuration['log_api_calls']) { - $this->logger->notice('Flex::voidPayment() API error - @reason:
@body
', ['@reason' => $e->getResponseBody()->reason ?? 'UNKNOWN', '@body' => print_r($e->getResponseBody(), TRUE)]); + $this->logger->notice('Flex::voidPayment() API error - @reason:
@body
', [ + '@reason' => $e->getResponseBody()->reason ?? 'UNKNOWN', + '@body' => print_r($e->getResponseBody(), TRUE), + ]); } throw new InvalidRequestException($e->getMessage(), $e->getCode()); diff --git a/src/PluginForm/CyberSourceSahcForm.php b/src/PluginForm/CyberSourceSahcForm.php index 7ca1939..cb9ed1a 100644 --- a/src/PluginForm/CyberSourceSahcForm.php +++ b/src/PluginForm/CyberSourceSahcForm.php @@ -197,8 +197,8 @@ class CyberSourceSahcForm extends BasePaymentOffsiteForm implements ContainerInj } } // Optional. Company name. - if (!empty($billing_address['organisation'])) { - $data['bill_to_company_name'] = $billing_address['organisation']; + if (!empty($billing_address['organization'])) { + $data['bill_to_company_name'] = $billing_address['organization']; } // Optional. Address line 1. if (!empty($billing_address['address_line1'])) { -- GitLab From 689d6e63c51b51c19452f67eaafdab89f5bcabc4 Mon Sep 17 00:00:00 2001 From: Jonathan Sacksick Date: Thu, 14 Aug 2025 13:40:09 +0300 Subject: [PATCH 8/8] Attempt to fix ESLINT issues. --- js/checkout.js | 119 ++++++++++++++++++++++++++----------------------- 1 file changed, 63 insertions(+), 56 deletions(-) diff --git a/js/checkout.js b/js/checkout.js index 0929785..6a9ee46 100644 --- a/js/checkout.js +++ b/js/checkout.js @@ -4,9 +4,6 @@ */ (function ($, Drupal, drupalSettings, once) { - - 'use strict'; - /** * Attaches the commerceCyberSource behavior. * @@ -18,72 +15,82 @@ * @see Drupal.commerceCyberSource */ Drupal.behaviors.commerceCyberSourceForm = { - attach: function (context) { - $(once('cybersource-processed', '.commerce-checkout-flow', context)).each(function () { - var $form = $(this); - var interval = setInterval(function () { - if ($('#cybersource-card-number').length && $('#cybersource-card-cvv').length) { - clearInterval(interval); - var captureContext = drupalSettings.commerceCyberSource.clientToken, - flex = new Flex(captureContext), - microform = flex.microform({ + attach(context) { + $(once('cybersource-processed', '.commerce-checkout-flow', context)).each( + function () { + const $form = $(this); + var interval = setInterval(function () { + if ( + $('#cybersource-card-number').length && + $('#cybersource-card-cvv').length + ) { + clearInterval(interval); + const captureContext = + drupalSettings.commerceCyberSource.clientToken; + const flex = new Flex(captureContext); + const microform = flex.microform({ styles: { input: { 'font-size': '14px', - 'font-family': 'Lucida Sans Unicode, Verdana, sans-serif' + 'font-family': 'Lucida Sans Unicode, Verdana, sans-serif', }, ':disabled': { - cursor: 'not-allowed' + cursor: 'not-allowed', }, valid: { - color: '#3c763d' + color: '#3c763d', }, invalid: { - color: '#a94442' - } - } + color: '#a94442', + }, + }, }); - microform - .createField('number', { placeholder: 'Enter card number' }) - .load('#cybersource-card-number'); + microform + .createField('number', { placeholder: 'Enter card number' }) + .load('#cybersource-card-number'); - microform - .createField('securityCode', { placeholder: '•••' }) - .load('#cybersource-card-cvv'); + microform + .createField('securityCode', { placeholder: '•••' }) + .load('#cybersource-card-cvv'); - $('.form-submit', $form).on('click', function () { - var $submit = $(this); + $('.form-submit', $form) + .on('click', function () { + const $submit = $(this); - microform.createToken({ - expirationMonth: $('.cybersource-month', $form).val(), - expirationYear: $('.cybersource-year', $form).val(), - }, function (err, token) { - if (err) { - switch (err.reason) { - case 'CREATE_TOKEN_TIMEOUT': - case 'CREATE_TOKEN_NO_FIELDS_LOADED': - case 'CREATE_TOKEN_NO_FIELDS': - case 'CREATE_TOKEN_VALIDATION_PARAMS': - case 'CREATE_TOKEN_VALIDATION_FIELDS': - case 'CREATE_TOKEN_VALIDATION_SERVERSIDE': - case 'CREATE_TOKEN_UNABLE_TO_START': - console.error(err.message); - return; - default: - console.error('Unknown error'); - return; - } - } - $('.cybersource-token', $form).val(token); - $form.submit(); - $submit.prop('disabled', true); - }); - }).attr('type', 'button'); - } - }, 10); - }); - } + microform.createToken( + { + expirationMonth: $('.cybersource-month', $form).val(), + expirationYear: $('.cybersource-year', $form).val(), + }, + function (err, token) { + if (err) { + switch (err.reason) { + case 'CREATE_TOKEN_TIMEOUT': + case 'CREATE_TOKEN_NO_FIELDS_LOADED': + case 'CREATE_TOKEN_NO_FIELDS': + case 'CREATE_TOKEN_VALIDATION_PARAMS': + case 'CREATE_TOKEN_VALIDATION_FIELDS': + case 'CREATE_TOKEN_VALIDATION_SERVERSIDE': + case 'CREATE_TOKEN_UNABLE_TO_START': + console.error(err.message); + return; + default: + console.error('Unknown error'); + return; + } + } + $('.cybersource-token', $form).val(token); + $form.submit(); + $submit.prop('disabled', true); + }, + ); + }) + .attr('type', 'button'); + } + }, 10); + }, + ); + }, }; - })(jQuery, Drupal, drupalSettings, once); -- GitLab