<?php

namespace GlobalPayments\Api\Builders\RequestBuilder\GpApi;

use GlobalPayments\Api\Builders\{AuthorizationBuilder, BaseBuilder};
use GlobalPayments\Api\Entities\{
    CustomerDocument,
    EncryptionData,
    IRequestBuilder,
    PayByLinkData,
    PhoneNumber,
    Product,
    StoredCredential
};
use GlobalPayments\Api\Entities\Enums\{
    AddressType,
    BankPaymentType,
    CaptureMode,
    CardType,
    Channel,
    DigitalWalletTokenFormat,
    EncyptedMobileType,
    EntryMethod,
    GatewayProvider,
    ManualEntryMethod,
    PayByLinkStatus,
    PaymentEntryMode,
    PaymentProvider,
    PaymentType,
    PhoneNumberType,
    TransactionModifier,
    TransactionType
};
use GlobalPayments\Api\Entities\Exceptions\{ApiException, UnsupportedTransactionException};
use GlobalPayments\Api\Entities\GpApi\DTO\{Card, PaymentMethod};
use GlobalPayments\Api\Entities\GpApi\GpApiRequest;
use GlobalPayments\Api\Gateways\OpenBankingProvider;
use GlobalPayments\Api\Mapping\EnumMapping;
use GlobalPayments\Api\PaymentMethods\{
    AlternativePaymentMethod,
    BankPayment,
    BNPL,
    Credit,
    CreditCardData,
    CreditTrackData,
    DebitTrackData,
    ECheck,
    FundsAccount
};
use GlobalPayments\Api\PaymentMethods\Interfaces\{ICardData, IEncryptable, ITokenizable, ITrackData};
use GlobalPayments\Api\ServiceConfigs\Gateways\GpApiConfig;
use GlobalPayments\Api\Utils\{CardUtils, EmvUtils, GenerationUtils, StringUtils};
use GlobalPayments\Api\Utils\Logging\ProtectSensitiveData;

class GpApiAuthorizationRequestBuilder implements IRequestBuilder
{
    /** @var AuthorizationBuilder */
    private mixed $builder = null;

    private array $maskedValues = [];

    /***
     * @param AuthorizationBuilder $builder
     *
     * @return bool
     */
    public static function canProcess(?BaseBuilder $builder = null): bool
    {
        if ($builder instanceof AuthorizationBuilder) {
            return true;
        }

        return false;
    }

