From 39906624142a98acff1383337576670c77a506b9 Mon Sep 17 00:00:00 2001 From: "david.owusu" Date: Thu, 13 Nov 2025 17:00:40 +0100 Subject: [PATCH 1/4] [CC-2973] Add sca transaction type. --- src/Constants/IdStrings.php | 1 + src/Constants/TransactionTypes.php | 1 + src/Interfaces/PaymentServiceInterface.php | 17 ++ src/Interfaces/ResourceServiceInterface.php | 26 ++ src/Resources/Payment.php | 54 ++++ src/Resources/TransactionTypes/Sca.php | 256 ++++++++++++++++++ src/Services/PaymentService.php | 29 ++ src/Services/ResourceService.php | 47 ++++ src/Unzer.php | 121 +++++++++ .../Fixtures/jsonData/scaPaymentResponse.json | 33 +++ test/Fixtures/jsonData/scaResponse.json | 33 +++ 11 files changed, 618 insertions(+) create mode 100644 src/Resources/TransactionTypes/Sca.php create mode 100644 test/Fixtures/jsonData/scaPaymentResponse.json create mode 100644 test/Fixtures/jsonData/scaResponse.json diff --git a/src/Constants/IdStrings.php b/src/Constants/IdStrings.php index 662c9ea9..4634335b 100755 --- a/src/Constants/IdStrings.php +++ b/src/Constants/IdStrings.php @@ -16,6 +16,7 @@ class IdStrings public const CHARGEBACK = 'cbk'; public const PAYOUT = 'out'; public const PREAUTHORIZE = 'preaut'; + public const SCA = 'sca'; public const SHIPMENT = 'shp'; // Payment Types diff --git a/src/Constants/TransactionTypes.php b/src/Constants/TransactionTypes.php index 3a4721b1..111bed02 100755 --- a/src/Constants/TransactionTypes.php +++ b/src/Constants/TransactionTypes.php @@ -18,4 +18,5 @@ class TransactionTypes public const SHIPMENT = 'shipment'; public const PAYOUT = 'payout'; public const CHARGEBACK = 'chargeback'; + public const SCA = 'sca'; } diff --git a/src/Interfaces/PaymentServiceInterface.php b/src/Interfaces/PaymentServiceInterface.php index d4613979..ad05a505 100644 --- a/src/Interfaces/PaymentServiceInterface.php +++ b/src/Interfaces/PaymentServiceInterface.php @@ -24,6 +24,7 @@ use UnzerSDK\Resources\TransactionTypes\Authorization; use UnzerSDK\Resources\TransactionTypes\Charge; use UnzerSDK\Resources\TransactionTypes\Payout; +use UnzerSDK\Resources\TransactionTypes\Sca; use UnzerSDK\Resources\TransactionTypes\Shipment; interface PaymentServiceInterface @@ -389,4 +390,20 @@ public function fetchInstallmentPlans( * @return PaylaterInstallmentPlans */ public function fetchPaylaterInstallmentPlans(InstallmentPlansQuery $plansRequest): PaylaterInstallmentPlans; + + /** + * Perform an SCA transaction. + * + * @param Sca $sca The SCA object. + * @param BasePaymentType|string $paymentType The payment type object or ID. + * @param Customer|string|null $customer The customer object or ID. + * @param Metadata|null $metadata The metadata object. + * @param Basket|null $basket The basket object. + * + * @return Sca The resulting SCA object. + * + * @throws UnzerApiException An UnzerApiException is thrown if there is an error returned on API-request. + * @throws RuntimeException A RuntimeException is thrown when there is an error while using the SDK. + */ + public function performSca(Sca $sca, $paymentType, $customer = null, ?Metadata $metadata = null, ?Basket $basket = null): Sca; } diff --git a/src/Interfaces/ResourceServiceInterface.php b/src/Interfaces/ResourceServiceInterface.php index 6f26b0ad..22c752c3 100644 --- a/src/Interfaces/ResourceServiceInterface.php +++ b/src/Interfaces/ResourceServiceInterface.php @@ -25,6 +25,7 @@ use UnzerSDK\Resources\TransactionTypes\Charge; use UnzerSDK\Resources\TransactionTypes\Chargeback; use UnzerSDK\Resources\TransactionTypes\Payout; +use UnzerSDK\Resources\TransactionTypes\Sca; use UnzerSDK\Resources\TransactionTypes\Shipment; interface ResourceServiceInterface @@ -452,4 +453,29 @@ public function fetchShipment($payment, string $shipmentId): Shipment; * @throws RuntimeException A RuntimeException is thrown when there is an error while using the SDK. */ public function fetchConfig(BasePaymentType $paymentType, ?Config $config = null): Config; + + /** + * Fetches an SCA transaction. + * + * @param Sca $sca The SCA object to fetch. + * + * @return Sca The fetched SCA object. + * + * @throws UnzerApiException An UnzerApiException is thrown if there is an error returned on API-request. + * @throws RuntimeException A RuntimeException is thrown when there is an error while using the SDK. + */ + public function fetchSca(Sca $sca): Sca; + + /** + * Fetches an SCA transaction by payment ID and SCA ID. + * + * @param Payment|string $payment The payment object or payment ID. + * @param string $scaId The SCA transaction ID. + * + * @return Sca The fetched SCA object. + * + * @throws UnzerApiException An UnzerApiException is thrown if there is an error returned on API-request. + * @throws RuntimeException A RuntimeException is thrown when there is an error while using the SDK. + */ + public function fetchScaById($payment, string $scaId): Sca; } diff --git a/src/Resources/Payment.php b/src/Resources/Payment.php index 4a75656b..65dd979b 100755 --- a/src/Resources/Payment.php +++ b/src/Resources/Payment.php @@ -19,6 +19,7 @@ use UnzerSDK\Resources\TransactionTypes\Chargeback; use UnzerSDK\Resources\TransactionTypes\Payout; use UnzerSDK\Resources\TransactionTypes\PreAuthorization; +use UnzerSDK\Resources\TransactionTypes\Sca; use UnzerSDK\Resources\TransactionTypes\Shipment; use UnzerSDK\Services\IdService; use UnzerSDK\Traits\HasInvoiceId; @@ -58,6 +59,9 @@ class Payment extends AbstractUnzerResource /** @var Chargeback[] $chargebacks */ private $chargebacks = []; + /** @var Sca|null $sca */ + private $sca; + /** * Associative array using the ID of the cancellations as the key. @@ -337,6 +341,32 @@ public function getChargeByIndex(int $index, bool $lazy = false) return $resource; } + /** + * Returns the SCA transaction of this Payment. + */ + public function getSca(bool $lazy = false): ?Sca + { + $sca = $this->sca; + if (!$lazy && $sca !== null) { + return $this->getResource($sca); + } + return $sca; + } + + /** + * Adds an SCA object to this Payment. + * + * @param Sca $sca + * + * @return $this + */ + public function setSca(Sca $sca): self + { + $sca->setPayment($this); + $this->sca = $sca; + return $this; + } + /** * Reference this payment object to the passed Customer resource. * The Customer resource can be passed as Customer object or the Id of a Customer resource. @@ -924,6 +954,9 @@ private function updateResponseTransactions(array $transactions = []): void case TransactionTypes::CHARGEBACK: $this->updateChargebackTransaction($transaction); break; + case TransactionTypes::SCA: + $this->updateScaTransaction($transaction); + break; default: // skip break; @@ -1191,4 +1224,25 @@ private function updateChargebackTransaction(stdClass $transaction): void $chargeback->handleResponse($transaction); } + + /** + * This updates the local SCA object referenced by this Payment with the given SCA transaction + * from the Payment response. + * + * @param stdClass $transaction The transaction from the Payment response containing the SCA data. + * + * @throws UnzerApiException An UnzerApiException is thrown if there is an error returned on API-request. + * @throws RuntimeException A RuntimeException is thrown when there is an error while using the SDK. + */ + private function updateScaTransaction(stdClass $transaction): void + { + $transactionId = IdService::getResourceIdFromUrl($transaction->url, IdStrings::SCA); + $sca = $this->sca; + if (!$sca instanceof Sca) { + $sca = (new Sca())->setPayment($this)->setId($transactionId); + $this->setSca($sca); + } + + $sca->handleResponse($transaction); + } } diff --git a/src/Resources/TransactionTypes/Sca.php b/src/Resources/TransactionTypes/Sca.php new file mode 100644 index 00000000..c0ba8c7e --- /dev/null +++ b/src/Resources/TransactionTypes/Sca.php @@ -0,0 +1,256 @@ +setAmount($amount); + $this->setCurrency($currency); + $this->setReturnUrl($returnUrl); + } + + /** + * @return float|null + */ + public function getAmount(): ?float + { + return $this->amount; + } + + /** + * @param float|null $amount + * + * @return self + */ + public function setAmount(?float $amount): self + { + $this->amount = $amount !== null ? round($amount, 4) : null; + return $this; + } + + /** + * @return float|null + */ + public function getCancelledAmount(): ?float + { + $amount = 0.0; + foreach ($this->getCancellations() as $cancellation) { + /** @var Cancellation $cancellation */ + if ($cancellation->isSuccess()) { + $amount += $cancellation->getAmount(); + } + } + + return $amount; + } + + /** + * @return string|null + */ + public function getCurrency(): ?string + { + return $this->currency; + } + + /** + * @param string|null $currency + * + * @return self + */ + public function setCurrency(?string $currency): self + { + $this->currency = $currency; + return $this; + } + + /** + * @return string|null + */ + public function getReturnUrl(): ?string + { + return $this->returnUrl; + } + + /** + * @param string|null $returnUrl + * + * @return self + */ + public function setReturnUrl(?string $returnUrl): self + { + $this->returnUrl = $returnUrl; + return $this; + } + + /** + * @return string|null + */ + public function getPaymentReference(): ?string + { + return $this->paymentReference; + } + + /** + * @param string|null $referenceText + * + * @return Sca + */ + public function setPaymentReference(?string $referenceText): Sca + { + $this->paymentReference = $referenceText; + return $this; + } + + /** + * @return bool|null + */ + public function isCard3ds(): ?bool + { + return $this->card3ds; + } + + /** + * @param bool|null $card3ds + * + * @return Sca + */ + public function setCard3ds(?bool $card3ds): Sca + { + $this->card3ds = $card3ds; + return $this; + } + + /** + * @return Authorization[] + */ + public function getAuthorizations(): array + { + return $this->authorizations; + } + + /** + * @param Authorization $authorization + * + * @return Sca + */ + public function addAuthorization(Authorization $authorization): Sca + { + $this->authorizations[] = $authorization; + return $this; + } + + /** + * @return Charge[] + */ + public function getCharges(): array + { + return $this->charges; + } + + /** + * @param Charge $charge + * + * @return Sca + */ + public function addCharge(Charge $charge): Sca + { + $this->charges[] = $charge; + return $this; + } + + /** + * {@inheritDoc} + */ + protected function getResourcePath(string $httpMethod = HttpAdapterInterface::REQUEST_GET): string + { + return 'sca'; + } + + /** + * Charge SCA transaction. + * + * @param float|null $amount + * + * @return Charge + * + * @throws UnzerApiException An UnzerApiException is thrown if there is an error returned on API-request. + * @throws RuntimeException A RuntimeException is thrown when there is an error while using the SDK. + */ + public function charge(?float $amount = null): Charge + { + $payment = $this->getPayment(); + if (!$payment instanceof Payment) { + throw new RuntimeException('Payment object is missing. Try fetching the object first!'); + } + return $this->getUnzerObject()->chargeScaTransaction($payment, $this->getId(), $amount); + } + + /** + * Authorize SCA transaction. + * + * @param float|null $amount + * + * @return Authorization + * + * @throws UnzerApiException An UnzerApiException is thrown if there is an error returned on API-request. + * @throws RuntimeException A RuntimeException is thrown when there is an error while using the SDK. + */ + public function authorize(?float $amount = null): Authorization + { + $payment = $this->getPayment(); + if (!$payment instanceof Payment) { + throw new RuntimeException('Payment object is missing. Try fetching the object first!'); + } + return $this->getUnzerObject()->authorizeScaTransaction($payment, $this->getId(), $amount); + } +} diff --git a/src/Services/PaymentService.php b/src/Services/PaymentService.php index 8eefc359..de695411 100755 --- a/src/Services/PaymentService.php +++ b/src/Services/PaymentService.php @@ -22,6 +22,7 @@ use UnzerSDK\Resources\TransactionTypes\Authorization; use UnzerSDK\Resources\TransactionTypes\Charge; use UnzerSDK\Resources\TransactionTypes\Payout; +use UnzerSDK\Resources\TransactionTypes\Sca; use UnzerSDK\Resources\TransactionTypes\Shipment; use UnzerSDK\Unzer; @@ -392,4 +393,32 @@ private function createPayment($paymentType): AbstractUnzerResource { return (new Payment($this->unzer))->setPaymentType($paymentType); } + + /** + * Perform an SCA transaction. + * + * @param Sca $sca The SCA object to be executed. + * @param BasePaymentType|string $paymentType The payment type object or its ID. + * @param Customer|string|null $customer The customer object or ID. + * @param Metadata|null $metadata The metadata object. + * @param Basket|null $basket The basket object. + * + * @return Sca The resulting SCA object. + * + * @throws UnzerApiException An UnzerApiException is thrown if there is an error returned on API-request. + * @throws RuntimeException A RuntimeException is thrown when there is an error while using the SDK. + */ + public function performSca(Sca $sca, $paymentType, $customer = null, ?Metadata $metadata = null, ?Basket $basket = null): Sca + { + $payment = $this->createPayment($paymentType); + $paymentType = $payment->getPaymentType(); + + /** @var Sca $sca */ + $sca->setSpecialParams($paymentType->getTransactionParams() ?? []); + $payment->setSca($sca)->setCustomer($customer)->setMetadata($metadata)->setBasket($basket); + + $this->getResourceService()->createResource($sca); + + return $sca; + } } diff --git a/src/Services/ResourceService.php b/src/Services/ResourceService.php index 16821523..90b09578 100755 --- a/src/Services/ResourceService.php +++ b/src/Services/ResourceService.php @@ -59,6 +59,7 @@ use UnzerSDK\Resources\TransactionTypes\Charge; use UnzerSDK\Resources\TransactionTypes\Chargeback; use UnzerSDK\Resources\TransactionTypes\Payout; +use UnzerSDK\Resources\TransactionTypes\Sca; use UnzerSDK\Resources\TransactionTypes\Shipment; use UnzerSDK\Resources\V2\Customer as CustomerV2; use UnzerSDK\Resources\V2\Paypage as PaypageV2; @@ -222,6 +223,12 @@ public function fetchResourceByUrl($url) case $resourceType === IdStrings::PAYOUT: $resource = $unzer->fetchPayout(IdService::getResourceIdFromUrl($url, IdStrings::PAYMENT)); break; + case $resourceType === IdStrings::SCA: + $resource = $unzer->fetchScaById( + IdService::getResourceIdFromUrl($url, IdStrings::PAYMENT), + $resourceId + ); + break; case $resourceType === IdStrings::PAYMENT: $resource = $unzer->fetchPayment($resourceId); break; @@ -980,4 +987,44 @@ public static function getTypeInstanceFromIdString($typeId): BasePaymentType } return $paymentType; } + + /** + * Fetches an SCA transaction. + * + * @param Sca $sca The SCA object to fetch. + * + * @return Sca The fetched SCA object. + * + * @throws UnzerApiException An UnzerApiException is thrown if there is an error returned on API-request. + * @throws RuntimeException A RuntimeException is thrown when there is an error while using the SDK. + */ + public function fetchSca(Sca $sca): Sca + { + $this->fetchResource($sca); + return $sca; + } + + /** + * Fetches an SCA transaction by payment ID and SCA ID. + * + * @param Payment|string $payment The payment object or payment ID. + * @param string $scaId The SCA transaction ID. + * + * @return Sca The fetched SCA object. + * + * @throws UnzerApiException An UnzerApiException is thrown if there is an error returned on API-request. + * @throws RuntimeException A RuntimeException is thrown when there is an error while using the SDK. + */ + public function fetchScaById($payment, string $scaId): Sca + { + $paymentObject = $this->fetchPayment($payment); + $sca = $paymentObject->getSca($scaId, true); + + if (!$sca instanceof Sca) { + throw new RuntimeException('The SCA object could not be found.'); + } + + $this->fetchResource($sca); + return $sca; + } } diff --git a/src/Unzer.php b/src/Unzer.php index a0bb6570..e0f8b45d 100644 --- a/src/Unzer.php +++ b/src/Unzer.php @@ -33,6 +33,7 @@ use UnzerSDK\Resources\TransactionTypes\Charge; use UnzerSDK\Resources\TransactionTypes\Chargeback; use UnzerSDK\Resources\TransactionTypes\Payout; +use UnzerSDK\Resources\TransactionTypes\Sca; use UnzerSDK\Resources\TransactionTypes\Shipment; use UnzerSDK\Resources\V2\Paypage as PaypageV2; use UnzerSDK\Resources\Webhook; @@ -812,6 +813,126 @@ public function performChargeOnPayment($payment, Charge $charge): Charge return $this->paymentService->performChargeOnPayment($payment, $charge); } + /** + * Perform an SCA transaction. + * + * @param Sca $sca The SCA object. + * @param BasePaymentType|string $paymentType The payment type object or ID. + * @param Customer|string|null $customer The customer object or ID. + * @param Metadata|null $metadata The metadata object. + * @param Basket|null $basket The basket object. + * + * @return Sca The resulting SCA object. + * + * @throws UnzerApiException An UnzerApiException is thrown if there is an error returned on API-request. + * @throws RuntimeException A RuntimeException is thrown when there is an error while using the SDK. + */ + public function performSca(Sca $sca, $paymentType, $customer = null, ?Metadata $metadata = null, ?Basket $basket = null): Sca + { + return $this->paymentService->performSca($sca, $paymentType, $customer, $metadata, $basket); + } + + /** + * Fetch an SCA transaction. + * + * @param Sca $sca The SCA object to fetch. + * + * @return Sca The fetched SCA object. + * + * @throws UnzerApiException An UnzerApiException is thrown if there is an error returned on API-request. + * @throws RuntimeException A RuntimeException is thrown when there is an error while using the SDK. + */ + public function fetchSca(Sca $sca): Sca + { + return $this->resourceService->fetchSca($sca); + } + + /** + * Fetch an SCA transaction by payment ID and SCA ID. + * + * @param Payment|string $payment The payment object or payment ID. + * @param string $scaId The SCA transaction ID. + * + * @return Sca The fetched SCA object. + * + * @throws UnzerApiException An UnzerApiException is thrown if there is an error returned on API-request. + * @throws RuntimeException A RuntimeException is thrown when there is an error while using the SDK. + */ + public function fetchScaById($payment, string $scaId): Sca + { + return $this->resourceService->fetchScaById($payment, $scaId); + } + + /** + * Charge an SCA transaction. + * + * @param Payment|string $payment The payment object or payment ID. + * @param string $scaId The SCA transaction ID. + * @param float|null $amount The amount to charge. + * @param string|null $orderId The order ID. + * @param string|null $invoiceId The invoice ID. + * + * @return Charge The resulting Charge object. + * + * @throws UnzerApiException An UnzerApiException is thrown if there is an error returned on API-request. + * @throws RuntimeException A RuntimeException is thrown when there is an error while using the SDK. + */ + public function chargeScaTransaction($payment, string $scaId, ?float $amount = null, ?string $orderId = null, ?string $invoiceId = null): Charge + { + $charge = new Charge($amount); + $charge->setOrderId($orderId)->setInvoiceId($invoiceId); + + $paymentObject = $this->resourceService->getPaymentResource($payment); + $sca = $paymentObject->getSca($scaId, true); + + if (!$sca instanceof Sca) { + throw new RuntimeException('SCA transaction not found.'); + } + + $sca->addCharge($charge); + $charge->setPayment($paymentObject); + $charge->setParentResource($sca); + + $this->resourceService->createResource($charge); + + return $charge; + } + + /** + * Authorize an SCA transaction. + * + * @param Payment|string $payment The payment object or payment ID. + * @param string $scaId The SCA transaction ID. + * @param float|null $amount The amount to authorize. + * @param string|null $orderId The order ID. + * @param string|null $invoiceId The invoice ID. + * + * @return Authorization The resulting Authorization object. + * + * @throws UnzerApiException An UnzerApiException is thrown if there is an error returned on API-request. + * @throws RuntimeException A RuntimeException is thrown when there is an error while using the SDK. + */ + public function authorizeScaTransaction($payment, string $scaId, ?float $amount = null, ?string $orderId = null, ?string $invoiceId = null): Authorization + { + $authorization = new Authorization($amount, null, null); + $authorization->setOrderId($orderId)->setInvoiceId($invoiceId); + + $paymentObject = $this->resourceService->getPaymentResource($payment); + $sca = $paymentObject->getSca($scaId, true); + + if (!$sca instanceof Sca) { + throw new RuntimeException('SCA transaction not found.'); + } + + $sca->addAuthorization($authorization); + $authorization->setPayment($paymentObject); + $authorization->setParentResource($sca); + + $this->resourceService->createResource($authorization); + + return $authorization; + } + /** * {@inheritDoc} */ diff --git a/test/Fixtures/jsonData/scaPaymentResponse.json b/test/Fixtures/jsonData/scaPaymentResponse.json new file mode 100644 index 00000000..f897f5a6 --- /dev/null +++ b/test/Fixtures/jsonData/scaPaymentResponse.json @@ -0,0 +1,33 @@ +{ + "id": "s-sca-1", + "isSuccess": true, + "isPending": false, + "isResumed": false, + "isError": false, + "card3ds": true, + "redirectUrl": "", + "message": { + "code": "COR.000.100.200", + "merchant": "Transaction succeeded", + "customer": "Your payments have been successfully processed." + }, + "amount": "119.0000", + "currency": "EUR", + "date": "2023-02-28 08:00:00", + "resources": { + "customerId": "", + "paymentId": "s-pay-123", + "basketId": "", + "metadataId": "", + "payPageId": "", + "traceId": "trace-123", + "typeId": "s-crd-123" + }, + "paymentReference": "", + "processing": { + "uniqueId": "31HA0xyz", + "shortId": "1234.1234.1234", + "3dsEci": "3DS_SUCCESS", + "traceId": "trace-123" + } +} \ No newline at end of file diff --git a/test/Fixtures/jsonData/scaResponse.json b/test/Fixtures/jsonData/scaResponse.json new file mode 100644 index 00000000..f897f5a6 --- /dev/null +++ b/test/Fixtures/jsonData/scaResponse.json @@ -0,0 +1,33 @@ +{ + "id": "s-sca-1", + "isSuccess": true, + "isPending": false, + "isResumed": false, + "isError": false, + "card3ds": true, + "redirectUrl": "", + "message": { + "code": "COR.000.100.200", + "merchant": "Transaction succeeded", + "customer": "Your payments have been successfully processed." + }, + "amount": "119.0000", + "currency": "EUR", + "date": "2023-02-28 08:00:00", + "resources": { + "customerId": "", + "paymentId": "s-pay-123", + "basketId": "", + "metadataId": "", + "payPageId": "", + "traceId": "trace-123", + "typeId": "s-crd-123" + }, + "paymentReference": "", + "processing": { + "uniqueId": "31HA0xyz", + "shortId": "1234.1234.1234", + "3dsEci": "3DS_SUCCESS", + "traceId": "trace-123" + } +} \ No newline at end of file From 32839cc84b90067acd2993db524cc591168b91db Mon Sep 17 00:00:00 2001 From: "david.owusu" Date: Fri, 14 Nov 2025 11:54:05 +0100 Subject: [PATCH 2/4] [CC-2973] Add basic sca transaction type tests. --- src/Resources/TransactionTypes/Sca.php | 85 ------ test/integration/TransactionTypes/ScaTest.php | 189 ++++++++++++ .../Resources/TransactionTypes/ScaTest.php | 272 ++++++++++++++++++ test/unit/Services/ResourceServiceTest.php | 3 +- 4 files changed, 463 insertions(+), 86 deletions(-) create mode 100644 test/integration/TransactionTypes/ScaTest.php create mode 100644 test/unit/Resources/TransactionTypes/ScaTest.php diff --git a/src/Resources/TransactionTypes/Sca.php b/src/Resources/TransactionTypes/Sca.php index c0ba8c7e..3f503437 100644 --- a/src/Resources/TransactionTypes/Sca.php +++ b/src/Resources/TransactionTypes/Sca.php @@ -2,10 +2,7 @@ namespace UnzerSDK\Resources\TransactionTypes; -use RuntimeException; use UnzerSDK\Adapter\HttpAdapterInterface; -use UnzerSDK\Exceptions\UnzerApiException; -use UnzerSDK\Resources\Payment; use UnzerSDK\Traits\HasAccountInformation; use UnzerSDK\Traits\HasCancellations; use UnzerSDK\Traits\HasDescriptor; @@ -39,12 +36,6 @@ class Sca extends AbstractTransactionType /** @var bool $card3ds */ protected $card3ds; - /** @var Authorization[] $authorizations */ - private $authorizations = []; - - /** @var Charge[] $charges */ - private $charges = []; - /** * Sca constructor. * @@ -170,44 +161,6 @@ public function setCard3ds(?bool $card3ds): Sca return $this; } - /** - * @return Authorization[] - */ - public function getAuthorizations(): array - { - return $this->authorizations; - } - - /** - * @param Authorization $authorization - * - * @return Sca - */ - public function addAuthorization(Authorization $authorization): Sca - { - $this->authorizations[] = $authorization; - return $this; - } - - /** - * @return Charge[] - */ - public function getCharges(): array - { - return $this->charges; - } - - /** - * @param Charge $charge - * - * @return Sca - */ - public function addCharge(Charge $charge): Sca - { - $this->charges[] = $charge; - return $this; - } - /** * {@inheritDoc} */ @@ -215,42 +168,4 @@ protected function getResourcePath(string $httpMethod = HttpAdapterInterface::RE { return 'sca'; } - - /** - * Charge SCA transaction. - * - * @param float|null $amount - * - * @return Charge - * - * @throws UnzerApiException An UnzerApiException is thrown if there is an error returned on API-request. - * @throws RuntimeException A RuntimeException is thrown when there is an error while using the SDK. - */ - public function charge(?float $amount = null): Charge - { - $payment = $this->getPayment(); - if (!$payment instanceof Payment) { - throw new RuntimeException('Payment object is missing. Try fetching the object first!'); - } - return $this->getUnzerObject()->chargeScaTransaction($payment, $this->getId(), $amount); - } - - /** - * Authorize SCA transaction. - * - * @param float|null $amount - * - * @return Authorization - * - * @throws UnzerApiException An UnzerApiException is thrown if there is an error returned on API-request. - * @throws RuntimeException A RuntimeException is thrown when there is an error while using the SDK. - */ - public function authorize(?float $amount = null): Authorization - { - $payment = $this->getPayment(); - if (!$payment instanceof Payment) { - throw new RuntimeException('Payment object is missing. Try fetching the object first!'); - } - return $this->getUnzerObject()->authorizeScaTransaction($payment, $this->getId(), $amount); - } } diff --git a/test/integration/TransactionTypes/ScaTest.php b/test/integration/TransactionTypes/ScaTest.php new file mode 100644 index 00000000..664967c6 --- /dev/null +++ b/test/integration/TransactionTypes/ScaTest.php @@ -0,0 +1,189 @@ +unzer->createPaymentType($this->createCardObject()); + $sca = new Sca(100.0, 'EUR', self::RETURN_URL); + $sca = $this->unzer->performSca($sca, $paymentType->getId()); + + $this->assertTransactionResourceHasBeenCreated($sca); + $this->assertInstanceOf(Payment::class, $sca->getPayment()); + $this->assertNotEmpty($sca->getPayment()->getId()); + $this->assertEquals(self::RETURN_URL, $sca->getReturnUrl()); + + return $sca; + } + + /** + * Verify SCA transaction can be performed with payment type object. + * + * @test + */ + public function scaShouldWorkWithTypeObject(): void + { + /** @var Card $paymentType */ + $paymentType = $this->unzer->createPaymentType($this->createCardObject()); + $sca = new Sca(100.0, 'EUR', self::RETURN_URL); + $sca = $this->unzer->performSca($sca, $paymentType); + + $this->assertTransactionResourceHasBeenCreated($sca); + $this->assertInstanceOf(Payment::class, $sca->getPayment()); + $this->assertNotEmpty($sca->getPayment()->getId()); + $this->assertEquals(self::RETURN_URL, $sca->getReturnUrl()); + } + + /** + * Verify SCA transaction accepts all parameters. + * + * @test + */ + public function scaShouldAcceptAllParameters(): Sca + { + // prepare test data + /** @var Card $paymentType */ + $paymentType = $this->unzer->createPaymentType($this->createCardObject()); + $customer = $this->getMinimalCustomer(); + $metadata = (new Metadata())->addMetadata('key', 'value'); + $basket = $this->createBasket(); + $paymentReference = 'scaPaymentReference'; + + // perform request + $sca = new Sca(119.0, 'EUR', self::RETURN_URL); + $sca->setRecurrenceType(RecurrenceTypes::ONE_CLICK, $paymentType); + $sca->setPaymentReference($paymentReference); + $sca->setCard3ds(true); + + $sca = $this->unzer->performSca($sca, $paymentType, $customer, $metadata, $basket); + + // verify the data sent and received match + $payment = $sca->getPayment(); + $this->assertSame($paymentType, $payment->getPaymentType()); + $this->assertEquals(119.0, $sca->getAmount()); + $this->assertEquals('EUR', $sca->getCurrency()); + $this->assertEquals(self::RETURN_URL, $sca->getReturnUrl()); + $this->assertSame($customer, $payment->getCustomer()); + $this->assertSame($metadata, $payment->getMetadata()); + $this->assertSame($basket, $payment->getBasket()); + $this->assertEquals($paymentReference, $sca->getPaymentReference()); + } + + /** + * Verify SCA transaction can be fetched. + * + * @test + * @depends scaShouldWorkWithTypeId + */ + public function scaTransactionCanBeFetched($sca): void + { + $this->assertTransactionResourceHasBeenCreated($sca); + + // Fetch the SCA transaction + $fetchedSca = $this->unzer->fetchScaById($sca->getPaymentId(), $sca->getId()); + + // Verify the fetched transaction matches the initial transaction + $this->assertEquals($sca->getId(), $fetchedSca->getId()); + $this->assertEquals($sca->getPaymentId(), $fetchedSca->getPaymentId()); + $this->assertEquals($sca->getAmount(), $fetchedSca->getAmount()); + $this->assertEquals($sca->getCurrency(), $fetchedSca->getCurrency()); + } + + /** + * Verify SCA transaction can be fetched using Sca object. + * + * @test + */ + public function scaTransactionCanBeFetchedUsingSca(): void + { + // Create SCA transaction + /** @var Card $paymentType */ + $paymentType = $this->unzer->createPaymentType($this->createCardObject()); + $sca = new Sca(100.0, 'EUR', self::RETURN_URL); + $sca = $this->unzer->performSca($sca, $paymentType); + + $this->assertTransactionResourceHasBeenCreated($sca); + + // Fetch the SCA transaction using the Sca object + $fetchedSca = $this->unzer->fetchSca($sca); + + // Verify the fetched transaction matches the initial transaction + $this->assertEquals($sca->getId(), $fetchedSca->getId()); + $this->assertEquals($sca->getAmount(), $fetchedSca->getAmount()); + $this->assertEquals($sca->getCurrency(), $fetchedSca->getCurrency()); + } + + /** + * Verify charge can be performed on SCA transaction. + * + * @test + */ + public function chargeCanBePerformedOnScaTransaction(): void + { + // Create SCA transaction + /** @var Card $paymentType */ + $paymentType = $this->unzer->createPaymentType($this->createCardObject()); + $sca = new Sca(100.0, 'EUR', self::RETURN_URL); + $sca = $this->unzer->performSca($sca, $paymentType); + + $this->assertTransactionResourceHasBeenCreated($sca); + + // Perform charge on SCA transaction + $charge = $this->unzer->chargeScaTransaction($sca->getPayment(), $sca->getId(), 50.0); + + $this->assertTransactionResourceHasBeenCreated($charge); + $this->assertInstanceOf(Charge::class, $charge); + $this->assertEquals(50.0, $charge->getAmount()); + $this->assertEquals($sca->getPayment()->getId(), $charge->getPayment()->getId()); + } + + /** + * Verify authorize can be performed on SCA transaction. + * + * @test + */ + public function authorizeCanBePerformedOnScaTransaction(): void + { + // Create SCA transaction + /** @var Card $paymentType */ + $paymentType = $this->unzer->createPaymentType($this->createCardObject()); + $sca = new Sca(100.0, 'EUR', self::RETURN_URL); + $sca = $this->unzer->performSca($sca, $paymentType); + + $this->assertTransactionResourceHasBeenCreated($sca); + + // Perform authorize on SCA transaction + $authorization = $this->unzer->authorizeScaTransaction($sca->getPayment(), $sca->getId(), 75.0); + + $this->assertTransactionResourceHasBeenCreated($authorization); + $this->assertInstanceOf(Authorization::class, $authorization); + $this->assertEquals(75.0, $authorization->getAmount()); + $this->assertEquals($sca->getPayment()->getId(), $authorization->getPayment()->getId()); + } +} diff --git a/test/unit/Resources/TransactionTypes/ScaTest.php b/test/unit/Resources/TransactionTypes/ScaTest.php new file mode 100644 index 00000000..54866dcf --- /dev/null +++ b/test/unit/Resources/TransactionTypes/ScaTest.php @@ -0,0 +1,272 @@ +assertNull($sca->getAmount()); + $this->assertNull($sca->getCurrency()); + $this->assertNull($sca->getReturnUrl()); + $this->assertNull($sca->getIban()); + $this->assertNull($sca->getBic()); + $this->assertNull($sca->getHolder()); + $this->assertNull($sca->getDescriptor()); + $this->assertNull($sca->getRecurrenceType()); + + $sca = new Sca(123.4, 'EUR', 'https://my-return-url.test'); + $this->assertEquals(123.4, $sca->getAmount()); + $this->assertEquals('EUR', $sca->getCurrency()); + $this->assertEquals('https://my-return-url.test', $sca->getReturnUrl()); + + $testResponse = new stdClass(); + $testResponse->amount = '789.0'; + $testResponse->currency = 'USD'; + $testResponse->returnUrl = 'https://return-url.test'; + $testResponse->Iban = 'DE89370400440532013000'; + $testResponse->Bic = 'COBADEFFXXX'; + $testResponse->Holder = 'Merchant Name'; + $testResponse->Descriptor = '4065.6865.6416'; + $testResponse->additionalTransactionData = (object)['card' => (object)['recurrenceType' => 'oneClick']]; + + $sca->handleResponse($testResponse); + $this->assertEquals(789.0, $sca->getAmount()); + $this->assertEquals('USD', $sca->getCurrency()); + $this->assertEquals('https://return-url.test', $sca->getReturnUrl()); + $this->assertEquals('DE89370400440532013000', $sca->getIban()); + $this->assertEquals('COBADEFFXXX', $sca->getBic()); + $this->assertEquals('Merchant Name', $sca->getHolder()); + $this->assertEquals('4065.6865.6416', $sca->getDescriptor()); + } + + /** + * Verify response with empty account data can be handled. + * + * @test + */ + public function verifyResponseWithEmptyAccountDataCanBeHandled() + { + $sca = new Sca(); + + $testResponse = new stdClass(); + $testResponse->Iban = ''; + $testResponse->Bic = ''; + $testResponse->Holder = ''; + $testResponse->Descriptor = ''; + + $sca->handleResponse($testResponse); + $this->assertNull($sca->getIban()); + $this->assertNull($sca->getBic()); + $this->assertNull($sca->getHolder()); + $this->assertNull($sca->getDescriptor()); + } + + /** + * Verify charge throws exception if payment is not set. + * + * @test + * + * @dataProvider chargeValueProvider + * + * @param float|null $value + */ + public function chargeShouldThrowExceptionIfPaymentIsNotSet($value): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Payment object is missing. Try fetching the object first!'); + + $sca = new Sca(); + $sca->charge($value); + } + + /** + * Verify charge() calls chargeScaTransaction() on Unzer object with the given amount. + * + * @test + */ + public function chargeShouldCallChargeScaTransactionOnUnzerObject(): void + { + $unzerMock = $this->getMockBuilder(Unzer::class) + ->disableOriginalConstructor() + ->setMethods(['chargeScaTransaction']) + ->getMock(); + /** @var Unzer $unzerMock */ + $payment = (new Payment())->setParentResource($unzerMock)->setId('myPayment'); + + $sca = new Sca(100.0, 'EUR', 'https://return-url.test'); + $sca->setPayment($payment); + $sca->setParentResource($unzerMock); + $sca->setId('s-sca-123'); + + $unzerMock->expects($this->exactly(2)) + ->method('chargeScaTransaction')->willReturn(new Charge()) + ->withConsecutive( + [$this->identicalTo($payment), 's-sca-123', $this->isNull()], + [$this->identicalTo($payment), 's-sca-123', 50.0] + ); + + $sca->charge(); + $sca->charge(50.0); + } + + /** + * Verify authorize throws exception if payment is not set. + * + * @test + * + * @dataProvider authorizeValueProvider + * + * @param float|null $value + */ + public function authorizeShouldThrowExceptionIfPaymentIsNotSet($value): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Payment object is missing. Try fetching the object first!'); + + $sca = new Sca(); + $sca->authorize($value); + } + + /** + * Verify authorize() calls authorizeScaTransaction() on Unzer object with the given amount. + * + * @test + */ + public function authorizeShouldCallAuthorizeScaTransactionOnUnzerObject(): void + { + $unzerMock = $this->getMockBuilder(Unzer::class) + ->disableOriginalConstructor() + ->setMethods(['authorizeScaTransaction']) + ->getMock(); + /** @var Unzer $unzerMock */ + $payment = (new Payment())->setParentResource($unzerMock)->setId('myPayment'); + + $sca = new Sca(100.0, 'EUR', 'https://return-url.test'); + $sca->setPayment($payment); + $sca->setParentResource($unzerMock); + $sca->setId('s-sca-123'); + + $unzerMock->expects($this->exactly(2)) + ->method('authorizeScaTransaction')->willReturn(new Authorization()) + ->withConsecutive( + [$this->identicalTo($payment), 's-sca-123', $this->isNull()], + [$this->identicalTo($payment), 's-sca-123', 75.0] + ); + + $sca->authorize(); + $sca->authorize(75.0); + } + + /** + * Verify getter for cancelled amount. + * + * @test + */ + public function getCancelledAmountReturnsTheCancelledAmount(): void + { + $sca = new Sca(); + $this->assertEquals(0.0, $sca->getCancelledAmount()); + + $sca = new Sca(123.4, 'EUR', 'https://my-return-url.test'); + $this->assertEquals(0.0, $sca->getCancelledAmount()); + + $cancellationJson = '{ + "type": "cancel-sca", + "status": "success", + "amount": "10" + }'; + + $cancellation1 = new Cancellation(); + $cancellation1->handleResponse(json_decode($cancellationJson)); + $sca->addCancellation($cancellation1); + $this->assertEquals(10.0, $sca->getCancelledAmount()); + + $cancellation2 = new Cancellation(); + $cancellation2->handleResponse(json_decode($cancellationJson)); + $sca->addCancellation($cancellation2); + $this->assertEquals(20.0, $sca->getCancelledAmount()); + } + + /** + * Verify authorizations and charges can be added. + * + * @test + */ + public function authorizationsAndChargesCanBeAdded(): void + { + $sca = new Sca(100.0, 'EUR', 'https://return-url.test'); + + $this->assertEmpty($sca->getAuthorizations()); + $this->assertEmpty($sca->getCharges()); + + $authorization = new Authorization(50.0, 'EUR', 'https://return-url.test'); + $sca->addAuthorization($authorization); + + $charge = new Charge(50.0, 'EUR', 'https://return-url.test'); + $sca->addCharge($charge); + + $this->assertCount(1, $sca->getAuthorizations()); + $this->assertCount(1, $sca->getCharges()); + $this->assertSame($authorization, $sca->getAuthorizations()[0]); + $this->assertSame($charge, $sca->getCharges()[0]); + } + + // + + /** + * Provide different amounts for charge + * + * @return array + */ + public function chargeValueProvider(): array + { + return [ + 'Amount = null' => [null], + 'Amount = 0.0' => [0.0], + 'Amount = 123.8' => [123.8] + ]; + } + + /** + * Provide different amounts for authorize + * + * @return array + */ + public function authorizeValueProvider(): array + { + return [ + 'Amount = null' => [null], + 'Amount = 0.0' => [0.0], + 'Amount = 123.8' => [123.8] + ]; + } + + // +} diff --git a/test/unit/Services/ResourceServiceTest.php b/test/unit/Services/ResourceServiceTest.php index 1bcc7086..6cb6de5c 100755 --- a/test/unit/Services/ResourceServiceTest.php +++ b/test/unit/Services/ResourceServiceTest.php @@ -1391,7 +1391,8 @@ public function fetchResourceByUrlShouldFetchTheDesiredResourceDP(): array 'v2Customer' => ['fetchCustomer', ['s-cst-123e4567-e89b-12d3-a456-426614174000'], 'https://api.unzer.com/v2/customers/s-cst-123e4567-e89b-12d3-a456-426614174000'], 'v1Basket' => ['fetchBasket', ['s-bsk-1254'], 'https://api.unzer.com/v1/baskets/s-bsk-1254/'], 'v2Basket' => ['fetchBasket', ['s-bsk-1254'], 'https://api.unzer.com/v2/baskets/s-bsk-1254/'], - 'Payout' => ['fetchPayout', ['s-pay-100746'], 'https://api.unzer.com/v1/payments/s-pay-100746/payout/s-out-1/'] + 'Payout' => ['fetchPayout', ['s-pay-100746'], 'https://api.unzer.com/v1/payments/s-pay-100746/payout/s-out-1/'], + 'Sca' => ['fetchScaById', ['s-pay-100746', 's-sca-1'], 'https://api.unzer.com/v1/payments/s-pay-100746/sca/s-sca-1/'] ]; } From 23c93cf3ac1e8697478829cab10b582e57ca7715 Mon Sep 17 00:00:00 2001 From: "david.owusu" Date: Thu, 20 Nov 2025 16:17:20 +0100 Subject: [PATCH 3/4] [CC-2973] Move Amount parameter up to abstract transaction class. --- .../AbstractTransactionType.php | 22 ++++++++++ .../TransactionTypes/Authorization.php | 19 --------- .../TransactionTypes/Cancellation.php | 30 -------------- src/Resources/TransactionTypes/Charge.php | 22 ---------- src/Resources/TransactionTypes/Chargeback.php | 24 ----------- src/Resources/TransactionTypes/Payout.php | 22 ---------- src/Resources/TransactionTypes/Sca.php | 40 ------------------- src/Resources/TransactionTypes/Shipment.php | 22 ---------- 8 files changed, 22 insertions(+), 179 deletions(-) diff --git a/src/Resources/TransactionTypes/AbstractTransactionType.php b/src/Resources/TransactionTypes/AbstractTransactionType.php index 70bd0802..05093caf 100755 --- a/src/Resources/TransactionTypes/AbstractTransactionType.php +++ b/src/Resources/TransactionTypes/AbstractTransactionType.php @@ -39,6 +39,9 @@ abstract class AbstractTransactionType extends AbstractUnzerResource use HasAdditionalTransactionData; use HasDate; + /** @var float $amount */ + protected $amount; + /** @var Payment $payment */ private $payment; @@ -242,4 +245,23 @@ protected function handleWeroTransactionData(stdClass $additionalTransactionData $this->setWeroTransactionData($weroTransactionData); } } + + /** + * @param float|null $amount + * + * @return Charge + */ + public function setAmount(?float $amount): self + { + $this->amount = $amount !== null ? round($amount, 4) : null; + return $this; + } + + /** + * @return float|null + */ + public function getAmount(): ?float + { + return $this->amount; + } } diff --git a/src/Resources/TransactionTypes/Authorization.php b/src/Resources/TransactionTypes/Authorization.php index 6936b8b8..4df8d113 100755 --- a/src/Resources/TransactionTypes/Authorization.php +++ b/src/Resources/TransactionTypes/Authorization.php @@ -62,25 +62,6 @@ public function __construct(?float $amount = null, ?string $currency = null, ?st $this->setReturnUrl($returnUrl); } - /** - * @return float|null - */ - public function getAmount(): ?float - { - return $this->amount; - } - - /** - * @param float|null $amount - * - * @return self - */ - public function setAmount(?float $amount): self - { - $this->amount = $amount !== null ? round($amount, 4) : null; - return $this; - } - /** * @return float|null */ diff --git a/src/Resources/TransactionTypes/Cancellation.php b/src/Resources/TransactionTypes/Cancellation.php index ad41d1c2..51f0d86e 100755 --- a/src/Resources/TransactionTypes/Cancellation.php +++ b/src/Resources/TransactionTypes/Cancellation.php @@ -16,13 +16,6 @@ */ class Cancellation extends AbstractTransactionType { - /** - * The cancellation amount will be transferred as grossAmount in case of Installment Secured payment type. - * - * @var float $amount - */ - protected $amount; - /** @var string $reasonCode */ protected $reasonCode; @@ -53,29 +46,6 @@ public function __construct(?float $amount = null) $this->setAmount($amount); } - /** - * Returns the cancellationAmount (equals grossAmount in case of Installment Secured). - * - * @return float|null - */ - public function getAmount(): ?float - { - return $this->amount; - } - - /** - * Sets the cancellationAmount (equals grossAmount in case of Installment Secured). - * - * @param float|null $amount - * - * @return Cancellation - */ - public function setAmount(?float $amount): self - { - $this->amount = $amount !== null ? round($amount, 4) : null; - return $this; - } - /** * Returns the reason code of the cancellation if set. * diff --git a/src/Resources/TransactionTypes/Charge.php b/src/Resources/TransactionTypes/Charge.php index 278cc9c3..338bca79 100755 --- a/src/Resources/TransactionTypes/Charge.php +++ b/src/Resources/TransactionTypes/Charge.php @@ -25,9 +25,6 @@ class Charge extends AbstractTransactionType use HasDescriptor; use HasChargebacks; - /** @var float $amount */ - protected $amount; - /** @var string $currency */ protected $currency; @@ -54,25 +51,6 @@ public function __construct(?float $amount = null, ?string $currency = null, ?st $this->setReturnUrl($returnUrl); } - /** - * @return float|null - */ - public function getAmount(): ?float - { - return $this->amount; - } - - /** - * @param float|null $amount - * - * @return self - */ - public function setAmount(?float $amount): self - { - $this->amount = $amount !== null ? round($amount, 4) : null; - return $this; - } - /** * @return float|null */ diff --git a/src/Resources/TransactionTypes/Chargeback.php b/src/Resources/TransactionTypes/Chargeback.php index 2ef22d03..607ebe7a 100644 --- a/src/Resources/TransactionTypes/Chargeback.php +++ b/src/Resources/TransactionTypes/Chargeback.php @@ -12,9 +12,6 @@ */ class Chargeback extends AbstractTransactionType { - /** @var float $amount */ - protected $amount; - /** @var string $currency */ protected $currency; @@ -29,27 +26,6 @@ public function __construct(?float $amount = null) $this->setAmount($amount); } - /** - * @return float|null - */ - public function getAmount(): ?float - { - return $this->amount; - } - - /** - * Sets the cancellationAmount (equals grossAmount in case of Installment Secured). - * - * @param float|null $amount - * - * @return Cancellation - */ - public function setAmount(?float $amount): self - { - $this->amount = $amount !== null ? round($amount, 4) : null; - return $this; - } - /** * @return string */ diff --git a/src/Resources/TransactionTypes/Payout.php b/src/Resources/TransactionTypes/Payout.php index 15f0767c..2f39d5a0 100644 --- a/src/Resources/TransactionTypes/Payout.php +++ b/src/Resources/TransactionTypes/Payout.php @@ -12,9 +12,6 @@ */ class Payout extends AbstractTransactionType { - /** @var float|null $amount */ - protected $amount; - /** @var string|null $currency */ protected $currency; @@ -31,25 +28,6 @@ public function __construct(?float $amount = null, ?string $currency = null, $re $this->setReturnUrl($returnUrl); } - /** - * @return float|null - */ - public function getAmount(): ?float - { - return $this->amount; - } - - /** - * @param float|null $amount - * - * @return self - */ - public function setAmount(?float $amount): self - { - $this->amount = $amount !== null ? round($amount, 4) : null; - return $this; - } - /** * @return string|null */ diff --git a/src/Resources/TransactionTypes/Sca.php b/src/Resources/TransactionTypes/Sca.php index 3f503437..7c6b2a18 100644 --- a/src/Resources/TransactionTypes/Sca.php +++ b/src/Resources/TransactionTypes/Sca.php @@ -4,7 +4,6 @@ use UnzerSDK\Adapter\HttpAdapterInterface; use UnzerSDK\Traits\HasAccountInformation; -use UnzerSDK\Traits\HasCancellations; use UnzerSDK\Traits\HasDescriptor; use UnzerSDK\Traits\HasRecurrenceType; @@ -16,14 +15,10 @@ */ class Sca extends AbstractTransactionType { - use HasCancellations; use HasRecurrenceType; use HasAccountInformation; use HasDescriptor; - /** @var float $amount */ - protected $amount; - /** @var string $currency */ protected $currency; @@ -50,41 +45,6 @@ public function __construct(?float $amount = null, ?string $currency = null, ?st $this->setReturnUrl($returnUrl); } - /** - * @return float|null - */ - public function getAmount(): ?float - { - return $this->amount; - } - - /** - * @param float|null $amount - * - * @return self - */ - public function setAmount(?float $amount): self - { - $this->amount = $amount !== null ? round($amount, 4) : null; - return $this; - } - - /** - * @return float|null - */ - public function getCancelledAmount(): ?float - { - $amount = 0.0; - foreach ($this->getCancellations() as $cancellation) { - /** @var Cancellation $cancellation */ - if ($cancellation->isSuccess()) { - $amount += $cancellation->getAmount(); - } - } - - return $amount; - } - /** * @return string|null */ diff --git a/src/Resources/TransactionTypes/Shipment.php b/src/Resources/TransactionTypes/Shipment.php index 55dfbc7d..d29e09cc 100755 --- a/src/Resources/TransactionTypes/Shipment.php +++ b/src/Resources/TransactionTypes/Shipment.php @@ -12,28 +12,6 @@ */ class Shipment extends AbstractTransactionType { - /** @var float|null $amount */ - protected $amount; - - /** - * @return float|null - */ - public function getAmount(): ?float - { - return $this->amount; - } - - /** - * @param float|null $amount - * - * @return Shipment - */ - public function setAmount(?float $amount): self - { - $this->amount = $amount !== null ? round($amount, 4) : null; - return $this; - } - /** * {@inheritDoc} */ From 6c73bcff5009dab9a83c9913859a21b8d85586f3 Mon Sep 17 00:00:00 2001 From: "david.owusu" Date: Thu, 20 Nov 2025 16:32:25 +0100 Subject: [PATCH 4/4] [CC-2973] Cleanup unit tests. --- .../Resources/TransactionTypes/ScaTest.php | 254 +----------------- 1 file changed, 12 insertions(+), 242 deletions(-) diff --git a/test/unit/Resources/TransactionTypes/ScaTest.php b/test/unit/Resources/TransactionTypes/ScaTest.php index 54866dcf..b587880c 100644 --- a/test/unit/Resources/TransactionTypes/ScaTest.php +++ b/test/unit/Resources/TransactionTypes/ScaTest.php @@ -11,262 +11,32 @@ namespace UnzerSDK\test\unit\Resources\TransactionTypes; -use RuntimeException; -use stdClass; -use UnzerSDK\Resources\Payment; -use UnzerSDK\Resources\TransactionTypes\Authorization; -use UnzerSDK\Resources\TransactionTypes\Cancellation; -use UnzerSDK\Resources\TransactionTypes\Charge; use UnzerSDK\Resources\TransactionTypes\Sca; use UnzerSDK\test\BasePaymentTest; -use UnzerSDK\Unzer; class ScaTest extends BasePaymentTest { - - /** - * Verify that an SCA can be updated on handle response. - * - * @test - */ - public function aScaShouldBeUpdatedThroughResponseHandling(): void - { - $sca = new Sca(); - $this->assertNull($sca->getAmount()); - $this->assertNull($sca->getCurrency()); - $this->assertNull($sca->getReturnUrl()); - $this->assertNull($sca->getIban()); - $this->assertNull($sca->getBic()); - $this->assertNull($sca->getHolder()); - $this->assertNull($sca->getDescriptor()); - $this->assertNull($sca->getRecurrenceType()); - - $sca = new Sca(123.4, 'EUR', 'https://my-return-url.test'); - $this->assertEquals(123.4, $sca->getAmount()); - $this->assertEquals('EUR', $sca->getCurrency()); - $this->assertEquals('https://my-return-url.test', $sca->getReturnUrl()); - - $testResponse = new stdClass(); - $testResponse->amount = '789.0'; - $testResponse->currency = 'USD'; - $testResponse->returnUrl = 'https://return-url.test'; - $testResponse->Iban = 'DE89370400440532013000'; - $testResponse->Bic = 'COBADEFFXXX'; - $testResponse->Holder = 'Merchant Name'; - $testResponse->Descriptor = '4065.6865.6416'; - $testResponse->additionalTransactionData = (object)['card' => (object)['recurrenceType' => 'oneClick']]; - - $sca->handleResponse($testResponse); - $this->assertEquals(789.0, $sca->getAmount()); - $this->assertEquals('USD', $sca->getCurrency()); - $this->assertEquals('https://return-url.test', $sca->getReturnUrl()); - $this->assertEquals('DE89370400440532013000', $sca->getIban()); - $this->assertEquals('COBADEFFXXX', $sca->getBic()); - $this->assertEquals('Merchant Name', $sca->getHolder()); - $this->assertEquals('4065.6865.6416', $sca->getDescriptor()); - } - - /** - * Verify response with empty account data can be handled. - * - * @test - */ - public function verifyResponseWithEmptyAccountDataCanBeHandled() - { - $sca = new Sca(); - - $testResponse = new stdClass(); - $testResponse->Iban = ''; - $testResponse->Bic = ''; - $testResponse->Holder = ''; - $testResponse->Descriptor = ''; - - $sca->handleResponse($testResponse); - $this->assertNull($sca->getIban()); - $this->assertNull($sca->getBic()); - $this->assertNull($sca->getHolder()); - $this->assertNull($sca->getDescriptor()); - } - - /** - * Verify charge throws exception if payment is not set. - * - * @test - * - * @dataProvider chargeValueProvider - * - * @param float|null $value - */ - public function chargeShouldThrowExceptionIfPaymentIsNotSet($value): void - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Payment object is missing. Try fetching the object first!'); - - $sca = new Sca(); - $sca->charge($value); - } - - /** - * Verify charge() calls chargeScaTransaction() on Unzer object with the given amount. - * - * @test - */ - public function chargeShouldCallChargeScaTransactionOnUnzerObject(): void - { - $unzerMock = $this->getMockBuilder(Unzer::class) - ->disableOriginalConstructor() - ->setMethods(['chargeScaTransaction']) - ->getMock(); - /** @var Unzer $unzerMock */ - $payment = (new Payment())->setParentResource($unzerMock)->setId('myPayment'); - - $sca = new Sca(100.0, 'EUR', 'https://return-url.test'); - $sca->setPayment($payment); - $sca->setParentResource($unzerMock); - $sca->setId('s-sca-123'); - - $unzerMock->expects($this->exactly(2)) - ->method('chargeScaTransaction')->willReturn(new Charge()) - ->withConsecutive( - [$this->identicalTo($payment), 's-sca-123', $this->isNull()], - [$this->identicalTo($payment), 's-sca-123', 50.0] - ); - - $sca->charge(); - $sca->charge(50.0); - } - - /** - * Verify authorize throws exception if payment is not set. - * - * @test - * - * @dataProvider authorizeValueProvider - * - * @param float|null $value - */ - public function authorizeShouldThrowExceptionIfPaymentIsNotSet($value): void - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Payment object is missing. Try fetching the object first!'); - - $sca = new Sca(); - $sca->authorize($value); - } - /** - * Verify authorize() calls authorizeScaTransaction() on Unzer object with the given amount. - * * @test */ - public function authorizeShouldCallAuthorizeScaTransactionOnUnzerObject(): void + public function constructorTest() { - $unzerMock = $this->getMockBuilder(Unzer::class) - ->disableOriginalConstructor() - ->setMethods(['authorizeScaTransaction']) - ->getMock(); - /** @var Unzer $unzerMock */ - $payment = (new Payment())->setParentResource($unzerMock)->setId('myPayment'); - - $sca = new Sca(100.0, 'EUR', 'https://return-url.test'); - $sca->setPayment($payment); - $sca->setParentResource($unzerMock); - $sca->setId('s-sca-123'); - - $unzerMock->expects($this->exactly(2)) - ->method('authorizeScaTransaction')->willReturn(new Authorization()) - ->withConsecutive( - [$this->identicalTo($payment), 's-sca-123', $this->isNull()], - [$this->identicalTo($payment), 's-sca-123', 75.0] - ); - - $sca->authorize(); - $sca->authorize(75.0); + $sca = new Sca(99.99, "EUR", "https://return-url.com"); + $this->assertEquals(99.99, $sca->getAmount()); + $this->assertEquals("EUR", $sca->getCurrency()); + $this->assertEquals("https://return-url.com", $sca->getReturnUrl()); } /** - * Verify getter for cancelled amount. - * * @test */ - public function getCancelledAmountReturnsTheCancelledAmount(): void + public function getterSetterTest() { - $sca = new Sca(); - $this->assertEquals(0.0, $sca->getCancelledAmount()); - - $sca = new Sca(123.4, 'EUR', 'https://my-return-url.test'); - $this->assertEquals(0.0, $sca->getCancelledAmount()); - - $cancellationJson = '{ - "type": "cancel-sca", - "status": "success", - "amount": "10" - }'; - - $cancellation1 = new Cancellation(); - $cancellation1->handleResponse(json_decode($cancellationJson)); - $sca->addCancellation($cancellation1); - $this->assertEquals(10.0, $sca->getCancelledAmount()); - - $cancellation2 = new Cancellation(); - $cancellation2->handleResponse(json_decode($cancellationJson)); - $sca->addCancellation($cancellation2); - $this->assertEquals(20.0, $sca->getCancelledAmount()); + $sca = new Sca(99.99, "EUR", "https://return-url.com"); + $sca->setPaymentReference('reference') + ->setCard3ds(false); + $this->assertEquals(99.99, $sca->getAmount()); + $this->assertEquals("EUR", $sca->getCurrency()); + $this->assertEquals("https://return-url.com", $sca->getReturnUrl()); } - - /** - * Verify authorizations and charges can be added. - * - * @test - */ - public function authorizationsAndChargesCanBeAdded(): void - { - $sca = new Sca(100.0, 'EUR', 'https://return-url.test'); - - $this->assertEmpty($sca->getAuthorizations()); - $this->assertEmpty($sca->getCharges()); - - $authorization = new Authorization(50.0, 'EUR', 'https://return-url.test'); - $sca->addAuthorization($authorization); - - $charge = new Charge(50.0, 'EUR', 'https://return-url.test'); - $sca->addCharge($charge); - - $this->assertCount(1, $sca->getAuthorizations()); - $this->assertCount(1, $sca->getCharges()); - $this->assertSame($authorization, $sca->getAuthorizations()[0]); - $this->assertSame($charge, $sca->getCharges()[0]); - } - - // - - /** - * Provide different amounts for charge - * - * @return array - */ - public function chargeValueProvider(): array - { - return [ - 'Amount = null' => [null], - 'Amount = 0.0' => [0.0], - 'Amount = 123.8' => [123.8] - ]; - } - - /** - * Provide different amounts for authorize - * - * @return array - */ - public function authorizeValueProvider(): array - { - return [ - 'Amount = null' => [null], - 'Amount = 0.0' => [0.0], - 'Amount = 123.8' => [123.8] - ]; - } - - // }