Skip to content
Closed
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
22 changes: 19 additions & 3 deletions src/Pay/Adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,22 @@ public function getCurrency(): string
abstract public function purchase(int $amount, string $customerId, ?string $paymentMethodId = null, array $additionalParams = []): array;

/**
* Authorize a payment (hold funds without capturing)
* Useful for scenarios where you need to ensure payment availability before providing service
* Authorize a payment
* Creates a payment intent without confirming it. Always call confirmAuthorization() after this.
*
* Flow with 'automatic' (default): authorize() → confirmAuthorization() → funds captured automatically.
* Flow with 'manual': authorize() → confirmAuthorization() → capture() to collect funds.
*
* You may call cancelAuthorization() before confirmAuthorization() to abort.
*
* @param int $amount Amount to authorize
* @param string $customerId Customer ID
* @param string|null $paymentMethodId Payment method ID (optional)
* @param string $captureMethod Capture method: 'automatic' (default) or 'manual'
* @param array<mixed> $additionalParams Additional parameters (optional)
* @return array<mixed> Result of the authorization including authorization ID
*/
abstract public function authorize(int $amount, string $customerId, ?string $paymentMethodId = null, array $additionalParams = []): array;
abstract public function authorize(int $amount, string $customerId, ?string $paymentMethodId = null, string $captureMethod = 'automatic', array $additionalParams = []): array;

/**
* Capture a previously authorized payment
Expand All @@ -103,6 +109,16 @@ abstract public function authorize(int $amount, string $customerId, ?string $pay
*/
abstract public function capture(string $paymentId, ?int $amount = null, array $additionalParams = []): array;

/**
* Confirm a previously created authorization
* Sends confirmation to process the authorization (e.g., off_session for saved cards)
*
* @param string $paymentId The payment/authorization ID to confirm
* @param array<mixed> $additionalParams Additional parameters (optional)
* @return array<mixed> Result of the confirmation
*/
abstract public function confirmAuthorization(string $paymentId, array $additionalParams = []): array;

/**
* Cancel/void a payment authorization
* Releases the hold on funds without capturing
Expand Down
30 changes: 25 additions & 5 deletions src/Pay/Adapter/Stripe.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,40 @@ public function purchase(int $amount, string $customerId, ?string $paymentMethod
}

/**
* Authorize a payment (hold funds without capturing)
* Creates a payment intent with capture_method set to manual
* Authorize a payment
* Creates a payment intent without confirming it. Always call confirmAuthorization() after this.
*
* Flow with 'automatic' (default): authorize() → confirmAuthorization() → funds captured automatically.
* Flow with 'manual': authorize() → confirmAuthorization() → capture() to collect funds.
*
* You may call cancelAuthorization() before confirmAuthorization() to abort.
*/
public function authorize(int $amount, string $customerId, ?string $paymentMethodId = null, array $additionalParams = []): array
public function authorize(int $amount, string $customerId, ?string $paymentMethodId = null, string $captureMethod = 'automatic', array $additionalParams = []): array
{
$path = '/payment_intents';
$requestBody = [
'amount' => $amount,
'currency' => $this->currency,
'customer' => $customerId,
'payment_method' => $paymentMethodId,
'capture_method' => 'manual',
'capture_method' => $captureMethod,
];

$requestBody = array_merge($requestBody, $additionalParams);
$result = $this->execute(self::METHOD_POST, $path, $requestBody);

return $result;
}

