commerce_alphabank_redirect-1.0.1/src/PluginForm/OffsiteRedirect/AlphabankPaymentRedirectForm.php

src/PluginForm/OffsiteRedirect/AlphabankPaymentRedirectForm.php
<?php

namespace Drupal\commerce_alphabank_redirect\PluginForm\OffsiteRedirect;

use Drupal\commerce_payment\PluginForm\PaymentOffsiteForm as BasePaymentOffsiteForm;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;

/**
 * Provides the redirect form.
 */
class AlphabankPaymentRedirectForm extends BasePaymentOffsiteForm implements ContainerInjectionInterface {

  /**
   * The logger.
   *
   * @var \Drupal\Core\Logger\LoggerChannelInterface
   */
  protected $logger;

  /**
   * Constructs a AlphabankPaymentRedirectForm object.
   *
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
   *   The logger.
   */
  public function __construct(LoggerChannelFactoryInterface $logger_factory) {
    $this->logger = $logger_factory->get('commerce_alphabank_redirect');
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('logger.factory')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
    $form = parent::buildConfigurationForm($form, $form_state);

    /** @var \Drupal\commerce_payment\Entity\PaymentInterface $payment */
    $payment = $this->entity;
    $payment_gateway_plugin = $payment->getPaymentGateway()?->getPlugin();
    $config = $payment_gateway_plugin->getConfiguration();
    /** @var \Drupal\commerce_order\Entity\OrderInterface $order */
    $order = $payment->getOrder();
    // Format amount.
    $amount = sprintf('%0.2f', $order->getTotalPrice()?->getNumber());

    // Save response data for validating response later.
    $order->setData("AlphabankGatewayData", [
      "shared_secret" => $config["shared_secret"],
      "payment_gateway" => $payment->getPaymentGateway()?->id(),
    ]);
    $order->save();

    // Calculate digest.
    $digest_data = "";
    $digest_data_array = [];

    $digest_data_array[1] = $config['version'];
    $digest_data_array[2] = $config['mid'];
    $digest_data_array[3] = "";
    $digest_data_array[4] = "";
    $digest_data_array[5] = $order->id() . 'at' . time();
    $digest_data_array[6] = "";
    $digest_data_array[7] = $amount;
    $digest_data_array[8] = $config['currency'];
    $digest_data_array[9] = "";
    $digest_data_array[10] = "";
    $digest_data_array[11] = "GR";
    $digest_data_array[12] = "";
    $digest_data_array[13] = $order->getBillingProfile()?->get('address')->postal_code;
    $digest_data_array[14] = $order->getBillingProfile()?->get('address')->locality;
    $digest_data_array[15] = $order->getBillingProfile()?->get('address')->address_line1;
    $digest_data_array[16] = "";
    $digest_data_array[17] = "";
    $digest_data_array[18] = "";
    $digest_data_array[19] = "";
    $digest_data_array[20] = "";
    $digest_data_array[21] = "";
    $digest_data_array[22] = "";
    $digest_data_array[23] = "";
    $digest_data_array[24] = "";
    $digest_data_array[25] = "";
    $digest_data_array[26] = "";
    $digest_data_array[27] = "";
    $digest_data_array[28] = "";
    $digest_data_array[29] = "";
    $digest_data_array[30] = "";
    $digest_data_array[31] = "";
    $digest_data_array[32] = "";
    $digest_data_array[33] = "";
    $digest_data_array[34] = $config['confirmUrl'];
    $digest_data_array[35] = $config['cancelUrl'];
    $digest_data_array[36] = "";
    $digest_data_array[37] = "";
    $digest_data_array[38] = "";
    $digest_data_array[39] = "";
    $digest_data_array[40] = "";
    $digest_data_array[41] = "";
    $digest_data_array[42] = "";
    $digest_data_array[43] = "";
    $digest_data_array[44] = "";
    $digest_data_array[45] = $config['shared_secret'];

    // if IRIS payment method, add additional fields
    if ($config['pay_method'] === 'iris') {
      // Allow other modules to alter the billing phone field name if it differs from 'field_phone'.
      $field_name = 'field_phone';
      \Drupal::moduleHandler()->alter('commerce_alphabank_redirect_billing_phone_field', $field_name);
      // Retrieve payer phone from the billing profile.
      $payerPhone = '';
      if (($billing_profile = $order->getBillingProfile()) && $billing_profile->hasField($field_name) && !$billing_profile->get($field_name)->isEmpty()) {
        $payerPhone = $billing_profile->get($field_name)->value;
      }

      // Populate digest data array with IRIS-specific fields.
      $digest_data_array[6] = $this->calculateDiasRFCode($config['beneficiary_code'], $order);
      $digest_data_array[9] = $order->getEmail();
      $digest_data_array[10] = $payerPhone;
      $digest_data_array[26] = 'IRIS';
    }

    $digest_data = implode("", $digest_data_array);
    $digest = base64_encode(hash("sha256", $digest_data, TRUE));

    // Prepare redirect form.
    $data = [
      'version' => $digest_data_array[1],
      'mid' => $digest_data_array[2],
      'lang' => $digest_data_array[3],
      'deviceCategory' => $digest_data_array[4],
      'orderid' => $digest_data_array[5],
      'orderDesc' => $digest_data_array[6],
      'orderAmount' => $digest_data_array[7],
      'currency' => $digest_data_array[8],
      'payerEmail' => $digest_data_array[9],
      'payerPhone' => $digest_data_array[10],
      'billCountry' => $digest_data_array[11],
      'billState' => $digest_data_array[12],
      'billZip' => $digest_data_array[13],
      'billCity' => $digest_data_array[14],
      'billAddress' => $digest_data_array[15],
      'weight' => $digest_data_array[16],
      'dimensions' => $digest_data_array[17],
      'shipCountry' => $digest_data_array[18],
      'shipState' => $digest_data_array[19],
      'shipZip' => $digest_data_array[20],
      'shipCity' => $digest_data_array[21],
      'shipAddress' => $digest_data_array[22],
      'addFraudScore' => $digest_data_array[23],
      'maxPayRetries' => $digest_data_array[24],
      'reject3dsU' => $digest_data_array[25],
      'payMethod' => $digest_data_array[26],
      'trType' => $digest_data_array[27],
      'extInstallmentoffset' => $digest_data_array[28],
      'extInstallmentperiod' => $digest_data_array[29],
      'extRecurringfrequency' => $digest_data_array[30],
      'extRecurringenddate' => $digest_data_array[31],
      'blockScore' => $digest_data_array[32],
      'cssUrl' => $digest_data_array[33],
      'confirmUrl' => $digest_data_array[34],
      'cancelUrl' => $digest_data_array[35],
      'var1' => $digest_data_array[36],
      'var2' => $digest_data_array[37],
      'var3' => $digest_data_array[38],
      'var4' => $digest_data_array[39],
      'var5' => $digest_data_array[40],
      'var6' => $digest_data_array[41],
      'var7' => $digest_data_array[42],
      'var8' => $digest_data_array[43],
      'var9' => $digest_data_array[44],
      'digest' => $digest,
    ];

    $form = $this->buildRedirectForm($form, $form_state, $config['postUrl'], $data, 'post');
    return $form;
  }

