wikimedia/wikimedia-fundraising-SmashPig

View on GitHub
PaymentProviders/dlocal/Tests/phpunit/CardPaymentProviderTest.php

Summary

Maintainability
F
6 days
Test Coverage
<?php

namespace SmashPig\PaymentProviders\dlocal\Tests;

use SmashPig\Core\ApiException;
use SmashPig\PaymentData\ErrorCode;
use SmashPig\PaymentData\FinalStatus;
use SmashPig\PaymentProviders\dlocal\Api;
use SmashPig\PaymentProviders\dlocal\CardPaymentProvider;
use SmashPig\PaymentProviders\dlocal\ErrorMapper;
use SmashPig\Tests\BaseSmashPigUnitTestCase;

/**
 * @group Dlocal
 */
class CardPaymentProviderTest extends BaseSmashPigUnitTestCase {

    protected $api;

    public function setUp(): void {
        parent::setUp();
        $providerConfig = $this->setProviderConfiguration( 'dlocal' );
        $this->api = $this->getMockBuilder( Api::class )
                ->disableOriginalConstructor()
                ->getMock();
        $providerConfig->overrideObjectInstance( 'api', $this->api );
    }

    public function testPaymentWithIncompleteParams(): void {
        $request = [
                "payment_token" => "fake-token",
                "order_id" => '123.3',
                "amount" => '1.00',
                "currency" => "USD"
        ];

        $provider = new CardPaymentProvider();
        $response = $provider->createPayment( $request );
        $validationError = $response->getValidationErrors();
        $this->assertTrue( count( $validationError ) > 0 );
        $this->assertFalse( $response->isSuccessful() );
    }

    public function testPaymentWithCompleteParamsSuccess(): void {
        $params = $this->getCreatePaymentRequestParams();
        $gateway_txn_id = "PAY2323243343543";
        $this->api->expects( $this->once() )
            ->method( 'cardAuthorizePayment' )
            ->with( $params )
            ->willReturn( [
                        "id" => $gateway_txn_id,
                        "amount" => 1,
                        "currency" => "ZAR",
                        "country" => "SA",
                        "payment_method_id" => "CARD",
                        "payment_method_type" => "CARD",
                        "payment_method_flow" => "DIRECT",
                        "card" => [
                            "holder_name" => "Lorem Ipsum",
                            "expiration_month" => 10,
                            "expiration_year" => 2040,
                            "last4" => "1111",
                            "brand" => "VI"
                        ],
                          "created_date" => "2018-02-15T15:14:52-00:00",
                          "approved_date" => "2018-02-15T15:14:52-00:00",
                          "status" => "AUTHORIZED",
                          "status_code" => "200",
                          "status_detail" => "The payment was paid.",
                          "order_id" => $params['order_id'],
                ] );

        $provider = new CardPaymentProvider();
        $response = $provider->createPayment( $params );
        $validationError = $response->getValidationErrors();
        $this->assertCount( 0, $validationError );
        $this->assertTrue( $response->isSuccessful() );
        $this->assertEquals( $response->getGatewayTxnId(), $gateway_txn_id );
        $this->assertEquals( FinalStatus::PENDING_POKE, $response->getStatus() );
        $this->assertEquals( 'visa', $response->getPaymentSubmethod() );
    }

    public function testPaymentWithCompleteParamsFail(): void {
        $params = $this->getCreatePaymentRequestParams();
        $gateway_txn_id = "PAY2323243343543";
        $this->api->expects( $this->once() )
            ->method( 'cardAuthorizePayment' )
            ->with( $params )
            ->willReturn( [
                        "id" => $gateway_txn_id,
                        "amount" => 1,
                        "currency" => "ZAR",
                        "country" => "SA",
                        "payment_method_id" => "CARD",
                        "payment_method_type" => "CARD",
                        "payment_method_flow" => "DIRECT",
                        "card" => [
                                "holder_name" => "Lorem Ipsum",
                                "expiration_month" => 10,
                                "expiration_year" => 2040,
                                "last4" => "1111",
                                "brand" => "VI"
                        ],
                        "created_date" => "2018-02-15T15:14:52-00:00",
                        "approved_date" => "2018-02-15T15:14:52-00:00",
                        "status" => "REJECTED",
                        "status_code" => "300",
                        "status_detail" => "The payment was rejected",
                        "order_id" => $params['order_id'],
                ] );

        $provider = new CardPaymentProvider();
        $response = $provider->createPayment( $params );
        $error = $response->getErrors();
        $this->assertCount( 1, $error );
        $this->assertFalse( $response->isSuccessful() );
        $this->assertEquals( $response->getGatewayTxnId(), $gateway_txn_id );
        $this->assertEquals( FinalStatus::FAILED, $response->getStatus() );
        $this->assertEquals( 'visa', $response->getPaymentSubmethod() );
    }