    /**
     * @param BaseBuilder $builder
     * @param GpApiConfig $config
     * @return GpApiRequest|string
     */
    public function buildRequest(BaseBuilder $builder, mixed $config): mixed
    {
        $this->builder = $builder;
        $requestData = null;
        /** @var AuthorizationBuilder $builder */
        switch ($builder->transactionType) {
            case TransactionType::SALE:
            case TransactionType::REFUND:
            case TransactionType::AUTH:
                $endpoint = GpApiRequest::TRANSACTION_ENDPOINT;
                $verb = 'POST';
                $requestData =  $this->createFromAuthorizationBuilder($builder, $config);
                break;
            case TransactionType::VERIFY:
                if ($builder->requestMultiUseToken && empty($builder->paymentMethod->token)) {
                    $endpoint = GpApiRequest::PAYMENT_METHODS_ENDPOINT;
                    $verb = 'POST';
                    $requestData = [];
                    $requestData['account_name'] = $config->accessTokenInfo->tokenizationAccountName;
                    $requestData['account_id'] = $config->accessTokenInfo->tokenizationAccountID;
                    $requestData['name'] = $builder->description ?: "";
                    $requestData['payer'] = ['id' => $builder->customerId ?: ""];
                    $requestData['reference'] = $builder->clientTransactionId ?: GenerationUtils::generateOrderId();
                    $requestData['usage_mode'] = $builder->paymentMethodUsageMode;
                    $requestData['fingerprint_mode'] =
                        (!empty($builder->customerData) & !empty($builder->customerData->deviceFingerPrint) ?
                            $builder->customerData->deviceFingerPrint : null);
                    $card = new Card();
                    $builderCard = $builder->paymentMethod;
                    $card->number = $builderCard->number;
                    $card->expiry_month = !empty($builderCard->expMonth) ?
                        str_pad((string) $builderCard->expMonth, 2, '0', STR_PAD_LEFT) : null;
                    $card->expiry_year = !empty($builderCard->expYear) ?
                        substr(
                            str_pad((string) $builderCard->expYear, 4, '0', STR_PAD_LEFT),
                            2,
                            2
                        ) : null;
                    $card->cvv = $builderCard->cvn;
                    $requestData['card'] = $card;
                    $this->maskedValues = ProtectSensitiveData::hideValues(
                        [
                            'card.expiry_month' => $card->expiry_month,
                            'card.expiry_year' => $card->expiry_year,
                            'card.cvv' => $card->cvv
                        ]
                    );
                    $this->maskedValues = ProtectSensitiveData::hideValue('card.number', $card->number, 4, 6);
                } else {
                    $endpoint = GpApiRequest::VERIFICATIONS_ENDPOINT;
                    $verb = 'POST';
                    $requestData = $this->generateVerificationRequest($builder, $config);
                }
                break;
            case TransactionType::DCC_RATE_LOOKUP:
                $endpoint = GpApiRequest::DCC_ENDPOINT;
                $verb = 'POST';
                $requestData['account_name'] = $config->accessTokenInfo->transactionProcessingAccountName;
                $requestData['account_id'] = $config->accessTokenInfo->transactionProcessingAccountID;
                $requestData['channel'] = $config->channel;
                $requestData['amount'] = StringUtils::toNumeric($builder->amount);
                $requestData['currency'] = $builder->currency;
                $requestData['country'] = $config->country;
                $requestData['reference'] = !empty($builder->clientTransactionId) ?
                    $builder->clientTransactionId : GenerationUtils::getGuid();
                $requestData['payment_method'] = $this->createPaymentMethodParam($builder, $config);
                break;
            case TransactionType::CREATE:
                if ($builder->payByLinkData instanceof PayByLinkData) {
                    /** @var PayByLinkData $payByLink */
                    $payByLink = $builder->payByLinkData;
                    $endpoint = GpApiRequest::PAYBYLINK_ENDPOINT;
                    $verb = 'POST';
                    $requestData['account_name'] = $config->accessTokenInfo->transactionProcessingAccountName;
                    $requestData['account_id'] = $config->accessTokenInfo->transactionProcessingAccountID;
                    $requestData['type'] = $payByLink->type;
                    $requestData['usage_mode'] = $payByLink->usageMode;
                    $requestData['usage_limit'] = (string) $payByLink->usageLimit;
                    $requestData['reference'] = $builder->clientTransactionId;
                    $requestData['name'] = $payByLink->name;
                    $requestData['description'] = $builder->description;
                    $requestData['shippable'] = StringUtils::boolToYesNo($payByLink->isShippable) ? StringUtils::boolToYesNo($payByLink->isShippable) : "NO";
                    $requestData['shipping_amount'] = StringUtils::toNumeric($payByLink->shippingAmount);
                    $requestData['expiration_date'] = !empty($payByLink->expirationDate) ?
                        (new \DateTime($payByLink->expirationDate))->format('Y-m-d\TH:i:s\Z') : null;
                    //@TODO - remove status when GP-API will fix the issue (status shouldn't be sent in request)
                    $requestData['status'] = PayByLinkStatus::ACTIVE;
                    $requestData['images'] = $payByLink->images;
                    $requestData['transactions'] = [
                        'amount' => StringUtils::toNumeric($builder->amount),
                        'channel' => $config->channel,
                        'currency' => $builder->currency,
                        'country' => $config->country,
                        'allowed_payment_methods' => $payByLink->allowedPaymentMethods
                    ];
                    $requestData['notifications'] = [
                        'return_url' => $payByLink->returnUrl,
                        'status_url' => $payByLink->statusUpdateUrl,
                        'cancel_url' => $payByLink->cancelUrl
                    ];
                }
                break;
            case TransactionType::TRANSFER_FUNDS:
                if (!$builder->paymentMethod instanceof FundsAccount) {
                    throw new UnsupportedTransactionException("Payment method doesn't support funds transfers");
                }
                $endpoint = GpApiRequest::MERCHANT_MANAGEMENT_ENDPOINT . '/' . $builder->paymentMethod->merchantId .
                    GpApiRequest::TRANSFER_ENDPOINT;
                $verb = 'POST';
                $requestData = [
                    'account_id' => $builder->paymentMethod->accountId ?? null,
                    'account_name' => $builder->paymentMethod->accountName ?? null,
                    'recipient_account_id' => $builder->paymentMethod->recipientAccountId ?? null,
                    'reference' => $builder->clientTransactionId ?? GenerationUtils::getGuid(),
                    'amount' => StringUtils::toNumeric($builder->amount),
                    'description' => $builder->description,
                    'usable_balance_mode' => $builder->paymentMethod->usableBalanceMode ?? null
                ];
                break;
            case TransactionType::HOSTED_PAYMENT_PAGE:
                // P.M: Similar to TransactionType::CREATE, but with more data in the request some of which is required 
                // for the 3DS challange.
                if ($builder->hostedPaymentData) {
                    $endpoint = GpApiRequest::PAYBYLINK_ENDPOINT;
                    $verb = 'POST';
                    $requestData = [];
                    $requestData['account_name'] = $config->accessTokenInfo->transactionProcessingAccountName;
                    $requestData['account_id'] = $config->accessTokenInfo->transactionProcessingAccountID;
                    $requestData['type'] = $builder->hostedPaymentData->type;
                    $requestData['name'] = $builder->hostedPaymentData->name;
                    $requestData['description'] = $builder->hostedPaymentData->description;
                    $requestData['reference'] = $builder->hostedPaymentData->reference;
                    if(property_exists($builder->hostedPaymentData, 'expirationDate') && !empty($builder->hostedPaymentData->expirationDate)) {
                        $requestData['expiration_date'] = (new \DateTime($builder->hostedPaymentData->expirationDate))->format('Y-m-d\TH:i:s\Z');
                    }
                    if(property_exists($builder->hostedPaymentData, "images") && !empty($builder->hostedPaymentData->images)) {
                        $requestData['images'] = $builder->hostedPaymentData->images;
                    }

                    $shippable = $builder->hostedPaymentData->shippable ?? false;
                    $requestData['shippable'] = is_bool($shippable) 
                        ? StringUtils::boolToYesNo($shippable) 
                        : "NO";
                    
                    // Add shipping_amount if shippable is YES and amount is provided
                    if ($requestData['shippable'] === 'YES' && !empty($builder->hostedPaymentData->shippingAmount)) {
                        $requestData['shipping_amount'] = StringUtils::toNumeric($builder->hostedPaymentData->shippingAmount);
                    }
                    
                    // Add usage_mode and usage_limit
                    if ($builder->hostedPaymentData->order && $builder->hostedPaymentData->order->HPPTransactionConfiguration) {
                        if (!empty($builder->hostedPaymentData->order->HPPTransactionConfiguration->usageMode)) {
                            $requestData['usage_mode'] = $builder->hostedPaymentData->order->HPPTransactionConfiguration->usageMode;
                        }
                        
                        if (!empty($builder->hostedPaymentData->order->HPPTransactionConfiguration->usageLimit)) {
                            $requestData['usage_limit'] = $builder->hostedPaymentData->order->HPPTransactionConfiguration->usageLimit;
                        }
                    }
                    
                    // Payer information - Already validation in place for this data
                    if ($builder->hostedPaymentData->payer) {
                        $payer = $builder->hostedPaymentData->payer;
                        $requestData['payer'] = [
                            'status' => $payer->status ?? "NEW",
                            // 'id' => $payer->id ?? "",
                            'name' => $payer->name ?? "",
                            'first_name' => $payer->firstName ?? "",
                            'last_name' => $payer->lastName ?? "",
                            'language' => $payer->language ?? 'en',
                            'email' => $payer->email ?? ""
                        ];
                        if(property_exists($payer, 'id') && !empty($payer->id)) {
                            $requestData['payer']['id'] = $payer->id;
                        }
                        
                        // Mobile phone - Allready validation in place for this data
                        if ($payer->mobilePhone) {
                            $requestData['payer']['mobile_phone'] = [
                                'country_code' => $payer->mobilePhone->countryCode ?? null,
                                'subscriber_number' => $payer->mobilePhone->number ?? null
                            ];
                        }
                        
                        // Billing address - Allready validation in place for this data
                        if ($payer->billingAddress) {
                            $requestData['payer']['billing_address'] = [
                                'line_1' => $payer->billingAddress->streetAddress1 ?? null,
                                'line_2' => $payer->billingAddress->streetAddress2 ?? null,
                                'line_3' => $payer->billingAddress->streetAddress3 ?? null,
                                'city' => $payer->billingAddress->city ?? null,
                                'postal_code' => $payer->billingAddress->postalCode ?? null,
                                'state' => $payer->billingAddress->state ?? null,
                                'country' => $payer->billingAddress->countryCode ?? null,
                                'type' => $payer->billingAddress->type ?? AddressType::BILLING
                            ];
                            if(property_exists($payer->billingAddress, 'phone') && !empty($payer->billingAddress->phone)) {
                                $requestData['payer']['billing_address']['phone'] = $payer->billingAddress->phone;
                            }
                        }
                        
                        // Address match indicator
                        if (!empty($payer->addressMatchIndicator)) {
                            $requestData['payer']['address_match_indicator'] = is_bool($payer->addressMatchIndicator) 
                                ? StringUtils::boolToYesNo($payer->addressMatchIndicator) 
                                : "NO";
                        }
                    }
                    
                    // Order information
                    if ($builder->hostedPaymentData->order) {
                        $order = $builder->hostedPaymentData->order;
                        $requestData['order'] = [
                            'amount' => StringUtils::toNumeric($order->amount),
                            'currency' => $order->currency
                        ];
                        
                        // Add order reference if available
                        if (!empty($order->reference)) {
                            $requestData['order']['reference'] = $order->reference;
                        }
                        
                        // Order Transaction configuration
                        if ($order->HPPTransactionConfiguration) {
                            $transactionConfig = $order->HPPTransactionConfiguration;
                            $requestData['order']['transaction_configuration'] = [
                                'channel' => $config->channel,
                                'country' => $config->country,
                                'allowed_payment_methods' => $transactionConfig->allowedPaymentMethods ?? ["CARD"],
                            ];
                            
                            // Add capture mode if available
                            if (!empty($transactionConfig->captureMode)) {
                                $requestData['order']['transaction_configuration']['capture_mode'] = $transactionConfig->captureMode;
                            }
                            
                            // Add currency conversion mode when explicitly configured.
                            if (!is_null($transactionConfig->currencyConversionMode)) {
                                $requestData['order']['transaction_configuration']['currency_conversion_mode'] =
                                    StringUtils::boolToYesNo($transactionConfig->currencyConversionMode);
                            }
                        }
                        
                        // Payment method configuration
                        if ($order->HPPPaymentMethodConfiguration) {
                            $paymentMethodConfig = $order->HPPPaymentMethodConfiguration;
                            $requestData['order']['payment_method_configuration'] = [];
                            // Authentications
                            if ($paymentMethodConfig->authentications) {
                                $auth = $paymentMethodConfig->authentications;
                                $requestData['order']['payment_method_configuration']['authentications'] = [
                                    'preference' => $auth->preference ?? "CHALLENGE_PREFERRED",
                                    'exempt_status' => $auth->exemptStatus ?? "LOW_VALUE",
                                    'billing_address_required' => is_bool($auth->billingAddressRequired) 
                                        ? StringUtils::boolToYesNo($auth->billingAddressRequired) 
                                        : 'NO'
                                ];
                            }
                            // APM configuration
                            if ($paymentMethodConfig->apm) {
                                $apm = $paymentMethodConfig->apm;
                                $requestData['order']['payment_method_configuration']['apm'] = [
                                    'shipping_address_enabled' => is_bool($apm->shippingAddressEnabled) 
                                        ? StringUtils::boolToYesNo($apm->shippingAddressEnabled) 
                                        : 'NO',
                                    'address_override' => is_bool($apm->addressOverride) 
                                        ? StringUtils::boolToYesNo($apm->addressOverride) 
                                        : 'NO',
                                ];
                            }
                            // Storage mode
                            if (!empty($paymentMethodConfig->storageMode)) {
                                $requestData['order']['payment_method_configuration']['storage_mode'] = $paymentMethodConfig->storageMode;
                            }
                            // Digital wallets
                            if (!empty($paymentMethodConfig->digitalWallets)) {
                                $requestData['order']['payment_method_configuration']['digital_wallets'] = $paymentMethodConfig->digitalWallets;
                            }
                        }
                        
                        // Shipping address 
                        if ($builder->hostedPaymentData->payer && $builder->hostedPaymentData->payer->shippingAddress) {
                            $shippingAddr = $builder->hostedPaymentData->payer->shippingAddress;
                            $requestData['order']['shipping_address'] = [
                                'line_1' => $shippingAddr->streetAddress1 ?? "",
                                'line_2' => $shippingAddr->streetAddress2 ?? "",
                                'line_3' => $shippingAddr->streetAddress3 ?? "",
                                'city' => $shippingAddr->city ?? "",
                                'postal_code' => $shippingAddr->postalCode ?? "",
                                'state' => $shippingAddr->state ?? "",
                                'country' => $shippingAddr->countryCode ?? "",
                                'type' => $shippingAddr->type ?? AddressType::SHIPPING
                            ];
                            if(property_exists($shippingAddr, 'phone') && !empty($shippingAddr->phone)) {
                                $requestData['order']['shipping_address']['phone'] = $shippingAddr->phone;
                            }
                        }
                        
                        // Shipping phone 
                        if ($builder->hostedPaymentData->payer && $builder->hostedPaymentData->payer->shippingPhone) {
                            $shippingPhone = $builder->hostedPaymentData->payer->shippingPhone;
                            $requestData['order']['shipping_phone'] = [
                                'country_code' => $shippingPhone->countryCode ?? null,
                                'subscriber_number' => $shippingPhone->number ?? null
                            ];
                        }
                    }
                    
                    // Notifications
                    if ($builder->hostedPaymentData->notifications) {
                        $requestData['notifications'] = [
                            'return_url' => $builder->hostedPaymentData->notifications->returnUrl,
                            'status_url' => $builder->hostedPaymentData->notifications->statusUrl,
                            'cancel_url' => $builder->hostedPaymentData->notifications->cancelUrl ?? ""
                        ];
                    }
                    
                    //Configuration for iframe callbacks
                    if (!empty($builder->hostedPaymentData->HPPDisplayConfiguration)) {
                        $requestData['display_configuration'] = $builder->hostedPaymentData->HPPDisplayConfiguration;
                    }
                    
                    // Hosted payment page functionality   
                    if (!empty($builder->hostedPaymentData->function)) {
                        $requestData['function'] = $builder->hostedPaymentData->function;
                    }
                    
                    //Referrer URL for hosted payment page
                    if (!empty($builder->hostedPaymentData->referrerUrl)) {
                        $requestData['referrer_url'] = $builder->hostedPaymentData->referrerUrl;
                    }
                    
                    //IP address and subnet mask for hosted payment page
                    if (!empty($builder->hostedPaymentData->ipAddress)) {
                        $requestData['ip_address'] = $builder->hostedPaymentData->ipAddress;
                        if (!empty($builder->hostedPaymentData->ipSubnetMask)) {
                            $requestData['ip_subnet_mask'] = $builder->hostedPaymentData->ipSubnetMask;
                        }
                    }
                    
                    //App email and app IDs for credential exchange functionality
                    if (!empty($builder->hostedPaymentData->appEmail)) {
                        $requestData['app_email'] = $builder->hostedPaymentData->appEmail;
                    }
                    
                    if (!empty($builder->hostedPaymentData->appIds)) {
                        $requestData['app_ids'] = $builder->hostedPaymentData->appIds;
                    }
                    
                    // Visa installments configuration
                    if (!empty($builder->hostedPaymentData->installments)) {
                        $installments = $builder->hostedPaymentData->installments;
                        $requestData['installments'] = [];
                        
                        if (!empty($installments->funding_mode)) {
                            $requestData['installments']['funding_mode'] = $installments->funding_mode;
                        }
                        
                        if (!empty($installments->terms)) {
                            $requestData['installments']['terms'] = [
                                'max_time_unit_number' => $installments->terms->max_time_unit_number ?? null,
                                'max_amount' => $installments->terms->max_amount ?? null
                            ];
                        }
                    }
                    
                    // Installments filtering configuration (HPP installments filtering feature)
                    if (
                        !empty($builder->hostedPaymentData->installments) &&
                        $builder->hostedPaymentData->installments instanceof \GlobalPayments\Api\Entities\InstallmentsConfiguration
                    ) {
                        $installments = $builder->hostedPaymentData->installments;
                        $requestData['installment'] = [
                            'funding_mode' => $installments->fundingMode
                        ];

                        // Add terms if available
                        if (!empty($installments->terms)) {
                            $requestData['installment']['terms'] = [];

                            if (!empty($installments->terms->maxTimeUnitNumber)) {
                                $requestData['installment']['terms']['max_time_unit_number'] = $installments->terms->maxTimeUnitNumber;
                            }

                            if (!empty($installments->terms->maxAmount)) {
                                $requestData['installment']['terms']['max_amount'] = $installments->terms->maxAmount;
                            }
                        }
                    }
                }
                break;
            default:
                return '';
        }
        GpApiRequest::$maskedValues = $this->maskedValues;

        return new GpApiRequest($endpoint, $verb, $requestData);
    }

