承接 sayed/payment-laravel 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

sayed/payment-laravel

Composer 安装命令:

composer require sayed/payment-laravel

包简介

A comprehensive Laravel payment integration package supporting Stripe, PayPal, and Paddle with unified interface, webhooks, and invoice payments

README 文档

README

This guide provides step-by-step instructions for integrating the Sayed Payment Laravel package into your application.

Table of Contents

  1. Installation
  2. Configuration
  3. Basic Setup
  4. Creating Payments
  5. Handling Webhooks
  6. Advanced Features

Installation

Step 1: Install the Package

Install via Composer:

composer require sayed/payment-laravel

Step 2: Publish Configuration

Publish the configuration file:

php artisan vendor:publish --tag=payment-config

This creates config/payment.php in your application.

Configuration

Step 3: Add Environment Variables

Add your payment provider credentials to .env:

# Choose your default provider
PAYMENT_PROVIDER=stripe

# Stripe Configuration
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=

# PayPal Configuration
PAYPAL_CLIENT_ID=
PAYPAL_CLIENT_SECRET=
PAYPAL_MODE=sandbox

# Paddle Configuration
PADDLE_VENDOR_ID=
PADDLE_VENDOR_AUTH_CODE=
PADDLE_PUBLIC_KEY=
PADDLE_ENVIRONMENT=

Step 4: Verify Configuration

Check config/payment.php to ensure providers are configured:

Basic Setup

Step 5: Create Payment Controller

Create a controller to handle payments:

php artisan make:controller PaymentController

app/Http/Controllers/PaymentController.php:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Sayed\Payment\Facades\Payment;
use Exception;

class PaymentController extends Controller
{
    /**
     * Create payment checkout session
     */
    public function createCheckout(Request $request)
    {
        try {
            $result = Payment::driver('stripe')->checkout([
                'currency' => 'usd',
                'amount' => 2000, // $20.00 in cents
                'products' => [
                    [
                        'title' => 'Premium Plan',
                        'amount' => 2000,
                        'quantity' => 1,
                    ]
                ],
                'is_subscription' => false,
                'success_url' => route('payment.success'),
                'cancel_url' => route('payment.cancel'),
                'metadata' => [
                    'user_id' => auth()->id(),
                    'order_id' => 'ORD-' . time(),
                ],
            ]);

            return redirect($result['paymentLinkUrl']);
        } catch (Exception $e) {
            return back()->with('error', 'Payment failed: ' . $e->getMessage());
        }
    }

}

One-Time Payment

use Sayed\Payment\Facades\Payment;

$result = Payment::driver('stripe')->checkout([
    'products' => [
        [
            'title' => 'Product Name',
            'amount' => 5000,
            'quantity' => 1,
        ]
    ],
    'is_subscription' => false,
    'success_url' => route('payment.success'),
    'cancel_url' => route('payment.cancel'),
]);

// Redirect user to payment page
return redirect($result['paymentLinkUrl']);

Subscription Payment

$result = Payment::driver('stripe')->checkout([
    'currency' => 'usd',
    'amount' => 2999, // $29.99
    'products' => [
        [
            'title' => 'Monthly Subscription',
            'amount' => 2999,
            'quantity' => 1,
        ]
    ],
    'is_subscription' => true,
    'interval' => 'month', // day, week, month, year
    'success_url' => route('subscription.success'),
    'cancel_url' => route('subscription.cancel'),
]);

Multiple Products

$result = Payment::driver('stripe')->checkout([
    'currency' => 'usd',
    'products' => [
        [
            'title' => 'Product 1',
            'amount' => 1000,
            'quantity' => 2,
        ],
        [
            'title' => 'Product 2',
            'amount' => 1500,
            'quantity' => 1,
        ],
    ],
    'is_subscription' => false,
    'success_url' => route('payment.success'),
    'cancel_url' => route('payment.cancel'),
]);

Invoice Payment (Stripe Only)

// Step 3: Create and pay invoice
$invoice = Payment::driver('stripe')->payWithInvoice([
    'customer_id' => $customer['customer_id'],
    'payment_method_id' => $request->payment_method_id,
    'currency' => 'usd',
    'items' => [
        [
            'description' => 'Service Fee',
            'amount' => 5000,
            'quantity' => 1,
        ],
    ],
]);