/**
* Confirm a previously created authorization
* Sends off_session: true to confirm without customer interaction (for saved cards)
*/
public function confirmAuthorization(string $paymentId, array $additionalParams = []): array
{
$path = '/payment_intents/'.$paymentId.'/confirm';
$requestBody = [
'off_session' => 'true',
'confirm' => 'true',
];

$requestBody = array_merge($requestBody, $additionalParams);
Expand Down
27 changes: 22 additions & 5 deletions src/Pay/Pay.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,19 +88,36 @@ public function purchase(int $amount, string $customerId, ?string $paymentMethod

/**
* Authorize
* Authorize a payment (hold funds without capturing)
* Useful for scenarios where you need to ensure payment availability before providing service
* Returns authorization ID on successful authorization
* Creates a payment intent without confirming it. Always call confirmAuthorization() after this.
*
* Flow with 'automatic' (default): authorize() → confirmAuthorization() → funds captured automatically.
* Flow with 'manual': authorize() → confirmAuthorization() → capture() to collect funds.
*
* You may call cancelAuthorization() before confirmAuthorization() to abort.
*
* @param int $amount
* @param string $customerId
* @param string|null $paymentMethodId
* @param string $captureMethod
* @param array<mixed> $additionalParams
* @return array<mixed>
*/
public function authorize(int $amount, string $customerId, ?string $paymentMethodId = null, string $captureMethod = 'automatic', array $additionalParams = []): array
{
return $this->adapter->authorize($amount, $customerId, $paymentMethodId, $captureMethod, $additionalParams);
}

/**
* Confirm Authorization
* Confirm a previously created authorization
*
* @param string $paymentId
* @param array<mixed> $additionalParams
* @return array<mixed>
*/
public function authorize(int $amount, string $customerId, ?string $paymentMethodId = null, array $additionalParams = []): array
public function confirmAuthorization(string $paymentId, array $additionalParams = []): array
{
return $this->adapter->authorize($amount, $customerId, $paymentMethodId, $additionalParams);
return $this->adapter->confirmAuthorization($paymentId, $additionalParams);
}

/**
Expand Down
31 changes: 21 additions & 10 deletions tests/Pay/Adapter/StripeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -631,16 +631,22 @@ public function testAuthorize(array $data): array
$customerId = $data['customerId'];
$paymentMethodId = $data['paymentMethodId'];

// Authorize payment - hold funds without capturing
$authorization = $this->stripe->authorize(10000, $customerId, $paymentMethodId);
// Authorize payment - creates intent without confirming
$authorization = $this->stripe->authorize(10000, $customerId, $paymentMethodId, 'manual');

$this->assertNotEmpty($authorization['id']);
$this->assertEquals('payment_intent', $authorization['object']);
$this->assertEquals(10000, $authorization['amount']);
$this->assertEquals('requires_capture', $authorization['status']);
$this->assertEquals('requires_confirmation', $authorization['status']);
$this->assertEquals('manual', $authorization['capture_method']);

$data['authorizationId'] = $authorization['id'];
// Confirm the authorization
$confirmed = $this->stripe->confirmAuthorization($authorization['id']);

$this->assertEquals($authorization['id'], $confirmed['id']);
$this->assertEquals('requires_capture', $confirmed['status']);

$data['authorizationId'] = $confirmed['id'];

return $data;
}
Expand Down Expand Up @@ -682,10 +688,14 @@ public function testPartialCapture(array $data): array
$paymentMethodId = $data['paymentMethodId'];

// Authorize payment
$authorization = $this->stripe->authorize(15000, $customerId, $paymentMethodId);
$authorization = $this->stripe->authorize(15000, $customerId, $paymentMethodId, 'manual');
$authorizationId = $authorization['id'];

$this->assertEquals('requires_capture', $authorization['status']);
$this->assertEquals('requires_confirmation', $authorization['status']);

// Confirm the authorization
$confirmed = $this->stripe->confirmAuthorization($authorizationId);
$this->assertEquals('requires_capture', $confirmed['status']);

// Capture partial amount (only 10000 of 15000)
$captured = $this->stripe->capture($authorizationId, 10000);
Expand All @@ -710,12 +720,12 @@ public function testCancelAuthorization(array $data): array
$paymentMethodId = $data['paymentMethodId'];

// Authorize payment
$authorization = $this->stripe->authorize(8000, $customerId, $paymentMethodId);
$authorization = $this->stripe->authorize(8000, $customerId, $paymentMethodId, 'manual');
$authorizationId = $authorization['id'];

$this->assertEquals('requires_capture', $authorization['status']);
$this->assertEquals('requires_confirmation', $authorization['status']);

// Cancel the authorization - release the hold
// Cancel the authorization - release the hold (can cancel before confirming)
$cancelled = $this->stripe->cancelAuthorization($authorizationId);

$this->assertNotEmpty($cancelled['id']);
Expand All @@ -742,6 +752,7 @@ public function testAuthorizeWithMetadata(array $data): void
12000,
$customerId,
$paymentMethodId,
'manual',
[
'metadata' => [
'domain' => 'example.com',
Expand All @@ -753,7 +764,7 @@ public function testAuthorizeWithMetadata(array $data): void
);

$this->assertNotEmpty($authorization['id']);
$this->assertEquals('requires_capture', $authorization['status']);
$this->assertEquals('requires_confirmation', $authorization['status']);
$this->assertEquals('example.com', $authorization['metadata']['domain']);
$this->assertEquals('ORD-12345', $authorization['metadata']['order_id']);
$this->assertEquals('domain_registration', $authorization['metadata']['resource_type']);
Expand Down