Integrating a Third-Party Payment Gateway effectively into WordPress plugins for selling digital products is essential. This guide demonstrates all the important procedures, starting with gateway selection and ending with complex security protocols and compliance standards. Your knowledge of developing payment plugins will help you reach a point of understanding how to build secure systems that scale and offer user-friendly features.

All payment-related traffic must use HTTPS. Use Really Simple SSL to redirect HTTP to HTTPS:
// In wp-config.php
define('FORCE_SSL_ADMIN', true);
Never hardcode API keys. Use environment variables or encrypted database storage.
// In wp-config.php
define('STRIPE_SECRET_KEY', 'sk_test_...');
define('STRIPE_WEBHOOK_SECRET', 'whsec_...');
// Encrypt keys before saving to the database
$encrypted_key = openssl_encrypt(STRIPE_SECRET_KEY, 'AES-256-CBC', SECURE_AUTH_KEY);
update_option('stripe_encrypted_secret', $encrypted_key);
Use client-side tokenization to avoid handling raw credit card data. For Stripe:
// Frontend: Create a Stripe Elements form
const stripe = Stripe('pk_test_...');
const elements = stripe.elements();
const cardElement = elements.create('card');
cardElement.mount('#card-element');
// On form submission
stripe.createToken(cardElement).then(function(result) {
if (result.error) {
alert(result.error.message);
} else {
// Send token to server
jQuery.post('/wp-admin/admin-ajax.php', {
action: 'process_payment',
stripe_token: result.token.id
});
}
});
Use wp_remote_post() to interact with the payment gateway’s API.
function process_payment() {
$stripe_secret = defined('STRIPE_SECRET_KEY') ? STRIPE_SECRET_KEY : get_option('stripe_secret_key');
$token = sanitize_text_field($_POST['stripe_token']);
$amount = absint($_POST['amount']); // In cents
$response = wp_remote_post('https://api.stripe.com/v1/charges', [
'headers' => [
'Authorization' => 'Bearer ' . $stripe_secret,
'Content-Type' => 'application/x-www-form-urlencoded',
],
'body' => [
'amount' => $amount,
'currency' => 'usd',
'source' => $token,
'description' => sanitize_text_field($_POST['product_name']),
],
]);
// Handle response (covered in Section 4)
}
add_action('wp_ajax_process_payment', 'process_payment');
add_action('wp_ajax_nopriv_process_payment', 'process_payment');
Sanitize and validate all user inputs:
$email = sanitize_email($_POST['email']);
$product_id = absint($_POST['product_id']);
$currency = sanitize_text_field($_POST['currency']);
Handle API errors, timeouts, and invalid responses:
if (is_wp_error($response)) {
error_log('Payment API error: ' . $response->get_error_message());
wp_send_json_error([
'message' => 'Payment gateway unreachable. Please try again.'
]);
} else {
$body = json_decode(wp_remote_retrieve_body($response), true);
if (isset($body['error'])) {
error_log('Stripe error: ' . $body['error']['message']);
wp_send_json_error(['message' => $body['error']['message']]);
} else {
// Save transaction ID to database
global $wpdb;
$wpdb->insert('wp_transactions', [
'transaction_id' => sanitize_text_field($body['id']),
'amount' => $body['amount'] / 100,
'status' => 'completed'
]);
wp_send_json_success(['receipt_url' => $body['receipt_url']]);
}
}
Use Query Monitor or custom logging to track errors:
function log_payment_error($error, $context = []) {
if (WP_DEBUG_LOG) {
error_log('[Payment Error] ' . $error . ' | Context: ' . print_r($context, true));
}
}
// Example usage
log_payment_error('Invalid currency', ['currency' => $_POST['currency']]);
Offer alternative payment methods if the primary gateway fails:
if ($gateway_unreachable) {
wp_send_json_error([
'message' => 'Stripe is unavailable. Please try PayPal.',
'fallback' => [
'url' => 'https://paypal.com/checkout',
'method' => 'paypal'
]
]);
}
Prevent brute-force attacks by limiting payment attempts:
$user_ip = $_SERVER['REMOTE_ADDR'];
$transient_key = 'payment_attempts_' . $user_ip;
$attempts = get_transient($transient_key) ?: 0;
if ($attempts >= 5) {
wp_send_json_error(['message' => 'Too many attempts. Try again in 1 hour.']);
} else {
set_transient($transient_key, $attempts + 1, HOUR_IN_SECONDS);
}
Add nonces to payment forms:
// Frontend
wp_nonce_field('process_payment_nonce', 'payment_nonce');
// Backend
if (!wp_verify_nonce($_POST['payment_nonce'], 'process_payment_nonce')) {
wp_send_json_error(['message' => 'Invalid request.']);
}
Use tools like WPScan to check for vulnerabilities and update dependencies.
$response = wp_remote_post('https://api.stripe.com/v1/charges', [
'headers' => ['Authorization' => 'Bearer ' . STRIPE_SECRET_KEY],
'body' => [
'amount' => 1000,
'currency' => 'usd',
'source' => $token,
'radar_options' => ['enable_risk_actions' => 'true']
]
]);
Create a custom REST API endpoint for webhooks:
add_action('rest_api_init', function() {
register_rest_route('payment/v1', '/stripe-webhook', [
'methods' => 'POST',
'callback' => 'handle_stripe_webhook',
'permission_callback' => '__return_true'
]);
});
function handle_stripe_webhook(WP_REST_Request $request) {
$payload = $request->get_body();
$sig_header = $request->get_header('stripe-signature');
try {
$event = \Stripe\Webhook::constructEvent(
$payload,
$sig_header,
STRIPE_WEBHOOK_SECRET
);
switch ($event->type) {
case 'payment_intent.succeeded':
// Update order status
break;
case 'charge.refunded':
// Process refund
break;
}
return new WP_REST_Response('Webhook processed', 200);
} catch (Exception $e) {
error_log('Webhook error: ' . $e->getMessage());
return new WP_Error('invalid_signature', 'Invalid signature', ['status' => 403]);
}
}
Use number_format_i18n() to format prices based on the user’s locale:
$price = 1000; // In cents
$formatted_price = number_format_i18n($price / 100, 2);
echo get_woocommerce_currency_symbol() . $formatted_price;
Detect the user country and set the currency dynamically:
$geo = new WC_Geolocation();
$country = $geo->geolocate_ip($_SERVER['REMOTE_ADDR'])['country'];
$currency = ($country === 'GB') ? 'GBP' : 'USD';
Create a subscription using Stripe API:
$response = wp_remote_post('https://api.stripe.com/v1/subscriptions', [
'headers' => ['Authorization' => 'Bearer ' . STRIPE_SECRET_KEY],
'body' => [
'customer' => $customer_id,
'items' => [['price' => 'price_12345']]
]
]);
Use webhooks to detect subscription cancellations and update your database:
case 'customer.subscription.deleted':
$subscription_id = $event->data->object->id;
$wpdb->update('wp_subscriptions', ['status' => 'canceled'], ['stripe_id' => $subscription_id]);
break;
Write PHPUnit tests for critical flows:
public function test_payment_success() {
$_POST['stripe_token'] = 'tok_visa';
$response = $this->process_payment();
$this->assertTrue($response['success']);
}
Integrating a third-party payment gateway into a WordPress plugin requires meticulous attention to security, error handling, and user experience. By leveraging tools like tokenization, webhooks, and fraud detection, you can build a system that’s both secure and scalable. Always prioritize compliance with PCI-DSS and GDPR, and rigorously test your integration under real-world conditions. For further learning, explore the Stripe API Documentation and WordPress REST API Handbook.

Hassan Tahir wrote this article, drawing on his experience to clarify WordPress concepts and enhance developer understanding. Through his work, he aims to help both beginners and professionals refine their skills and tackle WordPress projects with greater confidence.