return response()->json([
    'success' => true,
    'invoice_pdf' => $invoice['invoice_pdf'],
]);

Refund Payment

$refund = Payment::driver('stripe')->refundPayment(
    transactionId: 'ch_xxxxx',
    amount: 1000 // $10.00
);

Product Creation

Create products and subscription plans programmatically across all payment providers. All methods return DTOs for type-safe access.

Create One-Time Product

use Sayed\Payment\Facades\Payment;

// Create a one-time payment product
$product = Payment::driver('stripe')->createProduct([
    'name' => 'Premium E-book',
    'description' => 'Complete guide to Laravel',
    'amount' => 2999, // $29.99 in cents
    'currency' => 'usd',
]);

// Access product details
echo $product->productId;  // prod_xxxxx
echo $product->priceId;    // price_xxxxx
echo $product->amount;     // 2999

Create Recurring Subscription

// Create a monthly subscription
$subscription = Payment::driver('stripe')->createRecurringProduct([
    'name' => 'Pro Membership',
    'description' => 'Monthly access to premium features',
    'amount' => 1999, // $19.99 in cents
    'currency' => 'usd',
    'interval' => 'month', // day, week, month, year
    'interval_count' => 1,
]);

// Use in checkout
$checkout = Payment::driver('stripe')->checkout([
    'success_url' => route('payment.success'),
    'cancel_url' => route('payment.cancel'),
    'products' => [
        ['id' => $subscription->priceId, 'quantity' => 1]
    ],
    'is_subscription' => true,
]);

PayPal Product Creation

// Create PayPal product
$product = Payment::driver('paypal')->createProduct([
    'name' => 'Online Course',
    'description' => 'Complete PHP Course',
    'amount' => 4999,
    'currency' => 'USD',
]);

// Create PayPal subscription plan
$plan = Payment::driver('paypal')->createRecurringProduct([
    'name' => 'Monthly SaaS Plan',
    'amount' => 2999,
    'currency' => 'USD',
    'interval' => 'MONTH',
]);

echo $plan->planId; // Plan ID for subscriptions

Paddle Product Creation

// Create Paddle product
$product = Payment::driver('paddle')->createProduct([
    'name' => 'Software License',
    'amount' => 9999,
    'currency' => 'USD',
]);

// Create Paddle subscription with trial
$subscription = Payment::driver('paddle')->createRecurringProduct([
    'name' => 'Basic Plan',
    'amount' => 999,
    'currency' => 'USD',
    'interval' => 'month',
    'trial_days' => 14,
]);

Advanced Features

Dynamic Provider Selection

use Sayed\Payment\Facades\Payment;
use Sayed\Payment\Enums\PaymentMethod;

// Use default provider from config
$payment = Payment::driver();

// Use specific provider with string
$stripePayment = Payment::driver('stripe');
$paypalPayment = Payment::driver('paypal');
$paddlePayment = Payment::driver('paddle');

// Use specific provider with enum (type-safe)
$stripePayment = Payment::driver(PaymentMethod::STRIPE);
$paypalPayment = Payment::driver(PaymentMethod::PAYPAL);
$paddlePayment = Payment::driver(PaymentMethod::PADDLE);

// Dynamic selection with enum
$provider = PaymentMethod::from($request->payment_method);
$payment = Payment::driver($provider);

Using PaymentMethod Enum

use Sayed\Payment\Enums\PaymentMethod;

// Get all available payment methods
$methods = PaymentMethod::values(); // ['stripe', 'paypal', 'paddle']

// Display names
foreach (PaymentMethod::cases() as $method) {
    echo $method->displayName(); // Stripe, PayPal, Paddle
}

// Type-safe payment creation
$result = Payment::driver(PaymentMethod::STRIPE)->checkout([
    'currency' => 'usd',
    'products' => [
        ['title' => 'Product', 'amount' => 2999, 'quantity' => 1]
    ],
    'is_subscription' => false,
    'success_url' => route('payment.success'),
]);

Custom Metadata

Payment::driver('stripe')->checkout([
    // ... other fields
    'metadata' => [
        'user_id' => auth()->id(),
        'order_id' => $order->id,
        'plan' => 'premium',
        'source' => 'web',
    ],
]);

Get Invoice Details (Stripe)

$invoice = Payment::driver('stripe')->getInvoice('in_xxxxx');

