Table of Contents
Complete Directory Tree
app/
├─ controller/
│ └─ api/
│ └─ v1/
│ ├─ PaymentController.php # Payment API
│ ├─ RefundController.php # Refund API
│ └─ WebhookController.php # Webhook API
│
├─ model/
│ └─ eloquent/
│ ├─ Payment.php # Payment record
│ ├─ Refund.php # Refund record
│ └─ PaymentLog.php # Payment log
│
├─ middleware/
│ ├─ auth/
│ │ └─ WebhookSignatureMiddleware.php # Webhook signature verification
│ └─ rate_limit/
│ └─ PaymentRateLimitMiddleware.php # Payment rate limiting
│
├─ process/
│ └─ task/
│ ├─ PaymentStatusSyncTask.php # Payment status sync
│ └─ RefundProcessTask.php # Refund processing task
│
├─ service/
│ ├─ payment/
│ │ ├─ CreatePaymentService.php # Create payment
│ │ ├─ ProcessPaymentService.php # Process payment
│ │ ├─ CancelPaymentService.php # Cancel payment
│ │ └─ QueryPaymentService.php # Query payment
│ ├─ refund/
│ │ ├─ CreateRefundService.php # Create refund
│ │ └─ ProcessRefundService.php # Process refund
│ └─ webhook/
│ └─ HandleWebhookService.php # Handle Webhook
│
├─ domain/
│ ├─ payment/
│ │ ├─ entity/
│ │ │ ├─ Payment.php # Payment entity
│ │ │ └─ Refund.php # Refund entity
│ │ ├─ enum/ # Enums
│ │ │ ├─ PaymentStatus.php # Payment status
│ │ │ └─ PaymentMethod.php # Payment method
│ │ ├─ vo/ # Value Objects
│ │ │ ├─ Money.php # Money
│ │ │ └─ Currency.php # Currency
│ │ ├─ event/
│ │ │ ├─ PaymentCreated.php # Payment created
│ │ │ ├─ PaymentSucceeded.php # Payment succeeded
│ │ │ ├─ PaymentFailed.php # Payment failed
│ │ │ └─ RefundProcessed.php # Refund processed
│ │ └─ rule/
│ │ ├─ PaymentValidationRule.php # Payment validation rules
│ │ └─ RefundEligibilityRule.php # Refund eligibility rules
│ │
│ └─ gateway/
│ ├─ enum/ # Enums
│ │ └─ GatewayType.php # Gateway type
│ ├─ vo/ # Value Objects
│ │ └─ TransactionId.php # Transaction ID
│ └─ exception/
│ └─ GatewayException.php # Gateway exception
│
├─ contract/
│ ├─ repository/
│ │ ├─ PaymentRepositoryInterface.php
│ │ └─ RefundRepositoryInterface.php
│ └─ gateway/
│ ├─ PaymentGatewayInterface.php # Payment gateway interface
│ └─ RefundGatewayInterface.php # Refund gateway interface
│
├─ infrastructure/
│ ├─ repository/
│ │ └─ eloquent/
│ │ ├─ EloquentPaymentRepository.php
│ │ └─ EloquentRefundRepository.php
│ │
│ └─ gateway/
│ ├─ stripe/
│ │ ├─ StripeConnector.php # Stripe Saloon connector
│ │ ├─ StripePaymentGateway.php # Stripe payment implementation
│ │ └─ requests/
│ │ ├─ CreatePaymentIntentRequest.php
│ │ ├─ CapturePaymentRequest.php
│ │ └─ CreateRefundRequest.php
│ │
│ ├─ paypal/
│ │ ├─ PayPalConnector.php # PayPal Saloon connector
│ │ ├─ PayPalPaymentGateway.php # PayPal payment implementation
│ │ └─ requests/
│ │ ├─ CreateOrderRequest.php
│ │ └─ CaptureOrderRequest.php
│ │
│ └─ alipay/
│ ├─ AlipayConnector.php # Alipay Saloon connector
│ ├─ AlipayPaymentGateway.php # Alipay payment implementation
│ └─ requests/
│ ├─ CreateTradeRequest.php
│ └─ QueryTradeRequest.php
│
└─ support/
├─ helper/
│ └─ payment_helper.php
└─ exception/
├─ PaymentException.php
└─ RefundException.phpModule Division
Core Modules
- Payment Module - Create payment, process payment, query payment status
- Refund Module - Create refund, process refund, refund query
- Gateway Module - Multi-payment gateway adaptation (Stripe, PayPal, Alipay)
- Webhook Module - Handle payment gateway callback notifications
Directory Responsibilities
app/service/payment/
Responsibility: Payment business orchestration - Create payment flow, process payment results, status sync
app/domain/payment/
Responsibility: Payment domain logic - Payment entity, amount calculation, payment status management, business rule validation
app/infrastructure/gateway/
Responsibility: Payment gateway adaptation - Use Saloon to implement HTTP communication with various payment gateways
app/contract/gateway/
Responsibility: Gateway interface definition - Unified payment gateway interface, supporting multi-gateway switching
Key Code Examples
1. Payment Gateway Interface
php
<?php
declare(strict_types=1);
namespace app\contract\gateway;
use app\domain\payment\entity\Payment;
use app\domain\payment\vo\Money;
/**
* Payment Gateway Interface
*/
interface PaymentGatewayInterface
{
/**
* Create payment
*/
public function createPayment(
string $orderId,
Money $amount,
string $currency,
array $metadata = []
): array;
/**
* Capture payment (confirm payment)
*/
public function capturePayment(string $transactionId): array;
/**
* Cancel payment
*/
public function cancelPayment(string $transactionId): void;
/**
* Query payment status
*/
public function queryPayment(string $transactionId): array;
/**
* Verify Webhook signature
*/
public function verifyWebhookSignature(string $payload, string $signature): bool;
}2. Stripe Saloon Connector
php
<?php
declare(strict_types=1);
namespace app\infrastructure\gateway\stripe;
use Saloon\Http\Connector;
use Saloon\Traits\Plugins\AcceptsJson;
/**
* Stripe Saloon Connector
*/
final class StripeConnector extends Connector
{
use AcceptsJson;
public function __construct(
private readonly string $apiKey
) {
}
public function resolveBaseUrl(): string
{
return 'https://api.stripe.com/v1';
}
protected function defaultHeaders(): array
{
return [
'Authorization' => 'Bearer ' . $this->apiKey,
'Stripe-Version' => '2023-10-16',
];
}
}3. Stripe Payment Gateway Implementation
php
<?php
declare(strict_types=1);
namespace app\infrastructure\gateway\stripe;
use app\contract\gateway\PaymentGatewayInterface;
use app\domain\payment\vo\Money;
use app\infrastructure\gateway\stripe\requests\CreatePaymentIntentRequest;
use Saloon\Exceptions\Request\RequestException;
/**
* Stripe Payment Gateway
*/
final class StripePaymentGateway implements PaymentGatewayInterface
{
private StripeConnector $connector;
public function __construct(string $apiKey)
{
$this->connector = new StripeConnector($apiKey);
}
public function createPayment(
string $orderId,
Money $amount,
string $currency,
array $metadata = []
): array {
try {
$request = new CreatePaymentIntentRequest(
amount: $amount->toCents(),
currency: strtolower($currency),
metadata: array_merge($metadata, ['order_id' => $orderId])
);
$response = $this->connector->send($request);
return [
'transaction_id' => $response->json('id'),
'client_secret' => $response->json('client_secret'),
'status' => $response->json('status'),
];
} catch (RequestException $e) {
throw new \RuntimeException(
'Stripe payment creation failed: ' . $e->getMessage()
);
}
}
public function capturePayment(string $transactionId): array
{
// Implement capture payment logic
return [];
}
public function cancelPayment(string $transactionId): void
{
// Implement cancel payment logic
}
public function queryPayment(string $transactionId): array
{
// Implement query payment logic
return [];
}
public function verifyWebhookSignature(string $payload, string $signature): bool
{
// Stripe webhook signature verification
return true;
}
}4. Create Payment Service
php
<?php
declare(strict_types=1);
namespace app\service\payment;
use app\contract\repository\PaymentRepositoryInterface;
use app\contract\gateway\PaymentGatewayInterface;
use app\domain\payment\entity\Payment;
use app\domain\payment\vo\Money;
use app\domain\payment\values\PaymentMethod;
use support\Db;
/**
* Create Payment Service
*/
final class CreatePaymentService
{
public function __construct(
private readonly PaymentRepositoryInterface $paymentRepository,
private readonly PaymentGatewayInterface $paymentGateway
) {
}
public function handle(
string $orderId,
float $amount,
string $currency,
string $method
): Payment {
return Db::transaction(function () use ($orderId, $amount, $currency, $method) {
// 1. Call payment gateway to create payment
$gatewayResponse = $this->paymentGateway->createPayment(
orderId: $orderId,
amount: Money::fromDollars($amount),
currency: $currency,
metadata: ['source' => 'web']
);
// 2. Create payment entity
$payment = Payment::create(
orderId: $orderId,
transactionId: $gatewayResponse['transaction_id'],
amount: Money::fromDollars($amount),
currency: $currency,
method: PaymentMethod::from($method)
);
// 3. Persist
$this->paymentRepository->save($payment);
return $payment;
});
}
}5. Webhook Handler Service
php
<?php
declare(strict_types=1);
namespace app\service\webhook;
use app\contract\repository\PaymentRepositoryInterface;
use app\contract\gateway\PaymentGatewayInterface;
/**
* Webhook Handler Service
*/
final class HandleWebhookService
{
public function __construct(
private readonly PaymentRepositoryInterface $paymentRepository,
private readonly PaymentGatewayInterface $paymentGateway
) {
}
public function handle(string $payload, string $signature): void
{
// 1. Verify signature
if (!$this->paymentGateway->verifyWebhookSignature($payload, $signature)) {
throw new \RuntimeException('Invalid webhook signature');
}
// 2. Parse event
$event = json_decode($payload, true);
$eventType = $event['type'] ?? '';
// 3. Handle different event types
match ($eventType) {
'payment_intent.succeeded' => $this->handlePaymentSucceeded($event),
'payment_intent.payment_failed' => $this->handlePaymentFailed($event),
'charge.refunded' => $this->handleRefund($event),
default => null,
};
}
private function handlePaymentSucceeded(array $event): void
{
$transactionId = $event['data']['object']['id'];
$payment = $this->paymentRepository->findByTransactionId($transactionId);
if ($payment === null) {
return;
}
$payment->markAsSucceeded();
$this->paymentRepository->save($payment);
}
private function handlePaymentFailed(array $event): void
{
// Handle payment failure
}
private function handleRefund(array $event): void
{
// Handle refund
}
}Best Practices
Using Saloon HTTP Client
Advantages:
- Type Safety: Strongly typed requests and responses
- Testable: Easy to mock and test
- Maintainable: Clear request structure
- Extensible: Supports plugins and middleware
Multi-Gateway Support
- Unified Interface: Use
PaymentGatewayInterfaceto unify different gateways - Factory Pattern: Dynamically select gateway based on configuration
- Fallback Strategy: Switch to backup gateway when primary fails
Security
- Webhook Verification: Verify callback signatures to prevent forgery
- Idempotency: Use order ID to prevent duplicate payments
- Amount Validation: Server-side validation of payment amounts
- Logging: Record all payment operations
Exception Handling
- Gateway Exceptions: Catch and convert to business exceptions
- Retry Mechanism: Auto retry on network errors
- Fallback Handling: Alternative solutions when gateway is unavailable