laravel-shop/docs/04-stripe-checkout.md

7.0 KiB

Stripe Checkout Integration

This document describes the Stripe Checkout integration for the Laravel Shop package.

Configuration

Enable Stripe

Add the following to your .env file:

SHOP_STRIPE_ENABLED=true
STRIPE_KEY=pk_test_...
STRIPE_SECRET=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

Configure Services

In your config/services.php:

'stripe' => [
    'key' => env('STRIPE_KEY'),
    'secret' => env('STRIPE_SECRET'),
    'webhook_secret' => env('STRIPE_WEBHOOK_SECRET'),
],

Price Configuration

All products and product prices must have a stripe_price_id before they can be used in Stripe Checkout.

Setting Stripe Price ID

$product = Product::find($id);
$price = $product->defaultPrice()->first();
$price->update(['stripe_price_id' => 'price_...']);

Creating a Checkout Session

API Endpoint

POST /api/shop/stripe/checkout/{cartId}

Example Request

curl -X POST https://your-domain.com/api/shop/stripe/checkout/cart-uuid-here \
  -H "Authorization: Bearer YOUR_TOKEN"

Example Response

{
  "session_id": "cs_test_...",
  "url": "https://checkout.stripe.com/c/pay/cs_test_..."
}

Redirect the user to the url to complete payment.

Handling Success/Cancel

Success URL

GET /api/shop/stripe/success?session_id={SESSION_ID}&cart_id={CART_ID}

When payment is successful:

  • Cart status is updated to CONVERTED
  • Cart's converted_at is set
  • ProductPurchases are updated with:
    • statusCOMPLETED
    • charge_id → Stripe Payment Intent ID
    • amount_paid → Amount from Stripe (in dollars, converted from cents)

Cancel URL

GET /api/shop/stripe/cancel?cart_id={CART_ID}

When payment is cancelled, the cart remains in ACTIVE status and the user can try again.

Webhook Handler

Webhook URL

POST /api/shop/stripe/webhook

Supported Events

The webhook handler processes the following Stripe events:

  • checkout.session.completed - Updates cart to converted, updates purchases
  • checkout.session.async_payment_succeeded - Same as completed
  • checkout.session.async_payment_failed - Logs failure
  • charge.succeeded - Updates purchases with charge info
  • charge.failed - Marks purchases as FAILED
  • payment_intent.succeeded - Updates purchases
  • payment_intent.payment_failed - Marks purchases as FAILED

Configuring Webhook in Stripe

  1. Go to Stripe Dashboard → Developers → Webhooks
  2. Click "Add endpoint"
  3. Enter your webhook URL: https://your-domain.com/api/shop/stripe/webhook
  4. Select events to listen to (or select "receive all events")
  5. Copy the signing secret and add it to your .env as STRIPE_WEBHOOK_SECRET

Route Customization

Disabling Stripe Routes

The Stripe routes are automatically registered if:

  • config('shop.stripe.enabled') is true
  • Routes haven't already been defined in your Laravel app

You can manually define routes in your application's route files to override the default behavior.

Custom Routes Example

// routes/web.php or routes/api.php

use Blax\Shop\Http\Controllers\StripeCheckoutController;
use Blax\Shop\Http\Controllers\StripeWebhookController;

Route::post('custom/stripe/checkout/{cartId}', [StripeCheckoutController::class, 'createCheckoutSession'])
    ->name('shop.stripe.checkout');

Route::get('custom/stripe/success', [StripeCheckoutController::class, 'success'])
    ->name('shop.stripe.success');

Route::get('custom/stripe/cancel', [StripeCheckoutController::class, 'cancel'])
    ->name('shop.stripe.cancel');

Route::post('custom/stripe/webhook', [StripeWebhookController::class, 'handleWebhook'])
    ->name('shop.stripe.webhook');

ProductPurchase Updates

The webhook handler automatically updates ProductPurchase records with charge information if the columns exist:

  • charge_id - Stripe Payment Intent ID
  • amount_paid - Amount paid in dollars

These fields are automatically populated from the fillable array on the ProductPurchase model.

Error Handling

Missing Stripe Price ID

If a cart item doesn't have a stripe_price_id, the checkout session creation will fail with:

{
  "error": "Item 'Product Name' is missing a Stripe price ID"
}

Stripe API Errors

All Stripe API errors are caught and logged. The response will include:

{
  "error": "Failed to create checkout session: [error message]"
}

Pool Products with MayBePoolProduct Trait

Pool-related methods have been moved to the MayBePoolProduct trait to keep the Product model cleaner.

Using Pool Methods

All pool methods work the same way, they're just now in a trait:

$pool = Product::find($poolId);

// Check if pool
$pool->isPool(); // returns bool

// Get available quantity
$pool->getAvailableQuantity($from, $until);

// Get pool max quantity
$pool->getPoolMaxQuantity($from, $until);

// Claim pool stock
$pool->claimPoolStock($quantity, $reference, $from, $until);

// Release pool stock
$pool->releasePoolStock($reference);

// Pricing methods
$pool->getLowestPoolPrice();
$pool->getHighestPoolPrice();
$pool->getPoolPriceRange();
$pool->setPoolPricingStrategy('lowest'); // 'lowest', 'highest', 'average'

// Validation
$pool->validatePoolConfiguration();

// Availability methods
$pool->getPoolAvailabilityCalendar($start, $end, $quantity);
$pool->getSingleItemsAvailability($from, $until);
$pool->isPoolAvailable($from, $until, $quantity);
$pool->getPoolAvailablePeriods($start, $end, $quantity, $minDays);

Benefits of Trait

  • Cleaner Product model
  • Pool functionality can be used by other models in the future
  • Better separation of concerns
  • Easier testing and maintenance

Example Usage Flow

// 1. Create a product with Stripe price
$product = Product::create([...]);
$price = ProductPrice::create([
    'purchasable_id' => $product->id,
    'purchasable_type' => Product::class,
    'stripe_price_id' => 'price_1234567890',
    'unit_amount' => 2000, // $20.00 in cents
    'is_default' => true,
]);

// 2. Add to cart
$cart = auth()->user()->currentCart();
$cart->addToCart($product, 1);

// 3. Create Stripe checkout session
$response = Http::post('/api/shop/stripe/checkout/' . $cart->id);
$checkoutUrl = $response->json('url');

// 4. Redirect user to Stripe
return redirect($checkoutUrl);

// 5. Stripe redirects back to success URL
// 6. Webhook processes payment
// 7. Cart is converted, purchases are completed

Testing

Mock Stripe in your tests:

use Stripe\Stripe;
use Stripe\Checkout\Session as StripeSession;

// Mock Stripe session creation
Stripe::setApiKey('sk_test_fake');
StripeSession::create([...]); // Use test mode

Security Considerations

  1. Always verify webhook signatures - Set STRIPE_WEBHOOK_SECRET in production
  2. Use HTTPS - Stripe requires HTTPS for webhooks
  3. Validate cart ownership - Ensure users can only checkout their own carts
  4. Test mode first - Use Stripe test keys during development
  5. Monitor webhooks - Check Stripe Dashboard for webhook delivery issues