return view('invoice', [
    'invoice_pdf' => $invoice['invoice_pdf'],
    'amount' => $invoice['amount_paid'],
    'status' => $invoice['status'],
]);

Event-Driven Architecture

The package provides an event-driven architecture that allows you to easily handle payment events by creating custom event classes.

How It Works

When a webhook is received:

  1. The package validates and transforms the webhook data
  2. It dispatches events based on the event type (invoice, checkout, subscription)
  3. Your custom event classes are automatically instantiated and their handle() method is called
  4. You can also listen to these events using Laravel's event listener system

Step 1: Create Your Event Classes

Create event classes in app/Events by extending the base event classes provided by the package:

Invoice Events

<?php

namespace App\Events;

use Sayed\Payment\Events\InvoiceEvent;
use App\Models\Invoice;
use Illuminate\Support\Facades\Log;

class InvoicePaymentSucceeded extends InvoiceEvent
{
    public function getEventName(): string
    {
        return 'invoice.payment_succeeded';
    }

    public function handle(): void
    {
        Log::info('Invoice paid', ['invoice_id' => $this->invoiceId]);

        // Update your database
        Invoice::where('payment_invoice_id', $this->invoiceId)->update([
            'status' => 'paid',
            'paid_at' => now(),
        ]);

        // Your business logic here
        // - Grant access to product
        // - Send confirmation email
        // - Trigger fulfillment process
    }
}

Checkout Events

<?php

namespace App\Events;

use Sayed\Payment\Events\CheckoutEvent;
use App\Models\Order;

class CheckoutCompleted extends CheckoutEvent
{
    public function getEventName(): string
    {
        return 'checkout.completed';
    }

    public function handle(): void
    {
        // Get order from metadata
        $orderId = $this->metadata['order_id'] ?? null;
        
        if ($orderId) {
            Order::find($orderId)->update([
                'status' => 'paid',
                'transaction_id' => $this->transactionId,
                'paid_at' => now(),
            ]);
        }
    }
}

Subscription Events

<?php

namespace App\Events;

use Sayed\Payment\Events\SubscriptionEvent;
use App\Models\Subscription;

class SubscriptionCreated extends SubscriptionEvent
{
    public function getEventName(): string
    {
        return 'subscription.created';
    }

    public function handle(): void
    {
        Subscription::create([
            'payment_subscription_id' => $this->subscriptionId,
            'customer_id' => $this->customerId,
            'plan_id' => $this->planId,
            'status' => $this->status,
            'amount' => $this->amount,
            'currency' => $this->currency,
        ]);
    }
}

Step 2: Register Events in Config

Update config/payment.php to map webhook events to your custom classes:

return [
    // ... other config

    'events' => [
        'invoice' => [
            'payment_succeeded' => \App\Events\InvoicePaymentSucceeded::class,
            'payment_failed' => \App\Events\InvoicePaymentFailed::class,
            'finalized' => \App\Events\InvoiceFinalized::class,
            'updated' => \App\Events\InvoiceUpdated::class,
        ],
        'checkout' => [
            'completed' => \App\Events\CheckoutCompleted::class,
            'expired' => \App\Events\CheckoutExpired::class,
        ],
        'subscription' => [
            'created' => \App\Events\SubscriptionCreated::class,
            'updated' => \App\Events\SubscriptionUpdated::class,
            'deleted' => \App\Events\SubscriptionDeleted::class,
            'trial_ending' => \App\Events\SubscriptionTrialEnding::class,
        ],
    ],
];

Step 3: Access Event Data

Your event classes have access to the following properties:

InvoiceEvent Properties

$this->provider;          // 'stripe', 'paypal', or 'paddle'
$this->invoiceId;         // Provider's invoice ID
$this->status;            // Invoice status
$this->amount;            // Amount in cents (integer)
$this->currency;          // Currency code (e.g., 'usd')
$this->customerId;        // Customer ID (if available)
$this->subscriptionId;    // Subscription ID (if applicable)
$this->metadata;          // Custom metadata array
$this->rawPayload;        // Original webhook payload (JSON string)

CheckoutEvent Properties

