Skip to content

Commit

Permalink
Handle post method responses (#62)
Browse files Browse the repository at this point in the history
* Rename Request statuses to accommodate refunds and other transaction types

* handle redsys responses for POST method

* Fix styling

* update phpunit config

* add docs

---------

Co-authored-by: dtorras <[email protected]>
  • Loading branch information
dtorras and dtorras authored Jun 2, 2024
1 parent c50bf0f commit b812681
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 120 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
.php_cs
.php_cs.cache
.phpunit.result.cache
.phpunit.cache
build
composer.lock
coverage
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

All notable changes to `laravel-redsys` will be documented in this file.

## 3.0.0 - 2024-06-02

- New: Improved REST integration with handled responses.
- Breaking: Updated database schema to allow different transaction types (refunds for example) with the same order number.

Updated main dependency:

- Breaking: creagia/redsys-php v3 compatibility. Check the [changelog](https://github.com/creagia/redsys-php/blob/main/CHANGELOG.md).

## 2.0.0 - 2023-05-16

This version is a complete rewrite. Though there are lots of breaking changes, all features of v1 are retained.
Expand Down
9 changes: 9 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Upgrading

## From v2 to v3

Breaking changes:
- `RedsysRequestStatus` cases have been renamed from Denied/Paid to Error/Success.
- Database table `redsys_requests` has been updated with:
- Rename `status` enum column options to `['pending', 'error', 'success']`
- Drop `order_number` unique foreign.
- Change `pay_method` column to nullable.

## From v1 to v2

Version 2.x is a complete rewrite, so there isn't a step-by-step upgrade guide. We recommend you to read the updated docs
Expand Down
6 changes: 3 additions & 3 deletions database/migrations/create_redsys_payments_table.php.stub
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ return new class extends Migration
$table->boolean('save_card')->default(false);
$table->nullableMorphs('card_request_model', 'card_model_morph');
$table->uuid('uuid');
$table->enum('status', ['pending', 'denied', 'paid'])->default('pending');
$table->bigInteger('order_number')->unique();
$table->enum('status', ['pending', 'error', 'success'])->default('pending');
$table->bigInteger('order_number');
$table->bigInteger('amount');
$table->integer('currency');
$table->string('pay_method');
$table->string('pay_method')->nullable();
$table->string('transaction_type');
$table->string('response_code')->nullable();
$table->text('response_message')->nullable();
Expand Down
71 changes: 24 additions & 47 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,49 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
executionOrder="random"
failOnWarning="true"
failOnRisky="true"
failOnEmptyTestSuite="true"
beStrictAboutOutputDuringTests="true"
verbose="true"
>
<testsuites>
<testsuite name="Creagia Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<coverage>
<include>
<directory suffix=".php">./src</directory>
</include>
<report>
<html outputDirectory="build/coverage"/>
<text outputFile="build/coverage.txt"/>
<clover outputFile="build/logs/clover.xml"/>
</report>
</coverage>
<logging>
<junit outputFile="build/report.junit.xml"/>
</logging>
<php>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="REDSYS_ENVIRONMENT" value="test" />
<env name="REDSYS_MERCHANT_NAME" value="Merchant Name" />
<env name="REDSYS_MERCHANT_CODE" value="999008881" />
<env name="REDSYS_KEY" value="sq7HjrUOBfKmC576ILgskD5srU870gJ7" />
<env name="REDSYS_SUCCESSFUL_ROUTE_NAME" value="okroute" />
<env name="REDSYS_UNSUCCESSFUL_ROUTE_NAME" value="koroute" />
</php>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd" backupGlobals="false" bootstrap="vendor/autoload.php" colors="true" processIsolation="false" stopOnFailure="false" executionOrder="random" failOnWarning="true" failOnRisky="true" failOnEmptyTestSuite="true" beStrictAboutOutputDuringTests="true" cacheDirectory=".phpunit.cache" backupStaticProperties="false">
<testsuites>
<testsuite name="Creagia Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<logging>
<junit outputFile="build/report.junit.xml"/>
</logging>
<php>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="REDSYS_ENVIRONMENT" value="test"/>
<env name="REDSYS_MERCHANT_NAME" value="Merchant Name"/>
<env name="REDSYS_MERCHANT_CODE" value="999008881"/>
<env name="REDSYS_KEY" value="sq7HjrUOBfKmC576ILgskD5srU870gJ7"/>
<env name="REDSYS_SUCCESSFUL_ROUTE_NAME" value="okroute"/>
<env name="REDSYS_UNSUCCESSFUL_ROUTE_NAME" value="koroute"/>
</php>
<source>
<include>
<directory suffix=".php">./src</directory>
</include>
</source>
</phpunit>
91 changes: 91 additions & 0 deletions src/Actions/HandleRedsysResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

namespace Creagia\LaravelRedsys\Actions;

use Creagia\LaravelRedsys\Events\RedsysSuccessfulEvent;
use Creagia\LaravelRedsys\Events\RedsysUnsuccessfulEvent;
use Creagia\LaravelRedsys\Exceptions\RedsysRequestNotFound;
use Creagia\LaravelRedsys\RedsysCard;
use Creagia\LaravelRedsys\RedsysNotificationLog;
use Creagia\LaravelRedsys\RedsysRequestStatus;
use Creagia\LaravelRedsys\Request;
use Creagia\Redsys\Exceptions\DeniedRedsysPaymentResponseException;
use Creagia\Redsys\RedsysResponse;
use Creagia\Redsys\Support\PostRequestError;
use Illuminate\Support\Str;

class HandleRedsysResponse
{
public function __invoke(

Check failure on line 19 in src/Actions/HandleRedsysResponse.php

View workflow job for this annotation

GitHub Actions / phpstan

Method Creagia\LaravelRedsys\Actions\HandleRedsysResponse::__invoke() has no return type specified.
?Request $request,
RedsysResponse|PostRequestError $response,
) {
$redsysNotificationLog = RedsysNotificationLog::create([
'merchant_parameters' => $response instanceof PostRequestError
? $response->responseParameters
: $response->merchantParametersArray,
]);

if (! $request) {
throw new RedsysRequestNotFound('Redsys Request not found from bank response');
}

$redsysNotificationLog->redsysRequest()->associate($request);
$redsysNotificationLog->save();

if ($response instanceof PostRequestError) {
/**
* Error
*/
$request->status = RedsysRequestStatus::Error;
$request->response_message = $response->message;
$request->response_code = $response->code;
$request->save();

return;
}

$request->auth_code = (empty($authCode = trim($response->parameters->responseAuthorisationCode))) ? null : $authCode;
$request->response_code = $response->parameters->responseCode ?? null;
$request->response_message = $response->parameters->responseDescription;

try {
$notificationData = $response->checkResponse();

RedsysSuccessfulEvent::dispatch($request, $notificationData->toArray());
$request->status = RedsysRequestStatus::Success;
$request->save();

if ($request->model) {
$request->model->paidWithRedsys();
}

if (
$request->save_card
and filled($notificationData->merchantIdentifier)
and filled($notificationData->cofTransactionId)
) {
$redsysCard = new RedsysCard();
$redsysCard->uuid = Str::uuid();
$redsysCard->number = $notificationData->cardNumber;
$redsysCard->expiration_date = $notificationData->cardExpiryDate;
$redsysCard->merchant_identifier = $notificationData->merchantIdentifier;
$redsysCard->cof_transaction_id = $notificationData->cofTransactionId;

if ($request->card_request_model_id) {
$redsysCard->model_id = $request->card_request_model_id;
$redsysCard->model_type = $request->card_request_model_type;
}

$redsysCard->save();
}
} catch (DeniedRedsysPaymentResponseException $e) {
$errorMessage = $e->getMessage();
RedsysUnsuccessfulEvent::dispatch($request, $errorMessage);

$request->status = RedsysRequestStatus::Error;
$request->response_message = $errorMessage;
$request->save();
}
}
}
70 changes: 10 additions & 60 deletions src/Controllers/RedsysNotificationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@
namespace Creagia\LaravelRedsys\Controllers;

use Creagia\LaravelRedsys\Actions\CreateRedsysClient;
use Creagia\LaravelRedsys\Actions\HandleRedsysResponse;
use Creagia\LaravelRedsys\Events\RedsysNotificationEvent;
use Creagia\LaravelRedsys\Events\RedsysSuccessfulEvent;
use Creagia\LaravelRedsys\Events\RedsysUnsuccessfulEvent;
use Creagia\LaravelRedsys\Exceptions\RedsysConfigError;
use Creagia\LaravelRedsys\Exceptions\RedsysRequestNotFound;
use Creagia\LaravelRedsys\RedsysCard;
use Creagia\LaravelRedsys\RedsysNotificationLog;
use Creagia\LaravelRedsys\RedsysRequestStatus;
use Creagia\LaravelRedsys\Request;
use Creagia\Redsys\Exceptions\DeniedRedsysPaymentResponseException;
use Creagia\Redsys\Exceptions\ErrorRedsysResponseException;
use Creagia\Redsys\Exceptions\InvalidRedsysResponseException;
use Creagia\Redsys\RedsysResponse;
use Illuminate\Http\Request as HttpRequest;
use Illuminate\Support\Str;

class RedsysNotificationController
{
public function __construct(
private HandleRedsysResponse $handleRedsysResponse,
) {
}

/**
* @throws DeniedRedsysPaymentResponseException
* @throws RedsysRequestNotFound
Expand All @@ -37,60 +37,10 @@ public function __invoke(HttpRequest $httpRequest, CreateRedsysClient $createRed
$redsysResponse = new RedsysResponse($redsysClient);
$redsysResponse->setParametersFromResponse($inputs);

$redsysNotificationLog = RedsysNotificationLog::create([
'merchant_parameters' => $redsysResponse->merchantParametersArray,
]);

$request = Request::where('order_number', $redsysResponse->parameters->order)->first();

if (! $request) {
throw new RedsysRequestNotFound('Redsys Request not found from bank response');
}

$redsysNotificationLog->redsysRequest()->associate($request);
$redsysNotificationLog->save();

$request->response_code = $redsysResponse->parameters->responseCode ?? null;
$request->auth_code = (empty($authCode = trim($redsysResponse->parameters->responseAuthorisationCode))) ? null : $authCode;

try {
$notificationData = $redsysResponse->checkResponse();

RedsysSuccessfulEvent::dispatch($request, $notificationData->toArray());
$request->status = RedsysRequestStatus::Paid;

$request->save();

if ($request->model) {
$request->model->paidWithRedsys();
}

if (
$request->save_card
and filled($notificationData->merchantIdentifier)
and filled($notificationData->cofTransactionId)
) {
$redsysCard = new RedsysCard();
$redsysCard->uuid = Str::uuid();
$redsysCard->number = $notificationData->cardNumber;
$redsysCard->expiration_date = $notificationData->cardExpiryDate;
$redsysCard->merchant_identifier = $notificationData->merchantIdentifier;
$redsysCard->cof_transaction_id = $notificationData->cofTransactionId;

if ($request->card_request_model_id) {
$redsysCard->model_id = $request->card_request_model_id;
$redsysCard->model_type = $request->card_request_model_type;
}

$redsysCard->save();
}
} catch (DeniedRedsysPaymentResponseException $e) {
$errorMessage = $e->getMessage();
RedsysUnsuccessfulEvent::dispatch($request, $errorMessage);

$request->status = RedsysRequestStatus::Denied;
$request->response_message = $errorMessage;
$request->save();
}
($this->handleRedsysResponse)(
request: $request,
response: $redsysResponse,
);
}
}
4 changes: 2 additions & 2 deletions src/RedsysNotificationLog.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public function getStatus(): RedsysRequestStatus
}

return RedsysResponse::isAuthorisedCode($this->merchant_parameters['Ds_Response'])
? RedsysRequestStatus::Paid
: RedsysRequestStatus::Denied;
? RedsysRequestStatus::Success
: RedsysRequestStatus::Error;
}
}
4 changes: 2 additions & 2 deletions src/RedsysRequestStatus.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
enum RedsysRequestStatus: string
{
case Pending = 'pending';
case Denied = 'denied';
case Paid = 'paid';
case Error = 'error';
case Success = 'success';
}
19 changes: 16 additions & 3 deletions src/RequestBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
namespace Creagia\LaravelRedsys;

use Creagia\LaravelRedsys\Actions\CreateRedsysClient;
use Creagia\LaravelRedsys\Actions\HandleRedsysResponse;
use Creagia\LaravelRedsys\Controllers\RedsysNotificationController;
use Creagia\LaravelRedsys\Controllers\RedsysSuccessfulPaymentViewController;
use Creagia\LaravelRedsys\Controllers\RedsysUnsuccessfulPaymentViewController;
use Creagia\Redsys\Enums\CofType;
use Creagia\Redsys\Enums\PayMethod;
use Creagia\Redsys\RedsysRequest;
use Creagia\Redsys\RedsysResponse;
use Creagia\Redsys\Support\PostRequestError;
use Creagia\Redsys\Support\RequestParameters;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Response;
Expand All @@ -30,9 +33,12 @@ class RequestBuilder

public ?Request $request = null;

private HandleRedsysResponse $handleRedsysResponse;

public function __construct(
RequestParameters $requestParameters
RequestParameters $requestParameters,
) {
$this->handleRedsysResponse = app(HandleRedsysResponse::class);
$this->requestParameters = $requestParameters;
$this->uuid = Str::uuid();

Expand Down Expand Up @@ -151,10 +157,17 @@ public function redirect(): Response
return response($this->redsysRequest->getRedirectFormHtml());
}

public function post(): \Creagia\Redsys\Support\NotificationParameters|\Creagia\Redsys\Support\PostRequestError
public function post(): RedsysResponse|PostRequestError
{
$this->create();

return $this->redsysRequest->sendPostRequest();
$response = $this->redsysRequest->sendPostRequest();

($this->handleRedsysResponse)(
request: $this->request,
response: $response,
);

return $response;
}
}
Loading

0 comments on commit b812681

Please sign in to comment.