Skip to content
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: 5 additions & 1 deletion src/Exceptions/InvalidRequestException.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@

namespace Yiisoft\Payments\Exceptions;

class InvalidRequestException extends PaymentException

Check failure on line 7 in src/Exceptions/InvalidRequestException.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.3-ubuntu-latest

ClassMustBeFinal

src/Exceptions/InvalidRequestException.php:7:7: ClassMustBeFinal: Class Yiisoft\Payments\Exceptions\InvalidRequestException is never extended and is not part of the public API, and thus must be made final. (see https://psalm.dev/361)

Check failure on line 7 in src/Exceptions/InvalidRequestException.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.2-ubuntu-latest

ClassMustBeFinal

src/Exceptions/InvalidRequestException.php:7:7: ClassMustBeFinal: Class Yiisoft\Payments\Exceptions\InvalidRequestException is never extended and is not part of the public API, and thus must be made final. (see https://psalm.dev/361)
{
/**
* @param array<string,mixed>|null $details
*/
public function __construct(
string $message = 'Invalid request',
?string $errorCode = null,
?string $errorType = null,
?string $declineCode = null,
?string $param = null,
?array $details = null,
int $code = 400,

Check warning on line 19 in src/Exceptions/InvalidRequestException.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "IncrementInteger": @@ @@ /** * @param array<string,mixed>|null $details */ - public function __construct(string $message = 'Invalid request', ?string $errorCode = null, ?string $errorType = null, ?string $declineCode = null, ?string $param = null, ?array $details = null, int $code = 400, ?\Throwable $previous = null) + public function __construct(string $message = 'Invalid request', ?string $errorCode = null, ?string $errorType = null, ?string $declineCode = null, ?string $param = null, ?array $details = null, int $code = 401, ?\Throwable $previous = null) { parent::__construct($message, $errorCode, $errorType, $declineCode, $param, $details, $code, $previous); } }

Check warning on line 19 in src/Exceptions/InvalidRequestException.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "DecrementInteger": @@ @@ /** * @param array<string,mixed>|null $details */ - public function __construct(string $message = 'Invalid request', ?string $errorCode = null, ?string $errorType = null, ?string $declineCode = null, ?string $param = null, ?array $details = null, int $code = 400, ?\Throwable $previous = null) + public function __construct(string $message = 'Invalid request', ?string $errorCode = null, ?string $errorType = null, ?string $declineCode = null, ?string $param = null, ?array $details = null, int $code = 399, ?\Throwable $previous = null) { parent::__construct($message, $errorCode, $errorType, $declineCode, $param, $details, $code, $previous); } }
?\Throwable $previous = null
) {
parent::__construct($message, $errorCode, $errorType, $declineCode, $param, $code, $previous);
parent::__construct($message, $errorCode, $errorType, $declineCode, $param, $details, $code, $previous);
}
}
4 changes: 4 additions & 0 deletions src/Exceptions/PaymentException.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@