    public function testPaymentWithCompleteParamsPending(): void {
        $params = $this->getCreatePaymentRequestParams();
        $gateway_txn_id = "PAY2323243343543";
        $this->api->expects( $this->once() )
            ->method( 'cardAuthorizePayment' )
            ->with( $params )
            ->willReturn( [
                        "id" => $gateway_txn_id,
                        "amount" => 1,
                        "currency" => "ZAR",
                        "country" => "SA",
                        "payment_method_id" => "CARD",
                        "payment_method_type" => "CARD",
                        "payment_method_flow" => "DIRECT",
                        "card" => [
                                "holder_name" => "Lorem Ipsum",
                                "expiration_month" => 10,
                                "expiration_year" => 2040,
                                "last4" => "1111",
                                "brand" => "VI"
                        ],
                        "created_date" => "2018-02-15T15:14:52-00:00",
                        "approved_date" => "2018-02-15T15:14:52-00:00",
                        "status" => "AUTHORIZED",
                        "status_code" => "100",
                        "status_detail" => "The payment is pending.",
                        "order_id" => $params['order_id'],
                ] );

        $provider = new CardPaymentProvider();
        $response = $provider->createPayment( $params );
        $error = $response->getErrors();
        $this->assertCount( 0, $error );
        $this->assertTrue( $response->isSuccessful() );
        $this->assertEquals( $response->getGatewayTxnId(), $gateway_txn_id );
        $this->assertEquals( FinalStatus::PENDING_POKE, $response->getStatus() );
        $this->assertEquals( 'visa', $response->getPaymentSubmethod() );
    }

    public function testPaymentWithCompleteParamsFailsDueToUnknownStatus(): void {
        $params = $this->getCreatePaymentRequestParams();
        $gateway_txn_id = "PAY2323243343543";
        $this->api->expects( $this->once() )
                ->method( 'cardAuthorizePayment' )
                ->with( $params )
                ->willReturn( [
                        "id" => $gateway_txn_id,
                        "amount" => 1,
                        "currency" => "ZAR",
                        "country" => "SA",
                        "payment_method_id" => "CARD",
                        "payment_method_type" => "CARD",
                        "payment_method_flow" => "DIRECT",
                        "card" => [
                                "holder_name" => "Lorem Ipsum",
                                "expiration_month" => 10,
                                "expiration_year" => 2040,
                                "last4" => "1111",
                                "brand" => "VI"
                        ],
                        "created_date" => "2018-02-15T15:14:52-00:00",
                        "approved_date" => "2018-02-15T15:14:52-00:00",
                        "status" => "UNKNOWN",
                        "status_code" => "300",
                        "status_detail" => "The payment was rejected.",
                        "order_id" => $params['order_id'],
                ] );

        $provider = new CardPaymentProvider();
        $response = $provider->createPayment( $params );
        $error = $response->getErrors();
        $this->assertCount( 1, $error );
        $this->assertFalse( $response->isSuccessful() );
        $this->assertEquals( $response->getGatewayTxnId(), $gateway_txn_id );
        $this->assertEquals( FinalStatus::UNKNOWN, $response->getStatus() );
    }

