Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CC-1576/preauth #201

Merged
merged 8 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres
to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [3.11.0](https://github.com/unzerdev/php-sdk/compare/3.10.0..3.11.0)

### Added

* Add support for `preauthorize` transaction.

## [3.10.0](https://github.com/unzerdev/php-sdk/compare/3.9.0..3.10.0)

### Added
Expand Down
3 changes: 2 additions & 1 deletion src/Constants/IdStrings.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ class IdStrings
public const AUTHORIZE = 'aut';
public const CANCEL = 'cnl';
public const CHARGE = 'chg';
public const CHARGEBACK = 'cbk';
public const PAYOUT = 'out';
public const PREAUTHORIZE = 'preaut';
public const SHIPMENT = 'shp';
public const CHARGEBACK = 'cbk';

// Payment Types
public const ALIPAY = 'ali';
Expand Down
1 change: 1 addition & 0 deletions src/Constants/TransactionTypes.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
class TransactionTypes
{
public const AUTHORIZATION = 'authorize';
public const PREAUTHORIZATION = 'preauthorize';
public const CHARGE = 'charge';
public const REVERSAL = 'cancel-authorize';
public const REFUND = 'cancel-charge';
Expand Down
16 changes: 16 additions & 0 deletions src/Constants/WebhookEvents.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ class WebhookEvents
public const AUTHORIZE_RESUMED = 'authorize.resumed';
public const AUTHORIZE_SUCCEEDED = 'authorize.succeeded';

// preauthorize events
public const PREAUTHORIZE = 'preauthorize';
public const PREAUTHORIZE_CANCELED = 'preauthorize.canceled';
public const PREAUTHORIZE_EXPIRED = 'preauthorize.expired';
public const PREAUTHORIZE_FAILED = 'preauthorize.failed';
public const PREAUTHORIZE_PENDING = 'preauthorize.pending';
public const PREAUTHORIZE_RESUMED = 'preauthorize.resumed';
public const PREAUTHORIZE_SUCCEEDED = 'preauthorize.succeeded';

// charge events
public const CHARGE = 'charge';
public const CHARGE_CANCELED = 'charge.canceled';
Expand Down Expand Up @@ -69,6 +78,13 @@ class WebhookEvents
self::AUTHORIZE_PENDING,
self::AUTHORIZE_RESUMED,
self::AUTHORIZE_SUCCEEDED,
self::PREAUTHORIZE,
self::PREAUTHORIZE_CANCELED,
self::PREAUTHORIZE_EXPIRED,
self::PREAUTHORIZE_FAILED,
self::PREAUTHORIZE_PENDING,
self::PREAUTHORIZE_RESUMED,
self::PREAUTHORIZE_SUCCEEDED,
self::CHARGE,
self::CHARGE_CANCELED,
self::CHARGE_EXPIRED,
Expand Down
30 changes: 27 additions & 3 deletions src/Resources/Payment.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace UnzerSDK\Resources;

use RuntimeException;
use stdClass;
use UnzerSDK\Adapter\HttpAdapterInterface;
use UnzerSDK\Constants\CancelReasonCodes;
use UnzerSDK\Constants\IdStrings;
Expand All @@ -16,15 +18,13 @@
use UnzerSDK\Resources\TransactionTypes\Charge;
use UnzerSDK\Resources\TransactionTypes\Chargeback;
use UnzerSDK\Resources\TransactionTypes\Payout;
use UnzerSDK\Resources\TransactionTypes\PreAuthorization;
use UnzerSDK\Resources\TransactionTypes\Shipment;
use UnzerSDK\Services\IdService;
use UnzerSDK\Traits\HasInvoiceId;
use UnzerSDK\Traits\HasOrderId;
use UnzerSDK\Traits\HasPaymentState;
use UnzerSDK\Traits\HasTraceId;
use RuntimeException;
use stdClass;

use function is_string;

/**
Expand Down Expand Up @@ -903,6 +903,9 @@ private function updateResponseTransactions(array $transactions = []): void
case TransactionTypes::AUTHORIZATION:
$this->updateAuthorizationTransaction($transaction);
break;
case TransactionTypes::PREAUTHORIZATION:
$this->updatePreAuthorizationTransaction($transaction);
break;
case TransactionTypes::CHARGE:
$this->updateChargeTransaction($transaction);
break;
Expand Down Expand Up @@ -994,6 +997,27 @@ private function updateAuthorizationTransaction(stdClass $transaction): void
$authorization->handleResponse($transaction);
}

/**
* This updates the local Authorization object referenced by this Payment with the given Authorization transaction
* from the Payment response.
*
* @param stdClass $transaction The transaction from the Payment response containing the Authorization 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 updatePreAuthorizationTransaction(stdClass $transaction): void
{
$transactionId = IdService::getResourceIdFromUrl($transaction->url, IdStrings::PREAUTHORIZE);
$authorization = $this->getAuthorization(true);
if (!$authorization instanceof Authorization) {
$authorization = (new PreAuthorization())->setPayment($this)->setId($transactionId);
$this->setAuthorization($authorization);
}

$authorization->handleResponse($transaction);
}

/**
* This updates the local Charge object referenced by this Payment with the given Charge transaction from the
* Payment response.
Expand Down
22 changes: 22 additions & 0 deletions src/Resources/TransactionTypes/PreAuthorization.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace UnzerSDK\Resources\TransactionTypes;

use UnzerSDK\Adapter\HttpAdapterInterface;

/**
* This represents the pre-authorization transaction.
*
* @link https://docs.unzer.com/
*
*/
class PreAuthorization extends Authorization
{
/**
* {@inheritDoc}
*/
protected function getResourcePath(string $httpMethod = HttpAdapterInterface::REQUEST_GET): string
{
return 'preauthorize';
}
}
10 changes: 5 additions & 5 deletions src/Services/PaymentService.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,27 @@
namespace UnzerSDK\Services;

use DateTime;
use RuntimeException;
use UnzerSDK\Constants\TransactionTypes;
use UnzerSDK\Exceptions\UnzerApiException;
use UnzerSDK\Resources\EmbeddedResources\Paylater\InstallmentPlansQuery;
use UnzerSDK\Resources\PaylaterInstallmentPlans;
use UnzerSDK\Resources\PaymentTypes\PaylaterInstallment;
use UnzerSDK\Unzer;
use UnzerSDK\Interfaces\PaymentServiceInterface;
use UnzerSDK\Resources\AbstractUnzerResource;
use UnzerSDK\Resources\Basket;
use UnzerSDK\Resources\Customer;
use UnzerSDK\Resources\EmbeddedResources\Paylater\InstallmentPlansQuery;
use UnzerSDK\Resources\InstalmentPlans;
use UnzerSDK\Resources\Metadata;
use UnzerSDK\Resources\PaylaterInstallmentPlans;
use UnzerSDK\Resources\Payment;
use UnzerSDK\Resources\PaymentTypes\BasePaymentType;
use UnzerSDK\Resources\PaymentTypes\InstallmentSecured;
use UnzerSDK\Resources\PaymentTypes\PaylaterInstallment;
use UnzerSDK\Resources\PaymentTypes\Paypage;
use UnzerSDK\Resources\TransactionTypes\Authorization;
use UnzerSDK\Resources\TransactionTypes\Charge;
use UnzerSDK\Resources\TransactionTypes\Payout;
use UnzerSDK\Resources\TransactionTypes\Shipment;
use RuntimeException;
use UnzerSDK\Unzer;

/**
* This service provides for functionalities concerning payment transactions.
Expand Down
3 changes: 3 additions & 0 deletions src/Services/ResourceService.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,9 @@ public function fetchResourceByUrl($url)
case $resourceType === IdStrings::AUTHORIZE:
$resource = $unzer->fetchAuthorization(IdService::getResourceIdFromUrl($url, IdStrings::PAYMENT));
break;
case $resourceType === IdStrings::PREAUTHORIZE:
$resource = $unzer->fetchAuthorization(IdService::getResourceIdFromUrl($url, IdStrings::PAYMENT));
break;
case $resourceType === IdStrings::CHARGE:
$resource = $unzer->fetchChargeById(
IdService::getResourceIdFromUrl($url, IdStrings::PAYMENT),
Expand Down
5 changes: 4 additions & 1 deletion src/Unzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class Unzer implements
public const BASE_URL = 'api.unzer.com';
public const API_VERSION = 'v1';
public const SDK_TYPE = 'UnzerPHP';
public const SDK_VERSION = '3.10.0';
public const SDK_VERSION = '3.11.0';

/** @var string $key */
private $key;
Expand Down Expand Up @@ -685,6 +685,9 @@ public function performAuthorization(
return $this->paymentService->performAuthorization($authorization, $paymentType, $customer, $metadata, $basket);
}

/**
* {@inheritDoc}
*/
public function updateAuthorization($payment, Authorization $authorization): Authorization
{
return $this->paymentService->updateAuthorization($payment, $authorization);
Expand Down
182 changes: 182 additions & 0 deletions test/integration/TransactionTypes/PreAuthorizationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
<?php

/** @noinspection PhpUnhandledExceptionInspection */
/** @noinspection PhpDocMissingThrowsInspection */
/**
* This class defines integration tests to verify interface and
* functionality of the authorization transaction type.
*
* @link https://docs.unzer.com/
*
*/

namespace UnzerSDK\test\integration\TransactionTypes;

use UnzerSDK\Constants\RecurrenceTypes;
use UnzerSDK\Resources\Metadata;
use UnzerSDK\Resources\PaymentTypes\Card;
use UnzerSDK\Resources\PaymentTypes\Paypal;
use UnzerSDK\Resources\TransactionTypes\Authorization;
use UnzerSDK\Resources\TransactionTypes\PreAuthorization;
use UnzerSDK\test\BaseIntegrationTest;

/**
* @group CC-1576
*/
class PreAuthorizationTest extends BaseIntegrationTest
{
/**
* Verify Unzer object can perform an authorization based on the paymentTypeId.
*
* @test
*/
public function authorizeWithTypeId(): void
{
$paymentType = $this->unzer->createPaymentType($this->createCardObject());
$preauth = new PreAuthorization(100.0, 'EUR', self::RETURN_URL);
$this->unzer->performAuthorization($preauth, $paymentType->getId());
$this->assertNotNull($preauth);
$this->assertNotEmpty($preauth->getId());
$this->assertNotEmpty($preauth->getUniqueId());
$this->assertNotEmpty($preauth->getShortId());

$traceId = $preauth->getTraceId();
$this->assertNotEmpty($traceId);
$this->assertSame($traceId, $preauth->getPayment()->getTraceId());
$this->assertPending($preauth);
}

/**
* Verify authorization produces Payment and Customer.
*
* @test
*/
public function authorizationProducesPaymentAndCustomer(): void
{
$paymentType = $this->unzer->createPaymentType($this->createCardObject());

$customer = $this->getMinimalCustomer();
$this->assertNull($customer->getId());

$preauth = new PreAuthorization(100.0, 'EUR', self::RETURN_URL);
$this->unzer->performAuthorization($preauth, $paymentType, $customer);
$payment = $preauth->getPayment();
$this->assertNotNull($payment);
$this->assertNotNull($payment->getId());

$newCustomer = $payment->getCustomer();
$this->assertNotNull($newCustomer);
$this->assertNotNull($newCustomer->getId());
}

/**
* Verify authorization with customer Id.
*
* @test
*
* @return Authorization
*/
public function authorizationWithCustomerId(): Authorization
{
$paymentType = $this->unzer->createPaymentType($this->createCardObject());

$customerId = $this->unzer->createCustomer($this->getMinimalCustomer())->getId();
$orderId = microtime(true);
$preauth = (new PreAuthorization(100.0, 'EUR', self::RETURN_URL))->setOrderId($orderId);
$this->unzer->performAuthorization($preauth, $paymentType, $customerId);
$payment = $preauth->getPayment();
$this->assertNotNull($payment);
$this->assertNotNull($payment->getId());

$newCustomer = $payment->getCustomer();
$this->assertNotNull($newCustomer);
$this->assertNotNull($newCustomer->getId());

return $preauth;
}

/**
* Verify authorization can be fetched.
*
* @depends authorizationWithCustomerId
*
* @test
*
* @param Authorization $authorization
*/
public function authorizationCanBeFetched(Authorization $authorization): void
{
$fetchedAuthorization = $this->unzer->fetchAuthorization($authorization->getPaymentId());
$this->assertInstanceOf(PreAuthorization::class, $fetchedAuthorization);
$this->assertEquals($authorization->setCard3ds(true)->expose(), $fetchedAuthorization->expose());
}


/**
* Verify authorize accepts all parameters.
*
* @test
*/
public function requestAuthorizationShouldAcceptAllParameters(): void
{
/** @var Card $card */
$card = $this->unzer->createPaymentType($this->createCardObject());
$customer = $this->getMinimalCustomer();
$orderId = 'o' . self::generateRandomId();
$metadata = (new Metadata())->addMetadata('key', 'value');
$basket = $this->createBasket();
$invoiceId = 'i' . self::generateRandomId();
$paymentReference = 'paymentReference';

$preauth = new PreAuthorization(119.0, 'EUR', self::RETURN_URL);
$preauth->setRecurrenceType(RecurrenceTypes::ONE_CLICK, $card)
->setOrderId($orderId)
->setInvoiceId($invoiceId)
->setPaymentReference($paymentReference);

$preauth = $this->unzer->performAuthorization($preauth, $card, $customer, $metadata, $basket);
$payment = $preauth->getPayment();

$this->assertSame($card, $payment->getPaymentType());
$this->assertEquals(119.0, $preauth->getAmount());
$this->assertEquals('EUR', $preauth->getCurrency());
$this->assertEquals(self::RETURN_URL, $preauth->getReturnUrl());
$this->assertSame($customer, $payment->getCustomer());
$this->assertEquals($orderId, $preauth->getOrderId());
$this->assertSame($metadata, $payment->getMetadata());
$this->assertSame($basket, $payment->getBasket());
$this->assertTrue($preauth->isCard3ds());
$this->assertEquals($invoiceId, $preauth->getInvoiceId());
$this->assertEquals($paymentReference, $preauth->getPaymentReference());

$fetchedAuthorize = $this->unzer->fetchAuthorization($preauth->getPaymentId());
$fetchedPayment = $fetchedAuthorize->getPayment();

$this->assertEquals($payment->getPaymentType()->expose(), $fetchedPayment->getPaymentType()->expose());
$this->assertEquals($preauth->getAmount(), $fetchedAuthorize->getAmount());
$this->assertEquals($preauth->getCurrency(), $fetchedAuthorize->getCurrency());
$this->assertEquals($preauth->getReturnUrl(), $fetchedAuthorize->getReturnUrl());
$this->assertEquals($payment->getCustomer()->expose(), $fetchedPayment->getCustomer()->expose());
$this->assertEquals($preauth->getOrderId(), $fetchedAuthorize->getOrderId());
$this->assertEquals($payment->getMetadata()->expose(), $fetchedPayment->getMetadata()->expose());
$this->assertEquals($payment->getBasket()->expose(), $fetchedPayment->getBasket()->expose());
$this->assertEquals($preauth->isCard3ds(), $fetchedAuthorize->isCard3ds());
$this->assertEquals($preauth->getInvoiceId(), $fetchedAuthorize->getInvoiceId());
$this->assertEquals($preauth->getPaymentReference(), $fetchedAuthorize->getPaymentReference());
}

//<editor-fold desc="Data Providers">

/**
* @return array
*/
public function authorizeHasExpectedStatesDP(): array
{
return [
'card' => [$this->createCardObject(), 'pending'],
'paypal' => [new Paypal(), 'pending']
];
}

//</editor-fold>
}
Loading
Loading