Are you looking to accept payments on your WordPress site without paying extra fees beyond Stripe’s standard rates? While there are many payment plugins available, most charge additional fees ranging from 1% to 3% unless you purchase their premium versions. Today, I’ll show you how to create your own simple Stripe payment plugin that’s completely free and customizable.
What We’ll Build
We’ll create a WordPress plugin that:
- Accepts one-time payments via Stripe
- Collects customer information (name and email)
- Provides a clean, user-friendly payment form
- Handles successful payments and errors gracefully
- Uses Stripe’s latest API and security features
Prerequisites
Before we begin, make sure you have:
- A WordPress site
- A Stripe account (free to create)
- Basic knowledge of PHP and WordPress development
- Composer installed on your development machine
Step 1: Setting Up the Plugin Structure
First, create a new directory in your WordPress plugins folder named simple-stripe-payments
. Inside it, create these files:
simple-stripe-payments/
├── css/
│ └── simple-stripe-payments.css
├── js/
│ └── simple-stripe-payments.js
└── simple-stripe-payments.php
Install the Stripe PHP SDK using Composer inside the plugin folder:
composer require stripe/stripe-php
Step 2: The Main Plugin File
Here’s our main plugin file (simple-stripe-payments.php
):
<?php
/**
* Plugin Name: Simple Stripe Payments
* Plugin URI: https://singhnitin.com/create-a-custom-wordpress-plugin-for-stripe-payments
* Description: Accept one-time payments via Stripe without extra fees
* Version: 1.0
* Author: Nitin Singh
* Author URI: https://singhnitin.com
* License: GPL v2 or later
*/
if (!defined('ABSPATH')) {
exit;
}
// Require Composer autoloader
require_once plugin_dir_path(__FILE__) . 'vendor/autoload.php';
class Simple_Stripe_Payments {
private $stripe_secret_key;
private $stripe_public_key;
public function __construct() {
// Use test keys by default, override these with your live keys
$this->stripe_secret_key = 'sk_test_your_key_here';
$this->stripe_public_key = 'pk_test_your_key_here';
// Initialize Stripe
\Stripe\Stripe::setApiKey($this->stripe_secret_key);
// Register hooks
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
add_shortcode('stripe_payment_form', array($this, 'render_payment_form'));
add_action('wp_ajax_process_stripe_payment', array($this, 'handle_payment'));
add_action('wp_ajax_nopriv_process_stripe_payment', array($this, 'handle_payment'));
}
public function enqueue_scripts() {
wp_enqueue_style(
'simple-stripe-payments',
plugin_dir_url(__FILE__) . 'css/simple-stripe-payments.css',
array(),
'1.0'
);
wp_enqueue_script('stripe-js', 'https://js.stripe.com/v3/', array(), null, true);
wp_enqueue_script(
'simple-stripe-payments',
plugin_dir_url(__FILE__) . 'js/simple-stripe-payments.js',
array('jquery', 'stripe-js'),
'1.0',
true
);
wp_localize_script('simple-stripe-payments', 'stripeData', array(
'publishableKey' => $this->stripe_public_key,
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('stripe_payment_nonce')
));
}
public function render_payment_form() {
ob_start();
?>
<form id="stripe-payment-form" class="stripe-payment-form">
<?php wp_nonce_field('stripe_payment_nonce', 'stripe_nonce'); ?>
<div class="form-row">
<label for="customer_name">Name</label>
<input type="text" id="customer_name" name="customer_name" required>
</div>
<div class="form-row">
<label for="customer_email">Email</label>
<input type="email" id="customer_email" name="customer_email" required>
</div>
<div class="form-row">
<label for="amount">Amount ($)</label>
<input type="number" id="amount" name="amount" min="10" step="1" required>
</div>
<div class="form-row">
<div id="card-element"></div>
<div id="card-errors" role="alert"></div>
</div>
<button type="submit">Pay Now</button>
</form>
<?php
return ob_get_clean();
}
public function handle_payment() {
try {
if (!check_ajax_referer('stripe_payment_nonce', 'nonce', false)) {
throw new Exception('Invalid security token.');
}
$payment_method_id = sanitize_text_field($_POST['payment_method']);
$name = sanitize_text_field($_POST['customer_name']);
$email = sanitize_email($_POST['customer_email']);
$amount = floatval($_POST['amount']) * 100; // Convert to cents
// Create or retrieve customer
$customers = \Stripe\Customer::search([
'query' => "email:'$email'"
]);
if (empty($customers->data)) {
$customer = \Stripe\Customer::create([
'email' => $email,
'name' => $name,
'payment_method' => $payment_method_id,
]);
} else {
$customer = $customers->data[0];
// Update customer's payment method
\Stripe\PaymentMethod::attach($payment_method_id, [
'customer' => $customer->id,
]);
}
// Create PaymentIntent
$payment_intent = \Stripe\PaymentIntent::create([
'amount' => $amount,
'currency' => 'usd',
'customer' => $customer->id,
'payment_method' => $payment_method_id,
'confirm' => true,
'automatic_payment_methods' => [
'enabled' => true,
'allow_redirects' => 'never'
]
]);
wp_send_json_success([
'message' => 'Payment successful!'
]);
} catch (Exception $e) {
wp_send_json_error([
'message' => $e->getMessage()
]);
}
}
}
// Initialize the plugin
new Simple_Stripe_Payments();
PHPCode Walkthrough
1. Main Plugin Class
class Simple_Stripe_Payments {
private $stripe_secret_key;
private $stripe_public_key;
- Creates a class to encapsulate all plugin functionality.
- Stores Stripe API keys as private properties
2. Constructor and Initialization
public function __construct() {
$this->stripe_secret_key = 'sk_test_your_key_here';
$this->stripe_public_key = 'pk_test_your_key_here';
\Stripe\Stripe::setApiKey($this->stripe_secret_key);
- Initializes the plugin with Stripe API keys.
- Sets up the Stripe PHP library with your secret key.
- Registers WordPress hooks and shortcode.
3. Enqueuing Scripts
public function enqueue_scripts() {
wp_enqueue_style('simple-stripe-payments'...);
wp_enqueue_script('stripe-js'...);
- Loads necessary CSS and JavaScript files
- Includes Stripe.js library
- Passes necessary data to JavaScript using
wp_localize_script
4. Payment Form
public function render_payment_form() {
// Creates HTML form with fields for:
// - Name
// - Email
// - Amount
// - Card Element (from Stripe)
- Generates the HTML payment form
- Includes security nonce
- Creates placeholder for Stripe’s card element
5. Payment Processing
public function handle_payment() {
// 1. Validates security nonce
// 2. Sanitizes input data
// 3. Creates/updates Stripe customer
// 4. Creates payment intent
// 5. Returns success/error response
- Handles the server-side payment processing
- Creates or updates customer in Stripe
- Creates and confirms payment intent
- Handles errors and success responses
Step 3: The JavaScript File
Create js/simple-stripe-payments.js
:
document.addEventListener('DOMContentLoaded', function() {
const stripe = Stripe(stripeData.publishableKey);
const form = document.getElementById('stripe-payment-form');
if (!form) return;
const elements = stripe.elements();
const card = elements.create('card', {
style: {
base: {
fontSize: '16px',
color: '#32325d',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
}
});
card.mount('#card-element');
// Handle validation errors
card.addEventListener('change', function(event) {
const displayError = document.getElementById('card-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
});
// Handle form submission
form.addEventListener('submit', async function(event) {
event.preventDefault();
const submitButton = form.querySelector('button[type="submit"]');
const originalButtonText = submitButton.textContent;
submitButton.disabled = true;
submitButton.innerHTML = '<span class="spinner"></span> Processing...';
try {
const { paymentMethod, error } = await stripe.createPaymentMethod({
type: 'card',
card: card,
billing_details: {
name: form.querySelector('[name="customer_name"]').value,
email: form.querySelector('[name="customer_email"]').value
}
});
if (error) throw new Error(error.message);
const formData = new FormData();
formData.append('action', 'process_stripe_payment');
formData.append('nonce', stripeData.nonce);
formData.append('payment_method', paymentMethod.id);
formData.append('customer_name', form.querySelector('[name="customer_name"]').value);
formData.append('customer_email', form.querySelector('[name="customer_email"]').value);
formData.append('amount', form.querySelector('[name="amount"]').value);
const response = await fetch(stripeData.ajaxUrl, {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success) {
showMessage(form, result.data.message);
form.reset();
card.clear();
} else {
throw new Error(result.data.message);
}
} catch (error) {
showMessage(form, error.message, true);
const errorElement = document.getElementById('card-errors');
errorElement.textContent = error.message;
}
submitButton.disabled = false;
submitButton.textContent = originalButtonText;
});
});
function showMessage(form, message, isError = false) {
const existingMessage = form.querySelector('.payment-message');
if (existingMessage) {
existingMessage.remove();
}
const messageDiv = document.createElement('div');
messageDiv.className = `payment-message ${isError ? 'error' : 'success'}`;
messageDiv.innerHTML = `
<div class="message-content">
<span class="message-icon">${isError ? '❌' : '✅'}</span>
<span class="message-text">${message}</span>
</div>
`;
form.insertBefore(messageDiv, form.firstChild);
messageDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
JavaScriptJavaScript Implementation
The JavaScript file (simple-stripe-payments.js
) handles:
1. Stripe Initialization
const stripe = Stripe(stripeData.publishableKey);
const elements = stripe.elements();
- Initializes Stripe.js with your publishable key
- Creates Stripe Elements instance for card input
2. Form Handling
form.addEventListener('submit', async function(event) {
// 1. Creates payment method
// 2. Sends data to server
// 3. Handles response
// 4. Shows success/error message
- Captures form submission
- Creates Stripe payment method
- Sends data to WordPress for processing
- Handles response and updates UI
3. Error Handling
card.addEventListener('change', function(event) {
// Shows real-time card validation errors
- Provides real-time feedback on card input
- Displays validation errors
Step 4: Styling the Form
Create css/simple-stripe-payments.css
:
.stripe-payment-form {
max-width: 500px;
margin: 20px auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.form-row {
margin-bottom: 20px;
}
.form-row label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #32325d;
}
.form-row input {
width: 100%;
padding: 10px;
border: 1px solid #e6e6e6;
border-radius: 4px;
font-size: 16px;
}
#card-element {
padding: 12px;
border: 1px solid #e6e6e6;
border-radius: 4px;
background-color: white;
}
#card-errors {
color: #dc3545;
margin-top: 8px;
font-size: 14px;
}
button[type="submit"] {
background: #5469d4;
color: white;
padding: 12px 24px;
border: none;
border-radius: 4px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: background-color 0.2s;
}
button[type="submit"]:hover {
background: #4054b2;
}
.payment-message {
margin-bottom: 20px;
padding: 15px;
border-radius: 4px;
animation: slideIn 0.3s ease-out;
}
.payment-message.success {
background-color: #d4edda;
border: 1px solid #c3e6cb;
color: #155724;
}
.payment-message.error {
background-color: #f8d7da;
border: 1px solid #f5c6cb;
color: #721c24;
}
@keyframes slideIn {
from {
transform: translateY(-20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255,255,255,.3);
border-radius: 50%;
border-top-color: white;
animation: spin 1s ease-in-out infinite;
margin-right: 10px;
vertical-align: middle;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
CSSHow to Use the Plugin
- Install and activate the plugin on your WordPress site.
- Replace the test keys with your live Stripe API keys in the main plugin file.
- Add the shortcode
[stripe_payment_form]
to any page or post where you want to display the payment form. - Test the payment form using Stripe’s test card numbers (e.g., 4242 4242 4242 4242).
Conclusion
This simple yet powerful plugin gives you complete control over your Stripe payments without any additional fees. Feel free to modify and enhance it according to your needs!
Remember to always test thoroughly in Stripe’s test mode before going live with real payments.
Happy coding! If you have any questions or run into any issues, feel free to drop a comment below. I’m always here to help fellow WordPress developers.