laravel-services

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Laravel Services

Laravel 服务层

External services use Laravel's Manager pattern with multiple drivers.
Related guides:
  • Actions - Actions use services
  • Packages - Saloon for HTTP clients
  • Testing - Testing with null drivers
外部服务采用Laravel的Manager模式,支持多驱动实现。
相关指南:
  • Actions - 动作(Actions)基于服务层实现
  • Packages - 基于Saloon实现HTTP客户端
  • Testing - 基于Null驱动进行测试

When to Use

适用场景

Use service layer when:
  • Integrating external APIs
  • Multiple drivers for same service (email, payment, SMS)
  • Need to swap implementations
  • Want null driver for testing
在以下场景使用服务层:
  • 集成外部API
  • 同一服务需多驱动实现(如邮件、支付、短信)
  • 需要切换实现方案
  • 测试时需使用Null驱动

Structure

目录结构

Services/
└── Payment/
    ├── PaymentManager.php          # Manager (extends Laravel Manager)
    ├── Connectors/
    │   └── StripeConnector.php     # Saloon HTTP connector
    ├── Contracts/
    │   └── PaymentDriver.php       # Driver interface
    ├── Drivers/
    │   ├── StripeDriver.php        # Stripe implementation
    │   ├── PayPalDriver.php        # PayPal implementation
    │   └── NullDriver.php          # For testing
    ├── Exceptions/
    │   └── PaymentException.php
    ├── Facades/
    │   └── Payment.php             # Facade
    └── Requests/
        └── Stripe/
            ├── CreatePaymentIntentRequest.php
            └── RefundPaymentRequest.php
Services/
└── Payment/
    ├── PaymentManager.php          # 管理器(继承Laravel Manager)
    ├── Connectors/
    │   └── StripeConnector.php     # Saloon HTTP连接器
    ├── Contracts/
    │   └── PaymentDriver.php       # 驱动接口
    ├── Drivers/
    │   ├── StripeDriver.php        # Stripe实现
    │   ├── PayPalDriver.php        # PayPal实现
    │   └── NullDriver.php          # 测试用驱动
    ├── Exceptions/
    │   └── PaymentException.php
    ├── Facades/
    │   └── Payment.php             # 门面(Facade)
    └── Requests/
        └── Stripe/
            ├── CreatePaymentIntentRequest.php
            └── RefundPaymentRequest.php

Manager Class

管理器类

php
<?php

declare(strict_types=1);

namespace App\Services\Payment;

use App\Services\Payment\Drivers\NullDriver;
use App\Services\Payment\Drivers\StripeDriver;
use Illuminate\Support\Manager;

class PaymentManager extends Manager
{
    public function getDefaultDriver(): string
    {
        return $this->config->get('payment.default');
    }

    public function createStripeDriver(): StripeDriver
    {
        return new StripeDriver(
            apiKey: $this->config->get('payment.drivers.stripe.api_key'),
            webhookSecret: $this->config->get('payment.drivers.stripe.webhook_secret'),
        );
    }

    public function createNullDriver(): NullDriver
    {
        return new NullDriver;
    }
}
php
<?php

declare(strict_types=1);

namespace App\Services\Payment;

use App\Services\Payment\Drivers\NullDriver;
use App\Services\Payment\Drivers\StripeDriver;
use Illuminate\Support\Manager;

class PaymentManager extends Manager
{
    public function getDefaultDriver(): string
    {
        return $this->config->get('payment.default');
    }

    public function createStripeDriver(): StripeDriver
    {
        return new StripeDriver(
            apiKey: $this->config->get('payment.drivers.stripe.api_key'),
            webhookSecret: $this->config->get('payment.drivers.stripe.webhook_secret'),
        );
    }

    public function createNullDriver(): NullDriver
    {
        return new NullDriver;
    }
}

Driver Contract

驱动接口

php
<?php

declare(strict_types=1);

namespace App\Services\Payment\Contracts;

use App\Data\PaymentIntentData;

interface PaymentDriver
{
    public function createPaymentIntent(int $amount, string $currency): PaymentIntentData;

    public function refundPayment(string $paymentIntentId, ?int $amount = null): bool;

    public function retrievePaymentIntent(string $paymentIntentId): PaymentIntentData;
}
php
<?php

declare(strict_types=1);

namespace App\Services\Payment\Contracts;

use App\Data\PaymentIntentData;

interface PaymentDriver
{
    public function createPaymentIntent(int $amount, string $currency): PaymentIntentData;

    public function refundPayment(string $paymentIntentId, ?int $amount = null): bool;

    public function retrievePaymentIntent(string $paymentIntentId): PaymentIntentData;
}

Driver Implementation

驱动实现

php
<?php

declare(strict_types=1);

namespace App\Services\Payment\Drivers;