    private function generateVerificationRequest(AuthorizationBuilder $builder, GpApiConfig $config)
    {
        $requestBody = [];
        $requestBody['account_name'] = $config->accessTokenInfo->transactionProcessingAccountName;
        $requestBody['account_id'] = $config->accessTokenInfo->transactionProcessingAccountID;
        $requestBody['channel'] = $config->channel;
        $requestBody['reference'] = !empty($builder->clientTransactionId) ?
            $builder->clientTransactionId : GenerationUtils::getGuid();
        $requestBody['currency'] = $builder->currency;
        $requestBody['country'] = $config->country;
        $requestBody['payment_method'] = $this->createPaymentMethodParam($builder, $config);
        if (!empty($builder->storedCredential)) {
            $this->setRequestStoredCredentials($builder->storedCredential, $requestBody, $builder);
        }

        return $requestBody;
    }

    private function createFromAuthorizationBuilder($builder, GpApiConfig $config)
    {
        /** @var AuthorizationBuilder $builder */
        $captureMode = $this->getCaptureMode($builder);

        $requestBody = [];
        $requestBody['account_name'] = $config->accessTokenInfo->transactionProcessingAccountName;
        $requestBody['account_id'] = $config->accessTokenInfo->transactionProcessingAccountID;
        $requestBody['channel'] = $config->channel;
        $requestBody['country'] = $config->country;
        $requestBody['type'] = ($builder->transactionType == TransactionType::REFUND ?
            PaymentType::REFUND : PaymentType::SALE);
        $requestBody['capture_mode'] = !empty($captureMode) ? $captureMode : CaptureMode::AUTO;
        $requestBody['authorization_mode'] = !empty($builder->allowPartialAuth) ? 'PARTIAL' : null;
        $requestBody['amount'] = StringUtils::toNumeric($builder->amount);
        $requestBody['currency'] = $builder->currency;
        $requestBody['reference'] = !empty($builder->clientTransactionId) ?
            $builder->clientTransactionId : GenerationUtils::getGuid();
        if (
            $builder->paymentMethod instanceof Credit &&
            $builder->paymentMethod->mobileType == EncyptedMobileType::CLICK_TO_PAY
        ) {
            $requestBody['masked'] = $builder->maskedDataResponse === true ? 'YES' : 'NO';
        }
        $requestBody['description'] = $builder->description;
        $requestBody['order'] = ['reference' => $builder->orderId];
        $requestBody['gratuity_amount'] = StringUtils::toNumeric($builder->gratuity);
        $requestBody['surcharge_amount'] = StringUtils::toNumeric($builder->surchargeAmount);
        $requestBody['convenience_amount'] = StringUtils::toNumeric($builder->convenienceAmount);
        $requestBody['cashback_amount'] = StringUtils::toNumeric($builder->cashBackAmount);
        $requestBody['ip_address'] = $builder->customerIpAddress;
        $requestBody['merchant_category'] = $builder->merchantCategory ?? null;
        $requestBody['payer'] = ['id' => $builder->customerId ?: ""];
        $requestBody['payment_method'] = $this->createPaymentMethodParam($builder, $config);
        $requestBody['risk_assessment'] = !empty($builder->fraudFilter) ? [$this->mapFraudManagement()] : null;
        if (!empty($builder->paymentLinkId)) {
            $requestBody['link'] = [
                'id' => $builder->paymentLinkId
            ];
        }

        if (
            $builder->paymentMethod instanceof ECheck ||
            $builder->paymentMethod instanceof AlternativePaymentMethod ||
            $builder->paymentMethod instanceof BNPL
        ) {
            $requestBody['payer'] = $this->setPayerInformation($builder);
        }
        if (
            $builder->paymentMethod instanceof AlternativePaymentMethod ||
            $builder->paymentMethod instanceof BNPL ||
            !empty($builder->supplementaryData)
        ) {
            $this->setOrderInformation($builder, $requestBody);
        }
        if ($builder->paymentMethod instanceof AlternativePaymentMethod ||
            $builder->paymentMethod instanceof BNPL ||
            $builder->paymentMethod instanceof BankPayment
        ) {
            $this->setNotificationUrls($requestBody);
        }
        if (!empty($builder->storedCredential)) {
            $this->setRequestStoredCredentials($builder->storedCredential, $requestBody, $builder, $config);
        }

        if (!empty($builder->dccRateData)) {
            $requestBody['currency_conversion'] = [
                'id' => $builder->dccRateData->dccId
            ];
        }

        if (!empty($builder->installment)) {
            $installmentData = [];
            
            // Basic installment fields
            if (!empty($builder->installment->id)) {
                $installmentData['id'] = $builder->installment->id;
            }
            if (!empty($builder->installment->reference)) {
                $installmentData['reference'] = $builder->installment->reference;
            }
            if (!empty($builder->installment->program)) {
                $installmentData['program'] = $builder->installment->program;
            }
            if (!empty($builder->installment->mode)) {
                $installmentData['mode'] = $builder->installment->mode;
            }
            if (!empty($builder->installment->count)) {
                $installmentData['count'] = $builder->installment->count;
            }
            if (!empty($builder->installment->grace_period_count)) {
                $installmentData['grace_period_count'] = $builder->installment->grace_period_count;
            }
            
            // Visa installment fields
            if (!empty($builder->installment->funding_mode)) {
                $installmentData['funding_mode'] = $builder->installment->funding_mode;
            }
            if (!empty($builder->installment->terms)) {
                $termsData = [];
                if (!empty($builder->installment->terms->time_unit)) {
                    $termsData['time_unit'] = $builder->installment->terms->time_unit;
                }
                if (!empty($builder->installment->terms->language)) {
                    $termsData['language'] = $builder->installment->terms->language;
                }
                if (!empty($builder->installment->terms->version)) {
                    $termsData['version'] = $builder->installment->terms->version;
                }
                if (!empty($builder->installment->terms->max_time_unit_number)) {
                    $termsData['max_time_unit_number'] = $builder->installment->terms->max_time_unit_number;
                }
                if (!empty($builder->installment->terms->max_amount)) {
                    $termsData['max_amount'] = $builder->installment->terms->max_amount;
                }
                if (!empty($termsData)) {
                    $installmentData['terms'] = $termsData;
                }
            }
            if (!empty($builder->installment->eligible_plans)) {
                $installmentData['eligible_plans'] = $builder->installment->eligible_plans;
            }
            
            if (!empty($installmentData)) {
                $requestBody['installment'] = $installmentData;
            }
        }

        return $requestBody;
    }