  /**
   * Generates a payment reference code (Dias RF code) based on the provided beneficiary code,
   * order details, and calculated check digits.
   *
   * The function constructs the RF code using the following steps:
   * - Validates the total price of the order (must be positive).
   * - Calculates the "amount check digit" based on the total price.
   * - Formats the debtor code (order ID) to 15 digits, padded with zeros.
   * - Combines the beneficiary code, amount check digit, and debtor code
   *   into a temporary RF code.
   * - Computes the final check digits using the Mod97 algorithm.
   * - Builds the full RF code by adding the prefix "RF", the check digits,
   *   and the temporary RF code.
   *
   * @param string $beneficiary_code
   *   A unique string representing the beneficiary code.
   *   This code is used as the initial part of the RF code.
   *
   * @param \Drupal\commerce_order\Entity\OrderInterface $order
   *   The order object containing order details, including the total price.
   *   The order ID will be used as the debtor code, padded to 15 digits.
   *
   * @return string
   *   A 25-character string representing the complete RF payment code.
   *   Format: RF + 2-digit check digits + beneficiary code + amount check digit + debtor code.
   *
   * @throws \InvalidArgumentException
   *   Thrown when:
   *   - The total price of the order is not positive.
   *   - The resulting RF code does not meet the 25-character length requirement.
   *
   * @see calculateAmountCheckDigit()
   * @see calculateCheckDigits()
   */
  protected function calculateDiasRFCode($beneficiary_code, $order) {
    $total_price = round($order->getTotalPrice()?->getNumber(), 2);
    // Validate the total price amount
    if ($total_price <= 0) {
      throw new \InvalidArgumentException("The total price must be a positive amount.");
    }

    $amount_check_digit = $this->calculateAmountCheckDigit($total_price);
    $debtor_code = str_pad($order->id(),15,"0", STR_PAD_LEFT);

    $temporary_rf_code = $beneficiary_code . $amount_check_digit . $debtor_code;
    $check_digits = $this->calculateCheckDigits($temporary_rf_code);

    $rf_code = 'RF' . $check_digits . $temporary_rf_code;
    // Ensure the code is numeric and has exactly 25 digits
    if (strlen($rf_code) !== 25) {
      throw new \InvalidArgumentException("RF code must a string of exactly 25 digits.");
    }
    return $rf_code;
  }