class PaymentException extends \RuntimeException
{
/**
* @param array<string,mixed>|null $details
*/
public function __construct(
string $message = '',
public readonly ?string $errorCode = null,
public readonly ?string $errorType = null,
public readonly ?string $declineCode = null,
public readonly ?string $param = null,
public readonly ?array $details = null,
int $code = 0,
?\Throwable $previous = null
) {
Expand Down
78 changes: 69 additions & 9 deletions src/Gateways/AbstractGateway.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

abstract class AbstractGateway implements PaymentGatewayInterface
{
protected const API_VERSION = '1.0.0';

Check failure on line 20 in src/Gateways/AbstractGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.3-ubuntu-latest

MissingClassConstType

src/Gateways/AbstractGateway.php:20:21: MissingClassConstType: Class constant "Yiisoft\Payments\Gateways\AbstractGateway::API_VERSION" should have a declared type. (see https://psalm.dev/359)

public function __construct(
protected ClientInterface $httpClient,
Expand Down Expand Up @@ -56,7 +56,7 @@
->withHeader('User-Agent', 'PaymentGateway/' . self::API_VERSION);
}

protected function sendRequest($request): array

Check failure on line 59 in src/Gateways/AbstractGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.3-ubuntu-latest

MissingParamType

src/Gateways/AbstractGateway.php:59:36: MissingParamType: Parameter $request has no provided type (see https://psalm.dev/154)

Check failure on line 59 in src/Gateways/AbstractGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.2-ubuntu-latest

MissingParamType

src/Gateways/AbstractGateway.php:59:36: MissingParamType: Parameter $request has no provided type (see https://psalm.dev/154)

Check failure on line 59 in src/Gateways/AbstractGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.1-ubuntu-latest

MissingParamType

src/Gateways/AbstractGateway.php:59:36: MissingParamType: Parameter $request has no provided type (see https://psalm.dev/154)
{
try {
$response = $this->httpClient->sendRequest($request);
Expand All @@ -79,33 +79,93 @@

protected function handleErrorResponse(array $response, int $statusCode): void
{
$error = $response['error'] ?? [];
$message = $error['message'] ?? 'An error occurred with the payment gateway';
$code = $error['code'] ?? null;
$type = $error['type'] ?? null;
$declineCode = $error['decline_code'] ?? null;
$param = $error['param'] ?? null;
$error = isset($response['error']) && is_array($response['error'])
? $response['error']
: $response;

$message = $this->extractErrorMessage($error)
?? $this->extractErrorMessage($response)
?? 'An error occurred with the payment gateway';

$code = $this->extractErrorField($error, ['code', 'Code', 'error_code', 'ErrorCode', 'ResultCode']);
$type = $this->extractErrorField($error, ['type', 'Type', 'error_type', 'ErrorType']);
$declineCode = $this->extractErrorField($error, ['decline_code', 'DeclineCode']);
$param = $this->extractErrorField($error, ['param', 'Param', 'parameter', 'Parameter']);
$details = [
'response' => $response,
];

switch ($statusCode) {
case 400:
throw new \Yiisoft\Payments\Exceptions\InvalidRequestException(
throw new InvalidRequestException(
$message,
$code,
$type,
$declineCode,
$param,
$details,
$statusCode
);
// Add more specific exceptions as needed
default:
throw new \Yiisoft\Payments\Exceptions\PaymentException(
throw new PaymentException(
$message,
$code,
$type,
$declineCode,
$param,
$details,
$statusCode
);
}
}

private function extractErrorMessage(array $payload): ?string
{
$message = $this->extractErrorField(
$payload,
['message', 'Message', 'description', 'Description', 'detail', 'Detail', 'title', 'Title', 'error_description', 'error_message', 'Error']
);

if ($message !== null && $message !== '') {
return $message;
}

$errors = $payload['errors'] ?? $payload['Errors'] ?? null;
if (is_array($errors) && $errors !== []) {
$first = reset($errors);
if (is_array($first)) {
return $this->extractErrorMessage($first);
}

if (is_scalar($first)) {
return (string) $first;
}
}

$error = $payload['error'] ?? null;
if (is_scalar($error) && $error !== '') {
return (string) $error;
}

return null;
}

/**
* @param list<string> $keys
*/
private function extractErrorField(array $payload, array $keys): ?string
{
foreach ($keys as $key) {
if (!array_key_exists($key, $payload)) {
continue;
}

$value = $payload[$key];
if (is_scalar($value) && $value !== '') {
return (string) $value;
}
}

return null;
}
}
39 changes: 38 additions & 1 deletion src/Gateways/PayPalGateway.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@

protected function getBaseUri(): string
{
return $this->endpoints->getBaseUri($this->sandbox);

Check failure on line 55 in src/Gateways/PayPalGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.3-ubuntu-latest

PossiblyNullReference

src/Gateways/PayPalGateway.php:55:34: PossiblyNullReference: Cannot call method getBaseUri on possibly null value (see https://psalm.dev/083)

Check failure on line 55 in src/Gateways/PayPalGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.2-ubuntu-latest

PossiblyNullReference

src/Gateways/PayPalGateway.php:55:34: PossiblyNullReference: Cannot call method getBaseUri on possibly null value (see https://psalm.dev/083)

Check failure on line 55 in src/Gateways/PayPalGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.1-ubuntu-latest

PossiblyNullReference

src/Gateways/PayPalGateway.php:55:34: PossiblyNullReference: Cannot call method getBaseUri on possibly null value (see https://psalm.dev/083)
}

/**
Expand All @@ -60,6 +60,9 @@
*
* This method returns the given model with an assigned ID if it doesn't have one. No API call is made.
* The returned ID is stable only within the calling application (store it yourself if needed).
*
* @sandbox-support not_implemented
* @sandbox-reason PayPal public API does not expose a standalone customer resource or customer CRUD endpoints compatible with this interface. This operation cannot be implemented against the public PayPal API.
*/
public function createCustomer(Customer $customer): Customer
{
Expand All @@ -83,6 +86,10 @@
*
* This method returns a placeholder customer with the provided ID.
*/
/**
* @sandbox-support not_implemented
* @sandbox-reason PayPal public API does not expose a standalone customer resource or customer CRUD endpoints compatible with this interface. This operation cannot be implemented against the public PayPal API.
*/
public function retrieveCustomer(string $customerId): Customer
{
return new Customer(id: $customerId);
Expand All @@ -93,6 +100,10 @@
*
* This method returns the input customer unchanged.
*/
/**
* @sandbox-support not_implemented
* @sandbox-reason PayPal public API does not expose a standalone customer resource or customer update endpoint compatible with this interface. This operation cannot be implemented against the public PayPal API.
*/
public function updateCustomer(Customer $customer): Customer
{
return $customer;
Expand All @@ -102,6 +113,9 @@
* PayPal does not expose a generic "Customer" resource compatible with this library's interface.
*
* This method is a no-op.
*
* @sandbox-support not_implemented
* @sandbox-reason PayPal public API does not expose a standalone customer resource or customer deletion endpoint compatible with this interface. This operation cannot be implemented against the public PayPal API.
*/
public function deleteCustomer(string $customerId): void
{
Expand All @@ -119,11 +133,13 @@
* Optional metadata keys used by this gateway:
* - return_url: URL where PayPal will redirect the payer after approval (web flow)
* - cancel_url: URL where PayPal will redirect the payer if they cancel (web flow)
*
* @sandbox-support implemented
*/
public function createPaymentIntent(PaymentIntent $paymentIntent): PaymentIntent

Check failure on line 139 in src/Gateways/PayPalGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.3-ubuntu-latest

ParamNameMismatch

src/Gateways/PayPalGateway.php:139:55: ParamNameMismatch: Argument 1 of Yiisoft\Payments\Gateways\PayPalGateway::createPaymentIntent has wrong name $paymentIntent, expecting $intent as defined by Yiisoft\Payments\PaymentGatewayInterface::createPaymentIntent (see https://psalm.dev/230)

Check failure on line 139 in src/Gateways/PayPalGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.2-ubuntu-latest

ParamNameMismatch

src/Gateways/PayPalGateway.php:139:55: ParamNameMismatch: Argument 1 of Yiisoft\Payments\Gateways\PayPalGateway::createPaymentIntent has wrong name $paymentIntent, expecting $intent as defined by Yiisoft\Payments\PaymentGatewayInterface::createPaymentIntent (see https://psalm.dev/230)

Check failure on line 139 in src/Gateways/PayPalGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.1-ubuntu-latest

ParamNameMismatch

src/Gateways/PayPalGateway.php:139:55: ParamNameMismatch: Argument 1 of Yiisoft\Payments\Gateways\PayPalGateway::createPaymentIntent has wrong name $paymentIntent, expecting $intent as defined by Yiisoft\Payments\PaymentGatewayInterface::createPaymentIntent (see https://psalm.dev/230)
{
$currency = strtoupper($paymentIntent->currency);

Check failure on line 141 in src/Gateways/PayPalGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.3-ubuntu-latest

PossiblyNullArgument

src/Gateways/PayPalGateway.php:141:32: PossiblyNullArgument: Argument 1 of strtoupper cannot be null, possibly null value provided (see https://psalm.dev/078)

Check failure on line 141 in src/Gateways/PayPalGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.2-ubuntu-latest

PossiblyNullArgument

src/Gateways/PayPalGateway.php:141:32: PossiblyNullArgument: Argument 1 of strtoupper cannot be null, possibly null value provided (see https://psalm.dev/078)

Check failure on line 141 in src/Gateways/PayPalGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.1-ubuntu-latest

PossiblyNullArgument

src/Gateways/PayPalGateway.php:141:32: PossiblyNullArgument: Argument 1 of strtoupper cannot be null, possibly null value provided (see https://psalm.dev/078)
$amount = self::formatAmount($paymentIntent->amount);

Check failure on line 142 in src/Gateways/PayPalGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.3-ubuntu-latest

PossiblyNullArgument

src/Gateways/PayPalGateway.php:142:38: PossiblyNullArgument: Argument 1 of Yiisoft\Payments\Gateways\PayPalGateway::formatAmount cannot be null, possibly null value provided (see https://psalm.dev/078)

Check failure on line 142 in src/Gateways/PayPalGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.2-ubuntu-latest

PossiblyNullArgument

src/Gateways/PayPalGateway.php:142:38: PossiblyNullArgument: Argument 1 of Yiisoft\Payments\Gateways\PayPalGateway::formatAmount cannot be null, possibly null value provided (see https://psalm.dev/078)

Check failure on line 142 in src/Gateways/PayPalGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.1-ubuntu-latest

PossiblyNullArgument

src/Gateways/PayPalGateway.php:142:38: PossiblyNullArgument: Argument 1 of Yiisoft\Payments\Gateways\PayPalGateway::formatAmount cannot be null, possibly null value provided (see https://psalm.dev/078)

$data = [
'intent' => $paymentIntent->captureMethod ? 'AUTHORIZE' : 'CAPTURE',
Expand All @@ -144,7 +160,7 @@
$data['purchase_units'][0]['description'] = $paymentIntent->description;
}

if (!empty($paymentIntent->metadata)) {

Check failure on line 163 in src/Gateways/PayPalGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.3-ubuntu-latest

RiskyTruthyFalsyComparison

src/Gateways/PayPalGateway.php:163:14: RiskyTruthyFalsyComparison: Operand of type array<array-key, mixed>|null contains type array<array-key, mixed>, which can be falsy and truthy. This can cause possibly unexpected behavior. Use strict comparison instead. (see https://psalm.dev/356)

Check failure on line 163 in src/Gateways/PayPalGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.2-ubuntu-latest

RiskyTruthyFalsyComparison

src/Gateways/PayPalGateway.php:163:14: RiskyTruthyFalsyComparison: Operand of type array<array-key, mixed>|null contains type array<array-key, mixed>, which can be falsy and truthy. This can cause possibly unexpected behavior. Use strict comparison instead. (see https://psalm.dev/356)

Check failure on line 163 in src/Gateways/PayPalGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.1-ubuntu-latest

RiskyTruthyFalsyComparison

src/Gateways/PayPalGateway.php:163:14: RiskyTruthyFalsyComparison: Operand of type array<array-key, mixed>|null contains type array<array-key, mixed>, which can be falsy and truthy. This can cause possibly unexpected behavior. Use strict comparison instead. (see https://psalm.dev/356)
// A convenient way to pass application-specific identifiers into PayPal.
if (isset($paymentIntent->metadata['order_id'])) {
$data['purchase_units'][0]['custom_id'] = (string) $paymentIntent->metadata['order_id'];
Expand All @@ -170,8 +186,10 @@

/**
* Retrieves PayPal Order data.
*
* @sandbox-support implemented
*/
public function retrievePaymentIntent(string $paymentIntentId): PaymentIntent

Check failure on line 192 in src/Gateways/PayPalGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.3-ubuntu-latest

ParamNameMismatch

src/Gateways/PayPalGateway.php:192:50: ParamNameMismatch: Argument 1 of Yiisoft\Payments\Gateways\PayPalGateway::retrievePaymentIntent has wrong name $paymentIntentId, expecting $intentId as defined by Yiisoft\Payments\PaymentGatewayInterface::retrievePaymentIntent (see https://psalm.dev/230)

Check failure on line 192 in src/Gateways/PayPalGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.2-ubuntu-latest

ParamNameMismatch

src/Gateways/PayPalGateway.php:192:50: ParamNameMismatch: Argument 1 of Yiisoft\Payments\Gateways\PayPalGateway::retrievePaymentIntent has wrong name $paymentIntentId, expecting $intentId as defined by Yiisoft\Payments\PaymentGatewayInterface::retrievePaymentIntent (see https://psalm.dev/230)

Check failure on line 192 in src/Gateways/PayPalGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.1-ubuntu-latest

ParamNameMismatch

src/Gateways/PayPalGateway.php:192:50: ParamNameMismatch: Argument 1 of Yiisoft\Payments\Gateways\PayPalGateway::retrievePaymentIntent has wrong name $paymentIntentId, expecting $intentId as defined by Yiisoft\Payments\PaymentGatewayInterface::retrievePaymentIntent (see https://psalm.dev/230)
{
$order = $this->sendRequest(
$this->createRequest('GET', "/v2/checkout/orders/{$paymentIntentId}")
Expand All @@ -185,8 +203,11 @@
*
* For PayPal web flows, payer approval happens outside of the API (via approval link).
* This method simply re-fetches the current order state.
*
* @sandbox-support partial
* @sandbox-reason PayPal order approval is performed by the payer on PayPal-hosted pages. The public API does not expose a separate generic confirm endpoint compatible with this interface.
*/
public function confirmPaymentIntent(string $paymentIntentId, array $params = []): PaymentIntent

Check failure on line 210 in src/Gateways/PayPalGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.2-ubuntu-latest

ParamNameMismatch

src/Gateways/PayPalGateway.php:210:49: ParamNameMismatch: Argument 1 of Yiisoft\Payments\Gateways\PayPalGateway::confirmPaymentIntent has wrong name $paymentIntentId, expecting $intentId as defined by Yiisoft\Payments\PaymentGatewayInterface::confirmPaymentIntent (see https://psalm.dev/230)

Check failure on line 210 in src/Gateways/PayPalGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.1-ubuntu-latest

ParamNameMismatch

src/Gateways/PayPalGateway.php:210:49: ParamNameMismatch: Argument 1 of Yiisoft\Payments\Gateways\PayPalGateway::confirmPaymentIntent has wrong name $paymentIntentId, expecting $intentId as defined by Yiisoft\Payments\PaymentGatewayInterface::confirmPaymentIntent (see https://psalm.dev/230)
{
return $this->retrievePaymentIntent($paymentIntentId);
}
Expand All @@ -199,8 +220,10 @@
*
* If you already have an authorization ID and want to capture it explicitly, pass it as:
* $this->capturePaymentIntent($orderId, ['authorization_id' => '...'])
*
* @sandbox-support implemented
*/
public function capturePaymentIntent(string $paymentIntentId, array $params = []): PaymentIntent

Check failure on line 226 in src/Gateways/PayPalGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.1-ubuntu-latest

ParamNameMismatch

src/Gateways/PayPalGateway.php:226:53: ParamNameMismatch: Argument 1 of Yiisoft\Payments\Gateways\PayPalGateway::capturePaymentIntent has wrong name $paymentIntentId, expecting $intentId as defined by Yiisoft\Payments\PaymentGatewayInterface::capturePaymentIntent (see https://psalm.dev/230)
{
// Optional: confirm payment source (used for advanced card payments flows).
//
Expand All @@ -219,6 +242,7 @@
'paypal',
null,
'payment_source',
null,
400
);
}
Expand Down Expand Up @@ -288,7 +312,10 @@
]);
}

public function cancelPaymentIntent(string $paymentIntentId, array $params = []): PaymentIntent
/**
* @sandbox-support implemented
*/
public function cancelPaymentIntent(string $paymentIntentId, array $params = []): PaymentIntent

Check failure on line 318 in src/Gateways/PayPalGateway.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.1-ubuntu-latest

ParamNameMismatch

src/Gateways/PayPalGateway.php:318:48: ParamNameMismatch: Argument 1 of Yiisoft\Payments\Gateways\PayPalGateway::cancelPaymentIntent has wrong name $paymentIntentId, expecting $intentId as defined by Yiisoft\Payments\PaymentGatewayInterface::cancelPaymentIntent (see https://psalm.dev/230)
{
try {
$order = $this->sendRequest(
Expand All @@ -306,6 +333,9 @@
* PayPal does not expose a generic "PaymentMethod" resource compatible with this library's interface.
*
* This method returns the given model with an assigned ID if it doesn't have one. No API call is made.
*
* @sandbox-support not_implemented
* @sandbox-reason PayPal public API does not expose a standalone generic payment-method resource compatible with this interface. This operation cannot be implemented against the public PayPal API.
*/
public function createPaymentMethod(PaymentMethod $paymentMethod): PaymentMethod
{
Expand Down Expand Up @@ -357,6 +387,9 @@
* PayPal does not expose a generic "PaymentMethod" attachment API compatible with this library's interface.
*
* This method returns the payment method unchanged.
*
* @sandbox-support not_implemented
* @sandbox-reason PayPal public API does not expose a generic payment-method attachment endpoint compatible with this interface. This operation cannot be implemented against the public PayPal API.
*/
public function attachPaymentMethod(string $paymentMethodId, string $customerId): PaymentMethod
{
Expand All @@ -370,6 +403,8 @@
* To use this method you must pass a capture ID either:
* - as $paymentIntentId directly; OR
* - as ['capture_id' => '...'] in $params
*
* @sandbox-support implemented
*/
public function createRefund(string $paymentIntentId, array $params = []): array
{
Expand Down Expand Up @@ -440,6 +475,7 @@
'paypal',
null,
$param,
null,
$statusCode
);
}
Expand Down Expand Up @@ -470,6 +506,7 @@
'paypal',
null,
null,
null,
$response->getStatusCode()
);
}
Expand Down
Loading
Loading