    public function testPaymentWithCompleteParamsFailsAndMissingStatusInResponse(): void {
        $params = $this->getCreatePaymentRequestParams();
        $errorCode = 5008;
        $errorMessage = "Token not found or inactive";
        $this->api->expects( $this->once() )
                ->method( 'cardAuthorizePayment' )
                ->with( $params )
                ->willReturn( [
                        "code" => $errorCode,
                        "message" => $errorMessage
                ] );

        $provider = new CardPaymentProvider();
        $response = $provider->createPayment( $params );
        $error = $response->getErrors();
        $this->assertCount( 1, $error );
        $this->assertEquals( 'Status element missing from dlocal response.', $error[0]->getDebugMessage() );
        $this->assertEquals( ErrorCode::MISSING_REQUIRED_DATA, $error[0]->getErrorCode() );
        $this->assertFalse( $response->isSuccessful() );
        $this->assertEquals( FinalStatus::UNKNOWN, $response->getStatus() );
    }

    public function testPaymentWithCompleteParams3DSecureEnabled(): void {
        $params = $this->getCreatePaymentRequestParams();
        $params['3DSecure'] = true;

        $this->api->expects( $this->once() )
                ->method( 'cardAuthorizePayment' )
                ->with( $params )
            ->willReturn( [
                "id" => "PAY2323243343543",
                "amount" => 1,
                "currency" => "ZAR",
                "country" => "SA",
                "payment_method_id" => "CARD",
                "payment_method_type" => "CARD",
                "payment_method_flow" => "DIRECT",
                "card" => [
                    "holder_name" => "Lorem Ipsum",
                    "expiration_month" => 10,
                    "expiration_year" => 2040,
                    "last4" => "1111",
                    "brand" => "VI"
                ],
                'three_dsecure' =>
                    [
                        'redirect_url' => 'https://www.example.com/3d-secure-redirect',
                    ],
                'created_date' => '2023-02-09T14:47:49.000+0000',
                'status' => 'PENDING',
                'status_detail' => 'The payment is pending for 3ds authorization.',
                'status_code' => '101',
                'order_id' => '657434343',
                'notification_url' => 'http://merchant.com/notifications',

            ] );

        $provider = new CardPaymentProvider();
        $response = $provider->createPayment( $params );
        $error = $response->getErrors();
        $this->assertCount( 0, $error );
        $this->assertTrue( $response->isSuccessful() );
        $this->assertEquals( FinalStatus::PENDING, $response->getStatus() );
        $this->assertEquals( "https://www.example.com/3d-secure-redirect", $response->getRedirectUrl() );
    }

    public function testApprovePaymentSuccess(): void {
        $params = [
            "gateway_txn_id" => "T-2486-91e73695-3e0a-4a77-8594-f2220f8c6515",
            'amount' => 100,
            'currency' => 'BRL',
            'order_id' => '1234512345',
        ];

        $this->api->expects( $this->once() )
            ->method( 'capturePayment' )
            ->with( $params )
            ->willReturn( [
                'id' => 'T-2486-aa9c1884-9f54-409a-9223-ede614b78173',
                'amount' => 100,
                'currency' => 'BRL',
                'country' => 'BR',
                'status' => 'PAID',
                'status_detail' => 'The payment was paid.',
                'status_code' => '200',
                'order_id' => '1234512345',
                'notification_url' => 'http://merchant.com/notifications',
                'authorization_id' => 'T-2486-91e73695-3e0a-4a77-8594-f2220f8c6515',
        ] );

        $cardPaymentProvider = new CardPaymentProvider();
        $approvePaymentResponse = $cardPaymentProvider->approvePayment( $params );
        $this->assertTrue( $approvePaymentResponse->isSuccessful() );
        $this->assertEquals( FinalStatus::COMPLETE, $approvePaymentResponse->getStatus() );
    }

