What is Stripe Payment Intents Integration in CodeIgniter 4 and Why Use It?
Integrating Stripe into a CodeIgniter 4 application allows developers to accept secure online payments using credit cards, digital wallets, and more—without handling sensitive card data directly (thanks to PCI-compliant Stripe Elements and Stripe.js). At its core, this involves connecting your PHP-based CodeIgniter 4 app to Stripe's powerful API to process transactions, confirm payments, and log results in your database.
But why choose the modern Payment Intents API over the older Charges API in 2026? Stripe has marked the legacy Charges API as deprecated for new features. Payment Intents is now the recommended, unifying API that supports advanced requirements like Strong Customer Authentication (SCA), 3D Secure 2, dynamic fraud detection with Radar, and seamless handling of asynchronous payment statuses (e.g., requires_action for bank redirects or 3DS challenges). This ensures higher approval rates, better compliance with EU regulations, reduced chargebacks, and future-proof compatibility as Stripe continues to roll out new payment methods (Apple Pay, Google Pay, Klarna, etc.).
In short: If you're building or updating an e-commerce site, donation form, subscription service, or any paid feature in CodeIgniter 4, using Payment Intents delivers faster, safer, and more reliable payments. This step-by-step tutorial walks you through the complete modern integration—from installing the latest Stripe PHP SDK to implementing client-side Elements, creating Payment Intents server-side, handling confirmations, and adding basic webhook support for production reliability. By the end, you'll have a secure, working checkout ready for live testing and deployment.