  /**
   * Calculate the amount check digit (position 10) for the payment code.
   *
   * This function converts the given amount in euros to cents (removing the decimal point),
   * processes the digits from right to left using a multiplier sequence (1, 7, 3),
   * and computes the remainder when dividing the weighted sum by 8.
   *
   * @param float $amount The payment amount in euros.
   *
   * @return int The amount check digit (an integer between 0 and 7).
   *
   * @throws \InvalidArgumentException If the amount is negative or invalid.
   */
  protected function calculateAmountCheckDigit(float $amount): int {
    // Convert the amount to cents by multiplying by 100 and removing the decimal point.
    $amount_in_cents = (int)round($amount * 100);
    // Validate the amount in cents (ensure it's positive)
    if ($amount_in_cents <= 0) {
      throw new \InvalidArgumentException("Amount in cents must be a positive value.");
    }
    // Convert the amount to a string and reverse it to process digits from right to left.
    $amount_digits = str_split(strrev((string)$amount_in_cents));
    // Define the multiplier sequence.
    $multipliers = [1, 7, 3];
    $sum = 0;
    foreach ($amount_digits as $index => $digit) {
      $sum += (int)$digit * $multipliers[$index % count($multipliers)];
    }
    // Calculate the Mod8 remainder.
    $remainder = $sum % 8;

    // Return the check digit.
    return $remainder;
  }

  /**
   * Calculate the check digits (positions 3-4) for the payment code using the Mod97 algorithm.
   *
   * This function appends a fixed suffix (271500) to the given 21-digit temporary code,
   * calculates the Mod97 remainder, and computes the check digits as `98 - remainder`.
   *
   * @param string $temporary_code The 21-digit temporary payment code (excluding RF and check digits).
   *
   * @return string The 2-digit check digits (padded with a leading zero if necessary).
   *
   * @throws \InvalidArgumentException If the input is not a numeric string or its length is not 21 digits.
   */
  protected function calculateCheckDigits(string $temporary_code): string {
    // Ensure the code is numeric and has exactly 21 digits
    if (!is_numeric($temporary_code) || strlen($temporary_code) !== 21) {
      throw new \InvalidArgumentException("Temporary code must be a numeric string of exactly 21 digits.");
    }
    // Append the fixed value 271500 to the temporary 21-digit code.
    $code_with_suffix = $temporary_code . "271500";
    // Calculate the Mod97 of the resulting 27-digit number.
    // IMPORTANT!!! keep bcmod instead of % (for modulo operator) because $code_with_suffix is a large int.
    $remainder = bcmod($code_with_suffix, 97);
    // Compute the check digits.
    $check_digits = 98 - $remainder;
    // If the result is a single digit, prepend a 0.
    return str_pad((string)$check_digits, 2, "0", STR_PAD_LEFT);
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc