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

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

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

sayed/payment-laravel

最新稳定版本:v1.0.2

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,
            ]);
        });
    }
}

统计信息

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

GitHub 信息

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

其他信息

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