    public function testPaymentWithCompleteParamsPendingRecurringSetToTrue(): void {
        $params = $this->getCreatePaymentRequestParams();
        $params['recurring'] = "1";
        $params['description'] = "Wikimedia Foundation (Recurring)";
        $gateway_txn_id = "PAY2323243343543";
        $card_id = "CID-e41c183d-2657-4e82-b39a-b0069c2af657";
        $this->api->expects( $this->once() )
            ->method( 'cardAuthorizePayment' )
            ->with( $params )
            ->willReturn( [
                "id" => $gateway_txn_id,
                "amount" => 1,
                "currency" => "ZAR",
                "country" => "SA",
                "payment_method_id" => "CARD",
                "payment_method_type" => "CARD",
                "payment_method_flow" => "DIRECT",
                "card" => [
                    "holder_name" => "Lorem Ipsum",
                    "expiration_month" => 10,
                    "expiration_year" => 2040,
                    "last4" => "1111",
                    "brand" => "VI",
                    "card_id" => $card_id
                ],
                "created_date" => "2018-02-15T15:14:52-00:00",
                "approved_date" => "2018-02-15T15:14:52-00:00",
                "status" => "AUTHORIZED",
                "status_code" => "100",
                "status_detail" => "The payment is pending.",
                "order_id" => $params['order_id'],
            ] );

        $provider = new CardPaymentProvider();
        $response = $provider->createPayment( $params );
        $error = $response->getErrors();
        $this->assertCount( 0, $error );
        $this->assertTrue( $response->isSuccessful() );
        $this->assertEquals( $response->getGatewayTxnId(), $gateway_txn_id );
        $this->assertEquals( $response->getRecurringPaymentToken(), $card_id );
        $this->assertEquals( FinalStatus::PENDING_POKE, $response->getStatus() );
        $this->assertEquals( 'visa', $response->getPaymentSubmethod() );
    }

    public function testPaymentWithFiscalNumberValidationError(): void {
        $params = $this->getCreatePaymentRequestParams();
        $exception = new ApiException();
        $exception->setRawErrors( [
            'code' => 5001,
            'message' => 'Invalid parameter: payer.document',
            'param' => 'payer.document',
        ] );
        $this->api->expects( $this->once() )
            ->method( 'cardAuthorizePayment' )
            ->with( $params )
            ->will( $this->throwException( $exception ) );
        $provider = new CardPaymentProvider();
        $response = $provider->createPayment( $params );
        $this->assertTrue( $response->hasErrors() );
        $errors = $response->getValidationErrors();
        $this->assertCount( 1, $errors );
        $this->assertSame( 'fiscal_number', $errors[0]->getField() );
    }

    public function testPaymentWithCompleteParamsAndRecurringPaymentToken(): void {
        $params = $this->getCreatePaymentRequestParams();
        unset( $params['payment_token'] );
        $card_id = "CID-e41c183d-2657-4e82-b39a-b0069c2af657";
        $params['recurring_payment_token'] = $card_id;
        $gateway_txn_id = "PAY2323243343543";
        $this->api->expects( $this->once() )
            ->method( 'makeRecurringCardPayment' )
            ->with( $params )
            ->willReturn( [
                "id" => $gateway_txn_id,
                "amount" => 1,
                "currency" => "ZAR",
                "country" => "SA",
                "payment_method_id" => "CARD",
                "payment_method_type" => "CARD",
                "payment_method_flow" => "DIRECT",
                "card" => [
                    "holder_name" => "Lorem Ipsum",
                    "expiration_month" => 10,
                    "expiration_year" => 2040,
                    "last4" => "1111",
                    "brand" => "VI",
                    "card_id" => $card_id
                ],
                "created_date" => "2018-02-15T15:14:52-00:00",
                "approved_date" => "2018-02-15T15:14:52-00:00",
                "status" => "PAID",
                "status_code" => "200",
                "status_detail" => "The payment was paid.",
                "order_id" => $params['order_id'],
            ] );

        $provider = new CardPaymentProvider();
        $response = $provider->createPayment( $params );
        $error = $response->getErrors();
        $this->assertCount( 0, $error );
        $this->assertTrue( $response->isSuccessful() );
        $this->assertEquals( $response->getGatewayTxnId(), $gateway_txn_id );
        $this->assertEquals( $response->getRecurringPaymentToken(), $card_id );
        $this->assertEquals( FinalStatus::COMPLETE, $response->getStatus() );
        $this->assertEquals( 'visa', $response->getPaymentSubmethod() );
    }