$this->provider;          // Payment provider
$this->transactionId;     // Transaction/session ID
$this->status;            // Payment status
$this->amount;            // Amount in cents (integer)
$this->currency;          // Currency code
$this->customerId;        // Customer ID (if available)
$this->customerEmail;     // Customer email (if available)
$this->metadata;          // Custom metadata array
$this->rawPayload;        // Original webhook payload (JSON string)

SubscriptionEvent Properties

$this->provider;              // Payment provider
$this->subscriptionId;        // Subscription ID
$this->status;                // Subscription status
$this->amount;                // Amount in cents (integer, nullable)
$this->currency;              // Currency code (nullable)
$this->customerId;            // Customer ID (if available)
$this->customerEmail;         // Customer email (if available)
$this->planId;                // Plan/price ID (if available)
$this->currentPeriodStart;    // Period start date (nullable)
$this->currentPeriodEnd;      // Period end date (nullable)
$this->metadata;              // Custom metadata array
$this->rawPayload;            // Original webhook payload (JSON string)

Step 4: Using Laravel's Event System (Optional)

You can also use Laravel's native event listeners:

Create a Listener:

php artisan make:listener SendInvoicePaidNotification

Register in EventServiceProvider:

use App\Events\InvoicePaymentSucceeded;
use App\Listeners\SendInvoicePaidNotification;

protected $listen = [
    InvoicePaymentSucceeded::class => [
        SendInvoicePaidNotification::class,
    ],
];

Listener Example:

<?php

namespace App\Listeners;

use App\Events\InvoicePaymentSucceeded;
use Illuminate\Support\Facades\Mail;

class SendInvoicePaidNotification
{
    public function handle(InvoicePaymentSucceeded $event)
    {
        // Send email notification
        Mail::to($event->customerEmail)->send(
            new InvoicePaidMail($event->invoiceId, $event->amount)
        );
    }
}

Available Event Names

Stripe Event Mapping

Stripe Event Simplified Name Event Type
checkout.session.completed completed checkout
checkout.session.expired expired checkout
customer.subscription.created created subscription
customer.subscription.updated updated subscription
customer.subscription.deleted deleted subscription
customer.subscription.trial_will_end trial_ending subscription
invoice.created created invoice
invoice.finalized finalized invoice
invoice.paid payment_succeeded invoice
invoice.payment_failed payment_failed invoice
invoice.updated updated invoice

Real-World Example

Here's a complete example of handling invoice payments:

<?php

namespace App\Events;

use Sayed\Payment\Events\InvoiceEvent;
use App\Models\Invoice;
use App\Models\User;
use App\Jobs\SendInvoiceReceiptJob;
use App\Jobs\GrantProductAccessJob;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;

class InvoicePaymentSucceeded extends InvoiceEvent
{
    public function getEventName(): string
    {
        return 'invoice.payment_succeeded';
    }

    public function handle(): void
    {
        DB::transaction(function () {
            // 1. Update invoice in database
            $invoice = Invoice::where('payment_invoice_id', $this->invoiceId)
                ->lockForUpdate()
                ->first();

            if (!$invoice) {
                Log::error('Invoice not found', ['invoice_id' => $this->invoiceId]);
                return;
            }

            $invoice->update([
                'status' => 'paid',
                'paid_at' => now(),
                'payment_provider' => $this->provider,
                'payment_data' => $this->metadata,
            ]);

            // 2. Get user
            $user = $invoice->user;

            // 3. Grant access to product/service
            if ($invoice->product_id) {
                GrantProductAccessJob::dispatch($user, $invoice->product_id);
            }

            // 4. Send receipt email
            SendInvoiceReceiptJob::dispatch($user, $invoice);

            // 5. Log for analytics
            Log::info('Invoice payment processed successfully', [
                'invoice_id' => $this->invoiceId,
                'user_id' => $user->id,
                'amount' => $this->amount,
                'provider' => $this->provider,
            ]);
        });
    }
}

sayed/payment-laravel 适用场景与选型建议

sayed/payment-laravel 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 159 次下载、GitHub Stars 达 1, 最近一次更新时间为 2025 年 11 月 03 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「payment」 「subscription」 「paypal」 「stripe」 「invoice」 「laravel」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 sayed/payment-laravel 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 sayed/payment-laravel 我们能提供哪些服务?
定制开发 / 二次开发

基于 sayed/payment-laravel 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 159
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 2
  • 点击次数: 33
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 1
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-11-03