Table Of Content
1 Prerequisites
- PHP 8.1+ (CodeIgniter 4.5+ compatible)
- Composer installed
- MySQL database
- Stripe account: Get your test keys from the Stripe Dashboard (publishable key starts with pk_test_, secret key with sk_test_ or sk_live_ for production)
2 Introduction
3 Create / Install a Codeigniter 4 Project
3.1 Install Codeigniter 4 Project
Use the following command to install new Codeigniter Project.
composer create-project codeigniter4/appstarter ci4-stripe
Then, navigate to your project directory:
cd ci4-stripe
3.2 Configure Environment and MySql Database
# CI_ENVIRONMENT = production
CI_ENVIRONMENT = development
database.default.hostname = localhost
database.default.database = your_db_name
database.default.username = your_db_user
database.default.password = your_db_pass
database.default.DBDriver = MySQLi
4 Install Stripe PHP SDK
composer require stripe/stripe-php:^15.0
5 Configure Stripe Keys
# STRIPE
stripe.publishable_key = pk_test_your_publishable_key
stripe.secret_key = sk_test_your_secret_key
stripe.webhook_secret = whsec_your_webhook_secret # From Stripe Dashboard > Webhooks > Signing secret
6 Create Database Migration & Model for Payments Table
php spark make:migration Payments
Edit the migration file to define the table structure:
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class Payments extends Migration
{
public function up()
{
$this->forge->addField([
'id' => [
'type' => 'INT',
'constraint' => 11,
'unsigned' => true,
'auto_increment' => true,
],
'payment_intent_id' => [
'type' => 'VARCHAR',
'constraint' => 255,
],
'amount' => [
'type' => 'DECIMAL',
'constraint' => '10,2',
],
'currency' => [
'type' => 'VARCHAR',
'constraint' => 3,
],
'status' => [
'type' => 'VARCHAR',
'constraint' => 50,
],
'created_at' => [
'type' => 'DATETIME',
'null' => true,
],
]);
$this->forge->addKey('id', true);
$this->forge->createTable('payments');
}
public function down()
{
$this->forge->dropTable('payments');
}
}
Run the migration:
php spark migrate
php spark make:model Payment
Edit app/Models/Payment.php to configure fields for managing payment data.
<?php
namespace App\Models;
use CodeIgniter\Model;
class Payment extends Model
{
protected $table = 'payments';
protected $primaryKey = 'id';
protected $allowedFields = ['payment_intent_id', 'amount', 'currency', 'status', 'created_at'];
protected $useTimestamps = false;
protected $dateFormat = 'datetime';
}
7 Create Stripe Controller
php spark make:controller StripeController
Add index and createCharge methods to manage payment processes.
<?php
namespace App\Controllers;
use App\Models\Payment;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Stripe\Stripe;
use Stripe\PaymentIntent;
use Stripe\Webhook;
class StripeController extends BaseController
{
protected $stripeSecretKey;
protected $stripeWebhookSecret;
public function __construct()
{
$this->stripeSecretKey = env('stripe.secret_key');
$this->stripeWebhookSecret = env('stripe.webhook_secret');
Stripe::setApiKey($this->stripeSecretKey);
}
public function index()
{
$data['stripe_publishable_key'] = env('stripe.publishable_key');
return view('stripe_payment', $data);
}
public function createPaymentIntent()
{
$request = $this->request->getJSON(true);
$amount = $request['amount'] ?? 1000; // Default $10.00, in cents (make dynamic as needed)
try {
$intent = PaymentIntent::create([
'amount' => $amount,
'currency' => 'usd',
'payment_method_types' => ['card'],
]);
return $this->response->setJSON(['client_secret' => $intent->client_secret]);
} catch (\Exception $e) {
return $this->response->setJSON(['error' => $e->getMessage()], 500);
}
}
public function webhook()
{
$payload = $this->request->getBody();
$sig_header = $this->request->getHeaderLine('Stripe-Signature');
try {
$event = Webhook::constructEvent($payload, $sig_header, $this->stripeWebhookSecret);
} catch (\UnexpectedValueException $e) {
return $this->response->setStatusCode(400);
} catch (\Stripe\Exception\SignatureVerificationException $e) {
return $this->response->setStatusCode(400);
}
if ($event->type == 'payment_intent.succeeded') {
$intent = $event->data->object;
$paymentModel = new Payment();
$paymentModel->insert([
'payment_intent_id' => $intent->id,
'amount' => $intent->amount / 100,
'currency' => $intent->currency,
'status' => $intent->status,
'created_at' => date('Y-m-d H:i:s', $intent->created),
]);
// Additional logic: email receipt, update order status, etc.
}
// Handle other events as needed (e.g., payment_intent.payment_failed)
return $this->response->setStatusCode(200);
}
}
?>
8 Create Payment View
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CodeIgniter 4 Stripe Payment</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://js.stripe.com/v3/"></script>
</head>
<body>
<div class="container mt-5">
<h2>Pay with Stripe</h2>
<form id="payment-form">
<div class="form-group mb-3">
<label for="amount">Amount (USD)</label>
<input type="number" id="amount" class="form-control" value="10.00" step="0.01">
</div>
<div id="card-element" class="form-control mb-3"></div>
<div id="card-errors" class="text-danger mb-3"></div>
<button type="submit" class="btn btn-primary">Pay Now</button>
</form>
</div>
<script>
const stripe = Stripe('<?= $stripe_publishable_key ?>');
const elements = stripe.elements();
const card = elements.create('card');
card.mount('#card-element');
card.on('change', (event) => {
const displayError = document.getElementById('card-errors');
displayError.textContent = event.error ? event.error.message : '';
});
const form = document.getElementById('payment-form');
form.addEventListener('submit', async (event) => {
event.preventDefault();
const amount = parseFloat(document.getElementById('amount').value) * 100; // Convert to cents
// Create Payment Intent on server
const response = await fetch('/stripe/createPaymentIntent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount }),
});
const { client_secret } = await response.json();
// Confirm payment
const { error } = await stripe.confirmCardPayment(client_secret, {
payment_method: {
card: card,
billing_details: { name: 'Test User' },
},
});
if (error) {
document.getElementById('card-errors').textContent = error.message;
} else {
alert('Payment succeeded!');
// Redirect or update UI
}
});
</script>
</body>
</html>
9 Define a Route
use CodeIgniter\Router\RouteCollection;
$routes->get('/', 'Home::index');
$routes->get('/stripe', 'StripeController::index');
$routes->post('/stripe/createPaymentIntent', 'StripeController::createPaymentIntent');
$routes->post('/stripe/webhook', 'StripeController::webhook');
10 Set Up Stripe Webhook in Dashboard
- Go to Stripe Dashboard > Developers > Webhooks.
- Add endpoint: https://your-domain.com/stripe/webhook (use ngrok for local testing: ngrok http 4000 if running php spark serve).
- Select events: payment_intent.succeeded, payment_intent.payment_failed.
- Copy the signing secret to .env as stripe.webhook_secret.
11 Folder Structure
12 Run Web Server to Test the App
php spark serve
- Visit http://localhost:8080/stripe.
- Enter amount, use Stripe test card: 4242 4242 4242 4242, any future expiry, any CVC.
- On success, check your database for the logged payment (status: 'succeeded').
- Simulate webhook: Use Stripe CLI (stripe listen --forward-to localhost:8080/stripe/webhook) or dashboard test.
13 Conclusion
Reference URL
Written by Revathi M
PHP Developer & Technical Writer · 10+ years building web applications with CodeIgniter and Laravel
Revathi specializes in PHP backend development, authentication systems, and REST API design. She writes practical, production-tested tutorials at Get Sample Code to help developers build secure applications faster.
Frequently Asked Questions
You need PHP 8.2 or higher, Composer, a fresh CodeIgniter 4 installation, a configured MySQL database in .env, and a Stripe account to get test publishable (pk_test_...) and secret (sk_test_...) keys.
Run the command: composer require stripe/stripe-php. This installs the official Stripe PHP SDK.
Add STRIPE_KEY=your_publishable_key and STRIPE_SECRET=your_secret_key to your .env file. Access the secret key in the controller with getenv('STRIPE_SECRET').
Stripe.js creates a secure token from card details on the client side (no raw card data sent to server). The token is posted to the controller, which uses Stripe\\Charge::create() to process the one-time payment and saves details to the database.
Create a migration for a 'payments' table with fields like charge_id, transaction_id, amount, card_last_four, card_exp_month/year, etc. Use a PaymentModel with $allowedFields to save successful payment data.
Include 'https://js.stripe.com/v3/', initialize Stripe with your publishable key, mount a card element to a div, and use stripe.createToken(cardElement) to generate and submit the token on form submit.
Common issues: Invalid test keys, incorrect token, using live keys with test cards, or amount/currency mismatch. Check browser console for client-side errors and Stripe dashboard logs for server-side issues.
Yes, Stripe.js tokenization ensures raw card data never reaches your server, reducing PCI compliance scope. Use HTTPS in production and keep the secret key in .env only.
No, it focuses on one-time charges using the Charge API. For subscriptions, consider Stripe Billing or additional APIs like Customer and Subscription objects.
The Charge API is legacy. For modern features like 3D Secure and better fraud prevention, migrate to Payment Intents or Stripe Checkout sessions.