    public function testApprovePaymentFailMissingGatewayTxnId(): void {
        $params = [
            // gateway_txn_id is missing
            'amount' => 100,
            'currency' => 'BRL',
            'order_id' => '1234512345',
        ];

        $cardPaymentProvider = new CardPaymentProvider();
        $approvePaymentResponse = $cardPaymentProvider->approvePayment( $params );
        $this->assertFalse( $approvePaymentResponse->isSuccessful() );
        $this->assertEquals( FinalStatus::FAILED, $approvePaymentResponse->getStatus() );
        $this->assertCount( 1, $approvePaymentResponse->getValidationErrors() );
        $this->assertSame( 'gateway_txn_id', $approvePaymentResponse->getValidationErrors()[0]->getField() );
    }

    public function testApprovePaymentWithCompleteParamsFail(): void {
        $gateway_txn_id = "PAY2323243343543";
        $params = [
            'gateway_txn_id' => $gateway_txn_id,
            'amount' => 100,
            'currency' => 'BRL',
            'order_id' => '1234512345',
        ];

        $this->api->expects( $this->once() )
            ->method( 'capturePayment' )
            ->with( $params )
            ->willReturn( [
                        "id" => $gateway_txn_id,
                        "amount" => 1,
                        "currency" => "ZAR",
                        "country" => "SA",
                        "payment_method_id" => "CARD",
                        "payment_method_type" => "CARD",
                        "payment_method_flow" => "DIRECT",
                        "card" => [
                                "holder_name" => "Lorem Ipsum",
                                "expiration_month" => 10,
                                "expiration_year" => 2040,
                                "last4" => "1111",
                                "brand" => "VI"
                        ],
                        "created_date" => "2018-02-15T15:14:52-00:00",
                        "approved_date" => "2018-02-15T15:14:52-00:00",
                        "status" => "REJECTED",
                        "status_code" => "300",
                        "status_detail" => "The payment was rejected",
                        "order_id" => $params['order_id'],
                ] );

        $provider = new CardPaymentProvider();
        $response = $provider->approvePayment( $params );
        $error = $response->getErrors();
        $this->assertCount( 1, $error );
        $this->assertFalse( $response->isSuccessful() );
        $this->assertEquals( $response->getGatewayTxnId(), $gateway_txn_id );
        $this->assertEquals( FinalStatus::FAILED, $response->getStatus() );
    }

    public function testApprovePaymentWithCompleteParamsFailsAndMissingStatusInResponse(): void {
        $gateway_txn_id = "PAY2323243343543";
        $errorMessage = "placeholder text";
        $errorCode = 5008;
        $params = [
            'gateway_txn_id' => $gateway_txn_id,
            'amount' => 100,
            'currency' => 'BRL',
            'order_id' => '1234512345',
        ];
        $this->api->expects( $this->once() )
                ->method( 'capturePayment' )
                ->with( $params )
                ->willReturn( [
                        "code" => $errorCode,
                        "message" => $errorMessage
                ] );

        $provider = new CardPaymentProvider();
        $response = $provider->approvePayment( $params );
        $error = $response->getErrors();
        $this->assertCount( 1, $error );
        $this->assertEquals( "Status element missing from dlocal response.", $error[0]->getDebugMessage() );
        $this->assertEquals( ErrorCode::MISSING_REQUIRED_DATA, $error[0]->getErrorCode() );
        $this->assertFalse( $response->isSuccessful() );
        $this->assertEquals( FinalStatus::UNKNOWN, $response->getStatus() );
    }

    public function testApiAuthorizePaymentReturnsPaymentErrorWithMessageUserBlacklisted(): void {
        $params = $this->getCreatePaymentRequestParams();
        $apiError = [
            "code" => 5014,
            "message" => "User blacklisted"
        ];
        $apiException = new ApiException();
        $apiException->setRawErrors( $apiError );
        $this->api->expects( $this->once() )
                ->method( 'cardAuthorizePayment' )
                ->with( $params )
                ->will( $this->throwException( $apiException ) );

        $provider = new CardPaymentProvider();
        $response = $provider->createPayment( $params );
        $error = $response->getErrors();
        $this->assertCount( 1, $error );
        $this->assertEquals( $apiError["message"], $error[0]->getDebugMessage() );
        $this->assertEquals( ErrorMapper::$errorCodes[ $apiError["code"] ], $error[0]->getErrorCode() );
        $this->assertFalse( $response->isSuccessful() );
        $this->assertEquals( FinalStatus::FAILED, $response->getStatus() );
    }