    private function setRequestStoredCredentials(StoredCredential $storedCredential, &$request, $builder = null, $config = null)
    {
        $request['initiator'] = !empty($storedCredential->initiator) ?
            EnumMapping::mapStoredCredentialInitiator(GatewayProvider::GP_API, $storedCredential->initiator) : null;
        $request['stored_credential'] = [
            'model' => !empty($storedCredential->type) ? strtoupper((string) $storedCredential->type) : null,
            'reason' => !empty($storedCredential->reason) ? strtoupper((string) $storedCredential->reason) : null,
            'sequence' => !empty($storedCredential->sequence) ? strtoupper((string) $storedCredential->sequence) : null
        ];
        
        // Add contract reference if provided in StoredCredential and both currency is MXN and country is MX (Mexico)
         if (
            !empty($storedCredential->contract_reference)
            && (!empty($builder->currency) && strtoupper($builder->currency) === 'MXN')
            && (!empty($config->country) && strtoupper($config->country) === 'MX')
        ) {
            $request['stored_credential']['contract_reference'] = $storedCredential->contract_reference;
        }
    }

    /**
     * Sets the information related to the payer
     *
     * 
     * @param AuthorizationBuilder $builder
     * @return mixed
     */
    private function setPayerInformation($builder)
    {
        $payer['id'] = !empty($builder->customerId) ?
            $builder->customerId : (!empty($builder->customerData) ? $builder->customerData->id : null);
        $payer['reference'] = !empty($builder->customerData) ? $builder->customerData->key : null;
        switch (get_class($builder->paymentMethod)) {
            case AlternativePaymentMethod::class:
                $payer['home_phone'] = [
                    'country_code' => !empty($builder->homePhone) ?
                        StringUtils::validateToNumber($builder->homePhone->countryCode) : null,
                    'subscriber_number' => !empty($builder->homePhone) ?
                        StringUtils::validateToNumber($builder->homePhone->number) : null
                ];
                $payer['work_phone'] = [
                    'country_code' => !empty($builder->workPhone) ?
                        StringUtils::validateToNumber($builder->workPhone->countryCode) : null,
                    'subscriber_number' => !empty($builder->workPhone) ?
                        StringUtils::validateToNumber($builder->workPhone->number) : null,
                ];
                break;
            case ECheck::class:
                $payer['billing_address'] = [
                    'line_1' => $builder->billingAddress->streetAddress1,
                    'line_2' => $builder->billingAddress->streetAddress2,
                    'city' => $builder->billingAddress->city,
                    'postal_code' => $builder->billingAddress->postalCode,
                    'state' => $builder->billingAddress->state,
                    'country' => $builder->billingAddress->countryCode
                ];
                if (!empty($builder->customerData)) {
                    $payerName = $builder->customerData->firstName . ' ' . $builder->customerData->lastName;
                    $payer['name'] = $payerName;
                    $payer['date_of_birth'] = $builder->customerData->dateOfBirth;
                }
                list($phoneNumber, $phoneCountryCode) = $this->getPhoneNumber($builder, PhoneNumberType::HOME);
                $payer['landline_phone'] = $phoneCountryCode . $phoneNumber;;
                list($phoneNumber, $phoneCountryCode) = $this->getPhoneNumber($builder, PhoneNumberType::MOBILE);
                $payer['mobile_phone'] = $phoneCountryCode . $phoneNumber;
                break;
            case BNPL::class:
                if (empty($builder->customerData)) {
                    break;
                }
                $payer['email'] = $builder->customerData->email;
                $payer['date_of_birth'] = $builder->customerData->dateOfBirth;
                if (!empty($builder->billingAddress)) {
                    $payer['billing_address'] = [
                        'line_1' => $builder->billingAddress->streetAddress1,
                        'line_2' => $builder->billingAddress->streetAddress2,
                        'city' => $builder->billingAddress->city,
                        'postal_code' => $builder->billingAddress->postalCode,
                        'state' => $builder->billingAddress->state,
                        'country' => $builder->billingAddress->countryCode,
                        'first_name' => $builder->customerData->firstName ?? '',
                        'last_name' => $builder->customerData->lastName ?? ''
                    ];
                }
                if (isset($builder->customerData->phone)) {
                    $payer['contact_phone'] = [
                        'country_code' => !empty($builder->customerData->phone->countryCode) ?
                            StringUtils::validateToNumber($builder->customerData->phone->countryCode) : null,
                        'subscriber_number' => !empty($builder->customerData->phone->number) ?
                            StringUtils::validateToNumber($builder->customerData->phone->number) : null,
                    ];
                }
                if (!empty($builder->customerData->documents)) {
                    /** @var CustomerDocument $document */
                    foreach ($builder->customerData->documents as $document) {
                        $documents[] = [
                            'type' => $document->type,
                            'reference' => $document->reference,
                            'issuer' => $document->issuer
                        ];
                    }
                    $payer['documents'] = $documents ?? null;
                }
                break;
            default:
                break;
        }

        return $payer;
    }

