# 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: ```env SHOP_STRIPE_ENABLED=true STRIPE_KEY=pk_test_... STRIPE_SECRET=sk_test_... STRIPE_WEBHOOK_SECRET=whsec_... ``` ### Configure Services In your `config/services.php`: ```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 ```php $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 ```bash curl -X POST https://your-domain.com/api/shop/stripe/checkout/cart-uuid-here \ -H "Authorization: Bearer YOUR_TOKEN" ``` ### Example Response ```json { "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 (handled via webhook): - Cart status is updated to `CONVERTED` - Cart's `converted_at` is set - Order is created from the cart (if not already exists) - Payment is recorded on the order - ProductPurchases are created with: - `status` → `COMPLETED` - `charge_id` → Stripe Payment Intent ID - `amount` and `amount_paid` → Amount from Stripe (in cents) - Order status changes to `PROCESSING` ### 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 Events:** - `checkout.session.completed` - Converts cart, creates order if needed, records payment - `checkout.session.async_payment_succeeded` - Same as completed - `checkout.session.async_payment_failed` - Marks order as failed if exists - `checkout.session.expired` - Adds note to order **Charge Events:** - `charge.succeeded` - Updates purchases with charge info, records payment on order - `charge.failed` - Marks purchases as `FAILED`, adds note to order - `charge.refunded` - Records refund on order - `charge.dispute.created` - Puts order on hold, adds dispute note - `charge.dispute.closed` - Updates order based on dispute outcome **Payment Intent Events:** - `payment_intent.succeeded` - Records payment on order - `payment_intent.payment_failed` - Adds failure note to order - `payment_intent.canceled` - Adds cancellation note **Refund Events:** - `refund.created` - Records refund on order - `refund.updated` - Updates refund information **Invoice Events** (for subscriptions): - `invoice.payment_succeeded` - Handles subscription payments - `invoice.payment_failed` - Handles failed subscription payments ### 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 ```php // 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 and Order Updates The webhook handler automatically updates ProductPurchase records and creates/updates Order records: ### Purchase Updates - `charge_id` - Stripe Payment Intent ID - `amount` - Amount in cents - `amount_paid` - Amount paid in cents - `status` - Updated to COMPLETED, FAILED, or REFUNDED based on event ### Order Creation and Updates When a checkout session is completed: 1. Cart is marked as CONVERTED 2. Order is created from cart (if doesn't exist) via `Order::createFromCart($cart)` 3. Payment is recorded on order via `$order->recordPayment($amount, $reference, 'stripe', 'stripe')` 4. Order status is updated to PROCESSING when payment is successful 5. OrderNote records are created for payment events These fields are automatically populated: - `payment_reference` - Stripe Payment Intent ID - `payment_method` - 'stripe' - `payment_provider` - 'stripe' - `amount_paid` - Amount paid in cents - `paid_at` - Timestamp when payment was received ## Error Handling ### Missing Stripe Price ID If a cart item doesn't have a `stripe_price_id`, the checkout session creation will fail with: ```json { "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: ```json { "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: ```php $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 ```php // 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: ```php 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