Stripe Payment Gateway
Accept credit and debit card payments through Stripe. This add-on integrates Stripe’s Payment Intents API with the Larapen e-shop, providing secure, PCI-compliant card processing with 3D Secure support, webhook-driven order updates, and one-click refunds.
Payment Intents API
Uses Stripe’s latest Payment Intents flow for SCA-compliant, secure card payments.
3D Secure
Automatic or always-on 3D Secure verification for strong customer authentication (SCA).
Webhook Processing
Handles payment success, failure, refund, and dispute events via signed webhooks.
Encrypted Credentials
API keys are encrypted at rest using Laravel’s Crypt facade. Never stored in plaintext.
Use Cases
Online Store with Card Payments
You run an e-shop on Larapen selling physical or digital products. Customers select “Credit Card” at checkout, enter their card details in a Stripe Elements form, and pay instantly.
- Install the Stripe add-on alongside the Shop add-on.
- Enter your Stripe API keys in Admin → Stripe → Settings.
- Stripe appears as a payment option at checkout automatically.
- Orders are confirmed in real time via webhooks.
Digital Downloads with Instant Delivery
You sell digital products (e-books, software, templates). Stripe confirms payment immediately, triggering order completion and granting download access.
Multi-Currency International Sales
Configure the currency to match your target market (USD, EUR, GBP, etc.). Stripe handles currency conversion and international card networks.
Requirements
- Larapen CMS v1.0.0 or later
- PHP 8.3+
- MySQL 8.0+
- The Shop add-on (required dependency)
- A Stripe account with API keys (see Obtaining API Keys)
- The
stripe/stripe-phpComposer package
PaymentGatewayInterface contract and is automatically discovered by the shop checkout system.
Installation
Step 1: Place the Add-on
Copy or symlink the stripe folder into your Larapen "extensions/addons" directory:
Step 2: Install Stripe PHP SDK
Step 3: Activate the Add-on
Go to Admin → Add-ons → Installed Add-ons and activate Stripe Payment Gateway.
Step 4: Run Migrations
This creates 2 tables: stripe_customers and stripe_payment_intents.
Step 5: Configure API Keys
Navigate to Admin → Stripe → Settings and enter your Stripe Publishable Key, Secret Key, and (optionally) the Webhook Signing Secret. See Configuration.
Step 6: Set Up Webhooks
In the Stripe Dashboard, create a webhook endpoint pointing to:
Select the events listed in Handled Events. Copy the signing secret and paste it in the admin settings. See Webhook Setup.
Configuration
All settings are managed in Admin → Stripe → Settings (stored in the settings table, group stripe).
| Setting | Description | Default |
|---|---|---|
stripe_public_key |
Stripe Publishable Key (starts with pk_). Encrypted at rest. |
(empty) |
stripe_secret_key |
Stripe Secret Key (starts with sk_). Encrypted at rest. |
(empty) |
stripe_webhook_secret |
Webhook signing secret (starts with whsec_). Encrypted at rest. |
(empty) |
stripe_capture_method |
How payments are captured: automatic (immediate) or manual (authorize then capture later). |
automatic |
stripe_currency |
Three-letter ISO currency code in lowercase (e.g. usd, eur, gbp). |
usd |
stripe_request_three_d_secure |
When to request 3D Secure: automatic (only when required by the issuing bank) or any (always request). |
automatic |
Crypt::encryptString() before being
stored in the database. They are decrypted at runtime when needed. Never share your secret key.
Environment Variables
Environment variables serve as defaults. Settings saved in the admin panel override them.
Obtaining API Keys
- Log in to the Stripe Dashboard.
- Navigate to Developers → API Keys.
- Copy the Publishable key (starts with
pk_test_orpk_live_). - Reveal and copy the Secret key (starts with
sk_test_orsk_live_). - Paste both keys in Admin → Stripe → Settings.
pk_test_ / sk_test_) during development.
Switch to live keys for production. Test card numbers are available at
docs.stripe.com/testing.
Test Card Numbers
| Card Number | Scenario |
|---|---|
4242 4242 4242 4242 |
Successful payment |
4000 0025 0000 3155 |
Requires 3D Secure authentication |
4000 0000 0000 9995 |
Declined (insufficient funds) |
Use any future expiry date (e.g. 12/34) and any 3-digit CVC.
Admin: Settings
The settings page (Admin → Stripe → Settings) is organized into two sections:
API Keys
- Publishable Key: Masked password field with show/hide toggle. Starts with
pk_. - Secret Key: Masked password field with show/hide toggle. Starts with
sk_. Encrypted before storage. - Webhook Signing Secret: Masked password field. Starts with
whsec_. Used to verify incoming webhook signatures.
A warning banner reminds admins that keys are encrypted before storage and that fields should be left empty to retain current values.
An info box provides direct links to:
Payment Options
- Capture Method:
Automatic(charge immediately) orManual(authorize now, capture later). - Currency: Three-letter currency code in lowercase (e.g.
usd,eur,gbp). - 3D Secure:
Automatic(only when required by the issuing bank) orAlways request.
Payment Flow
The Stripe add-on uses the Payment Intents API for SCA-compliant card processing. Here is the complete payment flow:
- Checkout Selection: The customer selects “Credit Card” as the payment method at checkout. The Stripe payment form (Stripe Elements) appears.
- Order Creation: The shop creates an order and calls
StripeGateway::createPaymentIntent($order). - Payment Intent: The gateway creates a Stripe PaymentIntent via the API, stores the details
locally in
stripe_payment_intents, and returns theclient_secret. - Client-Side Confirmation: The browser uses
stripe.confirmCardPayment()with the client secret and card details collected via Stripe Elements. - 3D Secure: If the card requires authentication, Stripe displays a 3D Secure challenge. The customer completes it in a modal.
- Server Confirmation: On success, the browser redirects to
/stripe/confirm?payment_intent={id}. TheStripeController::confirm()method retrieves the PaymentIntent from Stripe, updates the local record, and marks the order as paid. - Webhook Backup: Stripe also sends a
payment_intent.succeededwebhook. This ensures the order is marked paid even if the customer closes the browser before the redirect completes.
Payment Confirmation
After client-side card confirmation succeeds, the browser redirects to the server-side confirmation endpoint:
/stripe/confirm?payment_intent={id}
Description
Retrieves the PaymentIntent from Stripe, verifies its status, updates the local record, and marks the associated order as paid.
Possible Outcomes
succeeded |
Order marked as paid. Redirect to success page. |
requires_action |
Additional authentication needed. Redirect back to checkout with client secret. |
| Other | Payment failed. Redirect to checkout with error message. |
Webhook Setup
Webhooks ensure your site receives payment status updates even if the customer closes their browser.
Creating a Webhook Endpoint in Stripe
- Go to Stripe Dashboard → Developers → Webhooks.
- Click Add endpoint.
- Enter your endpoint URL:
https://yoursite.com/stripe/webhook - Select the following events:
payment_intent.succeededpayment_intent.payment_failedcharge.refundedcharge.dispute.created
- Click Add endpoint to save.
- Reveal the Signing secret (starts with
whsec_) and paste it in Admin → Stripe → Settings.
/stripe/webhook) is excluded from CSRF verification.
Authentication is handled via Stripe’s signature verification instead.
Processing Refunds
Refunds are processed through the StripeGateway::refund() method, which can be called
from the shop’s order management interface.
Refund Flow
- Admin initiates a refund from the order detail page in the admin panel.
- The system calls
StripeGateway::refund($transaction, $amount, $reason). - A
Stripe\Refundis created via the API using the original PaymentIntent ID. - A new
Transactionrecord is created with typerefund. - The refund result is returned (success, pending, or failure).
Partial Refunds
The $amount parameter allows partial refunds. The amount is specified in the order’s currency
(not in cents: the gateway handles cent conversion internally).
Refund Statuses
| Status | Description |
|---|---|
succeeded |
Refund processed immediately. Transaction status: completed. |
pending |
Refund is being processed (can take 5–10 business days for some payment methods). Transaction status: pending. |
failed |
Refund could not be processed. Error details logged. |
Updating
Step 1: Replace Files
Replace the add-on directory with the new version.
Step 2: Update Stripe PHP SDK
Step 3: Run Migrations
Step 4: Clear Caches
Step 5: Verify
Visit Admin → Stripe → Settings and confirm your API keys are still configured. Process a test payment to verify the integration.
Troubleshooting
Stripe not appearing as a payment method at checkout
- Ensure the Stripe add-on is activated in Admin → Add-ons.
- Ensure the Shop add-on is also activated (Stripe depends on it).
- Verify that both
stripe_public_keyandstripe_secret_keyare configured. TheisAvailable()method returnsfalseif either is empty.
Payment fails with “Invalid API Key”
- Check that the secret key starts with
sk_test_(test mode) orsk_live_(production). - If you recently rotated your keys in the Stripe Dashboard, update them in the admin settings.
- Ensure the key is properly encrypted. Try clearing the value and re-entering it.
Webhooks returning 400 errors
- Verify the
stripe_webhook_secretis set in admin settings. - The secret must match the specific endpoint you created in the Stripe Dashboard (each endpoint has its own unique signing secret).
- Ensure your server’s clock is synchronized (Stripe rejects signatures with excessive time drift).
- Check that the webhook URL is
https://yoursite.com/stripe/webhook(nothttp://).
3D Secure challenge not appearing
- In test mode, use the test card
4000 0025 0000 3155which always requires 3D Secure. - With
automaticmode, 3DS only triggers when the issuing bank requires it. Switch toanyto force it for testing. - Ensure Stripe.js is loaded correctly. Check the browser console for JavaScript errors.
Order not marked as paid after successful payment
- Check server logs for errors during the
/stripe/confirmredirect. - Verify the webhook is configured and receiving events. Check the Stripe webhook logs for delivery attempts.
- Ensure the
stripe_payment_intentstable has a record for the PaymentIntent ID. If not, the payment was not initiated through the standard flow. - Check that the
Payableinterface is correctly implemented on the Order model with a workingmarkAsPaid()method.
Refund fails with “No such payment intent”
- The refund uses the
gateway_transaction_idfrom theTransactionrecord. Ensure this contains the original Stripe PaymentIntent ID (starts withpi_). - You cannot refund a PaymentIntent that has not yet succeeded.
Customer created on every payment
- The
getOrCreateCustomer()method checks for an existingstripe_customersrecord byuser_idbefore creating a new Stripe Customer. If duplicates appear, verify theuser_idforeign key is set correctly. - Guest checkouts (no authenticated user) do not create Stripe Customer objects.
Currency mismatch errors
- The
stripe_currencysetting must match the currency used by the shop. For example, if the shop prices are in EUR, set the Stripe currency toeur. - Currency codes must be lowercase (e.g.
usd, notUSD).