    /**
     * You can have the phone number set on customerData or directly to the builder
     *
     * @param AuthorizationBuilder $builder
     * @param string $type
     *
     * @return array
     */
    private function getPhoneNumber($builder, $type)
    {
        $phoneKey = strtolower($type) . 'Phone';
        $phoneCountryCode = $phoneNumber = '';
        if (
            isset($builder->customerData) &&
            isset($builder->customerData->{$phoneKey}) &&
            $builder->customerData->{$phoneKey} instanceof PhoneNumber
        ) {
            $phoneCountryCode = $builder->customerData->{$phoneKey}->countryCode;
            $phoneNumber = $builder->customerData->{$phoneKey}->number;
        }
        if (empty($phoneNumber) && isset($builder->{$phoneKey}) && $builder->{$phoneKey} instanceof PhoneNumber) {
            $phoneCountryCode = $builder->{$phoneKey}->countryCode;
            $phoneNumber = $builder->{$phoneKey}->number;
        }

        return [StringUtils::validateToNumber($phoneNumber), StringUtils::validateToNumber($phoneCountryCode)];
    }

    /**
     * @param AuthorizationBuilder $builder
     * @param GpApiConfig $config
     *
     * @return PaymentMethod
     */
    private function createPaymentMethodParam($builder, $config)
    {
        /** @var CreditCardData|CreditTrackData|DebitTrackData|ECheck|AlternativePaymentMethod|BNPL|BankPayment $paymentMethodContainer */
        $paymentMethodContainer = $builder->paymentMethod;
        $paymentMethod = new PaymentMethod();
        $paymentMethod->entry_mode = $this->getEntryMode($builder, $config->channel);
        $paymentMethod->usage_mode = $builder->paymentMethodUsageMode ?? null;
        $paymentMethod->name = $paymentMethodContainer instanceof AlternativePaymentMethod ?
            $paymentMethodContainer->accountHolderName : (!empty($paymentMethodContainer->cardHolderName) ?
                $paymentMethodContainer->cardHolderName : null);
        if (!empty($builder->customerData)) {
            $paymentMethod->first_name = $builder->customerData->firstName ?? null;
            $paymentMethod->last_name = $builder->customerData->lastName ?? null;
        }
        $paymentMethod->narrative = !empty($builder->dynamicDescriptor) ? $builder->dynamicDescriptor : null;
        if ($paymentMethodContainer instanceof IEncryptable && !empty($paymentMethodContainer->encryptionData)) {
            /** @var EncryptionData $encryptionData */
            $encryptionData = $paymentMethodContainer->encryptionData;
            $encryption['version'] = $encryptionData->version ?? null;
            if (!empty($encryptionData->ktb)) {
                $method = 'KBT';
                $info = $encryptionData->ktb;
            } elseif (!empty($encryptionData->ksn)) {
                $method = 'KSN';
                $info = $encryptionData->ksn;
            }
            if (!empty($info)) {
                $encryption['method'] = $method ?? null;
                $encryption['info'] = $info ?? null;
                $paymentMethod->encryption = $encryption;
            }
        }
        switch (get_class($paymentMethodContainer)) {
            case CreditCardData::class:
                $paymentMethod->fingerprint_mode =
                    (!empty($builder->customerData) & !empty($builder->customerData->deviceFingerPrint) ?
                        $builder->customerData->deviceFingerPrint : null);
                $secureEcom = $paymentMethodContainer->threeDSecure;
                if (!empty($secureEcom)) {
                    $paymentMethod->authentication =
                        [
                            'id' => $secureEcom->serverTransactionId,
                            'three_ds' => [
                                'exempt_status' => $secureEcom->exemptStatus,
                                'message_version' => $secureEcom->messageVersion,
                                'eci' => $secureEcom->eci,
                                'server_trans_ref' => $secureEcom->serverTransactionId,
                                'ds_trans_ref' => $secureEcom->directoryServerTransactionId,
                                'value' => $secureEcom->authenticationValue
                            ]
                        ];
                }
                break;
            case ECheck::class:
                $paymentMethod->name = $paymentMethodContainer->checkHolderName;
                $paymentMethod->narrative = $paymentMethodContainer->merchantNotes;
                $paymentMethod->bank_transfer = [
                    'account_number' => $paymentMethodContainer->accountNumber,
                    'account_type' => EnumMapping::mapAccountType(
                        GatewayProvider::GP_API,
                        $paymentMethodContainer->accountType
                    ),
                    'check_reference' => $paymentMethodContainer->checkReference,
                    'sec_code' => $paymentMethodContainer->secCode,
                    'bank' => [
                        'code' => $paymentMethodContainer->routingNumber,
                        'name' => $paymentMethodContainer->bankName,
                        'address' =>
                        [
                            'line_1' => $paymentMethodContainer->bankAddress->streetAddress1 ?? null,
                            'line_2' => $paymentMethodContainer->bankAddress->streetAddress2 ?? null,
                            'line_3' => $paymentMethodContainer->bankAddress->streetAddress3 ?? null,
                            'city' => $paymentMethodContainer->bankAddress->city ?? null,
                            'postal_code' => $paymentMethodContainer->bankAddress->postalCode ?? null,
                            'state' => $paymentMethodContainer->bankAddress->state ?? null,
                            'country' => $paymentMethodContainer->bankAddress->countryCode ?? null
                        ]
                    ]
                ];
                $this->maskedValues = ProtectSensitiveData::hideValues(
                    [
                        'payment_method.bank_transfer.account_number' => $paymentMethodContainer->accountNumber,
                        'payment_method.bank_transfer.bank.code' => $paymentMethodContainer->routingNumber,
                    ],
                    4
                );
                return $paymentMethod;
            case AlternativePaymentMethod::class:
                $paymentMethod->apm = [
                    'provider' => $paymentMethodContainer->alternativePaymentMethodType,
                    'address_override_mode' => !empty($paymentMethodContainer->addressOverrideMode) ?
                        $paymentMethodContainer->addressOverrideMode : null
                ];
                $paymentMethod->bank_transfer = [
                    'bank' => [
                        'name' => $paymentMethodContainer->bank
                    ]
                ];
                return $paymentMethod;
            case BankPayment::class:
                $paymentMethod->apm = [
                    'provider' => PaymentProvider::OPEN_BANKING,
                    'countries' => $paymentMethodContainer->countries ?? [],
                ];
                $bankPaymentType = !empty($paymentMethodContainer->bankPaymentType) ?
                    $paymentMethodContainer->bankPaymentType : OpenBankingProvider::getBankPaymentType($builder->currency);
                $paymentMethod->bank_transfer = [
                    'account_number' => $bankPaymentType == BankPaymentType::FASTERPAYMENTS ?
                        $paymentMethodContainer->accountNumber : '',
                    'iban' => $bankPaymentType == BankPaymentType::SEPA ? $paymentMethodContainer->iban : '',
                    'bank' => [
                        'code' => $paymentMethodContainer->sortCode,
                        'name' => $paymentMethodContainer->accountName
                    ],
                    'remittance_reference' => [
                        'type' => $builder->remittanceReferenceType,
                        'value' => $builder->remittanceReferenceValue
                    ]
                ];
                $this->maskedValues = ProtectSensitiveData::hideValues(
                    [
                        'payment_method.bank_transfer.account_number' => $paymentMethodContainer->accountNumber,
                        'payment_method.bank_transfer.iban' => $paymentMethodContainer->iban
                    ],
                    4
                );
                return $paymentMethod;
            case BNPL::class:
                if (!empty($builder->customerData->firstName) && !empty($builder->customerData->lastName)) {
                    $name = $builder->customerData->firstName . ' ' . $builder->customerData->lastName;
                }
                $paymentMethod->name = $name ?? null;
                $paymentMethod->bnpl = [
                    'provider' => $paymentMethodContainer->bnplType
                ];
                return $paymentMethod;
            default:
                break;
        }

        if (!in_array(
            $builder->transactionModifier,
            [TransactionModifier::ENCRYPTED_MOBILE, TransactionModifier::DECRYPTED_MOBILE]
        )) {
            if ($paymentMethodContainer instanceof ITokenizable && !empty($paymentMethodContainer->token)) {
                $paymentMethod->id = $paymentMethodContainer->token;
                if (!empty($paymentMethodContainer->cvn)) {
                    $paymentMethod->card = new Card();
                    $paymentMethod->card->cvv = $paymentMethodContainer->cvn;
                }
            }

            if (is_null($paymentMethod->id)) {
                $paymentMethod->card = CardUtils::generateCard($builder, GatewayProvider::GP_API, $this->maskedValues);
                if ($paymentMethodContainer instanceof ICardData && !empty($paymentMethodContainer->number)) {
                    $paymentMethod->card->brand = strtoupper((string)$paymentMethodContainer->getCardType());
                }
            }
        } else {
            /* digital wallet */
            switch ($builder->transactionModifier) {
                case TransactionModifier::ENCRYPTED_MOBILE:
                    switch ($paymentMethodContainer->mobileType){
                        case EncyptedMobileType::CLICK_TO_PAY:
                            $paymentToken = ['data' => $paymentMethodContainer->token];
                            break;
                        default:
                            $paymentToken = !empty($paymentMethodContainer->token) ?
                                json_decode(preg_replace('/(\\\)(\w)/', '${1}${1}${2}', $paymentMethodContainer->token)) : null;
                    }
                    $digitalWallet['payment_token'] = $paymentToken;
                    break;
                case TransactionModifier::DECRYPTED_MOBILE:
                    $digitalWallet['token'] = !empty($paymentMethodContainer->token) ?
                        $paymentMethodContainer->token : null;
                    $digitalWallet['token_format'] = DigitalWalletTokenFormat::CARD_NUMBER;
                    $digitalWallet['expiry_month'] = (string) $paymentMethodContainer->expMonth;
                    $digitalWallet['expiry_year'] = !empty($paymentMethodContainer->expYear) ?
                        substr(
                            str_pad($paymentMethodContainer->expYear, 4, '0', STR_PAD_LEFT),
                            2,
                            2
                        ) : null;
                    $digitalWallet['cryptogram'] = $paymentMethodContainer->cryptogram;
                    $digitalWallet['eci'] = !empty($paymentMethodContainer->eci) ?
                        $paymentMethodContainer->eci : $this->getEciCode($paymentMethodContainer);
                    $digitalWallet['avs_address'] =
                        (!empty($builder->billingAddress) ? $builder->billingAddress->streetAddress1 : '');
                    $digitalWallet['avs_postal_code'] =
                        (!empty($builder->billingAddress) ? $builder->billingAddress->postalCode : '');
                    break;
                default:
                    break;
            }
            $digitalWallet['provider'] = EnumMapping::mapDigitalWalletType(
                GatewayProvider::GP_API,
                $paymentMethodContainer->mobileType
            );
            $paymentMethod->digital_wallet = $digitalWallet;
        }
        if (!empty($builder->cardBrandTransactionId)) {
            if (!$paymentMethod->card instanceof Card) {
                $paymentMethod->card = new Card();
            }
            $paymentMethod->card->brand_reference = $builder->cardBrandTransactionId;
        }
        $paymentMethod->storage_mode = $builder->paymentMethodStorageMode
            ?? ($builder->requestMultiUseToken == true ? 'ON_SUCCESS' : null);

        return $paymentMethod;
    }