    public function testApiCapturePaymentReturnsPaymentErrorWithMessagePaymentNotFound(): void {
        $gateway_txn_id = "PAY2323243343543";
        $apiError = [
            "code" => 4000,
            "message" => "Payment not found"
        ];
        $apiException = new ApiException();
        $apiException->setRawErrors( $apiError );
        $params = [
            'gateway_txn_id' => $gateway_txn_id,
            'amount' => 100,
            'currency' => 'BRL',
            'order_id' => '1234512345',
        ];
        $this->api->expects( $this->once() )
                ->method( 'capturePayment' )
                ->with( $params )
                ->will( $this->throwException( $apiException ) );

        $provider = new CardPaymentProvider();
        $response = $provider->approvePayment( $params );
        $error = $response->getErrors();
        $this->assertCount( 1, $error );
        $this->assertEquals( $apiError["message"], $error[0]->getDebugMessage() );
        $this->assertEquals( ErrorMapper::$errorCodes[ $apiError["code"] ], $error[0]->getErrorCode() );
        $this->assertFalse( $response->isSuccessful() );
        $this->assertEquals( FinalStatus::FAILED, $response->getStatus() );
    }

    /**
     * Test the possibility of the provider changing the key properties in the error message
     * response.
     * Expectation is that this change returns a response in the form of a PaymentError indicating
     * that the parameters have been changed but the flow is not broken for it.
     */
    public function testApprovePaymentWithCompleteParamsFailsAndEmptyStatusInResponseNoErrorMessage(): void {
        $gateway_txn_id = "PAY2323243343543";
        $errorCode = 5008;
        $params = [
            'gateway_txn_id' => $gateway_txn_id,
            'amount' => 100,
            'currency' => 'BRL',
            'order_id' => '1234512345',
        ];
        $this->api->expects( $this->once() )
                ->method( 'capturePayment' )
                ->with( $params )
                ->willReturn( [
                    "code" => $errorCode
                ] );

        $provider = new CardPaymentProvider();
        $response = $provider->approvePayment( $params );
        $error = $response->getErrors();
        $this->assertCount( 1, $error );
        $this->assertEquals( "Status element missing from dlocal response.", $error[0]->getDebugMessage() );
        $this->assertEquals( ErrorCode::MISSING_REQUIRED_DATA, $error[0]->getErrorCode() );
        $this->assertFalse( $response->isSuccessful() );
        $this->assertEquals( FinalStatus::UNKNOWN, $response->getStatus() );
    }

    public function testPaymentWithUserLimitExceededError(): void {
        $params = $this->getCreatePaymentRequestParams();
        $exception = new ApiException();
        $exception->setRawErrors( [
            'code' => 5006,
            'message' => 'User limit exceeded'
        ] );
        $this->api->expects( $this->once() )
            ->method( 'cardAuthorizePayment' )
            ->with( $params )
            ->will( $this->throwException( $exception ) );
        $provider = new CardPaymentProvider();
        $response = $provider->createPayment( $params );

        $this->assertFalse( $response->isSuccessful() );
        $this->assertTrue( $response->hasErrors() );
        $errors = $response->getErrors();
        $this->assertCount( 1, $errors );
        $this->assertSame( 'User limit exceeded', $errors[0]->getDebugMessage() );
    }

    private function getCreatePaymentRequestParams(): array {
        return [
                        'payment_token' => 'fake-token',
                        'amount' => '1.00',
                        'currency' => 'ZAR',
                        'country' => 'SA',
                        'payment_method' => 'cc',
                        'payment_submethod' => 'visa',
                        'order_id' => '1234',
                        'first_name' => 'Lorem',
                        'last_name' => 'Ipsum',
                        'email' => 'li@mail.com',
                        'fiscal_number' => '12345',
                        'contact_id' => '12345',
                        'state_province' => 'lore',
                        'city' => 'lore',
                        'postal_code' => 'lore',
                        'street_address' => 'lore',
                        'street_number' => 2,
                        'user_ip' => '127.0.0.1'
        ];
    }
}