use App\Data\PaymentIntentData;
use App\Services\Payment\Connectors\StripeConnector;
use App\Services\Payment\Contracts\PaymentDriver;
use App\Services\Payment\Exceptions\PaymentException;
use App\Services\Payment\Requests\Stripe\CreatePaymentIntentRequest;
use Saloon\Http\Response;

class StripeDriver implements PaymentDriver
{
    private static ?StripeConnector $connector = null;

    public function __construct(
        private readonly string $apiKey,
        private readonly string $webhookSecret,
    ) {}

    public function createPaymentIntent(int $amount, string $currency): PaymentIntentData
    {
        $response = $this->sendRequest(
            new CreatePaymentIntentRequest($amount, $currency)
        );

        return PaymentIntentData::from($response->json());
    }

    public function refundPayment(string $paymentIntentId, ?int $amount = null): bool
    {
        // Implementation...
    }

    private function sendRequest(Request $request): Response
    {
        $response = $this->getConnector()->send($request);

        if ($response->failed()) {
            throw PaymentException::failedRequest($response);
        }

        return $response;
    }

    private function getConnector(): StripeConnector
    {
        if (static::$connector === null) {
            static::$connector = new StripeConnector($this->apiKey);
        }

        return static::$connector;
    }
}
php
<?php

declare(strict_types=1);

namespace App\Services\Payment\Drivers;

use App\Data\PaymentIntentData;
use App\Services\Payment\Connectors\StripeConnector;
use App\Services\Payment\Contracts\PaymentDriver;
use App\Services\Payment\Exceptions\PaymentException;
use App\Services\Payment\Requests\Stripe\CreatePaymentIntentRequest;
use Saloon\Http\Response;

class StripeDriver implements PaymentDriver
{
    private static ?StripeConnector $connector = null;

    public function __construct(
        private readonly string $apiKey,
        private readonly string $webhookSecret,
    ) {}

    public function createPaymentIntent(int $amount, string $currency): PaymentIntentData
    {
        $response = $this->sendRequest(
            new CreatePaymentIntentRequest($amount, $currency)
        );

        return PaymentIntentData::from($response->json());
    }

    public function refundPayment(string $paymentIntentId, ?int $amount = null): bool
    {
        // 实现逻辑...
    }

    private function sendRequest(Request $request): Response
    {
        $response = $this->getConnector()->send($request);

        if ($response->failed()) {
            throw PaymentException::failedRequest($response);
        }

        return $response;
    }

    private function getConnector(): StripeConnector
    {
        if (static::$connector === null) {
            static::$connector = new StripeConnector($this->apiKey);
        }

        return static::$connector;
    }
}

Saloon Connector

Saloon 连接器

php
<?php

declare(strict_types=1);

namespace App\Services\Payment\Connectors;

use Saloon\Http\Connector;

class StripeConnector extends Connector
{
    public function __construct(private readonly string $apiKey) {}

    public function resolveBaseUrl(): string
    {
        return 'https://api.stripe.com';
    }

    protected function defaultHeaders(): array
    {
        return [
            'Authorization' => "Bearer {$this->apiKey}",
            'Content-Type' => 'application/json',
        ];
    }
}
php
<?php

declare(strict_types=1);

namespace App\Services\Payment\Connectors;

use Saloon\Http\Connector;

class StripeConnector extends Connector
{
    public function __construct(private readonly string $apiKey) {}

    public function resolveBaseUrl(): string
    {
        return 'https://api.stripe.com';
    }

    protected function defaultHeaders(): array
    {
        return [
            'Authorization' => "Bearer {$this->apiKey}",
            'Content-Type' => 'application/json',
        ];
    }
}

Saloon Request

Saloon 请求类

php
<?php

declare(strict_types=1);

namespace App\Services\Payment\Requests\Stripe;

use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Traits\Body\HasJsonBody;

class CreatePaymentIntentRequest extends Request implements HasBody
{
    use HasJsonBody;

    protected Method $method = Method::POST;

    public function __construct(
        private readonly int $amount,
        private readonly string $currency,
    ) {}

    public function resolveEndpoint(): string
    {
        return '/v1/payment_intents';
    }

    protected function defaultBody(): array
    {
        return [
            'amount' => $this->amount,
            'currency' => $this->currency,
        ];
    }
}
php
<?php

declare(strict_types=1);

namespace App\Services\Payment\Requests\Stripe;

use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Traits\Body\HasJsonBody;

class CreatePaymentIntentRequest extends Request implements HasBody
{
    use HasJsonBody;

    protected Method $method = Method::POST;

    public function __construct(
        private readonly int $amount,
        private readonly string $currency,
    ) {}

    public function resolveEndpoint(): string
    {
        return '/v1/payment_intents';
    }

    protected function defaultBody(): array
    {
        return [
            'amount' => $this->amount,
            'currency' => $this->currency,
        ];
    }
}

Facade

门面(Facade)

php
<?php

declare(strict_types=1);

namespace App\Services\Payment\Facades;