    /**
     * @param CreditCardData $paymentMethod
     *
     * @return string|null
     */
    private function getEciCode($paymentMethod)
    {
        if (!$paymentMethod instanceof CreditCardData) {
            return null;
        }
        if (!empty($paymentMethod->eci)) {
            return $paymentMethod->eci;
        }
        $eciCode = null;
        switch (CardUtils::getBaseCardType($paymentMethod->getCardType())) {
            case CardType::VISA:
            case CardType::AMEX:
                $eciCode = '05';
                break;
            case CardType::MASTERCARD:
                $eciCode = '02';
                break;
            default:
                break;
        }

        return $eciCode;
    }

    /**
     * @param AuthorizationBuilder $builder
     * @param string $channel
     * @return string
     */
    private function getEntryMode(AuthorizationBuilder $builder, $channel)
    {
        if ($channel == Channel::CardPresent) {
            if ($builder->paymentMethod instanceof ITrackData) {
                if (!empty($builder->tagData)) {
                    if ($builder->paymentMethod->entryMethod == EntryMethod::PROXIMITY) {
                        return PaymentEntryMode::CONTACTLESS_CHIP;
                    }
                    $emvData = EmvUtils::parseTagData($builder->tagData);
                    if ($emvData->isContactlessMsd()) {
                        return  PaymentEntryMode::CONTACTLESS_SWIPE;
                    }

                    return PaymentEntryMode::CHIP;
                }
                if ($builder->paymentMethod->entryMethod == PaymentEntryMode::SWIPE) {
                    return PaymentEntryMode::SWIPE;
                }
            }
            if ($builder->paymentMethod instanceof ICardData && $builder->paymentMethod->cardPresent) {
                return PaymentEntryMode::MANUAL;
            }

            return PaymentEntryMode::SWIPE;
        } elseif ($channel == Channel::CardNotPresent) {
            if ($builder->paymentMethod instanceof ICardData) {
                if ($builder->paymentMethod->readerPresent === true) {
                    return PaymentEntryMode::ECOM;
                }
                if (
                    $builder->paymentMethod->readerPresent === false &&
                    !is_null($builder->paymentMethod->entryMethod)
                ) {
                    switch ($builder->paymentMethod->entryMethod) {
                        case ManualEntryMethod::PHONE:
                            return PaymentEntryMode::PHONE;
                        case ManualEntryMethod::MOTO:
                            return PaymentEntryMode::MOTO;
                        case ManualEntryMethod::MAIL:
                            return PaymentEntryMode::MAIL;
                        default:
                            break;
                    }
                }
                if (
                    $builder->transactionModifier == TransactionModifier::ENCRYPTED_MOBILE &&
                    $builder->paymentMethod instanceof CreditCardData &&
                    $builder->paymentMethod->hasInAppPaymentData()
                ) {
                    return PaymentEntryMode::IN_APP;
                }
            }

            return PaymentEntryMode::ECOM;
        }

        throw new ApiException("Please configure the channel!");
    }

