diff --git a/CHANGELOG.md b/CHANGELOG.md index 238ff4f8..6cd2c6ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ ## Latest version #### Enhancements: +- GP-API: Add BNPL feature +- GP-API: Click-to-Pay + +## v6.0.3 (12/06/2022) +#### Enhancements: - PAX: Adding tip after the Sale - Portico: APPLE PAY / GOOGLE PAY fix for token format @@ -19,7 +24,7 @@ - GP-API/GP-ECOM: Fix end-to-end examples - Portico: APPLE PAY / GOOGLE PAY fix -## v6.0.0 (11/03/2022) +## v6.0.1 (11/03/2022) #### Enhancements: - Security vulnerabilities fixes diff --git a/metadata.xml b/metadata.xml index 6744f7d4..4c5bac56 100644 --- a/metadata.xml +++ b/metadata.xml @@ -1,3 +1,3 @@ - 6.0.3 + 6.0.4 \ No newline at end of file diff --git a/src/Builders/AuthorizationBuilder.php b/src/Builders/AuthorizationBuilder.php index edf512d9..4f77b2c5 100644 --- a/src/Builders/AuthorizationBuilder.php +++ b/src/Builders/AuthorizationBuilder.php @@ -5,12 +5,14 @@ use GlobalPayments\Api\Entities\Address; use GlobalPayments\Api\Entities\AutoSubstantiation; use GlobalPayments\Api\Entities\EcommerceInfo; +use GlobalPayments\Api\Entities\Enums\BNPLShippingMethod; use GlobalPayments\Api\Entities\Enums\EmvFallbackCondition; use GlobalPayments\Api\Entities\Enums\EmvLastChipRead; use GlobalPayments\Api\Entities\Enums\FraudFilterMode; use GlobalPayments\Api\Entities\Enums\PaymentMethodUsageMode; use GlobalPayments\Api\Entities\Enums\PhoneNumberType; use GlobalPayments\Api\Entities\Enums\RemittanceReferenceType; +use GlobalPayments\Api\Entities\Exceptions\ArgumentException; use GlobalPayments\Api\Entities\FraudRuleCollection; use GlobalPayments\Api\Entities\HostedPaymentData; use GlobalPayments\Api\Entities\Enums\AddressType; @@ -23,6 +25,7 @@ use GlobalPayments\Api\Entities\PhoneNumber; use GlobalPayments\Api\Entities\StoredCredential; use GlobalPayments\Api\Entities\Transaction; +use GlobalPayments\Api\PaymentMethods\BNPL; use GlobalPayments\Api\PaymentMethods\EBTCardData; use GlobalPayments\Api\PaymentMethods\GiftCard; use GlobalPayments\Api\PaymentMethods\Interfaces\IPaymentMethod; @@ -505,6 +508,9 @@ class AuthorizationBuilder extends TransactionBuilder /** @var string */ public $remittanceReferenceValue; + /** @var BNPLShippingMethod */ + public $bnplShippingMethod; + /** * {@inheritdoc} * @@ -1415,4 +1421,19 @@ public function withRemittanceReference($remittanceReferenceType, $remittanceRef return $this; } + + /** + * @param BNPLShippingMethod $bnpShippingMethod + * + * @return $this + * @throws ArgumentException + */ + public function withBNPLShippingMethod($bnpShippingMethod) + { + if (!$this->paymentMethod instanceof BNPL) { + throw new ArgumentException("The selected payment method doesn't support this property!"); + } + $this->bnplShippingMethod = $bnpShippingMethod; + return $this; + } } diff --git a/src/Builders/RequestBuilder/GpApi/GpApiAuthorizationRequestBuilder.php b/src/Builders/RequestBuilder/GpApi/GpApiAuthorizationRequestBuilder.php index 337c5d2a..e0f85757 100644 --- a/src/Builders/RequestBuilder/GpApi/GpApiAuthorizationRequestBuilder.php +++ b/src/Builders/RequestBuilder/GpApi/GpApiAuthorizationRequestBuilder.php @@ -4,6 +4,7 @@ use GlobalPayments\Api\Builders\AuthorizationBuilder; use GlobalPayments\Api\Builders\BaseBuilder; +use GlobalPayments\Api\Entities\CustomerDocument; use GlobalPayments\Api\Entities\EncryptionData; use GlobalPayments\Api\Entities\Enums\CardType; use GlobalPayments\Api\Entities\Enums\Channel; @@ -25,7 +26,9 @@ use GlobalPayments\Api\Entities\IRequestBuilder; use GlobalPayments\Api\Entities\PayLinkData; use GlobalPayments\Api\Entities\PhoneNumber; +use GlobalPayments\Api\Entities\Product; use GlobalPayments\Api\Mapping\EnumMapping; +use GlobalPayments\Api\PaymentMethods\BNPL; use GlobalPayments\Api\PaymentMethods\CreditCardData; use GlobalPayments\Api\PaymentMethods\CreditTrackData; use GlobalPayments\Api\PaymentMethods\DebitTrackData; @@ -205,11 +208,12 @@ private function createFromAuthorizationBuilder($builder, GpApiConfig $config) if ( $builder->paymentMethod instanceof ECheck || - $builder->paymentMethod instanceof AlternativePaymentMethod + $builder->paymentMethod instanceof AlternativePaymentMethod || + $builder->paymentMethod instanceof BNPL ) { $requestBody['payer'] = $this->setPayerInformation($builder); } - if ($builder->paymentMethod instanceof AlternativePaymentMethod) { + if ($builder->paymentMethod instanceof AlternativePaymentMethod || $builder->paymentMethod instanceof BNPL) { $this->setOrderInformation($builder, $requestBody); $requestBody['notifications'] = [ @@ -280,6 +284,44 @@ private function setPayerInformation($builder) 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; } @@ -323,7 +365,7 @@ private function getPhoneNumber($builder, $type) */ private function createPaymentMethodParam($builder, $config) { - /** @var CreditCardData|CreditTrackData|DebitTrackData|ECheck|AlternativePaymentMethod $paymentMethodContainer */ + /** @var CreditCardData|CreditTrackData|DebitTrackData|ECheck|AlternativePaymentMethod|BNPL $paymentMethodContainer */ $paymentMethodContainer = $builder->paymentMethod; $paymentMethod = new PaymentMethod(); $paymentMethod->entry_mode = $this->getEntryMode($builder, $config->channel); @@ -397,6 +439,15 @@ private function createPaymentMethodParam($builder, $config) $paymentMethodContainer->addressOverrideMode : null ]; + 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; @@ -562,9 +613,9 @@ private function setOrderInformation($builder, &$requestBody) $builder->orderDetails->description : null; if (!empty($builder->shippingAddress)) { $order['shipping_address'] = [ - 'line1' => $builder->shippingAddress->streetAddress1, - 'line2' => $builder->shippingAddress->streetAddress2, - 'line3' => $builder->shippingAddress->streetAddress3, + '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, @@ -577,47 +628,19 @@ private function setOrderInformation($builder, &$requestBody) 'subscriber_number' => $phoneNumber ]; - if (!empty($builder->productData)) { - $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; + switch (get_class($builder->paymentMethod)) { + case AlternativePaymentMethod::class: + if (!empty($builder->productData)) { + $this->setItemDetailsListForApm($builder, $order); } - if (!empty($product['unit_amount'])) { - $itemsAmount += $unitAmount; + break; + case BNPL::class: + $order['shipping_method'] = $builder->bnplShippingMethod; + if (!empty($builder->productData)) { + $this->setItemDetailsListForBNPL($builder, $order); } - } - - $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; + break; } - $order['items'] = !empty($items) ? $items : null; if (!empty($orderAmount)) { $requestBody['amount'] = $orderAmount; @@ -630,6 +653,80 @@ private function setOrderInformation($builder, &$requestBody) 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)) { diff --git a/src/Entities/BNPLResponse.php b/src/Entities/BNPLResponse.php new file mode 100644 index 00000000..53bc0049 --- /dev/null +++ b/src/Entities/BNPLResponse.php @@ -0,0 +1,19 @@ + */ + public $documents = []; + /** * Adds a payment method to the customer * diff --git a/src/Entities/CustomerDocument.php b/src/Entities/CustomerDocument.php new file mode 100644 index 00000000..120ae6be --- /dev/null +++ b/src/Entities/CustomerDocument.php @@ -0,0 +1,24 @@ +reference = $reference; + $this->issuer = $issuer; + $this->type = $type; + } +} \ No newline at end of file diff --git a/src/Entities/Enums/BNPLShippingMethod.php b/src/Entities/Enums/BNPLShippingMethod.php new file mode 100644 index 00000000..31e2e185 --- /dev/null +++ b/src/Entities/Enums/BNPLShippingMethod.php @@ -0,0 +1,12 @@ +transactionReference->alternativePaymentResponse; } return null; + case 'bnplResponse': + if ($this->transactionReference !== null) { + return $this->transactionReference->bnplResponse; + } + return null; default: break; } @@ -557,7 +563,8 @@ public function __isset($name) 'authorizationId', 'paymentMethodType', 'clientTransactionId', - 'alternativePaymentResponse' + 'alternativePaymentResponse', + 'bnplResponse' ]) || isset($this->{$name}); } @@ -600,6 +607,12 @@ public function __set($name, $value) } $this->transactionReference->alternativePaymentResponse = $value; return; + case 'bnplResponse': + if (!$this->transactionReference instanceof TransactionReference) { + $this->transactionReference = new TransactionReference(); + } + $this->transactionReference->bnplResponse = $value; + return; default: break; } diff --git a/src/Gateways/Gateway.php b/src/Gateways/Gateway.php index 7136a36c..400aa859 100644 --- a/src/Gateways/Gateway.php +++ b/src/Gateways/Gateway.php @@ -4,7 +4,6 @@ use GlobalPayments\Api\Entities\IRequestLogger; use GlobalPayments\Api\Entities\IWebProxy; -use GlobalPayments\Api\Utils\Logging\Logger; abstract class Gateway { diff --git a/src/Gateways/RestGateway.php b/src/Gateways/RestGateway.php index 8aff4e4f..1a68c077 100644 --- a/src/Gateways/RestGateway.php +++ b/src/Gateways/RestGateway.php @@ -64,7 +64,7 @@ protected function doTransaction( (!empty($error->detailed_error_code) ? $error->detailed_error_code : null) ); if ($this->requestLogger) { - $this->requestLogger->responseError($gatewayException); + $this->requestLogger->responseError($gatewayException, $response->header); } throw $gatewayException; } else { diff --git a/src/Mapping/EnumMapping.php b/src/Mapping/EnumMapping.php index 6c23a512..b09109cc 100644 --- a/src/Mapping/EnumMapping.php +++ b/src/Mapping/EnumMapping.php @@ -12,6 +12,7 @@ use GlobalPayments\Api\Entities\Enums\ScheduleFrequency; use GlobalPayments\Api\Entities\Enums\MessageCategory; use GlobalPayments\Api\Entities\Enums\SdkUiType; +use GlobalPayments\Api\Entities\Enums\ShippingMethod; use GlobalPayments\Api\Entities\Enums\StoredCredentialInitiator; class EnumMapping @@ -52,6 +53,8 @@ public static function mapDigitalWalletType($gateway, $type) return 'APPLEPAY'; case EncyptedMobileType::GOOGLE_PAY: return 'PAY_BY_GOOGLE'; + case EncyptedMobileType::CLICK_TO_PAY: + return 'CLICK_TO_PAY'; default: return null; } diff --git a/src/Mapping/GpApiMapping.php b/src/Mapping/GpApiMapping.php index 2eff5691..524227b8 100644 --- a/src/Mapping/GpApiMapping.php +++ b/src/Mapping/GpApiMapping.php @@ -5,6 +5,7 @@ use GlobalPayments\Api\Entities\Address; use GlobalPayments\Api\Entities\AlternativePaymentResponse; use GlobalPayments\Api\Entities\BatchSummary; +use GlobalPayments\Api\Entities\BNPLResponse; use GlobalPayments\Api\Entities\CardIssuerResponse; use GlobalPayments\Api\Entities\DisputeDocument; use GlobalPayments\Api\Entities\DccRateData; @@ -50,11 +51,13 @@ class GpApiMapping const DCC_RESPONSE = 'RATE_LOOKUP'; const LINK_CREATE = "LINK_CREATE"; const LINK_EDIT = "LINK_EDIT"; + const TRN_INITIATE = "INITIATE"; const MERCHANT_CREATE = 'MERCHANT_CREATE'; const MERCHANT_LIST = 'MERCHANT_LIST'; const MERCHANT_SINGLE = 'MERCHANT_SINGLE'; const MERCHANT_EDIT = 'MERCHANT_EDIT'; const MERCHANT_EDIT_INITIATED = 'MERCHANT_EDIT_INITIATED'; + /** * Map a response to a Transaction object for further chaining * @@ -85,9 +88,7 @@ public static function mapResponse($response) } $transaction->transactionId = $response->id; - $transaction->balanceAmount = !empty($response->amount) ? StringUtils::toAmount($response->amount) : null; - $transaction->authorizedAmount = ($response->status == TransactionStatus::PREAUTHORIZED && !empty($response->amount)) ? - StringUtils::toAmount($response->amount) : null; + $transaction->clientTransactionId = !empty($response->reference) ? $response->reference : null; $transaction->timestamp = !empty($response->time_created) ? $response->time_created : ''; $transaction->referenceNumber = !empty($response->reference) ? $response->reference : null; $batchSummary = new BatchSummary(); @@ -95,14 +96,20 @@ public static function mapResponse($response) $batchSummary->totalAmount = !empty($response->amount) ? $response->amount : null; $batchSummary->transactionCount = !empty($response->transaction_count) ? $response->transaction_count : null; $transaction->batchSummary = $batchSummary; + $transaction->balanceAmount = !empty($response->amount) ? StringUtils::toAmount($response->amount) : null; + $transaction->authorizedAmount = ($response->status == TransactionStatus::PREAUTHORIZED && !empty($response->amount)) ? + StringUtils::toAmount($response->amount) : null; + $transaction->multiCapture = (!empty($response->capture_mode) && $response->capture_mode == CaptureMode::MULTIPLE); + $transaction->fingerprint = !empty($response->fingerprint) ? $response->fingerprint : null; + $transaction->fingerprintIndicator = !empty($response->fingerprint_presence_indicator) ? + $response->fingerprint_presence_indicator : null; + if (isset($response->payment_method->bnpl)) { + return self::mapBNPLResponse($response, $transaction); + } $transaction->token = substr($response->id, 0, 4) === PaymentMethod::PAYMENT_METHOD_TOKEN_PREFIX ? $response->id : null; $transaction->tokenUsageMode = !empty($response->usage_mode) ? $response->usage_mode : null; - $transaction->clientTransactionId = !empty($response->reference) ? $response->reference : null; - $transaction->fingerprint = !empty($response->fingerprint) ? $response->fingerprint : null; - $transaction->fingerprintIndicator = !empty($response->fingerprint_presence_indicator) ? - $response->fingerprint_presence_indicator : null; if (!empty($response->payment_method)) { $transaction->authorizationCode = $response->payment_method->result; if (!empty($response->payment_method->id)) { @@ -155,6 +162,7 @@ public static function mapResponse($response) $transaction->multiCapture = (!empty($response->capture_mode) && $response->capture_mode == CaptureMode::MULTIPLE); $transaction->fraudFilterResponse = !empty($response->risk_assessment) ? self::mapFraudManagement(reset($response->risk_assessment)) : null; + return $transaction; } @@ -333,6 +341,7 @@ public static function mapTransactionSummary($response) $summary->depositTimeCreated = !empty($response->deposit_time_created) ? new \DateTime($response->deposit_time_created) : ''; $summary->batchCloseDate = !empty($response->batch_time_created) ? new \DateTime($response->batch_time_created) : ''; + $summary->orderId = $response->order_reference ?? null; if (isset($response->system)) { $system = $response->system; $summary->merchantId = $system->mid; @@ -371,6 +380,12 @@ public static function mapTransactionSummary($response) $alternativePaymentResponse->providerReference = !empty($apm->provider_reference) ? $apm->provider_reference : null; $summary->alternativePaymentResponse = $alternativePaymentResponse; $summary->paymentType = PaymentMethodName::APM; + } elseif (isset($response->payment_method->bnpl)) { + $bnpl = $response->payment_method->bnpl; + $bnplResponse = new BNPLResponse(); + $bnplResponse->providerName = $bnpl->provider ?? null; + $summary->bnplResponse = $bnplResponse; + $summary->paymentType = PaymentMethodName::BNPL; } if (!empty($card)) { @@ -827,6 +842,28 @@ private static function createTransactionSummary($response) $transaction->amount = StringUtils::toAmount($response->amount); $transaction->currency = $response->currency; $transaction->referenceNumber = $transaction->clientTransactionId = $response->reference; + $transaction->description = $response->description ?? null; + $transaction->fingerprint = $response->payment_method->fingerprint ?? null; + $transaction->fingerprintIndicator = $response->payment_method->fingerprint_presence_indicator ?? null; + + return $transaction; + } + + /** + * @param $response + * @param Transaction $transaction + * + * @return Transaction + */ + private static function mapBNPLResponse($response,Transaction $transaction) + { + $transaction->paymentMethodType = PaymentMethodType::BNPL; + $bnplResponse = new BNPLResponse(); + $bnplResponse->redirectUrl = !empty($response->payment_method->redirect_url) ? + $response->payment_method->redirect_url : null; + $bnplResponse->providerName = !empty($response->payment_method->bnpl->provider) ? + $response->payment_method->bnpl->provider : null; + $transaction->bnplResponse = $bnplResponse; return $transaction; } diff --git a/src/PaymentMethods/BNPL.php b/src/PaymentMethods/BNPL.php new file mode 100644 index 00000000..136ec889 --- /dev/null +++ b/src/PaymentMethods/BNPL.php @@ -0,0 +1,66 @@ +bnplType = $bnplType; + } + + /** + * Authorizes the payment method + * + * @param string|float $amount Amount to authorize + * + * @return AuthorizationBuilder + */ + public function authorize($amount = null) + { + return (new AuthorizationBuilder(TransactionType::AUTH, $this)) + ->withModifier(TransactionModifier::BAY_NOW_PAY_LATER) + ->withAmount($amount); + } +} \ No newline at end of file diff --git a/src/PaymentMethods/TransactionReference.php b/src/PaymentMethods/TransactionReference.php index 63b650ed..d32aedec 100644 --- a/src/PaymentMethods/TransactionReference.php +++ b/src/PaymentMethods/TransactionReference.php @@ -3,6 +3,7 @@ namespace GlobalPayments\Api\PaymentMethods; use GlobalPayments\Api\Entities\AlternativePaymentResponse; +use GlobalPayments\Api\Entities\BNPLResponse; use GlobalPayments\Api\Entities\Enums\PaymentMethodType; use GlobalPayments\Api\PaymentMethods\Interfaces\IPaymentMethod; @@ -42,4 +43,7 @@ class TransactionReference implements IPaymentMethod /** @var AlternativePaymentResponse $alternativePaymentResponse */ public $alternativePaymentResponse; + + /** @var BNPLResponse $bnplResponse */ + public $bnplResponse; } diff --git a/src/Utils/Logging/SampleRequestLogger.php b/src/Utils/Logging/SampleRequestLogger.php index c602a527..3b26bb57 100644 --- a/src/Utils/Logging/SampleRequestLogger.php +++ b/src/Utils/Logging/SampleRequestLogger.php @@ -41,15 +41,14 @@ public function responseReceived(GatewayResponse $response) $this->logger->info("============================================="); } - public function responseError(\Exception $e) + public function responseError(\Exception $e, $headers = '') { $this->logger->info("Exception START"); + $this->logger->info("Response headers: ", [$headers]); $this->logger->info("Error occurred while communicating with the gateway"); $this->logger->info("Exception type: " . get_class($e)); $this->logger->info("Exception message: " . $e->getMessage()); $this->logger->info("Exception END"); $this->logger->info("============================================="); } - - } \ No newline at end of file diff --git a/test/Integration/Gateways/GpApiConnector/CreditCardNotPresentTest.php b/test/Integration/Gateways/GpApiConnector/CreditCardNotPresentTest.php index dc9237c9..00fd7606 100644 --- a/test/Integration/Gateways/GpApiConnector/CreditCardNotPresentTest.php +++ b/test/Integration/Gateways/GpApiConnector/CreditCardNotPresentTest.php @@ -6,7 +6,6 @@ use GlobalPayments\Api\Entities\Address; use GlobalPayments\Api\Entities\Customer; use GlobalPayments\Api\Entities\Enums\Channel; -use GlobalPayments\Api\Entities\Enums\Environment; use GlobalPayments\Api\Entities\Enums\ManualEntryMethod; use GlobalPayments\Api\Entities\Enums\PaymentMethodUsageMode; use GlobalPayments\Api\Entities\Enums\SortDirection; diff --git a/test/Integration/Gateways/GpApiConnector/GpApi3DSecureTest.php b/test/Integration/Gateways/GpApiConnector/GpApi3DSecureTest.php index 9f3322a3..471722cf 100644 --- a/test/Integration/Gateways/GpApiConnector/GpApi3DSecureTest.php +++ b/test/Integration/Gateways/GpApiConnector/GpApi3DSecureTest.php @@ -686,12 +686,13 @@ public function testCreditSaleTokenized_WithStoredCredentials_Recurring() $recurringPayment = $tokenizedCard->charge($this->amount) ->withCurrency($this->currency) ->withStoredCredential($storeCredentials) - ->withCardBrandStorage($response->cardBrandTransactionId) + ->withCardBrandStorage(StoredCredentialInitiator::MERCHANT, $response->cardBrandTransactionId) ->execute(); $this->assertNotNull($recurringPayment); $this->assertEquals('SUCCESS', $recurringPayment->responseCode); $this->assertEquals(TransactionStatus::CAPTURED, $recurringPayment->responseMessage); + $this->assertNotNull($response->cardBrandTransactionId); } /** @@ -841,7 +842,7 @@ public function testDecoupledAuth() $secureEcom = Secure3dService::checkEnrollment($tokenizedCard) ->withCurrency($this->currency) ->withAmount($this->amount) - ->withDecoupledFlowRequest('https://www.example.com/decoupledNotification') + ->withDecoupledNotificationUrl('https://www.example.com/decoupledNotification') ->execute(); $this->assertNotNull($secureEcom); @@ -859,7 +860,7 @@ public function testDecoupledAuth() ->withBrowserData($this->browserData) ->withDecoupledFlowRequest(true) ->withDecoupledFlowTimeout('9001') - ->withDecoupledFlowRequest('https://www.example.com/decoupledNotification') + ->withDecoupledNotificationUrl('https://www.example.com/decoupledNotification') ->execute(); $this->assertNotNull($initAuth); diff --git a/test/Integration/Gateways/GpApiConnector/GpApiBNPLTest.php b/test/Integration/Gateways/GpApiConnector/GpApiBNPLTest.php new file mode 100644 index 00000000..bfc18ec0 --- /dev/null +++ b/test/Integration/Gateways/GpApiConnector/GpApiBNPLTest.php @@ -0,0 +1,838 @@ +setUpConfig()); + + $this->paymentMethod = new BNPL(BNPLType::AFFIRM); + + $this->paymentMethod->returnUrl = 'https://7b8e82a17ac00346e91e984f42a2a5fb.m.pipedream.net'; + $this->paymentMethod->statusUpdateUrl = 'https://7b8e82a17ac00346e91e984f42a2a5fb.m.pipedream.net'; + $this->paymentMethod->cancelUrl = 'https://7b8e82a17ac00346e91e984f42a2a5fb.m.pipedream.net'; + + $this->currency = 'USD'; + + // billing address + $this->billingAddress = new Address(); + $this->billingAddress->streetAddress1 = '10 Glenlake Pkwy NE'; + $this->billingAddress->streetAddress2 = 'no'; + $this->billingAddress->city = 'Birmingham'; + $this->billingAddress->postalCode = '50001'; + $this->billingAddress->countryCode = 'US'; + $this->billingAddress->state = 'IL'; + + // shipping address + $this->shippingAddress = new Address(); + $this->shippingAddress->streetAddress1 = 'Apartment 852'; + $this->shippingAddress->streetAddress2 = 'Complex 741'; + $this->shippingAddress->streetAddress3 = 'no'; + $this->shippingAddress->city = 'Birmingham'; + $this->shippingAddress->postalCode = '50001'; + $this->shippingAddress->state = 'IL'; + $this->shippingAddress->countryCode = 'US'; + } + + public function setUpConfig() + { + $config = new GpApiConfig(); + $config->appId = 'uAGII1ChGyRk1CqzJBsOOGBTrDMMYjAp'; + $config->appKey = 'hgLnF6Fh7BIt3TDw'; + $config->environment = Environment::TEST; + $config->channel = Channel::CardNotPresent; + $config->requestLogger = new SampleRequestLogger(new Logger("logs")); + + return $config; + } + + public function testBNPL_FullCycle() + { + $customer = $this->setCustomerData(); + $products = $this->setProductList(); + + $transaction = $this->paymentMethod->authorize(550) + ->withCurrency($this->currency) + ->withProductData($products) + ->withAddress($this->shippingAddress, AddressType::SHIPPING) + ->withAddress($this->billingAddress, AddressType::BILLING) + ->withPhoneNumber('41', '57774873', PhoneNumberType::SHIPPING) + ->withCustomerData($customer) + ->withBNPLShippingMethod(BNPLShippingMethod::DELIVERY) + ->withOrderId('12365') + ->execute(); + + $this->assertNotNull($transaction); + $this->assertEquals('SUCCESS', $transaction->responseCode); + $this->assertEquals(TransactionStatus::INITIATED, $transaction->responseMessage); + $this->assertNotNull($transaction->bnplResponse->redirectUrl); + + fwrite(STDERR, print_r($transaction->bnplResponse->redirectUrl, TRUE)); + + sleep(45); + + $captureTrn = $transaction->capture()->execute(); + + $this->assertNotNull($captureTrn); + $this->assertEquals('SUCCESS', $captureTrn->responseCode); + $this->assertEquals(TransactionStatus::CAPTURED, $captureTrn->responseMessage); + + sleep(15); + + $trnRefund = $captureTrn->refund()->withCurrency($this->currency)->execute(); + $this->assertNotNull($trnRefund); + $this->assertEquals('SUCCESS', $trnRefund->responseCode); + $this->assertEquals(TransactionStatus::CAPTURED, $trnRefund->responseMessage); + } + + public function testFullRefund() + { + $response = ReportingService::findTransactionsPaged(1, 10) + ->orderBy(TransactionSortProperty::TIME_CREATED) + ->where(SearchCriteria::PAYMENT_METHOD_NAME, PaymentMethodName::BNPL) + ->andWith(SearchCriteria::TRANSACTION_STATUS, TransactionStatus::CAPTURED) + ->andWith(SearchCriteria::PAYMENT_TYPE, PaymentType::SALE) + ->execute(); + + $this->assertNotNull($response); + $this->assertNotCount(0, $response->result); + /** @var TransactionSummary $trnSummary */ + $trnSummary = $response->result[array_rand($response->result, 1)]; + $trn = Transaction::fromId($trnSummary->transactionId, null, $trnSummary->paymentType); + + $trnRefund = $trn->refund()->withCurrency($trnSummary->currency)->execute(); + $this->assertNotNull($trnRefund); + $this->assertEquals('SUCCESS', $trnRefund->responseCode); + $this->assertEquals(TransactionStatus::CAPTURED, $trnRefund->responseMessage); + } + + public function testBNPL_PartialRefund() { + $customer = $this->setCustomerData(); + $products = $this->setProductList(); + + $transaction = $this->paymentMethod->authorize(550) + ->withCurrency($this->currency) + ->withProductData($products) + ->withAddress($this->shippingAddress, AddressType::SHIPPING) + ->withAddress($this->billingAddress, AddressType::BILLING) + ->withPhoneNumber('41', '57774873', PhoneNumberType::SHIPPING) + ->withCustomerData($customer) + ->withBNPLShippingMethod(BNPLShippingMethod::DELIVERY) + ->withOrderId('12365') + ->execute(); + + $this->assertNotNull($transaction); + $this->assertEquals('SUCCESS', $transaction->responseCode); + $this->assertEquals(TransactionStatus::INITIATED, $transaction->responseMessage); + $this->assertNotNull($transaction->bnplResponse->redirectUrl); + + fwrite(STDERR, print_r($transaction->bnplResponse->redirectUrl, TRUE)); + + sleep(45); + + $captureTrn = $transaction->capture()->execute(); + + $this->assertNotNull($captureTrn); + $this->assertEquals('SUCCESS', $captureTrn->responseCode); + $this->assertEquals(TransactionStatus::CAPTURED, $captureTrn->responseMessage); + + sleep(15); + + $trnRefund = $captureTrn->refund(100) + ->withCurrency($this->currency) + ->execute(); + + $this->assertNotNull($trnRefund); + $this->assertEquals('SUCCESS', $trnRefund->responseCode); + $this->assertEquals(TransactionStatus::CAPTURED, $trnRefund->responseMessage); + } + + public function testBNPL_MultipleRefund() { + $customer = $this->setCustomerData(); + $products = $this->setProductList(); + + $transaction = $this->paymentMethod->authorize(550) + ->withCurrency($this->currency) + ->withProductData($products) + ->withAddress($this->shippingAddress, AddressType::SHIPPING) + ->withAddress($this->billingAddress, AddressType::BILLING) + ->withPhoneNumber('41', '57774873', PhoneNumberType::SHIPPING) + ->withCustomerData($customer) + ->withBNPLShippingMethod(BNPLShippingMethod::DELIVERY) + ->withOrderId('12365') + ->execute(); + + $this->assertNotNull($transaction); + $this->assertEquals('SUCCESS', $transaction->responseCode); + $this->assertEquals(TransactionStatus::INITIATED, $transaction->responseMessage); + $this->assertNotNull($transaction->bnplResponse->redirectUrl); + + fwrite(STDERR, print_r($transaction->bnplResponse->redirectUrl, TRUE)); + + sleep(45); + + $captureTrn = $transaction->capture()->execute(); + + $this->assertNotNull($captureTrn); + $this->assertEquals('SUCCESS', $captureTrn->responseCode); + $this->assertEquals(TransactionStatus::CAPTURED, $captureTrn->responseMessage); + + sleep(15); + + $trnRefund = $captureTrn->refund(100) + ->withCurrency($this->currency) + ->execute(); + + $this->assertNotNull($trnRefund); + $this->assertEquals('SUCCESS', $trnRefund->responseCode); + $this->assertEquals(TransactionStatus::CAPTURED, $trnRefund->responseMessage); + + $trnRefund = $captureTrn->refund(100) + ->withCurrency($this->currency) + ->execute(); + + $this->assertNotNull($trnRefund); + $this->assertEquals('SUCCESS', $trnRefund->responseCode); + $this->assertEquals(TransactionStatus::CAPTURED, $trnRefund->responseMessage); + } + + public function testBNPL_Reverse() + { + $customer = $this->setCustomerData(); + $products = $this->setProductList(); + + $transaction = $this->paymentMethod->authorize(550) + ->withCurrency($this->currency) + ->withProductData($products) + ->withAddress($this->shippingAddress, AddressType::SHIPPING) + ->withAddress($this->billingAddress, AddressType::BILLING) + ->withPhoneNumber('41', '57774873', PhoneNumberType::SHIPPING) + ->withCustomerData($customer) + ->withBNPLShippingMethod(BNPLShippingMethod::DELIVERY) + ->withOrderId('12365') + ->execute(); + + $this->assertNotNull($transaction); + $this->assertEquals('SUCCESS', $transaction->responseCode); + $this->assertEquals(TransactionStatus::INITIATED, $transaction->responseMessage); + $this->assertNotNull($transaction->bnplResponse->redirectUrl); + + fwrite(STDERR, print_r($transaction->bnplResponse->redirectUrl, TRUE)); + + sleep(45); + + $captureTrn = $transaction->reverse()->execute(); + + $this->assertNotNull($captureTrn); + $this->assertEquals('SUCCESS', $captureTrn->responseCode); + $this->assertEquals(TransactionStatus::REVERSED, $captureTrn->responseMessage); + } + + public function testBNPL_OnlyMandatory() + { + $customer = $this->setCustomerData(); + $products = $this->setProductList(); + + $transaction = $this->paymentMethod->authorize(550) + ->withCurrency($this->currency) + ->withProductData($products) + ->withAddress($this->shippingAddress, AddressType::SHIPPING) + ->withAddress($this->billingAddress, AddressType::BILLING) + ->withCustomerData($customer) + ->execute(); + + $this->assertNotNull($transaction); + $this->assertEquals('SUCCESS', $transaction->responseCode); + $this->assertEquals(TransactionStatus::INITIATED, $transaction->responseMessage); + $this->assertNotNull($transaction->bnplResponse->redirectUrl); + } + + public function testBNPL_KlarnaProvider() + { + $this->paymentMethod->bnplType = BNPLType::KLARNA; + $customer = $this->setCustomerData(); + $products = $this->setProductList(); + + $transaction = $this->paymentMethod->authorize(550) + ->withCurrency($this->currency) + ->withProductData($products) + ->withAddress($this->shippingAddress, AddressType::SHIPPING) + ->withAddress($this->billingAddress, AddressType::BILLING) + ->withCustomerData($customer) + ->execute(); + + $this->assertNotNull($transaction); + $this->assertEquals('SUCCESS', $transaction->responseCode); + $this->assertEquals(TransactionStatus::INITIATED, $transaction->responseMessage); + $this->assertNotNull($transaction->bnplResponse->redirectUrl); + + fwrite(STDERR, print_r($transaction->bnplResponse->redirectUrl, TRUE)); + + sleep(45); + + $captureTrn = $transaction->capture()->execute(); + + $this->assertNotNull($captureTrn); + $this->assertEquals('SUCCESS', $captureTrn->responseCode); + $this->assertEquals(TransactionStatus::CAPTURED, $captureTrn->responseMessage); + } + + public function testBNPL_ClearPayProvider() + { + $this->paymentMethod->bnplType = BNPLType::CLEARPAY; + $customer = $this->setCustomerData(); + $products = $this->setProductList(); + + $transaction = $this->paymentMethod->authorize(550) + ->withCurrency($this->currency) + ->withProductData($products) + ->withAddress($this->shippingAddress, AddressType::SHIPPING) + ->withAddress($this->billingAddress, AddressType::BILLING) + ->withCustomerData($customer) + ->execute(); + + $this->assertNotNull($transaction); + $this->assertEquals('SUCCESS', $transaction->responseCode); + $this->assertEquals(TransactionStatus::INITIATED, $transaction->responseMessage); + $this->assertNotNull($transaction->bnplResponse->redirectUrl); + } + + public function testBNPL_ClearPayProvider_PartialCapture() + { + $this->paymentMethod->bnplType = BNPLType::CLEARPAY; + $customer = $this->setCustomerData(); + $products = $this->setProductList(); + + $transaction = $this->paymentMethod->authorize(550) + ->withCurrency($this->currency) + ->withProductData($products) + ->withAddress($this->shippingAddress, AddressType::SHIPPING) + ->withAddress($this->billingAddress, AddressType::BILLING) + ->withCustomerData($customer) + ->execute(); + + $this->assertNotNull($transaction); + $this->assertEquals('SUCCESS', $transaction->responseCode); + $this->assertEquals(TransactionStatus::INITIATED, $transaction->responseMessage); + $this->assertNotNull($transaction->bnplResponse->redirectUrl); + + fwrite(STDERR, print_r($transaction->bnplResponse->redirectUrl, TRUE)); + + sleep(45); + + $captureTrn = $transaction->capture(100)->execute(); + + $this->assertNotNull($captureTrn); + $this->assertEquals('SUCCESS', $captureTrn->responseCode); + $this->assertEquals(TransactionStatus::CAPTURED, $captureTrn->responseMessage); + } + + public function testBNPL_ClearPayProvider_MultipleCapture() + { + $this->paymentMethod->bnplType = BNPLType::CLEARPAY; + $customer = $this->setCustomerData(); + $products = $this->setProductList(); + + $transaction = $this->paymentMethod->authorize(550) + ->withCurrency($this->currency) + ->withProductData($products) + ->withAddress($this->shippingAddress, AddressType::SHIPPING) + ->withAddress($this->billingAddress, AddressType::BILLING) + ->withCustomerData($customer) + ->withMultiCapture(true) + ->execute(); + + $this->assertNotNull($transaction); + $this->assertEquals('SUCCESS', $transaction->responseCode); + $this->assertEquals(TransactionStatus::INITIATED, $transaction->responseMessage); + $this->assertNotNull($transaction->bnplResponse->redirectUrl); + + fwrite(STDERR, print_r($transaction->bnplResponse->redirectUrl, TRUE)); + + sleep(45); + + $captureTrn = $transaction->capture(100)->execute(); + + $this->assertNotNull($captureTrn); + $this->assertEquals('SUCCESS', $captureTrn->responseCode); + $this->assertEquals(TransactionStatus::CAPTURED, $captureTrn->responseMessage); + + sleep(5); + + $captureTrn = $transaction->capture(100)->execute(); + + $this->assertNotNull($captureTrn); + $this->assertEquals('SUCCESS', $captureTrn->responseCode); + $this->assertEquals(TransactionStatus::CAPTURED, $captureTrn->responseMessage); + } + + public function testBNPL_InvalidStatusForCapture_NoRedirect() + { + $customer = $this->setCustomerData(); + $products = $this->setProductList(); + + $transaction = $this->paymentMethod->authorize(550) + ->withCurrency($this->currency) + ->withProductData($products) + ->withAddress($this->shippingAddress, AddressType::SHIPPING) + ->withAddress($this->billingAddress, AddressType::BILLING) + ->withPhoneNumber('41', '57774873', PhoneNumberType::SHIPPING) + ->withCustomerData($customer) + ->withBNPLShippingMethod(BNPLShippingMethod::DELIVERY) + ->withOrderId('12365') + ->execute(); + + $this->assertNotNull($transaction); + $this->assertEquals('SUCCESS', $transaction->responseCode); + $this->assertEquals(TransactionStatus::INITIATED, $transaction->responseMessage); + $this->assertNotNull($transaction->bnplResponse->redirectUrl); + + $exceptionCaught = false; + try { + $transaction->capture() + ->execute(); + } catch (GatewayException $e) { + $exceptionCaught = true; + $this->assertEquals('40090', $e->responseCode); + $this->assertEquals("Status Code: INVALID_REQUEST_DATA - id value is invalid. Please check the format and data provided is correct.", + $e->getMessage()); + } finally { + $this->assertTrue($exceptionCaught); + } + } + + public function testGetBNPLTransactionById() + { + $id = 'TRN_EryDeQRtqagH27G87DkSfZGL1kiE21'; + + $trnInfo = ReportingService::transactionDetail($id)->execute(); + + $this->assertEquals($id, $trnInfo->transactionId); + } + + public function testGetBNPL_RandomTransactionId() + { + $id = GenerationUtils::getGuid(); + $exceptionCaught = false; + + try { + ReportingService::transactionDetail($id) + ->execute(); + } catch (GatewayException $e) { + $exceptionCaught = true; + $this->assertEquals('40118', $e->responseCode); + $this->assertEquals(sprintf('Status Code: RESOURCE_NOT_FOUND - Transactions %s not found at this /ucp/transactions/%s', $id, $id), $e->getMessage()); + } finally { + $this->assertTrue($exceptionCaught); + } + } + + public function testGetBNPL_NullTransactionId() + { + $exceptionCaught = false; + + try { + ReportingService::transactionDetail(null) + ->execute(); + } catch (BuilderException $e) { + $exceptionCaught = true; + $this->assertEquals('transactionId cannot be null for this transaction type.', $e->getMessage()); + } finally { + $this->assertTrue($exceptionCaught); + } + } + + public function testBNPL_MissingProducts() + { + $customer = $this->setCustomerData(); + + $exceptionCaught = false; + try { + $this->paymentMethod->authorize(550) + ->withCurrency($this->currency) + ->withAddress($this->shippingAddress, AddressType::SHIPPING) + ->withAddress($this->billingAddress, AddressType::BILLING) + ->withPhoneNumber('41', '57774873', PhoneNumberType::SHIPPING) + ->withCustomerData($customer) + ->withBNPLShippingMethod(BNPLShippingMethod::DELIVERY) + ->withOrderId('12365') + ->execute(); + } catch (GatewayException $e) { + $exceptionCaught = true; + $this->assertEquals('40251', $e->responseCode); + $this->assertEquals("Status Code: MANDATORY_DATA_MISSING - Request expects the following fields: order.items.", + $e->getMessage()); + } finally { + $this->assertTrue($exceptionCaught); + } + } + + public function testBNPL_MissingShippingAddress() + { + $customer = $this->setCustomerData(); + $products = $this->setProductList(); + + $exceptionCaught = false; + try { + $this->paymentMethod->authorize(550) + ->withCurrency($this->currency) + ->withProductData($products) + ->withAddress($this->billingAddress, AddressType::BILLING) + ->withPhoneNumber('41', '57774873', PhoneNumberType::SHIPPING) + ->withCustomerData($customer) + ->withBNPLShippingMethod(BNPLShippingMethod::DELIVERY) + ->withOrderId('12365') + ->execute(); + } catch (GatewayException $e) { + $exceptionCaught = true; + $this->assertEquals('50002', $e->responseCode); + $this->assertEquals("Status Code: SYSTEM_ERROR - Bad Gateway", + $e->getMessage()); + } finally { + $this->assertTrue($exceptionCaught); + } + } + + public function testBNPL_MissingBillingAddress() + { + $customer = $this->setCustomerData(); + $products = $this->setProductList(); + + $exceptionCaught = false; + try { + $this->paymentMethod->authorize(550) + ->withCurrency($this->currency) + ->withProductData($products) + ->withAddress($this->shippingAddress, AddressType::SHIPPING) + ->withPhoneNumber('41', '57774873', PhoneNumberType::SHIPPING) + ->withCustomerData($customer) + ->withBNPLShippingMethod(BNPLShippingMethod::DELIVERY) + ->withOrderId('12365') + ->execute(); + } catch (GatewayException $e) { + $exceptionCaught = true; + $this->assertEquals("Status Code: MANDATORY_DATA_MISSING - One of the parameter is missing from the request body.", + $e->getMessage()); + $this->assertEquals('40297', $e->responseCode); + } finally { + $this->assertTrue($exceptionCaught); + } + } + + public function testBNPL_MissingCustomerData() + { + $products = $this->setProductList(); + + $exceptionCaught = false; + try { + $this->paymentMethod->authorize(550) + ->withCurrency($this->currency) + ->withProductData($products) + ->withAddress($this->shippingAddress, AddressType::SHIPPING) + ->withAddress($this->billingAddress, AddressType::BILLING) + ->withPhoneNumber('41', '57774873', PhoneNumberType::SHIPPING) + ->withBNPLShippingMethod(BNPLShippingMethod::DELIVERY) + ->withOrderId('12365') + ->execute(); + } catch (GatewayException $e) { + $exceptionCaught = true; + $this->assertEquals('40251', $e->responseCode); + $this->assertEquals("Status Code: MANDATORY_DATA_MISSING - Request expects the following fields: payer.email.", + $e->getMessage()); + } finally { + $this->assertTrue($exceptionCaught); + } + } + + public function testBNPL_MissingAmount() + { + $customer = $this->setCustomerData(); + $products = $this->setProductList(); + + $exceptionCaught = false; + try { + $this->paymentMethod->authorize() + ->withCurrency($this->currency) + ->withProductData($products) + ->withAddress($this->shippingAddress, AddressType::SHIPPING) + ->withAddress($this->billingAddress, AddressType::BILLING) + ->withCustomerData($customer) + ->withBNPLShippingMethod(BNPLShippingMethod::DELIVERY) + ->execute(); + } catch (GatewayException $e) { + $exceptionCaught = true; + $this->assertEquals('40005', $e->responseCode); + $this->assertEquals("Status Code: MANDATORY_DATA_MISSING - Request expects the following fields amount", + $e->getMessage()); + } finally { + $this->assertTrue($exceptionCaught); + } + } + + public function testBNPL_MissingCurrency() + { + $customer = $this->setCustomerData(); + $products = $this->setProductList(); + + $exceptionCaught = false; + try { + $this->paymentMethod->authorize(1) + ->withProductData($products) + ->withAddress($this->shippingAddress, AddressType::SHIPPING) + ->withAddress($this->billingAddress, AddressType::BILLING) + ->withCustomerData($customer) + ->withBNPLShippingMethod(BNPLShippingMethod::DELIVERY) + ->execute(); + } catch (GatewayException $e) { + $exceptionCaught = true; + $this->assertEquals('40005', $e->responseCode); + $this->assertEquals("Status Code: MANDATORY_DATA_MISSING - Request expects the following fields currency", + $e->getMessage()); + } finally { + $this->assertTrue($exceptionCaught); + } + } + + public function testBNPL_MissingCustomerEmail() + { + $customer = $this->setCustomerData(); + $customer->email = null; + $products = $this->setProductList(); + + $exceptionCaught = false; + try { + $this->paymentMethod->authorize(550) + ->withCurrency($this->currency) + ->withProductData($products) + ->withAddress($this->shippingAddress, AddressType::SHIPPING) + ->withAddress($this->billingAddress, AddressType::BILLING) + ->withCustomerData($customer) + ->execute(); + } catch (GatewayException $e) { + $exceptionCaught = true; + $this->assertEquals('40251', $e->responseCode); + $this->assertEquals("Status Code: MANDATORY_DATA_MISSING - Request expects the following fields: payer.email.", + $e->getMessage()); + } finally { + $this->assertTrue($exceptionCaught); + } + } + + public function testBNPL_MissingCustomerPhoneNumber() + { + $customer = $this->setCustomerData(); + $customer->phone = null; + $products = $this->setProductList(); + + $exceptionCaught = false; + try { + $this->paymentMethod->authorize(550) + ->withCurrency($this->currency) + ->withProductData($products) + ->withAddress($this->shippingAddress, AddressType::SHIPPING) + ->withAddress($this->billingAddress, AddressType::BILLING) + ->withCustomerData($customer) + ->execute(); + } catch (GatewayException $e) { + $exceptionCaught = true; + $this->assertEquals('40251', $e->responseCode); + $this->assertEquals("Status Code: MANDATORY_DATA_MISSING - Request expects the following fields: payer.contact_phone.country_code.", + $e->getMessage()); + } finally { + $this->assertTrue($exceptionCaught); + } + } + + public function testBNPL_MissingProductId() + { + $customer = $this->setCustomerData(); + $products = $this->setProductList(); + $products[0]->productId = null; + + $exceptionCaught = false; + try { + $this->paymentMethod->authorize(550) + ->withCurrency($this->currency) + ->withProductData($products) + ->withAddress($this->shippingAddress, AddressType::SHIPPING) + ->withAddress($this->billingAddress, AddressType::BILLING) + ->withCustomerData($customer) + ->execute(); + } catch (GatewayException $e) { + $exceptionCaught = true; + $this->assertEquals('40251', $e->responseCode); + $this->assertEquals("Status Code: MANDATORY_DATA_MISSING - Request expects the following fields: order.items[0].reference.", + $e->getMessage()); + } finally { + $this->assertTrue($exceptionCaught); + } + } + + public function testBNPL_MissingProductDescription() + { + $customer = $this->setCustomerData(); + $products = $this->setProductList(); + $products[0]->description = null; + + $exceptionCaught = false; + try { + $this->paymentMethod->authorize(550) + ->withCurrency($this->currency) + ->withProductData($products) + ->withAddress($this->shippingAddress, AddressType::SHIPPING) + ->withAddress($this->billingAddress, AddressType::BILLING) + ->withCustomerData($customer) + ->execute(); + } catch (GatewayException $e) { + $exceptionCaught = true; + $this->assertEquals('40251', $e->responseCode); + $this->assertEquals("Status Code: MANDATORY_DATA_MISSING - Request expects the following fields: order.items[0].description.", + $e->getMessage()); + } finally { + $this->assertTrue($exceptionCaught); + } + } + + public function testBNPL_MissingProductQuantity() + { + $customer = $this->setCustomerData(); + $products = $this->setProductList(); + $products[0]->quantity = null; + + $exceptionCaught = false; + try { + $this->paymentMethod->authorize(550) + ->withCurrency($this->currency) + ->withProductData($products) + ->withAddress($this->shippingAddress, AddressType::SHIPPING) + ->withAddress($this->billingAddress, AddressType::BILLING) + ->withCustomerData($customer) + ->execute(); + } catch (GatewayException $e) { + $exceptionCaught = true; + $this->assertEquals('50002', $e->responseCode); + $this->assertEquals("Status Code: SYSTEM_ERROR - Bad Gateway", + $e->getMessage()); + } finally { + $this->assertTrue($exceptionCaught); + } + } + + public function testBNPL_MissingProductUrl() + { + $customer = $this->setCustomerData(); + $products = $this->setProductList(); + $products[0]->url = null; + + $exceptionCaught = false; + try { + $this->paymentMethod->authorize(550) + ->withCurrency($this->currency) + ->withProductData($products) + ->withAddress($this->shippingAddress, AddressType::SHIPPING) + ->withAddress($this->billingAddress, AddressType::BILLING) + ->withCustomerData($customer) + ->execute(); + } catch (GatewayException $e) { + $exceptionCaught = true; + $this->assertEquals('40251', $e->responseCode); + $this->assertEquals("Status Code: MANDATORY_DATA_MISSING - Request expects the following fields: order.items[0].product_url.", + $e->getMessage()); + } finally { + $this->assertTrue($exceptionCaught); + } + } + + public function testBNPL_MissingProductImageUrl() + { + $customer = $this->setCustomerData(); + $products = $this->setProductList(); + $products[0]->imageUrl = null; + + $exceptionCaught = false; + try { + $this->paymentMethod->authorize(550) + ->withCurrency($this->currency) + ->withProductData($products) + ->withAddress($this->shippingAddress, AddressType::SHIPPING) + ->withAddress($this->billingAddress, AddressType::BILLING) + ->withCustomerData($customer) + ->execute(); + } catch (GatewayException $e) { + $exceptionCaught = true; + $this->assertEquals('40251', $e->responseCode); + $this->assertEquals("Status Code: MANDATORY_DATA_MISSING - Request expects the following fields: order.items[0].product_image_url.", + $e->getMessage()); + } finally { + $this->assertTrue($exceptionCaught); + } + } + + private function setCustomerData() + { + $customer = new Customer(); + $customer->id = "12345678"; + $customer->firstName = 'James'; + $customer->lastName = 'Mason'; + $customer->email = 'james.mason@example.com'; + $customer->phone = new PhoneNumber('41', '57774873', PhoneNumberType::HOME); + $customer->documents[] = new CustomerDocument('123456789', 'US', CustomerDocumentType::PASSPORT); + + return $customer; + } + + private function setProductList() + { + $product = new Product(); + $product->productId = GenerationUtils::getGuid(); + $product->productName = 'iPhone 13'; + $product->description = 'iPhone 13'; + $product->quantity = 1; + $product->unitPrice = 550; + $product->netUnitPrice = 550; + $product->taxAmount = 0; + $product->discountAmount = 0; + $product->taxPercentage = 0; + $product->url = "https://www.example.com/iphone.html"; + $product->imageUrl = "https://www.example.com/iphone.png"; + + return [$product]; + } +} \ No newline at end of file diff --git a/test/Integration/Gateways/GpApiConnector/GpApiDigitalWalletTest.php b/test/Integration/Gateways/GpApiConnector/GpApiDigitalWalletTest.php index 66bceebf..c0484b00 100644 --- a/test/Integration/Gateways/GpApiConnector/GpApiDigitalWalletTest.php +++ b/test/Integration/Gateways/GpApiConnector/GpApiDigitalWalletTest.php @@ -42,9 +42,23 @@ public function setUpConfig() return BaseGpApiTestConfig::gpApiSetupConfig(Channel::CardNotPresent); } + public function testClickToPayEncrypted() + { + $this->markTestSkipped('You need a valid ApplePay token that it is valid only for 60 sec'); + $this->card->token = '{"data":"9113329269393758302"}' ; + $this->card->mobileType = EncyptedMobileType::CLICK_TO_PAY; + + $response = $this->card->charge($this->amount) + ->withCurrency($this->currency) + ->withModifier(TransactionModifier::ENCRYPTED_MOBILE) + ->execute(); + + $this->assertTransactionResponse($response, TransactionStatus::CAPTURED); + } + public function testPayWithApplePayEncrypted() { - $this->markTestSkipped('You need a valid ApplePay token that it is valid only for 60 sec'); + $this->markTestSkipped('You need a valid ApplePay token that it is valid only for 60 sec'); $this->card->token = '{"version":"EC_v1","data":"Jguh2VrQWIpbjtmooCKw2B3yxhBQPwj0tU2FXhtJQatMmRiibhWyVcz1RwolGk2MH+zEL8o4Q3vvXQqb7XUFVaregAGm4mLn5unoTTw6/ltJjozThJ99BuNHo1QhHk6asnlNWy1JTliKq69uGvHcV9ZbBKA4pbUbcsLJu7rB5kakZXvNCLItGAFk2Iue2PMAJMGblTD76FhXbcDTpBFCJeSrupoBoEHk83HgbptaJUzUxsSCHnz0T0BPyLDcMk9cK0nzRowsUYEuH/X+lxjh6yJfkCnL6i6eFjZoonZsZXg37Mnt9kmcIammlHbGtxKXl76AeKieMuPwDMAcMDhnY9xPPM+QZo14dNksBxOV8GWuDLVYSBXmqzZ3GOruYQ29q6gpfZuqIZeiKTYArOhKH0S/ro+aX8fUbPDUP7xAkzc=","signature":"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","header":{"ephemeralPublicKey":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHM7m7LSYllJofL8/T7Ajf6OC1J48iOvXKw4IRCJ5YK+7hkVV0iDwdLijJjtVrCp22EywLXk1VFFeJFU1X/mbMg==","publicKeyHash":"rEYX/7PdO7F7xL7rH0LZVak/iXTrkeU89Ck7E9dGFO4=","transactionId":"c943bc79e49bd3c023988a0681be4df68a30ee64c8360feba1920a320cc29bd0"}}'; $this->card->mobileType = EncyptedMobileType::APPLE_PAY;