use App\Data\PaymentIntentData;
use Illuminate\Support\Facades\Facade;

/**
 * @method static PaymentIntentData createPaymentIntent(int $amount, string $currency)
 * @method static bool refundPayment(string $paymentIntentId, ?int $amount = null)
 * @method static PaymentIntentData retrievePaymentIntent(string $paymentIntentId)
 *
 * @see \App\Services\Payment\PaymentManager
 */
class Payment extends Facade
{
    protected static function getFacadeAccessor(): string
    {
        return \App\Services\Payment\PaymentManager::class;
    }
}
php
<?php

declare(strict_types=1);

namespace App\Services\Payment\Facades;

use App\Data\PaymentIntentData;
use Illuminate\Support\Facades\Facade;

/**
 * @method static PaymentIntentData createPaymentIntent(int $amount, string $currency)
 * @method static bool refundPayment(string $paymentIntentId, ?int $amount = null)
 * @method static PaymentIntentData retrievePaymentIntent(string $paymentIntentId)
 *
 * @see \App\Services\Payment\PaymentManager
 */
class Payment extends Facade
{
    protected static function getFacadeAccessor(): string
    {
        return \App\Services\Payment\PaymentManager::class;
    }
}

Usage

使用示例

php
use App\Services\Payment\Facades\Payment;

// Use default driver
$paymentIntent = Payment::createPaymentIntent(
    amount: 10000,  // $100.00 in cents
    currency: 'usd'
);

// Refund payment
Payment::refundPayment($paymentIntent->id);

// Use specific driver
Payment::driver('stripe')->createPaymentIntent(10000, 'usd');
Payment::driver('paypal')->createPaymentIntent(10000, 'usd');

// Use in actions
class ProcessPaymentAction
{
    public function __invoke(Order $order, PaymentData $data): Payment
    {
        $paymentIntent = Payment::createPaymentIntent(
            amount: $order->total,
            currency: 'usd'
        );

        // ...
    }
}
php
use App\Services\Payment\Facades\Payment;

// 使用默认驱动
$paymentIntent = Payment::createPaymentIntent(
    amount: 10000,  // 100美元,单位为美分
    currency: 'usd'
);

// 退款操作
Payment::refundPayment($paymentIntent->id);

// 指定驱动使用
Payment::driver('stripe')->createPaymentIntent(10000, 'usd');
Payment::driver('paypal')->createPaymentIntent(10000, 'usd');

// 在动作(Actions)中使用
class ProcessPaymentAction
{
    public function __invoke(Order $order, PaymentData $data): Payment
    {
        $paymentIntent = Payment::createPaymentIntent(
            amount: $order->total,
            currency: 'usd'
        );

        // ...
    }
}

Null Driver for Testing

测试用Null驱动

php
<?php

declare(strict_types=1);

namespace App\Services\Payment\Drivers;

use App\Data\PaymentIntentData;
use App\Services\Payment\Contracts\PaymentDriver;

class NullDriver implements PaymentDriver
{
    public function createPaymentIntent(int $amount, string $currency): PaymentIntentData
    {
        return PaymentIntentData::from([
            'id' => 'pi_test_' . uniqid(),
            'amount' => $amount,
            'currency' => $currency,
            'status' => 'succeeded',
        ]);
    }

    public function refundPayment(string $paymentIntentId, ?int $amount = null): bool
    {
        return true;
    }

    public function retrievePaymentIntent(string $paymentIntentId): PaymentIntentData
    {
        return PaymentIntentData::from([
            'id' => $paymentIntentId,
            'status' => 'succeeded',
        ]);
    }
}
php
<?php

declare(strict_types=1);

namespace App\Services\Payment\Drivers;

use App\Data\PaymentIntentData;
use App\Services\Payment\Contracts\PaymentDriver;

class NullDriver implements PaymentDriver
{
    public function createPaymentIntent(int $amount, string $currency): PaymentIntentData
    {
        return PaymentIntentData::from([
            'id' => 'pi_test_' . uniqid(),
            'amount' => $amount,
            'currency' => $currency,
            'status' => 'succeeded',
        ]);
    }

    public function refundPayment(string $paymentIntentId, ?int $amount = null): bool
    {
        return true;
    }

    public function retrievePaymentIntent(string $paymentIntentId): PaymentIntentData
    {
        return PaymentIntentData::from([
            'id' => $paymentIntentId,
            'status' => 'succeeded',
        ]);
    }
}

Summary

总结

Service layer provides:
  • Manager pattern for multiple drivers
  • Saloon for HTTP requests
  • Null drivers for testing
  • Clean abstraction over external services
  • Swappable implementations
Use for external API integrations only.
服务层特性:
  • 基于Manager模式实现多驱动管理
  • 采用Saloon处理HTTP请求
  • 提供Null驱动用于测试
  • 对外部服务实现清晰的抽象
  • 支持切换不同实现方案
仅适用于外部API集成场景。