{"id":367444,"date":"2026-09-29T20:27:18","date_gmt":"2026-09-29T20:27:18","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/m-pesa-payment-gateway-for-woocommerce\/"},"modified":"2026-09-29T20:27:07","modified_gmt":"2026-09-29T20:27:07","slug":"marupurupu-checkout-for-mpesa","status":"publish","type":"plugin","link":"https:\/\/wordpress.org\/plugins\/marupurupu-checkout-for-mpesa\/","author":23487298,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.6.3","stable_tag":"1.6.3","tested":"7.1.2","requires":"5.3","requires_php":"7.4","requires_plugins":null,"header_name":"Marupurupu Checkout for M-Pesa","header_author":"Martin Mburu","header_description":"Accept M-Pesa Till payments via STK Push for WooCommerce","assets_banners_color":"1886b3","last_updated":"2026-09-29 20:27:07","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/github.com\/marto-karanja\/marupurupu-checkout-for-mpesa","header_author_uri":"https:\/\/profiles.wordpress.org\/marto46\/","rating":0,"author_block_rating":0,"active_installs":0,"downloads":53,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.6.3":{"tag":"1.6.3","author":"marto46","date":"2026-09-29 20:27:07","revision":3719802}},"upgrade_notice":{"1.3.0":"<p>IMPORTANT: This security release clears your stored M-Pesa credentials (Consumer Key\/Secret, Passkey). You MUST re-enter and save them on the M-Pesa settings page after updating, or checkout will not work. See the full changelog for details.<\/p>","1.2.0":"<p>Telemetry is now a real, working opt-in feature (was previously scaffolded but inert). Off by default \u2014 no behavior change unless you explicitly enable it under Payments &gt; M-Pesa Till &gt; Anonymous Usage Data.<\/p>","1.1.1":"<p>Security hardening release: re-enables SSL certificate verification on M-Pesa API calls and adds callback authentication. Update is strongly recommended for all sites.<\/p>","1.1.0":"<p>This version adds support for WooCommerce block-based checkout. No breaking changes. Simply update and M-Pesa will work on both classic and block checkouts.<\/p>","1.0.0":"<p>Initial release. Please test in sandbox mode before using in production.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3719802,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3719802,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256},"icon.svg":{"filename":"icon.svg","revision":3719802,"resolution":false,"location":"assets","locale":false}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3719802,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3719802,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.6.3"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3719802,"resolution":"1","location":"assets","locale":"","width":1280,"height":900},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3719802,"resolution":"2","location":"assets","locale":"","width":1280,"height":900},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3719802,"resolution":"3","location":"assets","locale":"","width":1200,"height":800},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3719802,"resolution":"4","location":"assets","locale":"","width":1280,"height":1641},"screenshot-5.png":{"filename":"screenshot-5.png","revision":3719802,"resolution":"5","location":"assets","locale":"","width":1280,"height":1250},"screenshot-6.png":{"filename":"screenshot-6.png","revision":3719802,"resolution":"6","location":"assets","locale":"","width":1280,"height":900}},"screenshots":{"1":"Gateway settings page with M-Pesa configuration","2":"Checkout page showing M-Pesa payment option (Block checkout)","3":"STK Push prompt on customer's phone","4":"Transaction reports dashboard","5":"Order received page with payment status","6":"Payment settings and encryption management"}},"plugin_section":[],"plugin_tags":[37731,3050,6593,277553,286],"plugin_category":[45],"plugin_contributors":[283503],"plugin_business_model":[],"class_list":["post-367444","plugin","type-plugin","status-publish","hentry","plugin_tags-kenya","plugin_tags-mpesa","plugin_tags-payment-gateway","plugin_tags-safaricom","plugin_tags-woocommerce","plugin_category-ecommerce","plugin_contributors-marto46","plugin_committers-marto46"],"banners":{"banner":"https:\/\/ps.w.org\/marupurupu-checkout-for-mpesa\/assets\/banner-772x250.png?rev=3719802","banner_2x":"https:\/\/ps.w.org\/marupurupu-checkout-for-mpesa\/assets\/banner-1544x500.png?rev=3719802","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":"https:\/\/ps.w.org\/marupurupu-checkout-for-mpesa\/assets\/icon.svg?rev=3719802","icon":"https:\/\/ps.w.org\/marupurupu-checkout-for-mpesa\/assets\/icon.svg?rev=3719802","icon_2x":false,"generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/marupurupu-checkout-for-mpesa\/assets\/screenshot-1.png?rev=3719802","caption":"Gateway settings page with M-Pesa configuration"},{"src":"https:\/\/ps.w.org\/marupurupu-checkout-for-mpesa\/assets\/screenshot-2.png?rev=3719802","caption":"Checkout page showing M-Pesa payment option (Block checkout)"},{"src":"https:\/\/ps.w.org\/marupurupu-checkout-for-mpesa\/assets\/screenshot-3.png?rev=3719802","caption":"STK Push prompt on customer's phone"},{"src":"https:\/\/ps.w.org\/marupurupu-checkout-for-mpesa\/assets\/screenshot-4.png?rev=3719802","caption":"Transaction reports dashboard"},{"src":"https:\/\/ps.w.org\/marupurupu-checkout-for-mpesa\/assets\/screenshot-5.png?rev=3719802","caption":"Order received page with payment status"},{"src":"https:\/\/ps.w.org\/marupurupu-checkout-for-mpesa\/assets\/screenshot-6.png?rev=3719802","caption":"Payment settings and encryption management"}],"raw_content":"<!--section=description-->\n<p>Marupurupu Checkout for M-Pesa allows you to accept payments via M-Pesa (Safaricom) using the STK Push (Lipa Na M-Pesa Online) feature. This plugin integrates seamlessly with WooCommerce and supports both classic shortcode-based checkout and modern block-based checkout.<\/p>\n\n<p><strong>Independent plugin \u2014 no affiliation.<\/strong> This plugin is developed independently. It is not affiliated with, endorsed by, or sponsored by Safaricom, M-Pesa, WooCommerce or Automattic. M-Pesa, Safaricom and WooCommerce are trademarks of their respective owners, used here only to describe what the plugin works with.<\/p>\n\n<h4>Features<\/h4>\n\n<ul>\n<li><strong>STK Push Payments<\/strong> - Customers receive payment prompt directly on their phone<\/li>\n<li><strong>Block Checkout Support<\/strong> - Works with WooCommerce block-based checkout (NEW in v1.1.0)<\/li>\n<li><strong>Classic Checkout Support<\/strong> - Fully compatible with traditional shortcode checkout<\/li>\n<li><strong>Automatic Detection<\/strong> - Automatically works with both checkout types<\/li>\n<li><strong>Real-time Payment Status<\/strong> - Instant payment confirmation via callbacks<\/li>\n<li><strong>Transaction Reports<\/strong> - Comprehensive admin dashboard for transaction tracking<\/li>\n<li><strong>Secure Credentials<\/strong> - AES-256-GCM authenticated encryption for M-Pesa API credentials<\/li>\n<li><strong>Test Mode<\/strong> - Sandbox environment for testing before going live<\/li>\n<li><strong>Payment Callbacks<\/strong> - Automatic order status updates<\/li>\n<li><strong>Order Tracking<\/strong> - Enhanced order received page with payment status<\/li>\n<li><strong>Debug Logging<\/strong> - Detailed logs for troubleshooting<\/li>\n<li><strong>Telemetry<\/strong> - Optional usage tracking (opt-in)<\/li>\n<\/ul>\n\n<h4>Requirements<\/h4>\n\n<ul>\n<li>WordPress 5.3 or higher<\/li>\n<li>WooCommerce 3.0 or higher (5.5+ recommended for block checkout)<\/li>\n<li>PHP 7.4 or higher<\/li>\n<li>SSL Certificate (required for M-Pesa STK Push)<\/li>\n<li>M-Pesa Till Number and Daraja API credentials<\/li>\n<\/ul>\n\n<h4>Setup<\/h4>\n\n<ol>\n<li>Upload the plugin to <code>\/wp-content\/plugins\/marupurupu-checkout-for-mpesa\/<\/code><\/li>\n<li>Activate the plugin through the 'Plugins' menu in WordPress<\/li>\n<li>Go to WooCommerce &gt; Settings &gt; Payments<\/li>\n<li>Enable \"M-Pesa Till Payment\"<\/li>\n<li>Click \"Manage\" to configure your M-Pesa credentials<\/li>\n<li>Add your Business Short Code (Till Number), Consumer Key, Consumer Secret, and Passkey<\/li>\n<li>Configure callback URL (auto-generated)<\/li>\n<li>Save changes and test in Test Mode first<\/li>\n<\/ol>\n\n<h4>Configuration<\/h4>\n\n<p><strong>Required Settings:<\/strong>\n* Consumer Key (Production &amp; Test)\n* Consumer Secret (Production &amp; Test)\n* Business Short Code (Your Till Number)\n* Passkey (From Daraja Portal)\n* Test Mode toggle<\/p>\n\n<p><strong>Optional Settings:<\/strong>\n* Payment instructions for customers\n* Debug logging\n* Telemetry (usage tracking)<\/p>\n\n<h4>External services<\/h4>\n\n<p>This plugin connects to the third-party services below. Nothing is sent to Safaricom until you configure the plugin and a payment is attempted, and nothing is sent to the usage-statistics collector unless you opt in.<\/p>\n\n<p><strong>Safaricom Daraja API<\/strong> (<code>api.safaricom.co.ke<\/code>, or <code>sandbox.safaricom.co.ke<\/code> when Test Mode is on) \u2014 the service that actually takes the M-Pesa payment.\n* Used for: requesting an access token, sending an STK Push payment prompt to the customer's phone, and checking the status of a payment. Safaricom also posts the payment result back to your site's callback URL.\n* Sent, and when: your Consumer Key and Consumer Secret (to obtain a token \u2014 whenever one is needed and when you click \"Test M-Pesa Connection\"); and, when a customer pays, your Business Short Code and Till Number, a request password derived from your Passkey, the order amount, the customer's M-Pesa phone number, an order reference (\"Order-\" plus the order number) and your site's callback URL.\n* Provider: Safaricom PLC. Daraja developer portal: https:\/\/developer.safaricom.co.ke\/ \u2014 Terms and Conditions and Privacy Policy: https:\/\/developer.safaricom.co.ke\/terms<\/p>\n\n<p><strong>Plugin usage-statistics collector<\/strong> (<code>telemetry.billtoolbox.com<\/code>) \u2014 optional and off by default; used only if you tick \"Help improve this plugin by sharing anonymous usage data\" in the gateway settings. Exactly what is sent, and when, is listed field by field under \"Privacy Policy\" below. Operated by the plugin author. It has no separate terms document: the complete disclosure of what it receives, stores and for how long is the \"Privacy Policy\" section below.<\/p>\n\n<p><strong>WordPress.org secret-key generator<\/strong> (<code>api.wordpress.org\/secret-key\/1.1\/salt\/<\/code>) \u2014 only a link in an admin notice shown when your site's security keys are missing. The plugin sends nothing to it; your browser opens it only if you click the link.<\/p>\n\n<h3>Technical Details<\/h3>\n\n<h4>Compatibility<\/h4>\n\n<ul>\n<li>WordPress: 5.3+<\/li>\n<li>WooCommerce: 3.0+ (5.5+ for block checkout)<\/li>\n<li>PHP: 7.4, 8.0, 8.1, 8.2<\/li>\n<li>WooCommerce Blocks: 11.0+<\/li>\n<\/ul>\n\n<h4>Security<\/h4>\n\n<ul>\n<li>AES-256-GCM authenticated encryption for stored credentials (detects tampering\/wrong-key decryption cryptographically, not by guessing)<\/li>\n<li>Nonce verification for all forms<\/li>\n<li>Input sanitization and output escaping<\/li>\n<li>SQL injection prevention<\/li>\n<li>XSS protection<\/li>\n<li>Callback authentication via a per-site secret token<\/li>\n<li>Callback payment amount verified against the original order before marking paid<\/li>\n<li>Rate limiting on the customer-facing payment-retry endpoint<\/li>\n<\/ul>\n\n<h4>Performance<\/h4>\n\n<ul>\n<li>Minimal database queries<\/li>\n<li>Efficient caching<\/li>\n<li>Optimized asset loading<\/li>\n<li>No frontend JavaScript unless on checkout page<\/li>\n<\/ul>\n\n<h3>Privacy Policy<\/h3>\n\n<p>This plugin:\n* Stores M-Pesa transaction data in your WordPress database\n* Sends payment requests to Safaricom M-Pesa API\n* Optionally tracks anonymous usage data (opt-in telemetry) \u2014 see below\n* Does not share customer data with third parties (except Safaricom for payment processing)\n* Encrypts sensitive credentials at rest<\/p>\n\n<h4>Telemetry (opt-in)<\/h4>\n\n<p>Anonymous usage telemetry is <strong>off by default<\/strong>. It only activates if you\ncheck \"Help improve this plugin by sharing anonymous usage data\" under\nWooCommerce &gt; Settings &gt; Payments &gt; M-Pesa Till &gt; Anonymous Usage Data, and\nstops sending anything as soon as you uncheck it (any leftover scheduled\ntasks then do nothing). If you had opted in, deactivating the plugin sends one final <code>deactivation<\/code> event (described below).<\/p>\n\n<p><strong>Site identifier<\/strong>: every event includes a <code>site_id<\/code> \u2014 a SHA-256 hash of\nyour site's URL. This is a stable, unique-per-install pseudonymous\nidentifier, not full anonymity: because it's stable, events from your site\ncan be correlated with each other over time (e.g. to see version-upgrade\nhistory), even though your actual site URL, domain, or any other\nidentifying detail is never transmitted.<\/p>\n\n<p><strong>What's sent, by event type<\/strong> (the <code>feature_usage<\/code>, <code>error<\/code> and <code>performance<\/code> events are supported by the code but the plugin does not currently trigger them; they are listed so the disclosure stays complete if that changes):<\/p>\n\n<ul>\n<li><code>heartbeat<\/code> (weekly) \u2014 WordPress, WooCommerce, PHP, and MySQL version\nnumbers; server software string; PHP memory limit and max execution\ntime; whether OpenSSL and cURL are available; whether High-Performance\nOrder Storage (HPOS) is enabled; site language and timezone; whether the\nsite is a WordPress Multisite install; whether stored M-Pesa credentials\nare encrypted (a boolean only \u2014 never the credentials themselves).<\/li>\n<li><code>daily_stats<\/code> (daily) \u2014 transaction counts by status (completed\/pending\/\nfailed), success rate, <strong>minimum\/maximum\/average transaction amounts<\/strong>,\nand a distribution of M-Pesa result\/failure codes. No order IDs, phone\nnumbers, or customer identities are included \u2014 only aggregate numbers.<\/li>\n<li><code>feature_usage<\/code> (weekly aggregate) \u2014 which plugin features were used and\nhow many times, with no reference to which orders\/customers triggered\nthem.<\/li>\n<li><code>error<\/code> \u2014 an error category and code (e.g. <code>api_error<\/code> \/ <code>1037<\/code>), plus a\nshort sanitized context string. The context field only ever contains the\nfixed category labels already used internally by the plugin's error\ntracking \u2014 never raw request bodies, stack traces, or user input.<\/li>\n<li><code>performance<\/code> (weekly aggregate) \u2014 timing metrics (count\/min\/max\/average\nduration in milliseconds) for named internal operations, with no\nreference to which orders they came from.<\/li>\n<li><code>deactivation<\/code> \u2014 sent once when the plugin is deactivated (and only if you\nhad opted in): just the event name, the anonymous site identifier, the\nplugin version and a timestamp. No event is sent on activation.<\/li>\n<\/ul>\n\n<p><strong>Never included, in any event, ever<\/strong>: phone numbers, order details,\ncustomer names or addresses, or M-Pesa credentials (Consumer Key\/Secret,\nPasskey, callback secret) in any form.<\/p>\n\n<p>Telemetry, when enabled, is sent to a dedicated collector endpoint operated\nby the plugin author (<code>telemetry.billtoolbox.com<\/code>) \u2014 a separate WordPress\ninstall used only for this purpose, isolated from any other site.<\/p>\n\n<p><strong>Also recorded by the collector<\/strong>: like any web server, it receives the IP\naddress of the server that sends each event, and stores it with the event. It\nis used only for rate limiting and abuse investigation and is not shown on the\ncollector's dashboard. Events (including that IP address) are currently kept\nuntil they are deleted manually \u2014 there is no automatic expiry. To have the\nevents for your site removed, ask in this plugin's support forum on\nWordPress.org and include your site's identifier (<code>site_id<\/code>: the SHA-256 hash\nof your site URL described above).<\/p>\n\n<h3>Credits<\/h3>\n\n<p>Developed by: Martin Mburu\nBased on: Safaricom Daraja API\nUses: WooCommerce Payment Gateway API\nBlocks Integration: WooCommerce Blocks API<\/p>\n\n<h4>Third-party libraries<\/h4>\n\n<ul>\n<li><a href=\"https:\/\/www.chartjs.org\/\">Chart.js<\/a> 4.5.1 (MIT License) \u2014 draws the charts on the Reports page. Bundled locally as <code>assets\/js\/chart.umd.js<\/code> (the library's own official minified build, unmodified). Human-readable source: https:\/\/github.com\/chartjs\/Chart.js (the <code>v4.5.1<\/code> tag) or https:\/\/www.npmjs.com\/package\/chart.js\/v\/4.5.1.<\/li>\n<\/ul>\n\n<h3>Additional Information<\/h3>\n\n<ul>\n<li>Source code: https:\/\/github.com\/marto-karanja\/marupurupu-checkout-for-mpesa<\/li>\n<li>API Reference: https:\/\/developer.safaricom.co.ke\/docs<\/li>\n<li>WooCommerce Blocks: https:\/\/woocommerce.com\/checkout-blocks\/<\/li>\n<\/ul>\n\n<p>This plugin is not officially affiliated with, endorsed by, or sponsored by Safaricom or M-Pesa. It integrates with Safaricom's publicly documented Daraja API.<\/p>\n\n<h3>Support<\/h3>\n\n<p>Need help?\n1. Read the FAQ above.\n2. Turn on WordPress debug logging (WP_DEBUG_LOG) and check WooCommerce &gt; Status &gt; Logs (source \"mpesa-till-callback\") and wp-content\/debug.log.\n3. Ask in this plugin's support forum on WordPress.org, with your WordPress, WooCommerce and PHP versions and the relevant log lines. Never post your Consumer Key, Consumer Secret or Passkey.<\/p>\n\n<!--section=installation-->\n<h4>Automatic Installation<\/h4>\n\n<ol>\n<li>Log in to your WordPress dashboard<\/li>\n<li>Navigate to Plugins &gt; Add New<\/li>\n<li>Search for \"Marupurupu Checkout for M-Pesa\"<\/li>\n<li>Click \"Install Now\"<\/li>\n<li>Activate the plugin<\/li>\n<\/ol>\n\n<h4>Manual Installation<\/h4>\n\n<ol>\n<li>Download the plugin ZIP file<\/li>\n<li>Log in to your WordPress dashboard<\/li>\n<li>Navigate to Plugins &gt; Add New &gt; Upload Plugin<\/li>\n<li>Choose the downloaded ZIP file<\/li>\n<li>Click \"Install Now\"<\/li>\n<li>Activate the plugin<\/li>\n<\/ol>\n\n<h4>After Installation<\/h4>\n\n<ol>\n<li>Go to WooCommerce &gt; Settings &gt; Payments<\/li>\n<li>Enable \"M-Pesa Till Payment\"<\/li>\n<li>Configure your M-Pesa credentials<\/li>\n<li>Test in Test Mode before going live<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"does%20this%20work%20with%20woocommerce%20block%20checkout%3F\"><h3>Does this work with WooCommerce block checkout?<\/h3><\/dt>\n<dd><p>Yes! Version 1.1.0 adds full support for WooCommerce block-based checkout while maintaining backward compatibility with classic checkout.<\/p><\/dd>\n<dt id=\"do%20i%20need%20an%20ssl%20certificate%3F\"><h3>Do I need an SSL certificate?<\/h3><\/dt>\n<dd><p>Yes, M-Pesa STK Push requires your site to have a valid SSL certificate (HTTPS).<\/p><\/dd>\n<dt id=\"how%20do%20i%20get%20m-pesa%20api%20credentials%3F\"><h3>How do I get M-Pesa API credentials?<\/h3><\/dt>\n<dd><ol>\n<li>Register on Safaricom Daraja Portal: https:\/\/developer.safaricom.co.ke\/<\/li>\n<li>Create a new app<\/li>\n<li>Get your Consumer Key and Consumer Secret<\/li>\n<li>Request for production credentials after testing<\/li>\n<\/ol><\/dd>\n<dt id=\"what%20is%20a%20till%20number%3F\"><h3>What is a Till Number?<\/h3><\/dt>\n<dd><p>A Till Number is your M-Pesa Buy Goods business number. This plugin currently supports Till (Buy Goods) payments only; Paybill numbers are not supported.<\/p><\/dd>\n<dt id=\"can%20i%20test%20before%20going%20live%3F\"><h3>Can I test before going live?<\/h3><\/dt>\n<dd><p>Yes! Enable \"Test Mode\" in settings and use the test credentials from Daraja Portal.<\/p><\/dd>\n<dt id=\"where%20can%20i%20see%20transaction%20reports%3F\"><h3>Where can I see transaction reports?<\/h3><\/dt>\n<dd><p>Go to WooCommerce &gt; M-Pesa Transactions to view all payment transactions.<\/p><\/dd>\n<dt id=\"what%20if%20payment%20fails%3F\"><h3>What if payment fails?<\/h3><\/dt>\n<dd><p>The plugin includes comprehensive error logging. Enable debug mode and check wp-content\/debug.log for details.<\/p><\/dd>\n<dt id=\"does%20it%20support%20multiple%20currencies%3F\"><h3>Does it support multiple currencies?<\/h3><\/dt>\n<dd><p>Currently supports KES (Kenyan Shillings) only, as required by M-Pesa.<\/p><\/dd>\n<dt id=\"how%20do%20callbacks%20work%3F\"><h3>How do callbacks work?<\/h3><\/dt>\n<dd><p>The plugin automatically generates a callback URL, including a secret token unique to your site. M-Pesa sends payment confirmations to this URL; the plugin verifies the token before updating order status automatically.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.6.3 - 2026-09-24<\/h4>\n\n<ul>\n<li>Fixed: The M-Pesa Transactions and Reports pages logged PHP 8.1 \"Passing null to number_format()\" deprecations on a store with no transactions yet.<\/li>\n<li>Fixed: If the plugin was activated while WooCommerce was inactive, its transactions table was never created. The activation hook is now registered before the WooCommerce check, and a missing table is created on the next load once WooCommerce is active. The \"requires WooCommerce\" notice is now translatable, and network-activated WooCommerce is recognised. The plugin header now declares <code>Requires Plugins: woocommerce<\/code>.<\/li>\n<li>Fixed: A failed payment attempt on the block-based checkout now shows the gateway's own message instead of a generic error (the gateway returned <code>fail<\/code>, which WooCommerce Blocks does not recognise; it now returns <code>failure<\/code>).<\/li>\n<li>Changed: If the gateway is enabled but its credentials, shortcode, till number or passkey are not filled in, a payment attempt now stops with a friendly message instead of contacting Safaricom with empty credentials and showing \"Failed to get access token\".<\/li>\n<li>Changed: \"Test M-Pesa Connection\" now also lists any of Business Shortcode, Till Number or Passkey that is still empty, instead of reporting success when only the Consumer Key\/Secret were checked.<\/li>\n<li>Fixed: The Transactions list no longer shows a \"View Order\" button (and no longer logs a PHP deprecation) for transactions whose order has been deleted. Button\/placeholder text inside HTML attributes is now escaped as attribute text, and the plugin-list \"Settings\" link is translatable.<\/li>\n<li>Changed: The order-received page's payment-status script is fully translatable, no longer writes debug output to the browser console, and inserts messages as plain text rather than HTML.<\/li>\n<li>Security: CSV exports (transactions and reports) now prefix any text cell starting with <code>=<\/code>, <code>+<\/code>, <code>-<\/code> or <code>@<\/code> so spreadsheet programs cannot run it as a formula.<\/li>\n<li>Changed: The usage-statistics opt-in no longer describes (or attempts) an \"activation\" event, which never fired. Only the opt-in \"deactivation\" event remains, and the readme now says exactly what it contains.<\/li>\n<li>Changed: <code>WC tested up to<\/code> raised to 11.1; the amount shown on the order-received page is escaped.<\/li>\n<li>Fixed: The transactions table definition now uses the exact <code>CREATE TABLE<\/code> form that WordPress's <code>dbDelta()<\/code> expects (it previously read <code>IF NOT EXISTS<\/code> as the table name, so future column changes could never be applied).<\/li>\n<li>Changed: <code>Author URI<\/code> now points to the author's WordPress.org profile.<\/li>\n<\/ul>\n\n<h4>1.6.2 - 2026-09-22<\/h4>\n\n<ul>\n<li>Fixed: On admin pages, activating the plugin with opt-in telemetry enabled could trigger a WordPress \"translation loading triggered too early\" notice. The telemetry check was reading the gateway's settings before WordPress had finished its own startup sequence, which also forced the gateway itself to load earlier than it should have; both now wait until WordPress is ready. No setting, saved credential, or behavior changes as a result.<\/li>\n<\/ul>\n\n<h4>1.6.1 - 2026-09-19<\/h4>\n\n<ul>\n<li>Changed: The plugin's display name is now \"Marupurupu Checkout for M-Pesa\" (the \"and WooCommerce\" suffix was removed), following WordPress.org Plugin Review Team feedback that \"WooCommerce\" is a restricted term in plugin names. The slug, text domain, settings, saved credentials, orders and the Safaricom callback URL are all unchanged. No functional change.<\/li>\n<li>Removed: Unused internal feature-flag code that was never connected to anything. Every feature remains available to everyone, as before.<\/li>\n<li>Changed: One more admin-screen check now sanitizes the <code>section<\/code> query argument before comparing it.<\/li>\n<\/ul>\n\n<h4>1.6.0 - 2026-09-19<\/h4>\n\n<ul>\n<li>Changed: <strong>Unique naming prefix.<\/strong> Every class, function, constant, option, transient, scheduled event, hook, AJAX action, script handle and admin menu slug the plugin registers now uses the prefix <code>marupurupu_<\/code> \/ <code>Marupurupu_<\/code> \/ <code>MARUPURUPU_<\/code> instead of the generic <code>mpesa<\/code> (which could collide with other M-Pesa plugins). The custom transactions table is now <code>{prefix}marupurupu_transactions<\/code>.<\/li>\n<li>Added: <strong>Automatic one-time data migration.<\/strong> On the first request after updating, data stored under the old names is moved across: the transactions table is renamed (a single atomic <code>RENAME TABLE<\/code>; no rows are copied or lost), saved options and dismissed notices are carried over, and leftover scheduled events are cleared. Your gateway settings, saved credentials and payment history are unchanged, and no action is needed. The migration retries by itself if it cannot finish, and never overwrites newer data.<\/li>\n<li>Unchanged on purpose: the payment method id (<code>mpesa_till<\/code>), your saved gateway settings, and the Safaricom callback URL (<code>\/wc-api\/wc_mpesa_till_callback\/<\/code>) \u2014 orders, settings and Safaricom callbacks depend on them.<\/li>\n<li>Added: The phone number is now also validated again on the server at the start of payment processing, before any request is made to Safaricom (whether WooCommerce's block checkout runs the classic field validation has varied between WooCommerce versions, so this makes the check independent of it). A trailing newline is no longer accepted as part of a phone number.<\/li>\n<li>Removed: The one-time credential-reset routine from the 2026-08 encryption upgrade (every existing install has already been through it; it could only ever cause harm on a fresh one).<\/li>\n<li>Added: A unit-test suite (<code>composer test<\/code>) covering credential encryption, the payment webhook, the STK Push request, the migration and the order-state guard.<\/li>\n<\/ul>\n\n<h4>1.5.6 - 2026-09-19<\/h4>\n\n<ul>\n<li>Fixed: <strong>Block-based checkout could not collect a phone number.<\/strong> The block checkout component only displayed the payment description; the phone-number field, its validation and the code that submits it with the order had been lost in an earlier source-tree consolidation, so paying with M-Pesa on the WooCommerce Cart &amp; Checkout blocks could not work. Restored. Classic checkout was never affected.<\/li>\n<li>Added: Declares WooCommerce Cart &amp; Checkout blocks compatibility (<code>cart_checkout_blocks<\/code>), so WooCommerce no longer lists the gateway as incompatible with block checkout.<\/li>\n<li>Fixed: Readme said Paybill numbers were supported; the plugin supports Till (Buy Goods) only. Corrected, along with the minimum WordPress version (5.3), the Support section (no documentation files ship with the plugin) and the telemetry opt-out description.<\/li>\n<li>Added: \"External services\" section in the readme documenting the Safaricom Daraja API and the optional usage-statistics collector \u2014 what is sent, when, and links to their terms and privacy policies.<\/li>\n<li>Removed: <code>config-sample.php<\/code>, which described a constants-based credential mechanism that the plugin never implemented. Credentials are entered on the gateway settings screen and stored encrypted.<\/li>\n<\/ul>\n\n<h4>1.5.5 - 2026-09-18<\/h4>\n\n<ul>\n<li>Changed: <strong>Renamed<\/strong> to \"Marupurupu Checkout for M-Pesa\" (slug, folder, main file, and text domain <code>marupurupu-checkout-for-mpesa<\/code>), following WordPress.org Plugin Review Team feedback that the previous name led with a third-party trademark. \"Marupurupu\" is a Swahili word meaning \"allowances\". No functional change: internal identifiers, the database table (<code>wp_mpesa_till_transactions<\/code>), the option keys, and the Daraja callback URL (<code>\/wc-api\/wc_mpesa_till_callback\/<\/code>) are all unchanged. Existing installs need a manual reinstall to pick up the new folder name (WordPress cannot rename an installed plugin's folder in an update); stored settings are unaffected.<\/li>\n<li>Changed: \"Not affiliated\" disclaimer wording made explicit in the plugin description.<\/li>\n<li>Changed: <code>Plugin URI<\/code> and the readme's source-code link now point to the renamed public repository, <code>github.com\/marto-karanja\/marupurupu-checkout-for-mpesa<\/code> (the previous repository name led with a third-party trademark).<\/li>\n<\/ul>\n\n<h4>1.5.4 - 2026-09-18<\/h4>\n\n<ul>\n<li>Fixed: On the gateway settings page, the \"Change\" button for an already-saved Consumer Key\/Secret\/Passkey did nothing and there was no field to type a new value into, so saved credentials could not be replaced from the settings screen. Credentials can be changed again.<\/li>\n<li>Fixed: A stale daily \"clean old logs\" scheduled task, left behind by older versions for a function that no longer exists, could raise a fatal error on PHP 8 each time WordPress cron ran it. It is now unscheduled.<\/li>\n<li>Fixed: Bulk \"Export to CSV\" on the M-Pesa Transactions page sent its download headers after the page had already started rendering, so the file could not be delivered cleanly. It now runs before any output.<\/li>\n<li>Fixed: The transactions page stylesheet (<code>admin.css<\/code>) was referenced but not shipped, so the page's styles came only from an inline block. It is now a real, enqueued stylesheet.<\/li>\n<li>Fixed: The customer-facing \"retry payment\" and \"already paid? enter your code\" actions could be used on an order that was already paid (overwriting its confirmed M-Pesa receipt and moving it back to on-hold) or on an order placed with a different payment method. Both now only act on unpaid orders placed with this gateway.<\/li>\n<li>Fixed: The retry-payment form on the thank-you page could re-appear after a payment succeeded within the first 2 minutes.<\/li>\n<li>Fixed: Blocks-checkout script now uses the plugin version as its cache-busting version, instead of a hardcoded <code>1.0.0<\/code>.<\/li>\n<li>Security: Safaricom callback data is now sanitized (<code>sanitize_text_field<\/code>) before it is logged, stored, or shown in order notes; nonces are sanitized before verification; the callback secret is sanitized before comparison; the bulk-export <code>IN (...)<\/code> list now goes through <code>$wpdb-&gt;prepare()<\/code>.<\/li>\n<li>Changed: All inline <code>&lt;script&gt;<\/code>\/<code>&lt;style&gt;<\/code> blocks and <code>onclick<\/code> handlers moved to properly enqueued files (<code>wp_enqueue_script<\/code>\/<code>wp_enqueue_style<\/code>, with report data passed via <code>wp_localize_script<\/code>).<\/li>\n<li>Changed: Late output escaping added where WooCommerce\/WordPress return pre-built HTML (<code>wc_price<\/code>, <code>paginate_links<\/code>, <code>wpautop<\/code>), and <code>wp_die()<\/code> messages now use <code>esc_html__()<\/code>.<\/li>\n<li>Changed: Bundled Chart.js updated from 3.9.1 to 4.5.1 (see Credits).<\/li>\n<li>Removed: Unused <code>mpesa-blocks-improved.js<\/code> (never loaded by the plugin).<\/li>\n<\/ul>\n\n<h4>1.5.3 - 2026-09-09<\/h4>\n\n<ul>\n<li>Fixed: <code>Plugin URI<\/code> and <code>Author URI<\/code> in the plugin header were identical (<code>https:\/\/billtoolbox.com<\/code>) \u2014 flagged during WordPress.org submission review (both must be different, or one omitted). <code>Plugin URI<\/code> now points to the public GitHub source; <code>Author URI<\/code> stays <code>https:\/\/billtoolbox.com<\/code>.<\/li>\n<\/ul>\n\n<h4>1.5.2 - 2026-09-09<\/h4>\n\n<ul>\n<li>Changed: <strong>Slug, folder, main file, and text domain renamed<\/strong> from <code>mpesa-till-gateway<\/code> to <code>mpesa-payment-gateway<\/code>, matching the v1.5.1 display-name change and made ahead of first WordPress.org submission (self-service slug changes go away once review starts). No functional change \u2014 internal identifiers, the database table (<code>wp_mpesa_till_transactions<\/code>), the <code>WC_Mpesa_Till_Gateway<\/code> class name, and the Daraja callback URL (<code>\/wc-api\/wc_mpesa_till_callback\/<\/code>) are all unchanged, matching the same precedent as the earlier <code>wc-mpesa-till-payment<\/code> \u2192 <code>mpesa-till-gateway<\/code> rename. <strong>Existing installs on bonbargains.com\/nairobistalls.com need a manual reinstall to pick this up<\/strong> \u2014 WordPress cannot rename an installed plugin's folder via a normal update; deactivate, delete the old <code>mpesa-till-gateway<\/code> folder, install this version fresh, then reactivate. Stored settings are unaffected (kept under a WooCommerce option key, not tied to the folder name).<\/li>\n<\/ul>\n\n<h4>1.5.1 - 2026-09-09<\/h4>\n\n<ul>\n<li>Changed: Display name updated from \"M-Pesa Till Gateway for WooCommerce\" to \"M-Pesa Payment Gateway for WooCommerce\" ahead of first WordPress.org submission \u2014 cosmetic only. The plugin slug (<code>mpesa-till-gateway<\/code> at the time; renamed again in 1.5.2, see above), text domain, folder name, main file name, database table, and the Daraja callback URL were all unchanged by this specific release; existing installs were unaffected.<\/li>\n<\/ul>\n\n<h4>1.5.0 - 2026-08-31<\/h4>\n\n<ul>\n<li><strong>Renamed<\/strong> from <code>wc-mpesa-till-payment<\/code> to <code>mpesa-till-gateway<\/code> (slug, folder, main file, text domain) \u2014 see the 1.5.2 and 1.5.5 entries above for the further renames, most recently to <code>marupurupu-checkout-for-mpesa<\/code>. WordPress.org restricts the term \"wc\" in plugin slugs \u2014 the previous name could never have been submitted. No functional change; internal identifiers, the database table, and the M-Pesa\/Daraja callback URL are all unchanged, so existing installs keep working exactly as before once updated.<\/li>\n<li>Changed: Outbound Safaricom API calls (OAuth token, STK Push, status query) now use WordPress's own HTTP API (<code>wp_remote_get()<\/code>\/<code>wp_remote_post()<\/code>) instead of calling cURL directly \u2014 same behavior (timeouts, SSL verification), but works correctly on hosts that restrict direct cURL usage and follows WordPress.org coding standards.<\/li>\n<li>Fixed: 8 \"Creation of dynamic property is deprecated\" warnings on every settings-page load (PHP 8.2) \u2014 the gateway's credential fields are now properly declared class properties.<\/li>\n<li>Fixed: numerous smaller correctness\/compliance items found via a full run of the official WordPress.org Plugin Check tool \u2014 missing output escaping, missing translator comments on translatable strings with placeholders, <code>date()<\/code> calls affected by server timezone changed to <code>gmdate()<\/code>, superglobal reads missing <code>wp_unslash()<\/code>, <code>wp_redirect()<\/code> changed to <code>wp_safe_redirect()<\/code> for two admin actions added in 1.4.x, and a stale \"Tested up to\" header.<\/li>\n<li>Removed: two long-dead, unused logging methods that hand-rolled log files inside the plugin's own (web-accessible) directory \u2014 this exact pattern was already fixed elsewhere in 2026-08-01 by switching to WooCommerce's own logger; these had no remaining callers.<\/li>\n<\/ul>\n\n<h4>1.4.2 - 2026-08-28<\/h4>\n\n<ul>\n<li>Fixed: <strong>Critical<\/strong> \u2014 every successful M-Pesa payment triggered a PHP fatal error partway through the callback handler (order was correctly marked paid, but processing stopped there \u2014 stock was never reduced, and any later code\/hooks in that request never ran). Caused by a redundant, duplicate <code>do_action('woocommerce_payment_complete', ...)<\/code> call that passed a raw database string instead of an integer order ID, which crashes WooCommerce core's own stock-reduction code (<code>get_stock_reduced()<\/code>) on this WooCommerce version. <code>WC_Order::payment_complete()<\/code>, called immediately before it, already fires this same hook correctly \u2014 the duplicate call is removed rather than just type-fixed, since it was firing every other plugin's payment-complete listeners twice per order. Found live on a production install 2026-08-28; confirmed via WooCommerce core source that the fix doesn't lose any behavior.<\/li>\n<\/ul>\n\n<h4>1.4.1 - 2026-08-28<\/h4>\n\n<ul>\n<li>Changed: Saved Consumer Key\/Secret\/Passkey fields now show a masked fingerprint (bullets plus the last 4 real characters, same convention as a masked card number) with a \"Change\" button, instead of rendering completely blank. A blank field with only a placeholder was mistaken for \"nothing saved\" by a real merchant, causing them to keep re-entering credentials on every save. The real value is still never placed in the page as plaintext \u2014 only 4 of 32-64 characters are ever shown, purely to confirm at a glance that a specific value is genuinely stored.<\/li>\n<\/ul>\n\n<h4>1.4.0 - 2026-08-28<\/h4>\n\n<ul>\n<li>Added: \"Test M-Pesa Connection\" button on the settings page \u2014 checks the currently saved Consumer Key\/Secret against Safaricom right now and reports plainly whether they work, instead of only finding out via a failed checkout. Added after a real support case where a merchant couldn't tell \"saved and correct\" from \"saved but wrong\" since credential fields never redisplay their value<\/li>\n<li>Fixed: A failed OAuth token request (wrong\/invalid Consumer Key or Secret) now logs Safaricom's HTTP status and response to the PHP error log \u2014 previously this specific failure mode logged nothing at all, making \"Failed to get access token\" undiagnosable from the server side<\/li>\n<\/ul>\n\n<h4>1.3.0 - 2026-08-25<\/h4>\n\n<ul>\n<li>Security: Rewrote credential encryption from AES-256-CBC to AES-256-GCM (authenticated encryption) with an explicit format marker, fixing a real bug where a plaintext credential that happened to look like valid base64 could be silently stored unencrypted, then misreported as an \"encryption key changed\" error when the plugin later tried to decrypt it<\/li>\n<li>Security: <strong>Stored M-Pesa credentials are cleared once during this update<\/strong> and must be re-entered \u2014 the new encryption format is not compatible with the old one, and safely converting old encrypted values wasn't possible without risking corrupted credentials being silently used against the live payment API. See the admin notice on the M-Pesa settings page after updating<\/li>\n<li>Security: Settings screen no longer redisplays your decrypted Consumer Key\/Secret\/Passkey in the page HTML on every visit \u2014 fields now stay blank (matching how Stripe\/PayPal gateway plugins handle secrets) and only change what's stored if you actually type a new value<\/li>\n<li>Security: The M-Pesa callback handler now verifies the confirmed payment amount against the original order total before marking an order as paid, and ignores duplicate callback deliveries for an already-processed transaction (Safaricom is known to occasionally redeliver the same callback)<\/li>\n<li>Security: Outbound Daraja API calls now set an explicit connection\/request timeout, and explicitly require SSL certificate verification instead of relying on PHP's default<\/li>\n<li>Security: The customer-facing payment-retry endpoint is now rate-limited (max 3 attempts per order per 10 minutes) \u2014 it previously allowed unlimited unsolicited STK Push prompts to any phone number, not just the order's own<\/li>\n<li>Changed: Encryption key derivation now uses HKDF (combining WordPress's AUTH_KEY and AUTH_SALT) instead of a bare hash of AUTH_KEY alone<\/li>\n<\/ul>\n\n<h4>1.2.0 - 2026-08-14<\/h4>\n\n<ul>\n<li>Added: Anonymous usage telemetry is now fully functional (opt-in, off by default) \u2014 restored the settings checkbox, fixed a bug where activation could report in before the opt-in check ran, and pointed it at a real, dedicated collector backend<\/li>\n<li>Security: Telemetry activation reporting is now gated by the same opt-in check as every other telemetry event, with no exceptions<\/li>\n<li>Changed: Privacy Policy section now documents the exact field list sent for every telemetry event type<\/li>\n<\/ul>\n\n<h4>1.1.1 - 2026-08-02<\/h4>\n\n<ul>\n<li>Security: Removed disabled SSL certificate verification on all outbound Daraja API calls<\/li>\n<li>Security: M-Pesa callback URL now includes a per-site secret token; requests without it are rejected<\/li>\n<li>Security: Callback logs now use WooCommerce's protected logger instead of an unprotected custom log file<\/li>\n<li>Fixed: Encryption admin notices now proactively warn about a weak encryption key or degraded encryption instead of only logging silently<\/li>\n<li>Changed: Renamed to \"M-Pesa Till Gateway for WooCommerce\"<\/li>\n<li>Changed: Bundled Chart.js locally instead of loading it from a CDN<\/li>\n<li>Removed: Dead <code>class-mpesa-blocks-support-v2.php<\/code> (superseded by the active blocks support class)<\/li>\n<\/ul>\n\n<h4>1.1.0 - 2026-01-13<\/h4>\n\n<ul>\n<li>Added: WooCommerce Blocks support for modern checkout<\/li>\n<li>Added: React-based payment method registration<\/li>\n<li>Added: Automatic checkout type detection<\/li>\n<li>Improved: Documentation and troubleshooting guides<\/li>\n<li>Fixed: Payment method not showing on block checkout<\/li>\n<li>Maintained: 100% backward compatibility with classic checkout<\/li>\n<\/ul>\n\n<h4>1.0.0 - 2025-12-24<\/h4>\n\n<ul>\n<li>Initial release<\/li>\n<li>STK Push payment integration<\/li>\n<li>Payment callbacks and confirmations<\/li>\n<li>AES-256-CBC credential encryption<\/li>\n<li>Transaction reports and tracking<\/li>\n<li>Test mode support<\/li>\n<li>Admin dashboard<\/li>\n<li>Telemetry tracking (opt-in)<\/li>\n<li>Debug logging<\/li>\n<\/ul>","raw_excerpt":"Accept M-Pesa Till payments via STK Push for WooCommerce. Supports both classic and block-based checkout.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/367444","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=367444"}],"author":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/marto46"}],"wp:attachment":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=367444"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=367444"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=367444"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=367444"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=367444"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=367444"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}