    private function getCaptureMode(AuthorizationBuilder $builder)
    {
        if ($builder->multiCapture) {
            return CaptureMode::MULTIPLE;
        }
        if ($builder->transactionType == TransactionType::AUTH) {
            return CaptureMode::LATER;
        }

        return CaptureMode::AUTO;
    }

    private function setNotificationUrls(&$requestBody)
    {
        $requestBody['notifications'] = [
            'return_url' => $this->builder->paymentMethod->returnUrl ?? null,
            'status_url' => $this->builder->paymentMethod->statusUpdateUrl ?? null,
            'cancel_url' => $this->builder->paymentMethod->cancelUrl ?? null
        ];
    }

    private function setOrderInformation($builder, &$requestBody)
    {
        $order['description'] = !empty($builder->orderDetails) ?
            $builder->orderDetails->description : null;
        if (!empty($builder->shippingAddress)) {
            $order['shipping_address'] = [
                'line_1' => $builder->shippingAddress->streetAddress1,
                'line_2' => $builder->shippingAddress->streetAddress2,
                'line_3' => $builder->shippingAddress->streetAddress3,
                'city' => $builder->shippingAddress->city,
                'postal_code' => $builder->shippingAddress->postalCode,
                'state' => $builder->shippingAddress->state,
                'country' => $builder->shippingAddress->countryCode
            ];
        }
        list($phoneNumber, $phoneCountryCode) = $this->getPhoneNumber($builder, PhoneNumberType::SHIPPING);
        $order['shipping_phone'] = [
            'country_code' => $phoneCountryCode,
            'subscriber_number' => $phoneNumber
        ];

        switch (get_class($builder->paymentMethod)) {
            case AlternativePaymentMethod::class:
                if (!empty($builder->productData)) {
                    $this->setItemDetailsListForApm($builder, $order);
                }
                break;
            case BNPL::class:
                $order['shipping_method'] = $builder->bnplShippingMethod;
                if (!empty($builder->productData)) {
                    $this->setItemDetailsListForBNPL($builder, $order);
                }
                break;
        }

        if (!empty($builder->supplementaryData) && is_array($builder->supplementaryData)) {
            foreach ($builder->supplementaryData as $type => $fields) {
                if ($type !== '' && !empty($fields)) {
                    $order['supplementary_data'][] = [
                        'type' => $type,
                        'fields' => (array) $fields
                    ];
                }
            }
        }

        if (!empty($order['amount'])) {
            $requestBody['amount'] = $order['amount'];
        }

        if (!empty($requestBody['order'])) {
            $order = array_merge($requestBody['order'], $order);
        }
        $requestBody['order'] = $order;

        return $requestBody;
    }

    private function setItemDetailsListForBNPL($builder, &$order)
    {
        /** @var Product $product */
        foreach ($builder->productData as $product) {
            $qta = !empty($product->quantity) ? (int) $product->quantity : 0;
            $unitAmount = !empty($product->unitPrice) ? StringUtils::toNumeric($product->unitPrice) : 0;
            $taxAmount = !empty($product->taxAmount) ? StringUtils::toNumeric($product->taxAmount) : 0;
            $netUnitAmount = !empty($product->netUnitPrice) ? StringUtils::toNumeric($product->netUnitPrice) : 0;
            $discountAmount = !empty($product->discountAmount) ? StringUtils::toNumeric($product->discountAmount) : 0;
            $items[] = [
                'reference' => !empty($product->productId) ? $product->productId : null,
                'label' => !empty($product->productName) ? $product->productName : null,
                'description' => !empty($product->description) ? $product->description : null,
                'quantity' => (string) $qta,
                'unit_amount' => (string) $unitAmount,
                'total_amount' => (string) ($qta * $unitAmount),
                'tax_amount' => (string)$taxAmount,
                'discount_amount' => (string)$discountAmount,
                'tax_percentage' => !empty($product->taxPercentage) ? StringUtils::toNumeric($product->taxPercentage) : "0",
                'net_unit_amount' => (string) $netUnitAmount,
                'url' => !empty($product->url) ? $product->url : null,
                'image_url' => !empty($product->imageUrl) ? $product->imageUrl : null,
            ];
        }
        if (isset($builder->customerData)) {
            $order['shipping_address']['first_name'] = $builder->customerData->firstName;
            $order['shipping_address']['last_name'] = $builder->customerData->lastName;
        }
        $order['items'] = $items ?? null;
    }

    private function setItemDetailsListForApm($builder, &$order)
    {
        $taxTotalAmount = $itemsAmount = 0;
        foreach ($builder->productData as $product) {
            $qta = !empty($product['quantity']) ? $product['quantity'] : 0;
            $taxAmount = !empty($product['tax_amount']) ? StringUtils::toNumeric($product['tax_amount']) : 0;
            $unitAmount = !empty($product['unit_amount']) ? StringUtils::toNumeric($product['unit_amount']) : 0;
            $items[] = [
                'reference' => !empty($product['reference']) ? $product['reference'] : null,
                'label' => !empty($product['label']) ? $product['label'] : null,
                'description' => !empty($product['description']) ? $product['description'] : null,
                'quantity' => $qta,
                'unit_amount' => $unitAmount,
                'unit_currency' => !empty($product['unit_currency']) ? $product['unit_currency'] : null,
                'tax_amount' => $taxAmount,
                'amount' => $qta * $unitAmount
            ];
            if (!empty($product['tax_amount'])) {
                $taxTotalAmount += $taxAmount;
            }
            if (!empty($product['unit_amount'])) {
                $itemsAmount += $unitAmount;
            }
        }

        $order['tax_amount'] = $taxTotalAmount;
        $order['item_amount'] = $itemsAmount;
        $order['shipping_amount'] = !empty($builder->shippingAmount) ?
            StringUtils::toNumeric($builder->shippingAmount) : 0;
        $order['insurance_offered'] = !empty($builder->orderDetails) && !is_null($builder->orderDetails->hasInsurance) ?
            ($builder->orderDetails->hasInsurance === true ? 'YES' : 'NO') : null;
        $order['shipping_discount'] = !empty($builder->shippingDiscount) ?
            StringUtils::toNumeric($builder->shippingDiscount) : 0;
        $order['insurance_amount'] = !empty($builder->orderDetails->insuranceAmount) ?
            StringUtils::toNumeric($builder->orderDetails->insuranceAmount) : 0;
        $order['handling_amount'] = !empty($builder->orderDetails->handlingAmount) ?
            StringUtils::toNumeric($builder->orderDetails->handlingAmount) : 0;
        $orderAmount = $itemsAmount + $taxTotalAmount + $order['handling_amount'] + $order['insurance_amount'] + $order['shipping_amount'];
        $order['amount'] = $orderAmount;
        $order['currency'] = $builder->currency;
        $order['items'] = $items ?? null;
    }

    public function mapFraudManagement()
    {
        if (!empty($this->builder->fraudRules)) {
            foreach ($this->builder->fraudRules as $fraudRule) {
                $rules[] = [
                    'reference' => $fraudRule->key,
                    'mode' => $fraudRule->mode
                ];
            }
        }

        return [
            'mode' => $this->builder->fraudFilter,
            'rules' => $rules ?? null
        ];
    }

    public function buildRequestFromJson(mixed $jsonRequest, mixed $config): mixed
    {
        throw new \GlobalPayments\Api\Entities\Exceptions\NotImplementedException();
    }
}


