├── CHANGELOG.md ├── CONTRUBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── assets ├── css │ └── checkout.css ├── img │ ├── amex.svg │ ├── diners.svg │ ├── discover.svg │ ├── flutterwave-full.svg │ ├── mastercard.svg │ ├── rave.png │ ├── rave.svg │ ├── screen1.PNG │ ├── screen2.PNG │ ├── screen3.PNG │ └── visa.svg └── js │ ├── checkout.js │ └── checkout.min.js ├── changelog.txt ├── client ├── api │ └── blocks.js ├── blocks │ ├── credit-card │ │ └── .gitkeep │ ├── index.js │ ├── normalize.js │ ├── payment-method │ │ ├── constants.js │ │ └── index.js │ ├── payment-request │ │ ├── apple-pay-preview.js │ │ ├── branded-buttons.js │ │ ├── constants.js │ │ ├── custom-button.js │ │ ├── event-handlers.js │ │ ├── hooks.js │ │ ├── index.js │ │ └── payment-request-express.js │ └── utils.js └── payment-method-icons │ └── .gitkeep ├── i18n └── languages │ ├── README.md │ └── rave-woocommerce-payment-gateway.pot ├── includes ├── blocks │ └── class-flutterwave-wc-gateway-blocks-support.php ├── class-flutterwave.php ├── class-flw-wc-payment-gateway-event-handler.php ├── class-flw-wc-payment-gateway-subscriptions.php ├── class-flw-wc-payment-gateway.php ├── client │ ├── class-flw-wc-payment-gateway-client.php │ ├── class-flw-wc-payment-gateway-request.php │ └── class-flw-wc-payment-gateway-sdk.php ├── contracts │ └── class-flw-wc-payment-gateway-event-handler-interface.php ├── notices │ └── class-flw-wc-payment-gateway-notices.php └── views │ └── html-admin-missing-woocommerce.php ├── package-lock.json ├── package.json ├── rave-woocommerce-payment-gateway.php └── readme.txt /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | ## 2.3.6 | 01-09-2025 3 | Bug Fixes and Webhook Handler improvements. 4 | ### Version Changes 5 | - [FIXED] Dynamic Adjustment to Custom Permalink Set by Merchant. 6 | - [FIXED] Redirect Payment option return a Payment Mismatch Error. 7 | - [FIXED] Reject Invalid Order Reference hooks. 8 | ## 2.3.5 | 01-01-2024 9 | Added Support for WooCommerce HPOS. 10 | ### Version Changes 11 | - [ADDED] Support for WooCommerce HPOS. 12 | - [FIXED] WooCommerce Blocks Compatibility Issues with WooCommerce 7.0 to 6.9.1. 13 | - [FIXED] Payment Option alignment on WooCommerce Checkout Block. 14 | - [FIXED] Handle Cancel Event on redirect Checkout option. 15 | ## 2.3.4 | 01-11-2023 16 | Handle Webhook Acknowledgement. 17 | ### Version Changes 18 | - [FIXED] Aknowledge hooks sent to prevent unsuccessful webhook delivery. 19 | 20 | ## 2.3.3 | 24-07-2023 21 | Update Order Notes and Confirmation Alerts. 22 | ### Version Changes 23 | - [FIXED] Order Note Details and Confirmation Alerts. 24 | 25 | ## 2.3.2 | 25-04-2023 26 | Wordpress requirement changes and updates. 27 | ### Version changes 28 | - [ADDED] Add support for WooCommerce Blocks. 29 | - [CHANGED] Updated Payment Gateway Checkout Process for better user experience. 30 | - [CHANGED] Updated Payment Tokenization for saved cards feature. 31 | - [CHANGED] Support for Flutterwave V3 API. 32 | - [CHANGED] Updated WooCommerce Subscription Integration. 33 | - [REMOVED] Remove outdated PHP Software Development Kit (SDK) from the plugin. 34 | 35 | 36 | ## 2.3.0 | 25-10-2022 37 | Routine maintenance. Resolved bug on Mobile money. 38 | ### Version changes 39 | - [FIXED] Handled MobileMoney Payment Handler Error. 40 | 41 | 42 | ## 2.2.9 | 23-09-2022 43 | Bug fix 44 | ### Version changes 45 | - [FIXED] PHP 8 support for v3 Webhook Handler. 46 | 47 | 48 | ## 2.2.8 | 20-06-2022 49 | Bugfixes 50 | ### Version changes 51 | - [CHANGED] Switch to WC-Logger class for logging. 52 | - [FIXED] Fix processing function error on Woocommerce Subscription. 53 | 54 | 55 | ## 2.2.7 | 30-05-2022 56 | Bugfixes 57 | ### Version changes 58 | - [FIXED] Fix redirect to order reciept page in redirect method. 59 | - [FIXED] Add support for PHP 8.0. 60 | 61 | 62 | 63 | ## 2.2.0 | 06-07-2018 64 | Updated base URL for API calls and added support for recurring payment 65 | ### Version changes 66 | - [ADDED] Add support for Woocommerce recurring to allow merchants collect recurring payments. 67 | - [CHANGED] Update base URL to support both transactions on both test and live mode. 68 | 69 | 70 | 71 | ## 2.0.0 72 | New payment currencies 73 | ### Version changes 74 | - [ADDED] Add support for new currencies (ZMW, UGX, RWF, TZS, SLL). 75 | 76 | 77 | 78 | ## 1.0.1 79 | Bugfixes 80 | ### Version changes 81 | - [ADDED] Add redirect style with admin toogle for redirect or popup payment style. 82 | - [CHANGED] Add custom gateway name. 83 | - [FIXED] fix bugs for country. 84 | 85 | 86 | ## 1.0.0 87 | Initial release 88 | ### Version changes 89 | - [ADDED] First plugin release. 90 | -------------------------------------------------------------------------------- /CONTRUBUTING.md: -------------------------------------------------------------------------------- 1 | Thank you for taking the time to contribute to our plugin🙌🏾. 2 | 3 | In this section, we detail everything you need to know about contributing to this plugin. 4 | 5 | 6 | **[Code of Conduct](https://github.com/probot/template/blob/master/CODE_OF_CONDUCT.md)** 7 | 8 | ## **I don't want to contribute, I have a question** 9 | 10 | Please don't raise an issue to ask a question. You can ask questions on our [forum](http://forum.flutterwave.com) or developer [slack](https://bit.ly/34Vkzcg). We have an army of Engineers on hand to answer your questions there. 11 | 12 | ## How can I contribute? 13 | 14 | ### Reporting a bug 15 | 16 | Have you spotted a bug? Fantastic! Before raising an issue, here are some things to do: 17 | 18 | 1. Search to see if another user has reported the bug. For existing issues that are still open, add a comment instead of creating a new one. 19 | 2. Check our forum and developer slack to confirm that we did not address it there. 20 | 21 | When you report an issue, it is important to: 22 | 23 | 1. Explain the problem 24 | - Use a clear and descriptive title to help us to identify the problem. 25 | - Describe steps we can use to replicate the bug and be as precise as possible. 26 | - Include screenshots of the error messages. 27 | 2. Include details about your configuration and setup 28 | - What version of the library are you using? 29 | - Did you experience the bug on test mode or live? 30 | - Do you have the recommended versions of the library dependencies? 31 | 32 | 37 | 38 | ### Requesting a feature 39 | 40 | If you need an additional feature added to the library, kindly send us an email at developers@flutterwavego.com. Be sure to include the following in your request: 41 | 42 | 1. A clear title that helps us to identify the requested feature. 43 | 2. A brief description of the use case for that feature. 44 | 3. Explain how this feature would be helpful to your integration. 45 | 4. Library name and version. 46 | 47 | ### Submitting changes (PR) 48 | 49 | Generally, you can make any of the following changes to the library: 50 | 51 | 1. Bug fixes 52 | 2. Performance improvement 53 | 3. Documentation update 54 | 4. Functionality change (usually new features) 55 | 56 | 61 | 62 | Follow these steps when making a pull request to the library: 63 | 64 | 1. Fork the repository and create your branch from master. 65 | 2. For all types of changes (excluding documentation updates), add tests for the changes. 66 | 3. If you are making a functionality change, update the docs to show how to use the new feature. 67 | 4. Ensure all your tests pass. 68 | 5. Make sure your code lints. 69 | 6. Write clear log messages for your commits. one-liners are fine for small changes, but bigger changes should have a more descriptive commit message (see sample below). 70 | 7. Use present tense for commit messages, "Add feature" not "Added feature”. 71 | 8. Ensure that you fill out all sections of the PR template. 72 | 9. Raise the PR against the `staging` branch. 73 | 10. After you submit the PR, verify that all [status checks](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/about-status-checks) are passing 74 | 75 | ```markdown 76 | $ git commit -m "A brief summary of the commit 77 | > 78 | > A paragraph describing what changed and its impact." 79 | ``` 80 | 81 | 86 | 87 | We encourage you to contribute and help make the library better for the community. Got questions? send us a [message](https://bit.ly/34Vkzcg). 88 | 89 | Thank you. 90 | 91 | The Flutterwave team 🦋 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Flutterwave 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := init 2 | 3 | %: 4 | @: 5 | 6 | init: 7 | echo "Specify an Action" 8 | 9 | up: 10 | docker-compose -f .docker/docker-compose.yml up --build --force-recreate -d && ./bin/docker-setup.sh 11 | 12 | down: 13 | docker-compose -f .docker/docker-compose.yml down 14 | 15 | 16 | dev-js: 17 | npm run start 18 | 19 | build-production-js: 20 | npm run preuglify && npm run uglify 21 | 22 | build-production-docs: 23 | npm run docs:build 24 | 25 | dev-docs: 26 | npm run docs:dev 27 | 28 | wp-format: 29 | npm run format 30 | 31 | i18n-pot: 32 | composer run makepot 33 | 34 | zip: 35 | rm rave-woocommerce-payment-gateway.zip && npm run plugin-zip 36 | 37 | inspection: 38 | ./vendor/bin/phpcs -p . --standard=PHPCompatibilityWP 39 | 40 | build: 41 | npm run build 42 | 43 | release: build 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | # Flutterwave WooCommerce 6 | 7 | ## Introduction 8 | 9 | The WooCommerce Plugin makes it very easy and quick to add Flutterwave Payment option on Checkout for your online store. Accept Credit card, Debit card and Bank account payment directly on your store with the Rave payment gateway for WooCommerce. 10 | 11 | Available features include: 12 | 13 | - Collections: Card, Account, Mobile money, Bank Transfers, USSD, Barter, NQR. 14 | - Recurring payments: Tokenization and Subscriptions (WooCommerce Subscriptions). 15 | - Split payments: Split payments between multiple recipients. 16 | 17 | 18 | ## Table of Contents 19 | 20 | 1. [Requirements](#requirements) 21 | 2. [Installation](#installation) 22 | 3. [Initialization](#initialization) 23 | 4. [Best Practices](#best-practices) 24 | 5. [Debugging Errors](#debugging-errors) 25 | 6. [Support](#support) 26 | 7. [Contribution guidelines](#contribution-guidelines) 27 | 9. [License](#license) 28 | 10. [Changelog](#changelog) 29 | 30 | 31 | ## Requirements 32 | 33 | 1. Flutterwave for business [API Keys](https://developer.flutterwave.com/docs/integration-guides/authentication) 34 | 2. [WooCommerce](https://woocommerce.com/) 35 | 6. Supported PHP version: 5.6.0 - 8.1.0 36 | 37 | 38 | ## Installation 39 | 40 | ### Github Zip Download 41 | 42 | To install the plugin, you need to first clone the repository from [GitHub](https://github.com/Flutterwave/WooCommerce) and then upload the folder to your WordPress plugins directory. For WooCommerce Blocks support, you would need to build block assets by running the command `npm install && npm run build:webpack`. 43 | 44 | ### Automatic Installation 45 | 46 | - Login to your WordPress Dashboard. 47 | - Click on "Plugins > Add New" from the left menu. 48 | - In the search box type **Flutterwave Woocommerce**. 49 | - Click on **Install Now** on **Flutterwave WooCommerce** to install the plugin on your site. 50 | - Confirm the installation. 51 | - Activate the plugin. 52 | - Click on "WooCommerce > Settings" from the left menu and click the **"Payments"** tab. 53 | - Click on the **Flutterwave** link from the available Checkout Options 54 | - Configure your **Flutterwave** settings accordingly. 55 | 56 | ### Manual Installation 57 | 58 | - Download the plugin zip file. 59 | - Login to your WordPress Admin. Click on "Plugins > Add New" from the left menu. 60 | - Click on the "Upload" option, then click "Choose File" to select the zip file you downloaded. Click "OK" and "Install Now" to complete the installation. 61 | - Activate the plugin. 62 | - Click on "WooCommerce > Settings" from the left menu and click the **"Payments"** tab. 63 | - Click on the **Flutterwave** link from the available Checkout Options 64 | - Configure your **Flutterwave** settings accordingly. 65 | 66 | For FTP manual installation, [check here](https://wordpress.org/documentation/article/manage-plugins/). 67 | 68 | ## Best Practices 69 | 70 | - When in doubt about a transaction, always check the Flutterwave Dashboard to confirm the status of a transaction. 71 | - Always ensure you keep your API keys securely and privately. Do not share with anyone. 72 | - Ensure you change from the default secret hash on the Wordpress admin and apply same on the Flutterwave Dashboard. 73 | - Always ensure you install the most recent version of the Flutterwave WooCommerce plugin. 74 | 75 | 76 | ## Debugging Errors 77 | 78 | We understand that you may run into some errors while integrating our plugin. You can read more about our error messages [here](https://developer.flutterwave.com/docs/integration-guides/errors). 79 | 80 | For `authorization` and `validation` error responses, double-check your API keys and request. If you get a `server` error, kindly engage the team for support. 81 | 82 | 83 | ## Support 84 | 85 | For additional assistance using this library, contact the developer experience (DX) team via [email](mailto:developers@flutterwavego.com) or on [slack](https://bit.ly/34Vkzcg). 86 | 87 | You can also follow us [@FlutterwaveEng](https://twitter.com/FlutterwaveEng) and let us know what you think 😊. 88 | 89 | 90 | ## Contribution guidelines 91 | 92 | We love to get your input. Read more about our community contribution guidelines [here](/CONTRIBUTING.md) 93 | 94 | 95 | ## License 96 | 97 | By contributing to the Rave WooCommerce Plugin, you agree that your contributions will be licensed under its [MIT license](/LICENSE). 98 | 99 | Copyright (c) Flutterwave Inc. 100 | 101 | 102 | ## Changelog 103 | 104 | Read about the changes we have made in our recent releases [here](/CHANGELOG.md) 105 | -------------------------------------------------------------------------------- /assets/css/checkout.css: -------------------------------------------------------------------------------- 1 | #flutterwave-button { 2 | background-color: #f5a623; 3 | border: none; 4 | border-radius: 4px; 5 | color: #fff; 6 | cursor: pointer; 7 | font-size: 16px; 8 | font-weight: 600; 9 | height: 40px; 10 | line-height: 40px; 11 | padding: 0 14px; 12 | text-align: center; 13 | text-decoration: none; 14 | text-transform: uppercase; 15 | transition: all 0.3s ease-in-out; 16 | width: 100%; 17 | } -------------------------------------------------------------------------------- /assets/img/amex.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/img/diners.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/img/discover.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/img/flutterwave-full.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/img/mastercard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/img/rave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutterwave/Woocommerce-v2/cc6be063ff8e8e8ce516c4bbea2ff97dc351ba98/assets/img/rave.png -------------------------------------------------------------------------------- /assets/img/rave.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rave 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /assets/img/screen1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutterwave/Woocommerce-v2/cc6be063ff8e8e8ce516c4bbea2ff97dc351ba98/assets/img/screen1.PNG -------------------------------------------------------------------------------- /assets/img/screen2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutterwave/Woocommerce-v2/cc6be063ff8e8e8ce516c4bbea2ff97dc351ba98/assets/img/screen2.PNG -------------------------------------------------------------------------------- /assets/img/screen3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutterwave/Woocommerce-v2/cc6be063ff8e8e8ce516c4bbea2ff97dc351ba98/assets/img/screen3.PNG -------------------------------------------------------------------------------- /assets/img/visa.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/js/checkout.js: -------------------------------------------------------------------------------- 1 | jQuery( function ( $ ) { 2 | $.blockUI({message: '

Loading Payment Modal...

'}); 3 | let payment_made = false; 4 | const redirectPost = function (location, args) { 5 | let form = ""; 6 | $.each(args, function (key, value) { 7 | // value = value.split('"').join('\"') 8 | form += ''; 9 | }); 10 | $('
' + form + "
") 11 | .appendTo($(document.body)) 12 | .submit(); 13 | }; 14 | 15 | const processData = () => { 16 | return { 17 | public_key: flw_payment_args.public_key, 18 | tx_ref: flw_payment_args.tx_ref, 19 | amount: flw_payment_args.amount, 20 | currency: flw_payment_args.currency, 21 | payment_options: flw_payment_args.payment_options, 22 | redirect_url: flw_payment_args.redirect_url, 23 | onclose: function () { 24 | $.unblockUI(); 25 | if (payment_made) { 26 | $.blockUI({message: '

confirming transaction ...

'}); 27 | redirectPost(flw_payment_args.redirect_url + "?tx_ref=" + flw_payment_args.tx_ref, {}); 28 | } else { 29 | $.blockUI({message: '

Canceling Payment ...

'}); 30 | window.location.href = flw_payment_args.cancel_url; 31 | } 32 | }, 33 | callback: function (response) { 34 | let tr = response.tx_ref; 35 | if ( 'successful' === response.status ) { 36 | payment_made = true; 37 | $.blockUI({message: '

confirming transaction ...

'}); 38 | redirectPost(flw_payment_args.redirect_url + "?txref=" + tr, response); 39 | } 40 | this.close(); // close modal 41 | }, 42 | meta: { 43 | consumer_id: flw_payment_args.consumer_id, 44 | }, 45 | customer: { 46 | email: flw_payment_args.email, 47 | phone_number: flw_payment_args.phone_number, 48 | name: flw_payment_args.first_name + " " + flw_payment_args.last_name, 49 | }, 50 | customizations: { 51 | title: flw_payment_args.title, 52 | description: flw_payment_args.description, 53 | logo: flw_payment_args.logo, 54 | }, 55 | } 56 | } 57 | let payload = processData(); 58 | let x = window.FlutterwaveCheckout(payload); 59 | } ); 60 | -------------------------------------------------------------------------------- /assets/js/checkout.min.js: -------------------------------------------------------------------------------- 1 | jQuery(function(t){t.blockUI({message:"

Loading Payment Modal...

"});let n=!1;function r(e,a){let n="";t.each(a,function(e,a){n+=''}),t('
'+n+"
").appendTo(t(document.body)).submit()}var e={public_key:flw_payment_args.public_key,tx_ref:flw_payment_args.tx_ref,amount:flw_payment_args.amount,currency:flw_payment_args.currency,payment_options:flw_payment_args.payment_options,redirect_url:flw_payment_args.redirect_url,onclose:function(){t.unblockUI(),n?(t.blockUI({message:"

confirming transaction ...

"}),r(flw_payment_args.redirect_url+"?tx_ref="+flw_payment_args.tx_ref,{})):(t.blockUI({message:"

Canceling Payment ...

"}),window.location.href=flw_payment_args.cancel_url)},callback:function(e){var a=e.tx_ref;"successful"===e.status&&(n=!0,t.blockUI({message:"

confirming transaction ...

"}),r(flw_payment_args.redirect_url+"?txref="+a,e)),this.close()},meta:{consumer_id:flw_payment_args.consumer_id},customer:{email:flw_payment_args.email,phone_number:flw_payment_args.phone_number,name:flw_payment_args.first_name+" "+flw_payment_args.last_name},customizations:{title:flw_payment_args.title,description:flw_payment_args.description,logo:flw_payment_args.logo}};window.FlutterwaveCheckout(e)}); 2 | -------------------------------------------------------------------------------- /changelog.txt: -------------------------------------------------------------------------------- 1 | == Changelog == 2 | = 2.3.6 = 3 | * Fixed: Dynamic Adjustment to Custom Permalink Set by Merchant. 4 | * Fixed: Redirect Payment option return a Payment Mismatch Error. 5 | = 2.3.5 = 6 | * Added: Support for WooCommerce HPOS. 7 | * Fixed: WooCommerce Blocks Compatibility Issues with WooCommerce 7.0 to 6.9.1. 8 | * Fixed: Payment Option alignment on WooCommerce Checkout Block. 9 | = 2.3.4 = 10 | * Fix: Webhook Handler Acknowledgement. 11 | = 2.3.2 = 12 | * Added: Support for WooCommerce Blocks. 13 | * Updated: WooCommerce Checkout Process. 14 | = 2.3.0 = 15 | * Fix: Handled MobileMoney Payment Handler Error. 16 | = 2.2.9 = 17 | * Fixed: PHP 8 support for v3 Webhook Handler. 18 | = 2.2.8 = 19 | * Fixed: Woocommerce Subscription processing function error. 20 | * New Feat: Switched to WC-Logger class for logging. 21 | = 2.2.7 = 22 | * fix: on payment completion redirect to order reciept page (redirect Method) 23 | * fix: PHP 8.0 compatibility ( optional method parameter ) 24 | = 2.2.0 = 25 | * Use one base URL for live and test mode. 26 | 27 | * Merchants can get their [test and live](https://developer.flutterwave.com/docs/api-keys) keys [here](https://rave.flutterwave.com/dashboard/settings/apis) 28 | 29 | * Using test keys keeps you in test mode, to move to live mode add live keys. 30 | 31 | * Support for Woocommerce recurring, this allows merchants to collect recurring payments in woocommerce. 32 | 33 | = 2.1.0 = 34 | * Support for Woocommerce recurring, this allows merchants to collect recurring payments in woocommerce. 35 | 36 | = 2.0.0 = 37 | * Support for new currencies (ZMW, UGX, RWF, TZS, SLL). 38 | 39 | = 1.0.1 = 40 | * Add redirect style with admin toogle for redirect or popup payment style 41 | * Custom gateway name 42 | * Bug fixes for country 43 | 44 | = 1.0.0 = 45 | * First release 46 | -------------------------------------------------------------------------------- /client/api/blocks.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import { 3 | normalizeOrderData, 4 | normalizeAddress, 5 | } from 'wcflutterwave/blocks/normalize'; 6 | import { getBlocksConfiguration } from 'wcflutterwave/blocks/utils'; 7 | 8 | /** 9 | * Construct WC AJAX endpoint URL. 10 | * 11 | * @param {string} endpoint Request endpoint URL. 12 | * @param {string} prefix Endpoint URI prefix (default: 'wc_rave_'). 13 | * @return {string} URL with interpolated endpoint. 14 | */ 15 | const getAjaxUrl = ( endpoint, prefix = 'wc_rave_' ) => { 16 | return getBlocksConfiguration() 17 | ?.ajax_url?.toString() 18 | ?.replace( '%%endpoint%%', prefix + endpoint ); 19 | }; 20 | 21 | export const getCartDetails = () => { 22 | const data = { 23 | security: getBlocksConfiguration()?.nonce?.payment, 24 | }; 25 | 26 | return $.ajax( { 27 | type: 'POST', 28 | data, 29 | url: getAjaxUrl( 'get_cart_details' ), 30 | } ); 31 | }; 32 | 33 | export const createOrder = ( sourceEvent, paymentRequestType ) => { 34 | const data = normalizeOrderData( sourceEvent, paymentRequestType ); 35 | 36 | return $.ajax( { 37 | type: 'POST', 38 | data, 39 | dataType: 'json', 40 | url: getAjaxUrl( 'create_order' ), 41 | } ); 42 | }; -------------------------------------------------------------------------------- /client/blocks/credit-card/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutterwave/Woocommerce-v2/cc6be063ff8e8e8ce516c4bbea2ff97dc351ba98/client/blocks/credit-card/.gitkeep -------------------------------------------------------------------------------- /client/blocks/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WooCommerce dependencies 3 | */ 4 | import { 5 | registerPaymentMethod, 6 | } from '@woocommerce/blocks-registry'; 7 | 8 | /** 9 | * Internal dependencies 10 | * 11 | * reference: https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/checkout-payment-methods/payment-method-integration.md 12 | */ 13 | import paymentMethod from 'wcflutterwave/blocks/payment-method'; 14 | 15 | // Register Flutterwave Payment Request. 16 | registerPaymentMethod( paymentMethod ); 17 | 18 | // TODO: implement a Direct Card payment method. 19 | -------------------------------------------------------------------------------- /client/blocks/normalize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('@woocommerce/type-defs/registered-payment-method-props').PreparedCartTotalItem} CartTotalItem 3 | * @typedef {import('@woocommerce/type-defs/cart').CartShippingOption} CartShippingOption 4 | * @typedef {import('@woocommerce/type-defs/shipping').ShippingAddress} CartShippingAddress 5 | * @typedef {import('@woocommerce/type-defs/billing').BillingData} CartBillingAddress 6 | */ 7 | 8 | import { getBlocksConfiguration } from 'wcflutterwave/blocks/utils'; 9 | 10 | /** 11 | * Normalizes order data received upon creating an order using the store's AJAX API. 12 | * 13 | * @param {Object} sourceEvent - The source event that triggered the creation of the order. 14 | * @param {string} paymentRequestType - The payment request type. 15 | */ 16 | const normalizeOrderData = ( sourceEvent, paymentRequestType ) => { 17 | const { source } = sourceEvent; 18 | const email = source?.owner?.email; 19 | const phone = source?.owner?.phone; 20 | const billing = source?.owner?.address; 21 | const name = source?.owner?.name; 22 | const shipping = sourceEvent?.shippingAddress; 23 | 24 | const data = { 25 | _wpnonce: getBlocksConfiguration()?.nonce?.checkout, 26 | billing_first_name: 27 | name?.split( ' ' )?.slice( 0, 1 )?.join( ' ' ) ?? '', 28 | billing_last_name: name?.split( ' ' )?.slice( 1 )?.join( ' ' ) ?? '', 29 | billing_company: '', 30 | billing_email: email ?? sourceEvent?.payerEmail, 31 | billing_phone: 32 | phone ?? sourceEvent?.payerPhone?.replace( '/[() -]/g', '' ), 33 | billing_country: billing?.country ?? '', 34 | billing_address_1: billing?.line1 ?? '', 35 | billing_address_2: billing?.line2 ?? '', 36 | billing_city: billing?.city ?? '', 37 | billing_state: billing?.state ?? '', 38 | billing_postcode: billing?.postal_code ?? '', 39 | shipping_first_name: '', 40 | shipping_last_name: '', 41 | shipping_company: '', 42 | shipping_country: '', 43 | shipping_address_1: '', 44 | shipping_address_2: '', 45 | shipping_city: '', 46 | shipping_state: '', 47 | shipping_postcode: '', 48 | shipping_method: [ sourceEvent?.shippingOption?.id ], 49 | order_comments: '', 50 | payment_method: 'rave', 51 | ship_to_different_address: 1, 52 | terms: 1, 53 | payment_request_type: paymentRequestType, 54 | }; 55 | 56 | if ( shipping ) { 57 | data.shipping_first_name = shipping?.recipient 58 | ?.split( ' ' ) 59 | ?.slice( 0, 1 ) 60 | ?.join( ' ' ); 61 | data.shipping_last_name = shipping?.recipient 62 | ?.split( ' ' ) 63 | ?.slice( 1 ) 64 | ?.join( ' ' ); 65 | data.shipping_company = shipping?.organization; 66 | data.shipping_country = shipping?.country; 67 | data.shipping_address_1 = shipping?.addressLine?.[ 0 ] ?? ''; 68 | data.shipping_address_2 = shipping?.addressLine?.[ 1 ] ?? ''; 69 | data.shipping_city = shipping?.city; 70 | data.shipping_state = shipping?.region; 71 | data.shipping_postcode = shipping?.postalCode; 72 | } 73 | 74 | return data; 75 | }; 76 | 77 | /** 78 | * Normalizes an address received upon updating shipping options using the store's AJAX API. 79 | * 80 | * @param {Object} address - The address that needs to be normalized. 81 | * @return {Object} The normalized address. 82 | */ 83 | const normalizeAddress = ( address ) => { 84 | return { 85 | country: address.country, 86 | state: address.region, 87 | postcode: address.postalCode, 88 | city: address.city, 89 | address: 90 | typeof address.addressLine[ 0 ] === 'undefined' 91 | ? '' 92 | : address.addressLine[ 0 ], 93 | address_2: 94 | typeof address.addressLine[ 1 ] === 'undefined' 95 | ? '' 96 | : address.addressLine[ 1 ], 97 | }; 98 | }; 99 | 100 | export { normalizeOrderData, normalizeAddress }; 101 | 102 | 103 | -------------------------------------------------------------------------------- /client/blocks/payment-method/constants.js: -------------------------------------------------------------------------------- 1 | export const PAYMENT_METHOD_NAME = 'rave'; 2 | export const PAYMENT_METHOD_VERSION = '2.3.2'; -------------------------------------------------------------------------------- /client/blocks/payment-method/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies 3 | */ 4 | import { __ } from '@wordpress/i18n'; 5 | import { decodeEntities } from '@wordpress/html-entities'; 6 | 7 | /** 8 | * Internal dependencies 9 | */ 10 | import { PAYMENT_METHOD_NAME } from './constants'; 11 | import { 12 | getBlocksConfiguration, 13 | } from 'wcflutterwave/blocks/utils'; 14 | 15 | /** 16 | * Content component 17 | */ 18 | const Content = () => { 19 | return
{ decodeEntities( getBlocksConfiguration()?.description || __('You may be redirected to a secure page to complete your payment.', 'rave-woocommerce-payment-gateway') ) }
; 20 | }; 21 | 22 | const FLW_ASSETS = getBlocksConfiguration()?.asset_url ?? null; 23 | 24 | 25 | const paymentMethod = { 26 | name: PAYMENT_METHOD_NAME, 27 | label: ( 28 |
29 | { 35 |

Flutterwave

36 |
37 | ), 38 | placeOrderButtonLabel: __( 39 | 'Proceed to Flutterwave', 40 | 'rave-woocommerce-payment-gateway' 41 | ), 42 | ariaLabel: decodeEntities( 43 | getBlocksConfiguration()?.title || 44 | __( 'Payment via Flutterwave', 'rave-woocommerce-payment-gateway' ) 45 | ), 46 | canMakePayment: () => true, 47 | content: , 48 | edit: , 49 | paymentMethodId: PAYMENT_METHOD_NAME, 50 | supports: { 51 | features: getBlocksConfiguration()?.supports ?? [], 52 | }, 53 | } 54 | 55 | export default paymentMethod; 56 | -------------------------------------------------------------------------------- /client/blocks/payment-request/apple-pay-preview.js: -------------------------------------------------------------------------------- 1 | export const applePayImage = 2 | "data:image/svg+xml,%3Csvg width='264' height='48' viewBox='0 0 264 48' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='264' height='48' rx='3' fill='black'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M125.114 16.6407C125.682 15.93 126.067 14.9756 125.966 14C125.135 14.0415 124.121 14.549 123.533 15.2602C123.006 15.8693 122.539 16.8641 122.661 17.7983C123.594 17.8797 124.526 17.3317 125.114 16.6407Z' fill='white'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M125.955 17.982C124.601 17.9011 123.448 18.7518 122.801 18.7518C122.154 18.7518 121.163 18.0224 120.092 18.0421C118.696 18.0629 117.402 18.8524 116.694 20.1079C115.238 22.6196 116.31 26.3453 117.726 28.3909C118.414 29.4028 119.242 30.5174 120.334 30.4769C121.366 30.4365 121.77 29.8087 123.024 29.8087C124.277 29.8087 124.641 30.4769 125.733 30.4567C126.865 30.4365 127.573 29.4443 128.261 28.4313C129.049 27.2779 129.373 26.1639 129.393 26.1027C129.373 26.0825 127.209 25.2515 127.189 22.7606C127.169 20.6751 128.888 19.6834 128.969 19.6217C127.998 18.1847 126.481 18.0224 125.955 17.982Z' fill='white'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M136.131 23.1804H138.834C140.886 23.1804 142.053 22.0752 142.053 20.1592C142.053 18.2432 140.886 17.1478 138.845 17.1478H136.131V23.1804ZM139.466 15.1582C142.411 15.1582 144.461 17.1903 144.461 20.1483C144.461 23.1172 142.369 25.1596 139.392 25.1596H136.131V30.3498H133.775V15.1582H139.466Z' fill='white'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M152.198 26.224V25.3712L149.579 25.5397C148.106 25.6341 147.339 26.182 147.339 27.14C147.339 28.0664 148.138 28.6667 149.39 28.6667C150.988 28.6667 152.198 27.6449 152.198 26.224ZM145.046 27.2032C145.046 25.2551 146.529 24.1395 149.263 23.971L152.198 23.7922V22.9498C152.198 21.7181 151.388 21.0442 149.947 21.0442C148.758 21.0442 147.896 21.6548 147.717 22.5916H145.592C145.656 20.6232 147.507 19.1914 150.01 19.1914C152.703 19.1914 154.459 20.602 154.459 22.7917V30.351H152.282V28.5298H152.229C151.609 29.719 150.241 30.4666 148.758 30.4666C146.571 30.4666 145.046 29.1612 145.046 27.2032Z' fill='white'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M156.461 34.4145V32.5934C156.608 32.6141 156.965 32.6354 157.155 32.6354C158.196 32.6354 158.785 32.1932 159.142 31.0564L159.353 30.3824L155.366 19.3281H157.827L160.604 28.298H160.657L163.434 19.3281H165.832L161.698 30.9402C160.752 33.6038 159.668 34.4778 157.376 34.4778C157.197 34.4778 156.618 34.4565 156.461 34.4145Z' fill='white'/%3E%3C/svg%3E%0A"; -------------------------------------------------------------------------------- /client/blocks/payment-request/branded-buttons.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from '@wordpress/element'; 2 | import { getBlocksConfiguration } from 'wcflutterwave/blocks/utils'; 3 | 4 | export const shouldUseGooglePayBrand = () => { 5 | const ua = window.navigator.userAgent.toLowerCase(); 6 | const isChrome = 7 | /chrome/.test( ua ) && 8 | ! /edge|edg|opr|brave\//.test( ua ) && 9 | window.navigator.vendor === 'Google Inc.'; 10 | // newer versions of Brave do not have the userAgent string 11 | const isBrave = isChrome && window.navigator?.brave; 12 | return isChrome && ! isBrave; 13 | }; 14 | 15 | const useLocalizedGoogleSvg = ( type, theme, locale ) => { 16 | // If we're using the short button type (i.e. logo only) make sure we get the logo only SVG. 17 | const googlePlaySvg = 18 | type === 'long' 19 | ? `https://www.gstatic.com/instantbuy/svg/${ theme }/${ locale }.svg` 20 | : `https://www.gstatic.com/instantbuy/svg/${ theme }_gpay.svg`; 21 | 22 | const [ url, setUrl ] = useState( googlePlaySvg ); 23 | 24 | useEffect( () => { 25 | const im = document.createElement( 'img' ); 26 | im.addEventListener( 'error', () => { 27 | setUrl( 28 | `https://www.gstatic.com/instantbuy/svg/${ theme }/en.svg` 29 | ); 30 | } ); 31 | im.src = url; 32 | }, [ url, theme ] ); 33 | 34 | return url; 35 | }; 36 | 37 | export const GooglePayButton = ( { onButtonClicked } ) => { 38 | const { 39 | theme = 'dark', 40 | locale = 'en', 41 | height = '44', 42 | } = getBlocksConfiguration()?.button; 43 | 44 | const allowedTypes = [ 'short', 'long' ]; 45 | const { branded_type } = getBlocksConfiguration()?.button; // eslint-disable-line camelcase 46 | const type = allowedTypes.includes( branded_type ) ? branded_type : 'long'; // eslint-disable-line camelcase 47 | 48 | // Allowed themes for Google Pay button image are 'dark' and 'light'. 49 | // We may include 'light-outline' as a theme, so we ensure only 'dark' or 'light' are possible 50 | // here. 51 | const gpayButtonTheme = theme === 'dark' ? 'dark' : 'light'; 52 | 53 | // Let's make sure the localized Google Pay button exists, otherwise we fall back to the 54 | // english version. This test element is not used on purpose. 55 | const backgroundUrl = useLocalizedGoogleSvg( 56 | type, 57 | gpayButtonTheme, 58 | locale 59 | ); 60 | 61 | return ( 62 | 22 | ); 23 | }; -------------------------------------------------------------------------------- /client/blocks/payment-request/event-handlers.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutterwave/Woocommerce-v2/cc6be063ff8e8e8ce516c4bbea2ff97dc351ba98/client/blocks/payment-request/event-handlers.js -------------------------------------------------------------------------------- /client/blocks/payment-request/hooks.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useCallback } from '@wordpress/element'; 2 | 3 | import { paymentProcessingHandler } from './event-handlers'; 4 | 5 | import { 6 | getBlocksConfiguration, 7 | createPaymentRequestUsingCart, 8 | } from 'wcflutterwave/blocks/utils'; 9 | 10 | export function usePaymentRequest() { 11 | 12 | } 13 | 14 | export function useProcessPaymentHandler() { 15 | 16 | } 17 | 18 | export function useOnClickHandler() { 19 | 20 | } 21 | 22 | export function useCancelHandler() { 23 | 24 | } -------------------------------------------------------------------------------- /client/blocks/payment-request/index.js: -------------------------------------------------------------------------------- 1 | import { getSetting } from '@woocommerce/settings'; 2 | import { PAYMENT_METHOD_NAME } from './constants'; 3 | import { PaymentRequestExpress } from './payment-request-express'; 4 | import { applePayImage } from './apple-pay-preview'; 5 | import { 6 | getBlocksConfiguration, 7 | } from 'wcflutterwave/blocks/utils'; 8 | 9 | const ApplePayPreview = () => ; 10 | 11 | const public_key = getBlocksConfiguration()?.public_key ?? null; 12 | 13 | const paymentRequestPaymentMethod = { 14 | name: PAYMENT_METHOD_NAME, 15 | content: , 16 | edit: , 17 | canMakePayment: ( cartData ) => { 18 | return true; 19 | }, 20 | paymentMethodId: PAYMENT_METHOD_NAME, 21 | supports: { 22 | features: getBlocksConfiguration()?.supports ?? [], 23 | }, 24 | }; 25 | 26 | export default paymentRequestPaymentMethod; -------------------------------------------------------------------------------- /client/blocks/payment-request/payment-request-express.js: -------------------------------------------------------------------------------- 1 | import { __ } from '@wordpress/i18n'; 2 | import { FlutterWaveButton, closePaymentModal } from 'flutterwave-react-v3'; 3 | import { 4 | usePaymentRequest, 5 | useProcessPaymentHandler, 6 | useOnClickHandler, 7 | useCancelHandler, 8 | } from './hooks'; 9 | import { getBlocksConfiguration } from 'wcflutterwave/blocks/utils'; 10 | 11 | 12 | export const PaymentRequestExpress = ({ payment_details } ) => { 13 | 14 | const fwConfig = { 15 | ...payment_details, 16 | text: __('Pay with Flutterwave!', 'rave-woocommerce-payment-gateway'), 17 | callback: (response) => { 18 | console.log(response); 19 | closePaymentModal() 20 | }, 21 | onClose: () => {}, 22 | } 23 | return ( 24 |
25 | 26 |
27 | ); 28 | }; -------------------------------------------------------------------------------- /client/blocks/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WooCommerce dependencies 3 | */ 4 | import { getSetting, WC_ } from '@woocommerce/settings'; 5 | 6 | export const getBlocksConfiguration = () => { 7 | const flutterwaveServerData = getSetting( 'rave_data', null ); 8 | 9 | if ( ! flutterwaveServerData ) { 10 | throw new Error( 'Flutterwave initialization data is not available' ); 11 | } 12 | 13 | return flutterwaveServerData; 14 | }; 15 | 16 | /** 17 | * Creates a payment request using cart data from WooCommerce. 18 | * 19 | * @param {Object} flutterwave - The Flutterwave JS object. 20 | * @param {Object} cart - The cart data response from the store's AJAX API. 21 | * 22 | * @return {Object} A Flutterwave payment request. 23 | */ 24 | export const createPaymentRequestUsingCart = ( flutterwave, cart ) => { 25 | const options = { 26 | total: cart.order_data.total, 27 | currency: cart.order_data.currency, 28 | country: cart.order_data.country_code, 29 | requestPayerName: true, 30 | requestPayerEmail: true, 31 | requestPayerPhone: getBlocksConfiguration()?.checkout 32 | ?.needs_payer_phone, 33 | requestShipping: !!cart.shipping_required, 34 | displayItems: cart.order_data.displayItems, 35 | }; 36 | 37 | if ( options.country === 'PR' ) { 38 | options.country = 'US'; 39 | } 40 | 41 | return flutterwave.paymentRequest( options ); 42 | }; 43 | 44 | /** 45 | * Updates the given PaymentRequest using the data in the cart object. 46 | * 47 | * @param {Object} paymentRequest The payment request object. 48 | * @param {Object} cart The cart data response from the store's AJAX API. 49 | */ 50 | export const updatePaymentRequestUsingCart = ( paymentRequest, cart ) => { 51 | const options = { 52 | total: cart.order_data.total, 53 | currency: cart.order_data.currency, 54 | displayItems: cart.order_data.displayItems, 55 | }; 56 | 57 | paymentRequest.update( options ); 58 | }; 59 | 60 | /** 61 | * Returns the Flutterwave public key 62 | * 63 | * @throws Error 64 | * @return {string} The public api key for the Flutterwave payment method. 65 | */ 66 | export const getPublicKey = () => { 67 | const public_key = getBlocksConfiguration()?.public_key; 68 | if ( ! public_key ) { 69 | throw new Error( 70 | 'There is no public key available for Flutterwave. Make sure it is available on the wc.flutterwave_data.public_key property.' 71 | ); 72 | } 73 | return public_key; 74 | }; 75 | 76 | -------------------------------------------------------------------------------- /client/payment-method-icons/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutterwave/Woocommerce-v2/cc6be063ff8e8e8ce516c4bbea2ff97dc351ba98/client/payment-method-icons/.gitkeep -------------------------------------------------------------------------------- /i18n/languages/README.md: -------------------------------------------------------------------------------- 1 | # Languages 2 | 3 | ## Generating POT 4 | 5 | The generated POT template file is not included in this repository. It gets generated when building the project: 6 | 7 | ``` 8 | make release 9 | ``` 10 | 11 | After the build completes, you'll find a `rave-woocommerce-payment-gateway.pot` strings file in this directory. -------------------------------------------------------------------------------- /i18n/languages/rave-woocommerce-payment-gateway.pot: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2023 Flutterwave Developers 2 | # This file is distributed under the MIT License. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: Flutterwave WooCommerce 2.3.4\n" 6 | "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/rave-woocommerce-payment-gateway\n" 7 | "Last-Translator: FULL NAME \n" 8 | "Language-Team: LANGUAGE \n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "POT-Creation-Date: 2023-12-26T17:58:20+00:00\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "X-Generator: WP-CLI 2.5.0\n" 15 | "X-Domain: rave-woocommerce-payment-gateway\n" 16 | 17 | #. Plugin Name of the plugin 18 | #. Translators: %s Plugin name. 19 | #: includes/class-flw-wc-payment-gateway.php:227 20 | #: includes/views/html-admin-missing-woocommerce.php:15 21 | msgid "Flutterwave WooCommerce" 22 | msgstr "" 23 | 24 | #. Plugin URI of the plugin 25 | msgid "https://developer.flutterwave.com/" 26 | msgstr "" 27 | 28 | #. Description of the plugin 29 | msgid "Official WooCommerce payment gateway for Flutterwave." 30 | msgstr "" 31 | 32 | #. Author of the plugin 33 | msgid "Flutterwave Developers" 34 | msgstr "" 35 | 36 | #. Author URI of the plugin 37 | msgid "http://flutterwave.com/us" 38 | msgstr "" 39 | 40 | #: includes/blocks/class-flutterwave-wc-gateway-blocks-support.php:156 41 | msgid "Visa" 42 | msgstr "" 43 | 44 | #: includes/blocks/class-flutterwave-wc-gateway-blocks-support.php:160 45 | msgid "American Express" 46 | msgstr "" 47 | 48 | #: includes/blocks/class-flutterwave-wc-gateway-blocks-support.php:164 49 | msgid "Mastercard" 50 | msgstr "" 51 | 52 | #: includes/blocks/class-flutterwave-wc-gateway-blocks-support.php:171 53 | msgctxt "Name of credit card" 54 | msgid "Discover" 55 | msgstr "" 56 | 57 | #: includes/blocks/class-flutterwave-wc-gateway-blocks-support.php:175 58 | msgid "JCB" 59 | msgstr "" 60 | 61 | #: includes/blocks/class-flutterwave-wc-gateway-blocks-support.php:179 62 | msgid "Diners" 63 | msgstr "" 64 | 65 | #: includes/class-flw-wc-payment-gateway-event-handler.php:64 66 | msgid "Payment initialized via Flutterwave" 67 | msgstr "" 68 | 69 | #: includes/class-flw-wc-payment-gateway-event-handler.php:66 70 | msgid "Your transaction reference: " 71 | msgstr "" 72 | 73 | #: includes/class-flw-wc-payment-gateway-event-handler.php:83 74 | msgid "Attention: New order has been placed on hold because of incorrect payment amount or currency. Please, look into it." 75 | msgstr "" 76 | 77 | #: includes/class-flw-wc-payment-gateway-event-handler.php:84 78 | msgid "Amount paid: " 79 | msgstr "" 80 | 81 | #: includes/class-flw-wc-payment-gateway-event-handler.php:84 82 | msgid "Order amount: " 83 | msgstr "" 84 | 85 | #: includes/class-flw-wc-payment-gateway-event-handler.php:84 86 | msgid " Reference: " 87 | msgstr "" 88 | 89 | #: includes/class-flw-wc-payment-gateway-event-handler.php:124 90 | msgid "The payment failed on Flutterwave" 91 | msgstr "" 92 | 93 | #: includes/class-flw-wc-payment-gateway-event-handler.php:129 94 | msgid "Reason for Failure : " 95 | msgstr "" 96 | 97 | #: includes/class-flw-wc-payment-gateway-event-handler.php:141 98 | msgid "Confirming payment on Flutterwave" 99 | msgstr "" 100 | 101 | #: includes/class-flw-wc-payment-gateway-event-handler.php:151 102 | msgid "An error occured while confirming payment on Flutterwave" 103 | msgstr "" 104 | 105 | #: includes/class-flw-wc-payment-gateway-event-handler.php:156 106 | msgid "Attention: New order has been placed on hold because we could not confirm the payment. Please, look into it." 107 | msgstr "" 108 | 109 | #: includes/class-flw-wc-payment-gateway-event-handler.php:172 110 | msgid "The customer clicked on the cancel button on Checkout." 111 | msgstr "" 112 | 113 | #: includes/class-flw-wc-payment-gateway-event-handler.php:174 114 | msgid "Attention: Customer clicked on the cancel button on the payment gateway. We have updated the order to cancelled status. " 115 | msgstr "" 116 | 117 | #: includes/class-flw-wc-payment-gateway-event-handler.php:175 118 | msgid "Please, confirm from the order notes that there is no note of a successful transaction. If there is, this means that the user was debited and you either have to give value for the transaction or refund the customer." 119 | msgstr "" 120 | 121 | #: includes/class-flw-wc-payment-gateway-event-handler.php:189 122 | msgid "The payment didn't return a valid response. It could have timed out or abandoned by the customer on Flutterwave" 123 | msgstr "" 124 | 125 | #: includes/class-flw-wc-payment-gateway-event-handler.php:194 126 | msgid "Attention: New order has been placed on hold because we could not get a definite response from the payment gateway. Kindly contact the Rave support team at hi@flutterwave.com to confirm the payment." 127 | msgstr "" 128 | 129 | #: includes/class-flw-wc-payment-gateway-event-handler.php:195 130 | msgid "Payment Reference: " 131 | msgstr "" 132 | 133 | #. translators: 1: payment reference 2: transaction reference 134 | #: includes/class-flw-wc-payment-gateway-subscriptions.php:163 135 | msgid "Payment via Flutterwave successful (Payment Reference: %1$s, Transaction Reference: %2$s)" 136 | msgstr "" 137 | 138 | #: includes/class-flw-wc-payment-gateway.php:152 139 | msgid "allows you to accept payment from cards and bank accounts in multiple currencies. You can also accept payment offline via USSD and POS." 140 | msgstr "" 141 | 142 | #: includes/class-flw-wc-payment-gateway.php:231 143 | msgid "Webhook Instruction" 144 | msgstr "" 145 | 146 | #: includes/class-flw-wc-payment-gateway.php:235 147 | msgid "Please copy this webhook URL and paste on the webhook section on your dashboard" 148 | msgstr "" 149 | 150 | #: includes/class-flw-wc-payment-gateway.php:256 151 | msgid "Enable/Disable" 152 | msgstr "" 153 | 154 | #: includes/class-flw-wc-payment-gateway.php:257 155 | msgid "Enable Flutterwave" 156 | msgstr "" 157 | 158 | #: includes/class-flw-wc-payment-gateway.php:259 159 | msgid "Enable Flutterwave as a payment option on the checkout page" 160 | msgstr "" 161 | 162 | #: includes/class-flw-wc-payment-gateway.php:264 163 | msgid "Enter Secret Hash" 164 | msgstr "" 165 | 166 | #: includes/class-flw-wc-payment-gateway.php:266 167 | msgid "Please change from default hash and ensure that SECRET HASH is the same with the one on your Flutterwave dashboard" 168 | msgstr "" 169 | 170 | #: includes/class-flw-wc-payment-gateway.php:270 171 | msgid "Payment method title" 172 | msgstr "" 173 | 174 | #: includes/class-flw-wc-payment-gateway.php:272 175 | #: includes/class-flw-wc-payment-gateway.php:278 176 | msgid "Optional" 177 | msgstr "" 178 | 179 | #: includes/class-flw-wc-payment-gateway.php:276 180 | msgid "Payment method description" 181 | msgstr "" 182 | 183 | #: includes/class-flw-wc-payment-gateway.php:282 184 | msgid "Test Public Key" 185 | msgstr "" 186 | 187 | #: includes/class-flw-wc-payment-gateway.php:284 188 | msgid "Required! Enter your Flutterwave test public key here" 189 | msgstr "" 190 | 191 | #: includes/class-flw-wc-payment-gateway.php:288 192 | msgid "Test Secret Key" 193 | msgstr "" 194 | 195 | #: includes/class-flw-wc-payment-gateway.php:290 196 | msgid "Required! Enter your Flutterwave test secret key here" 197 | msgstr "" 198 | 199 | #: includes/class-flw-wc-payment-gateway.php:294 200 | msgid "Live Public Key" 201 | msgstr "" 202 | 203 | #: includes/class-flw-wc-payment-gateway.php:296 204 | msgid "Required! Enter your Flutterwave live public key here" 205 | msgstr "" 206 | 207 | #: includes/class-flw-wc-payment-gateway.php:300 208 | msgid "Live Secret Key" 209 | msgstr "" 210 | 211 | #: includes/class-flw-wc-payment-gateway.php:302 212 | msgid "Required! Enter your Flutterwave live secret key here" 213 | msgstr "" 214 | 215 | #: includes/class-flw-wc-payment-gateway.php:306 216 | msgid "Payment Style on checkout" 217 | msgstr "" 218 | 219 | #: includes/class-flw-wc-payment-gateway.php:308 220 | msgid "Optional - Choice of payment style to use. Either inline or redirect. (Default: inline)" 221 | msgstr "" 222 | 223 | #: includes/class-flw-wc-payment-gateway.php:310 224 | msgctxt "payment_style" 225 | msgid "Popup(Keep payment experience on the website)" 226 | msgstr "" 227 | 228 | #: includes/class-flw-wc-payment-gateway.php:311 229 | msgctxt "payment_style" 230 | msgid "Redirect" 231 | msgstr "" 232 | 233 | #: includes/class-flw-wc-payment-gateway.php:316 234 | msgid "Autocomplete Order After Payment" 235 | msgstr "" 236 | 237 | #: includes/class-flw-wc-payment-gateway.php:317 238 | msgid "Autocomplete Order" 239 | msgstr "" 240 | 241 | #: includes/class-flw-wc-payment-gateway.php:320 242 | msgid "If enabled, the order will be marked as complete after successful payment" 243 | msgstr "" 244 | 245 | #: includes/class-flw-wc-payment-gateway.php:325 246 | msgid "Payment Options" 247 | msgstr "" 248 | 249 | #: includes/class-flw-wc-payment-gateway.php:327 250 | msgid "Optional - Choice of payment method to use. Card, Account etc." 251 | msgstr "" 252 | 253 | #: includes/class-flw-wc-payment-gateway.php:329 254 | msgctxt "payment_options" 255 | msgid "All" 256 | msgstr "" 257 | 258 | #: includes/class-flw-wc-payment-gateway.php:330 259 | msgctxt "payment_options" 260 | msgid "Card Only" 261 | msgstr "" 262 | 263 | #: includes/class-flw-wc-payment-gateway.php:331 264 | msgctxt "payment_options" 265 | msgid "Account Only" 266 | msgstr "" 267 | 268 | #: includes/class-flw-wc-payment-gateway.php:332 269 | msgctxt "payment_options" 270 | msgid "USSD Only" 271 | msgstr "" 272 | 273 | #: includes/class-flw-wc-payment-gateway.php:333 274 | msgctxt "payment_options" 275 | msgid "QR Only" 276 | msgstr "" 277 | 278 | #: includes/class-flw-wc-payment-gateway.php:334 279 | msgctxt "payment_options" 280 | msgid "Mpesa Only" 281 | msgstr "" 282 | 283 | #: includes/class-flw-wc-payment-gateway.php:335 284 | msgctxt "payment_options" 285 | msgid "Ghana MM Only" 286 | msgstr "" 287 | 288 | #: includes/class-flw-wc-payment-gateway.php:336 289 | msgctxt "payment_options" 290 | msgid "Rwanda MM Only" 291 | msgstr "" 292 | 293 | #: includes/class-flw-wc-payment-gateway.php:337 294 | msgctxt "payment_options" 295 | msgid "Zambia MM Only" 296 | msgstr "" 297 | 298 | #: includes/class-flw-wc-payment-gateway.php:338 299 | msgctxt "payment_options" 300 | msgid "Tanzania MM Only" 301 | msgstr "" 302 | 303 | #: includes/class-flw-wc-payment-gateway.php:343 304 | msgid "Mode" 305 | msgstr "" 306 | 307 | #: includes/class-flw-wc-payment-gateway.php:344 308 | msgid "Live mode" 309 | msgstr "" 310 | 311 | #: includes/class-flw-wc-payment-gateway.php:346 312 | msgid "Check this box if you're using your live keys." 313 | msgstr "" 314 | 315 | #: includes/class-flw-wc-payment-gateway.php:351 316 | #: includes/class-flw-wc-payment-gateway.php:352 317 | msgid "Disable Logging" 318 | msgstr "" 319 | 320 | #: includes/class-flw-wc-payment-gateway.php:354 321 | msgid "Check this box if you're disabling logging." 322 | msgstr "" 323 | 324 | #: includes/class-flw-wc-payment-gateway.php:359 325 | #: includes/class-flw-wc-payment-gateway.php:360 326 | msgid "Disable Barter" 327 | msgstr "" 328 | 329 | #: includes/class-flw-wc-payment-gateway.php:362 330 | msgid "Check the box if you want to disable barter." 331 | msgstr "" 332 | 333 | #. translators: %s: url 334 | #: includes/class-flw-wc-payment-gateway.php:453 335 | msgid "Flutterwave is enabled, but the API keys are not set. Please set your Flutterwave API keys to be able to accept payments." 336 | msgstr "" 337 | 338 | #. translators: %s: shop cart url 339 | #: includes/class-flw-wc-payment-gateway.php:490 340 | msgid "Sorry, your session has expired. Return to shop" 341 | msgstr "" 342 | 343 | #: includes/class-flw-wc-payment-gateway.php:504 344 | msgid "We were unable to process your order, please try again." 345 | msgstr "" 346 | 347 | #: includes/class-flw-wc-payment-gateway.php:551 348 | msgid "Order Payment" 349 | msgstr "" 350 | 351 | #: includes/client/class-flw-wc-payment-gateway-request.php:126 352 | msgid "Payment for order " 353 | msgstr "" 354 | 355 | #. translators: $1. Minimum WooCommerce version. $2. Current WooCommerce version. 356 | #: includes/notices/class-flw-wc-payment-gateway-notices.php:32 357 | msgid "Flutterwave WooCommerce requires WooCommerce %1$s or greater to be installed and active. kindly upgrade to a higher version of WooCommerce or downgrade to a lower version of Flutterwave WooCommerce that supports WooCommerce version %2$s." 358 | msgstr "" 359 | 360 | #. Translators: %s Plugin name. 361 | #: includes/views/html-admin-missing-woocommerce.php:15 362 | msgid "%s requires WooCommerce to be installed and activated in order to serve updates." 363 | msgstr "" 364 | 365 | #: includes/views/html-admin-missing-woocommerce.php:25 366 | msgid "Activate WooCommerce" 367 | msgstr "" 368 | 369 | #: includes/views/html-admin-missing-woocommerce.php:28 370 | #: includes/views/html-admin-missing-woocommerce.php:42 371 | msgid "Turn off Flutterwave WooCommerce" 372 | msgstr "" 373 | 374 | #: includes/views/html-admin-missing-woocommerce.php:40 375 | msgid "Install WooCommerce" 376 | msgstr "" 377 | 378 | #: build/index.js:1 379 | #: client/blocks/payment-method/index.js:19 380 | #: build/index.js:102 381 | msgid "You may be redirected to a secure page to complete your payment." 382 | msgstr "" 383 | 384 | #: build/index.js:1 385 | #: client/blocks/payment-method/index.js:32 386 | #: build/index.js:115 387 | msgid "Flutterwave" 388 | msgstr "" 389 | 390 | #: build/index.js:1 391 | #: client/blocks/payment-method/index.js:38 392 | #: build/index.js:121 393 | msgid "Proceed to Flutterwave" 394 | msgstr "" 395 | 396 | #: build/index.js:1 397 | #: client/blocks/payment-method/index.js:44 398 | #: build/index.js:127 399 | msgid "Payment via Flutterwave" 400 | msgstr "" 401 | 402 | #: client/blocks/payment-request/custom-button.js:8 403 | msgid "Buy now" 404 | msgstr "" 405 | 406 | #: client/blocks/payment-request/payment-request-express.js:16 407 | msgid "Pay with Flutterwave!" 408 | msgstr "" 409 | -------------------------------------------------------------------------------- /includes/blocks/class-flutterwave-wc-gateway-blocks-support.php: -------------------------------------------------------------------------------- 1 | settings = get_option( 'woocommerce_rave_settings', array() ); 47 | 48 | if ( version_compare( WC_VERSION, '6.9.1', '<' ) ) { 49 | // For backwards compatibility. 50 | if ( ! class_exists( 'FLW_WC_Payment_Gateway' ) ) { 51 | require_once dirname( FLW_WC_PLUGIN_FILE ) . '/includes/class-flw-wc-payment-gateway.php'; 52 | } 53 | 54 | $this->gateway = new FLW_WC_Payment_Gateway(); 55 | } else { 56 | $gateways = WC()->payment_gateways->payment_gateways(); 57 | $this->gateway = $gateways[ $this->name ]; 58 | } 59 | } 60 | 61 | /** 62 | * Returns if this payment method should be active. If false, the scripts will not be enqueued. 63 | * 64 | * @return boolean 65 | */ 66 | public function is_active(): bool { 67 | if ( version_compare( WC_VERSION, '6.9.0', '>' ) ) { 68 | $gateways = WC()->payment_gateways->payment_gateways(); 69 | 70 | if ( ! isset( $gateways[ $this->name ] ) ) { 71 | return false; 72 | } 73 | } 74 | 75 | return $this->gateway->is_available(); 76 | } 77 | 78 | /** 79 | * Returns an array of supported features. 80 | * 81 | * @return string[] 82 | */ 83 | public function get_supported_features(): array { 84 | return $this->gateway->supports; 85 | } 86 | 87 | /** 88 | * Returns an array of scripts/handles to be registered for this payment method. 89 | * 90 | * @return array 91 | */ 92 | public function get_payment_method_script_handles(): array { 93 | wp_register_script( 94 | 'flutterwave', 95 | 'https://checkout.flutterwave.com/v3.js', 96 | array(), 97 | FLW_WC_VERSION, 98 | true 99 | ); 100 | 101 | $asset_path = dirname( FLW_WC_PLUGIN_FILE ) . '/build/index.asset.php'; 102 | $version = FLW_WC_VERSION; 103 | $dependencies = array(); 104 | if ( file_exists( $asset_path ) ) { 105 | $asset = require $asset_path; 106 | $version = is_array( $asset ) && isset( $asset['version'] ) 107 | ? $asset['version'] 108 | : $version; 109 | $dependencies = is_array( $asset ) && isset( $asset['dependencies'] ) 110 | ? $asset['dependencies'] 111 | : $dependencies; 112 | } 113 | wp_register_script( 114 | 'wc-flutterwave-blocks', 115 | FLW_WC_URL . '/build/index.js', 116 | array_merge( array( 'flutterwave' ), $dependencies ), 117 | $version, 118 | true 119 | ); 120 | wp_set_script_translations( 121 | 'wc-flutterwave-blocks', 122 | 'rave-woocommerce-payment-gateway' 123 | ); 124 | 125 | return array( 126 | 'wc-flutterwave-blocks', 127 | ); 128 | } 129 | 130 | /** 131 | * Returns an array of key=>value pairs of data made available to the payment methods script. 132 | * 133 | * @return array 134 | */ 135 | public function get_payment_method_data(): array { 136 | return array( 137 | 'icons' => $this->get_icons(), 138 | 'supports' => array_filter( $this->get_supported_features(), array( $this->gateway, 'supports' ) ), 139 | 'isAdmin' => is_admin(), 140 | 'public_key' => ( 'yes' === $this->settings['go_live'] ) ? $this->settings['live_public_key'] : $this->settings['test_public_key'], 141 | 'asset_url' => plugins_url( 'assets', FLW_WC_PLUGIN_FILE ), 142 | 'title' => $this->settings['title'], 143 | 'description' => $this->settings['description'], 144 | ); 145 | } 146 | 147 | /** 148 | * Returns an array of icons for the payment method. 149 | * 150 | * @return array 151 | */ 152 | private function get_icons(): array { 153 | $icons_src = array( 154 | 'visa' => array( 155 | 'src' => dirname( FLW_WC_PLUGIN_FILE ) . '/assets/img/visa.svg', 156 | 'alt' => __( 'Visa', 'rave-woocommerce-payment-gateway' ), 157 | ), 158 | 'amex' => array( 159 | 'src' => dirname( FLW_WC_PLUGIN_FILE ) . '/assets/img/amex.svg', 160 | 'alt' => __( 'American Express', 'rave-woocommerce-payment-gateway' ), 161 | ), 162 | 'mastercard' => array( 163 | 'src' => dirname( FLW_WC_PLUGIN_FILE ) . '/assets/img/mastercard.svg', 164 | 'alt' => __( 'Mastercard', 'rave-woocommerce-payment-gateway' ), 165 | ), 166 | ); 167 | 168 | if ( 'USD' === get_woocommerce_currency() ) { 169 | $icons_src['discover'] = array( 170 | 'src' => dirname( FLW_WC_PLUGIN_FILE ) . '/assets/img/discover.svg', 171 | 'alt' => _x( 'Discover', 'Name of credit card', 'rave-woocommerce-payment-gateway' ), 172 | ); 173 | $icons_src['jcb'] = array( 174 | 'src' => dirname( FLW_WC_PLUGIN_FILE ) . '/assets/img/jcb.svg', 175 | 'alt' => __( 'JCB', 'rave-woocommerce-payment-gateway' ), 176 | ); 177 | $icons_src['diners'] = array( 178 | 'src' => dirname( FLW_WC_PLUGIN_FILE ) . '/assets/img/diners.svg', 179 | 'alt' => __( 'Diners', 'rave-woocommerce-payment-gateway' ), 180 | ); 181 | } 182 | return $icons_src; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /includes/class-flutterwave.php: -------------------------------------------------------------------------------- 1 | define_constants(); 51 | $this->load_plugin_textdomain(); 52 | $this->includes(); 53 | $this->init(); 54 | } 55 | 56 | /** 57 | * Define constant if not already set. 58 | * 59 | * @param string $name Constant name. 60 | * @param string|bool $value Constant value. 61 | */ 62 | private function define( string $name, $value ) { 63 | if ( ! defined( $name ) ) { 64 | define( $name, $value ); 65 | } 66 | } 67 | 68 | /** 69 | * Define Flutterwave Constants. 70 | */ 71 | private function define_constants() { 72 | $this->define( 'FLUTTERWAVEACCESS', 1 ); 73 | $this->define( 'FLW_WC_PLUGIN_URL', plugin_dir_url( FLW_WC_PLUGIN_FILE ) ); 74 | $this->define( 'FLW_WC_PLUGIN_BASENAME', plugin_basename( FLW_WC_PLUGIN_FILE ) ); 75 | $this->define( 'FLW_WC_PLUGIN_DIR', plugin_dir_path( FLW_WC_PLUGIN_FILE ) ); 76 | $this->define( 'FLW_WC_DIR_PATH', plugin_dir_path( FLW_WC_PLUGIN_FILE ) ); 77 | $this->define( 'FLW_WC_VERSION', $this->version ); 78 | $this->define( 'FLW_WC_MIN_WC_VER', '6.9.1' ); 79 | $this->define( 'FLW_WC_URL', trailingslashit( plugins_url( '/', FLW_WC_PLUGIN_FILE ) ) ); 80 | $this->define( 'FLW_WC_EPSILON', 0.01 ); 81 | } 82 | 83 | /** 84 | * Load plugin textdomain. 85 | * 86 | * @since 2.3.2 87 | */ 88 | public function load_plugin_textdomain() { 89 | $locale = determine_locale(); 90 | 91 | load_plugin_textdomain( 'rave-woocommerce-payment-gateway', false, dirname( FLW_WC_PLUGIN_BASENAME ) . '/i18n/languages' ); 92 | } 93 | 94 | /** 95 | * Initialize the plugin. 96 | * Checks for an existing instance of this class in the global scope and if it doesn't find one, creates it. 97 | * 98 | * @return void 99 | */ 100 | private function init() { 101 | $notices = new FLW_WC_Payment_Gateway_Notices(); 102 | 103 | // Check if WooCommerce is active. 104 | if ( ! class_exists( 'WooCommerce' ) ) { 105 | 106 | add_action( 'admin_notices', array( $notices, 'woocommerce_not_installed' ) ); 107 | return; 108 | } 109 | 110 | if ( ! class_exists( 'WC_Payment_Gateway' ) ) { 111 | return; 112 | } 113 | 114 | if ( version_compare( WC_VERSION, FLW_WC_MIN_WC_VER, '<' ) ) { 115 | add_action( 'admin_notices', array( $notices, 'woocommerce_wc_not_supported' ) ); 116 | return; 117 | } 118 | 119 | register_activation_hook( __FILE__, array( $this, 'activate' ) ); 120 | register_deactivation_hook( __FILE__, array( $this, 'deactivate' ) ); 121 | 122 | $this->register_payment_gateway(); 123 | } 124 | 125 | /** 126 | * Cloning is forbidden. 127 | * 128 | * @since 2.3.2 129 | */ 130 | public function __clone() {} 131 | 132 | /** 133 | * Unserializing instances of this class is forbidden. 134 | * 135 | * @since 2.3.2 136 | */ 137 | public function __wakeup() {} 138 | 139 | /** 140 | * Include required core files used in admin and on the frontend. 141 | */ 142 | public function includes() { 143 | // Include classes that can run on WP Freely. 144 | include_once dirname( FLW_WC_PLUGIN_FILE ) . '/includes/notices/class-flw-wc-payment-gateway-notices.php'; 145 | } 146 | 147 | /** 148 | * This handles actions on plugin activation. 149 | * 150 | * @return void 151 | */ 152 | public static function activate() { 153 | if ( ! class_exists( 'WooCommerce' ) ) { 154 | $notices = new FLW_WC_Payment_Gateway_Notices(); 155 | add_action( 'admin_notices', array( $notices, 'woocommerce_not_installed' ) ); 156 | } 157 | } 158 | 159 | /** 160 | * This handles actions on plugin deactivation. 161 | * 162 | * @return void 163 | */ 164 | public static function deactivate() { 165 | // Deactivation logic. 166 | } 167 | 168 | /** 169 | * Register Flutterwave as a Payment Gateway. 170 | * 171 | * @return void 172 | */ 173 | public function register_payment_gateway() { 174 | require_once dirname( FLW_WC_PLUGIN_FILE ) . '/includes/class-flw-wc-payment-gateway.php'; 175 | if ( class_exists( 'WC_Subscriptions_Order' ) && class_exists( 'WC_Payment_Gateway_CC' ) ) { 176 | require_once FLW_WC_DIR_PATH . 'includes/class-flw-wc-payment-gateway-subscriptions.php'; 177 | 178 | } 179 | 180 | add_filter( 'woocommerce_payment_gateways', array( 'Flutterwave', 'add_gateway_to_woocommerce_gateway_list' ), 99 ); 181 | } 182 | 183 | /** 184 | * Add the Gateway to WooCommerce 185 | * 186 | * @param array $methods Existing gateways in WooCommerce. 187 | * 188 | * @return array Gateway list with our gateway added 189 | */ 190 | public static function add_gateway_to_woocommerce_gateway_list( array $methods ): array { 191 | 192 | if ( class_exists( 'WC_Subscriptions_Order' ) && class_exists( 'WC_Payment_Gateway_CC' ) ) { 193 | 194 | $methods[] = 'FLW_WC_Payment_Gateway_Subscriptions'; 195 | 196 | } else { 197 | 198 | $methods[] = 'FLW_WC_Payment_Gateway'; 199 | } 200 | 201 | return $methods; 202 | } 203 | 204 | /** 205 | * Add the Settings link to the plugin 206 | * 207 | * @param array $links Existing links on the plugin page. 208 | * 209 | * @return array Existing links with our settings link added 210 | */ 211 | public static function plugin_action_links( array $links ): array { 212 | 213 | $rave_settings_url = esc_url( get_admin_url( null, 'admin.php?page=wc-settings&tab=checkout§ion=rave' ) ); 214 | array_unshift( $links, "Settings" ); 215 | 216 | return $links; 217 | 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /includes/class-flw-wc-payment-gateway-event-handler.php: -------------------------------------------------------------------------------- 1 | order = $order; 37 | } 38 | 39 | /** 40 | * Check Amount Equals. 41 | * 42 | * Checks to see whether the given amounts are equal using a proper floating 43 | * point comparison with an Epsilon which ensures that insignificant decimal 44 | * places are ignored in the comparison. 45 | * 46 | * eg. 100.00 is equal to 100.0001 47 | * 48 | * @param Float $amount1 1st amount for comparison. 49 | * @param Float $amount2 2nd amount for comparison. 50 | * @since 2.3.3 51 | * @return bool 52 | */ 53 | public function amounts_equal( $amount1, $amount2 ): bool { 54 | return ! ( abs( floatval( $amount1 ) - floatval( $amount2 ) ) > FLW_WC_EPSILON ); 55 | } 56 | 57 | /** 58 | * This is called when the Flutterwave class is initialized 59 | * 60 | * @param object $initialization_data - This is the transaction data as returned from the Flutterwave payment gateway. 61 | */ 62 | public function on_init( object $initialization_data ) { 63 | // Save the transaction to your DB. 64 | $this->order->add_order_note( esc_html__( 'Payment initialized via Flutterwave', 'rave-woocommerce-payment-gateway' ) ); 65 | update_post_meta( $this->order->get_id(), '_flw_payment_txn_ref', $initialization_data->txref ); 66 | $this->order->add_order_note( esc_html__( 'Your transaction reference: ', 'rave-woocommerce-payment-gateway' ) . $initialization_data->txref ); 67 | } 68 | 69 | /** 70 | * This is called only when a transaction is successful. 71 | * 72 | * @param object $transaction_data - This is the transaction data as returned from the Flutterwave payment gateway. 73 | */ 74 | public function on_successful( object $transaction_data ) { 75 | if ( 'successful' === $transaction_data->status ) { 76 | $amount = (float) $transaction_data->amount; 77 | 78 | if ( $transaction_data->currency !== $this->order->get_currency() || ! $this->amounts_equal( $amount, $this->order->get_total() ) ) { 79 | $this->order->update_status( 'on-hold' ); 80 | $customer_note = 'Thank you for your order.
'; 81 | $customer_note .= 'Your payment successfully went through, but we have to put your order on-hold '; 82 | $customer_note .= 'because the we couldn\t verify your order. Please, contact us for information regarding this order.'; 83 | $admin_note = esc_html__( 'Attention: New order has been placed on hold because of incorrect payment amount or currency. Please, look into it.', 'rave-woocommerce-payment-gateway' ) . '
'; 84 | $admin_note .= esc_html__( 'Amount paid: ', 'rave-woocommerce-payment-gateway' ) . $transaction_data->currency . ' ' . $amount . '
' . esc_html__( 'Order amount: ', 'rave-woocommerce-payment-gateway' ) . $this->order->get_currency() . ' ' . $this->order->get_total() . '
' . esc_html__( ' Reference: ', 'rave-woocommerce-payment-gateway' ) . $transaction_data->tx_ref; 85 | 86 | $this->order->add_order_note( $customer_note, 1 ); 87 | $this->order->add_order_note( $admin_note ); 88 | 89 | } else { 90 | $this->order->payment_complete( $this->order->get_id() ); 91 | $payment_method = $this->order->get_payment_method(); 92 | 93 | $flw_settings = get_option( 'woocommerce_' . $payment_method . '_settings' ); 94 | 95 | if ( isset( $flw_settings['autocomplete_order'] ) && 'yes' === $flw_settings['autocomplete_order'] ) { 96 | $this->order->update_status( 'completed' ); 97 | } 98 | $this->order->add_order_note( 'Payment was successful on Flutterwave' ); 99 | $this->order->add_order_note( 'Flutterwave transaction reference: ' . $transaction_data->flw_ref ); 100 | 101 | $customer_note = 'Thank you for your order.
'; 102 | $customer_note .= 'Your payment was successful, we are now processing your order.'; 103 | $this->order->add_order_note( $customer_note, 1 ); 104 | } 105 | wc_add_notice( $customer_note, 'notice' ); 106 | // get order_id from the txref. 107 | $get_order_id = explode( '_', $transaction_data->tx_ref ); 108 | $order_id = $get_order_id[1]; 109 | // save the card token returned here. 110 | FLW_WC_Payment_Gateway::save_card_details( $transaction_data, $this->order->get_user_id(), $order_id ); 111 | WC()->cart->empty_cart(); 112 | } else { 113 | $this->on_failure( $transaction_data ); 114 | } 115 | } 116 | 117 | /** 118 | * This is called only when a transaction failed 119 | * 120 | * @param object $transaction_data - This is the transaction data as returned from the Flutterwave payment gateway. 121 | */ 122 | public function on_failure( object $transaction_data ) { 123 | $this->order->update_status( 'failed' ); 124 | $this->order->add_order_note( esc_html__( 'The payment failed on Flutterwave', 'rave-woocommerce-payment-gateway' ) ); 125 | $customer_note = 'Your payment failed. '; 126 | $customer_note .= 'Please, try again or use another Payment Method on the modal.'; 127 | $reason = $transaction_data->processor_response ?? ' - '; 128 | 129 | $this->order->add_order_note( esc_html__( 'Reason for Failure : ', 'rave-woocommerce-payment-gateway' ) . $reason ); 130 | 131 | wc_add_notice( $customer_note, 'notice' ); 132 | } 133 | 134 | /** 135 | * This is called when a transaction is requeryed from the payment gateway 136 | * 137 | * @param string $transaction_reference - This is the transaction reference (txref) of the transaction you want to requery. 138 | * */ 139 | public function on_requery( string $transaction_reference ) { 140 | // Do something, anything!. 141 | $this->order->add_order_note( esc_html__( 'Confirming payment on Flutterwave', 'rave-woocommerce-payment-gateway' ) ); 142 | } 143 | 144 | /** 145 | * This is called a transaction requery returns with an error 146 | * 147 | * @param object $requery_response - This is the response from the payment gateway when a transaction is requeryed. 148 | * */ 149 | public function on_requery_error( $requery_response ) { 150 | // Do something, anything!. 151 | $this->order->add_order_note( esc_html__( 'An error occured while confirming payment on Flutterwave', 'rave-woocommerce-payment-gateway' ) ); 152 | $this->order->update_status( 'on-hold' ); 153 | $customer_note = 'Thank you for your order.
'; 154 | $customer_note .= 'We had an issue confirming your payment, but we have put your order on-hold. '; 155 | $customer_note .= 'Please, contact us for information regarding this order.'; 156 | $admin_note = esc_html__( 'Attention: New order has been placed on hold because we could not confirm the payment. Please, look into it.', 'rave-woocommerce-payment-gateway' ) . '
'; 157 | $admin_note .= esc_html( 'Payment Responce: ' ) . $requery_response->message; 158 | 159 | $this->order->add_order_note( $customer_note, 1 ); 160 | $this->order->add_order_note( $admin_note ); 161 | 162 | wc_add_notice( $customer_note, 'notice' ); 163 | } 164 | 165 | /** 166 | * This is called when a transaction is canceled by the user 167 | * 168 | * @param string $transaction_reference - This is the transaction reference (txref) of the transaction you want to requery. 169 | * */ 170 | public function on_cancel( string $transaction_reference ) { 171 | // Note: Sometimes a payment can be successful, before a user clicks the cancel button so proceed with caution. 172 | $this->order->add_order_note( esc_html__( 'The customer clicked on the cancel button on Checkout.', 'rave-woocommerce-payment-gateway' ) ); 173 | $this->order->update_status( 'cancelled' ); 174 | $admin_note = esc_html__( 'Attention: Customer clicked on the cancel button on the payment gateway. We have updated the order to cancelled status. ', 'rave-woocommerce-payment-gateway' ) . '
'; 175 | $admin_note .= esc_html__( 'Please, confirm from the order notes that there is no note of a successful transaction. If there is, this means that the user was debited and you either have to give value for the transaction or refund the customer.', 'rave-woocommerce-payment-gateway' ); 176 | $this->order->add_order_note( $admin_note ); 177 | } 178 | 179 | /** 180 | * This is called when a transaction doesn't return with a success or a failure response. This can be a timedout transaction on the Rave server or an abandoned transaction by the customer. 181 | * 182 | * @param string $transaction_reference - This is the transaction reference (txref) of the transaction you want to requery. 183 | * @param object $data - This is the data returned from the payment gateway. 184 | * */ 185 | public function on_timeout( string $transaction_reference, object $data ) { 186 | // Get the transaction from your DB using the transaction reference (txref) 187 | // Queue it for requery. Preferably using a queue system. The requery should be about 15 minutes after. 188 | // Ask the customer to contact your support and you should escalate this issue to the flutterwave support team. Send this as an email and as a notification on the page. just incase the page timesout or disconnects. 189 | $this->order->add_order_note( esc_html__( 'The payment didn\'t return a valid response. It could have timed out or abandoned by the customer on Flutterwave', 'rave-woocommerce-payment-gateway' ) ); 190 | $this->order->update_status( 'on-hold' ); 191 | $customer_note = 'Thank you for your order.
'; 192 | $customer_note .= 'We had an issue confirming your payment, but we have put your order on-hold. '; 193 | $customer_note .= esc_html__( 'Please, contact us for information regarding this order.', 'woocomerce-rave' ); 194 | $admin_note = esc_html__( 'Attention: New order has been placed on hold because we could not get a definite response from the payment gateway. Kindly contact the Rave support team at hi@flutterwave.com to confirm the payment.', 'rave-woocommerce-payment-gateway' ) . '
'; 195 | $admin_note .= esc_html__( 'Payment Reference: ', 'rave-woocommerce-payment-gateway' ) . $transaction_reference; 196 | 197 | $this->order->add_order_note( $customer_note, 1 ); 198 | $this->order->add_order_note( $admin_note ); 199 | 200 | wc_add_notice( $customer_note, 'notice' ); 201 | } 202 | 203 | /** 204 | * This is called when a webhook is received from the payment gateway 205 | * 206 | * @param string $event_type The type of event received. eg: charge.successful. 207 | * @param object $event_data The data sent with the event. 208 | * */ 209 | public function on_webhook( string $event_type, object $event_data ) { 210 | $status = 'pending'; 211 | // TODO: Save the event data to clients database. 212 | } 213 | } 214 | 215 | 216 | -------------------------------------------------------------------------------- /includes/class-flw-wc-payment-gateway-subscriptions.php: -------------------------------------------------------------------------------- 1 | id, array( $this, 'scheduled_subscription_payment' ), 10, 2 ); 40 | } 41 | } 42 | 43 | /** 44 | * Check if an order contains a subscription 45 | * 46 | * @param WC_Order $order The order. 47 | */ 48 | public function order_contains_subscription( WC_Order $order ): bool { 49 | return function_exists( 'wcs_order_contains_subscription' ) && ( wcs_order_contains_subscription( $order ) || wcs_order_contains_renewal( $order ) ); 50 | } 51 | 52 | /** 53 | * Process a trial subscription order with 0 total. 54 | * 55 | * @param int $order_id The order ID. 56 | */ 57 | public function process_payment( $order_id ): array { 58 | 59 | $order = wc_get_order( $order_id ); 60 | // Check for trial subscription order with 0 total. 61 | if ( $this->order_contains_subscription( $order ) && $order->get_total() === 0 ) { 62 | 63 | $order->payment_complete(); 64 | $order->add_order_note( 'This subscription has a free trial, reason for the 0 amount' ); 65 | return array( 66 | 'result' => 'success', 67 | 'redirect' => $this->get_return_url( $order ), 68 | ); 69 | 70 | } else { 71 | 72 | return parent::process_payment( $order_id ); 73 | 74 | } 75 | } 76 | 77 | /** 78 | * Process a subscription renewal. 79 | * 80 | * @param float $amount_to_charge The amount to charge. 81 | * @param WC_Order $renewal_order The order object. 82 | */ 83 | public function scheduled_subscription_payment( float $amount_to_charge, WC_Order $renewal_order ) { 84 | 85 | $response = $this->process_subscription_payment( $renewal_order, $amount_to_charge ); 86 | 87 | if ( is_wp_error( $response ) ) { 88 | $renewal_order->update_status( 'failed', sprintf( 'Rave Transaction Failed: (%s)', $response->get_error_message() ) ); 89 | } 90 | } 91 | 92 | /** 93 | * Process a subscription renewal payment. 94 | * 95 | * @param WC_Order $order The order object. 96 | * @param float $amount The amount to charge. 97 | */ 98 | public function process_subscription_payment( $order = '', $amount = 0 ) { 99 | 100 | $order_id = $order->get_id(); 101 | // get token attached for this subscription id. 102 | $auth_code = get_post_meta( $order_id, '_rave_wc_token', true ); 103 | if ( $auth_code ) { 104 | 105 | $headers = array( 106 | 'Content-Type' => 'application/json', 107 | 'Authorization' => 'Bearer ' . $this->get_secret_key(), 108 | ); 109 | 110 | $txnref = 'WC_' . $order_id . '_' . time(); 111 | $order_currency = $order->get_currency(); 112 | $first_name = $order->get_billing_first_name(); 113 | $last_name = $order->get_billing_last_name(); 114 | $email = $order->get_billing_email(); 115 | 116 | if ( strpos( $auth_code, '##' ) !== false ) { 117 | 118 | $payment_token = explode( '##', $auth_code ); 119 | $token_code = $payment_token[0]; 120 | 121 | } else { 122 | 123 | $token_code = $auth_code; 124 | 125 | } 126 | 127 | $body = array( 128 | 'token' => $token_code, 129 | 'currency' => $order_currency, 130 | 'amount' => $amount, 131 | 'email' => $email, 132 | 'firstname' => $first_name, 133 | 'lastname' => $last_name, 134 | 'tx_ref' => $txnref, 135 | ); 136 | 137 | $args = array( 138 | 'headers' => $headers, 139 | 'body' => wp_json_encode( $body ), 140 | 'timeout' => 60, 141 | ); 142 | 143 | // tokenize url. 144 | $tokenized_url = 'https://api.flutterwave.com/v3/tokenized-charges'; 145 | $request = wp_remote_post( $tokenized_url, $args ); 146 | 147 | if ( ! is_wp_error( $request ) && 200 === wp_remote_retrieve_response_code( $request ) ) { 148 | 149 | $response = json_decode( wp_remote_retrieve_body( $request ) ); 150 | $status = $response->status; 151 | $response_status = $response->data->status; 152 | $payment_currency = $response->data->currency; 153 | 154 | if ( 'success' === $status && 'successful' === $response_status && $payment_currency === $order_currency ) { 155 | $txn_ref = $response->data->tx_ref; 156 | $payment_ref = $response->data->flw_ref; 157 | $amount_charged = $response->data->charged_amount; 158 | 159 | $order->payment_complete( $order_id ); 160 | $order->add_order_note( 161 | sprintf( 162 | /* translators: 1: payment reference 2: transaction reference */ 163 | __( 'Payment via Flutterwave successful (Payment Reference: %1$s, Transaction Reference: %2$s)', 'rave-woocommerce-payment-gateway' ), 164 | $payment_ref, 165 | $txn_ref 166 | ) 167 | ); 168 | 169 | $flw_settings = get_option( 'woocommerce_' . $order->get_payment_method() . '_settings' ); 170 | 171 | if ( isset( $flw_settings['autocomplete_order'] ) && 'yes' === $flw_settings['autocomplete_order'] ) { 172 | $order->update_status( 'completed' ); 173 | } 174 | return true; 175 | } else { 176 | 177 | return new WP_Error( 'flutterwave_error', 'Flutterwave payment failed. ' . $response->message ); 178 | 179 | } 180 | } 181 | } 182 | 183 | return new WP_Error( 'flutterwave_error', 'This subscription can\'t be renewed automatically. The customer will have to login to his account to renew his subscription' ); 184 | } 185 | } 186 | 187 | 188 | -------------------------------------------------------------------------------- /includes/class-flw-wc-payment-gateway.php: -------------------------------------------------------------------------------- 1 | base_url = 'https://api.flutterwave.com'; 148 | $this->id = 'rave'; 149 | $this->icon = plugins_url( 'assets/img/rave.png', FLW_WC_PLUGIN_FILE ); 150 | $this->has_fields = false; 151 | $this->method_title = 'Flutterwave'; 152 | $this->method_description = 'Flutterwave ' . __( 'allows you to accept payment from cards and bank accounts in multiple currencies. You can also accept payment offline via USSD and POS.', 'rave-woocommerce-payment-gateway' ); 153 | 154 | $this->init_form_fields(); 155 | $this->init_settings(); 156 | 157 | $this->title = $this->get_option( 'title' ); 158 | $this->description = $this->get_option( 'description' ); 159 | $this->enabled = $this->get_option( 'enabled' ); 160 | $this->test_public_key = $this->get_option( 'test_public_key' ); 161 | $this->test_secret_key = $this->get_option( 'test_secret_key' ); 162 | $this->live_public_key = $this->get_option( 'live_public_key' ); 163 | $this->live_secret_key = $this->get_option( 'live_secret_key' ); 164 | $this->auto_complete_order = get_option( 'autocomplete_order' ); 165 | $this->go_live = $this->get_option( 'go_live' ); 166 | $this->payment_options = $this->get_option( 'payment_options' ); 167 | $this->payment_style = $this->get_option( 'payment_style' ); 168 | $this->barter = $this->get_option( 'barter' ); 169 | $this->logging_option = 'yes' === $this->get_option( 'logging_option', 'no' ); 170 | $this->country = ''; 171 | self::$log_enabled = $this->logging_option; 172 | $this->supports = array( 173 | 'products', 174 | 'tokenization', 175 | 'subscriptions', 176 | 'subscription_cancellation', 177 | 'subscription_suspension', 178 | 'subscription_reactivation', 179 | 'subscription_amount_changes', 180 | 'subscription_date_changes', 181 | 'subscription_payment_method_change', 182 | 'subscription_payment_method_change_customer', 183 | 'subscription_payment_method_change_admin', 184 | 'multiple_subscriptions', 185 | 'gateway_scheduled_payments', 186 | ); 187 | 188 | add_action( 'admin_notices', array( $this, 'admin_notices' ) ); 189 | add_action( 'woocommerce_receipt_' . $this->id, array( $this, 'receipt_page' ) ); 190 | add_action( 'woocommerce_api_flw_wc_payment_gateway', array( $this, 'flw_verify_payment' ) ); 191 | 192 | // Webhook listener/API hook. 193 | add_action( 'woocommerce_api_flw_wc_payment_webhook', array( $this, 'flutterwave_webhooks' ) ); 194 | 195 | if ( is_admin() ) { 196 | add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) ); 197 | } 198 | 199 | $this->public_key = $this->test_public_key; 200 | $this->secret_key = $this->test_secret_key; 201 | 202 | if ( 'yes' === $this->go_live ) { 203 | $this->public_key = $this->live_public_key; 204 | $this->secret_key = $this->live_secret_key; 205 | } 206 | 207 | $this->sdk = new FlwSdk( $this->secret_key, self::$log_enabled ); 208 | 209 | add_action( 'wp_enqueue_scripts', array( $this, 'payment_scripts' ) ); 210 | 211 | } 212 | 213 | /** 214 | * Get Secret Key 215 | * 216 | * @return string 217 | */ 218 | public function get_secret_key(): string { 219 | return $this->secret_key; 220 | } 221 | 222 | /** 223 | * WooCommerce admin settings override. 224 | */ 225 | public function admin_options() { 226 | ?> 227 |

228 | 229 | 230 | 233 | 238 | 239 | generate_settings_html(); 241 | ?> 242 |
231 | 232 | 234 |

235 |

api_request_url( 'Flw_WC_Payment_Webhook' ) ); ?>
Flutterwave Account 236 |

237 |
243 | form_fields = array( 254 | 255 | 'enabled' => array( 256 | 'title' => __( 'Enable/Disable', 'rave-woocommerce-payment-gateway' ), 257 | 'label' => __( 'Enable Flutterwave', 'rave-woocommerce-payment-gateway' ), 258 | 'type' => 'checkbox', 259 | 'description' => __( 'Enable Flutterwave as a payment option on the checkout page', 'rave-woocommerce-payment-gateway' ), 260 | 'default' => 'no', 261 | 'desc_tip' => true, 262 | ), 263 | 'secret_hash' => array( 264 | 'title' => __( 'Enter Secret Hash', 'rave-woocommerce-payment-gateway' ), 265 | 'type' => 'text', 266 | 'description' => __( 'Please change from default hash and ensure that SECRET HASH is the same with the one on your Flutterwave dashboard', 'rave-woocommerce-payment-gateway' ), 267 | 'default' => hash( 'sha256', 'Rave-Secret-Hash' ), 268 | ), 269 | 'title' => array( 270 | 'title' => __( 'Payment method title', 'rave-woocommerce-payment-gateway' ), 271 | 'type' => 'text', 272 | 'description' => __( 'Optional', 'rave-woocommerce-payment-gateway' ), 273 | 'default' => 'Flutterwave', 274 | ), 275 | 'description' => array( 276 | 'title' => __( 'Payment method description', 'rave-woocommerce-payment-gateway' ), 277 | 'type' => 'text', 278 | 'description' => __( 'Optional', 'rave-woocommerce-payment-gateway' ), 279 | 'default' => 'Powered by Flutterwave: Accepts Mastercard, Visa, Verve, Discover, AMEX, Diners Club and Union Pay.', 280 | ), 281 | 'test_public_key' => array( 282 | 'title' => __( 'Test Public Key', 'rave-woocommerce-payment-gateway' ), 283 | 'type' => 'text', 284 | 'description' => __( 'Required! Enter your Flutterwave test public key here', 'rave-woocommerce-payment-gateway' ), 285 | 'default' => '', 286 | ), 287 | 'test_secret_key' => array( 288 | 'title' => __( 'Test Secret Key', 'rave-woocommerce-payment-gateway' ), 289 | 'type' => 'password', 290 | 'description' => __( 'Required! Enter your Flutterwave test secret key here', 'rave-woocommerce-payment-gateway' ), 291 | 'default' => '', 292 | ), 293 | 'live_public_key' => array( 294 | 'title' => __( 'Live Public Key', 'rave-woocommerce-payment-gateway' ), 295 | 'type' => 'text', 296 | 'description' => __( 'Required! Enter your Flutterwave live public key here', 'rave-woocommerce-payment-gateway' ), 297 | 'default' => '', 298 | ), 299 | 'live_secret_key' => array( 300 | 'title' => __( 'Live Secret Key', 'rave-woocommerce-payment-gateway' ), 301 | 'type' => 'password', 302 | 'description' => __( 'Required! Enter your Flutterwave live secret key here', 'rave-woocommerce-payment-gateway' ), 303 | 'default' => '', 304 | ), 305 | 'payment_style' => array( 306 | 'title' => __( 'Payment Style on checkout', 'rave-woocommerce-payment-gateway' ), 307 | 'type' => 'select', 308 | 'description' => __( 'Optional - Choice of payment style to use. Either inline or redirect. (Default: inline)', 'rave-woocommerce-payment-gateway' ), 309 | 'options' => array( 310 | 'inline' => esc_html_x( 'Popup(Keep payment experience on the website)', 'payment_style', 'rave-woocommerce-payment-gateway' ), 311 | 'redirect' => esc_html_x( 'Redirect', 'payment_style', 'rave-woocommerce-payment-gateway' ), 312 | ), 313 | 'default' => 'inline', 314 | ), 315 | 'autocomplete_order' => array( 316 | 'title' => __( 'Autocomplete Order After Payment', 'rave-woocommerce-payment-gateway' ), 317 | 'label' => __( 'Autocomplete Order', 'rave-woocommerce-payment-gateway' ), 318 | 'type' => 'checkbox', 319 | 'class' => 'wc-flw-autocomplete-order', 320 | 'description' => __( 'If enabled, the order will be marked as complete after successful payment', 'rave-woocommerce-payment-gateway' ), 321 | 'default' => 'no', 322 | 'desc_tip' => true, 323 | ), 324 | 'payment_options' => array( 325 | 'title' => __( 'Payment Options', 'rave-woocommerce-payment-gateway' ), 326 | 'type' => 'select', 327 | 'description' => __( 'Optional - Choice of payment method to use. Card, Account etc.', 'rave-woocommerce-payment-gateway' ), 328 | 'options' => array( 329 | 'card,ussd,account,mpesa,banktransfer,mobilemoneyghana,mobilemoneyfranco,mobilemoneyrwanda, mobilemoneyzambia,mobilemoneyuganda,ussd' => esc_html_x( 'All', 'payment_options', 'rave-woocommerce-payment-gateway' ), 330 | 'card' => esc_html_x( 'Card Only', 'payment_options', 'rave-woocommerce-payment-gateway' ), 331 | 'account' => esc_html_x( 'Account Only', 'payment_options', 'rave-woocommerce-payment-gateway' ), 332 | 'ussd' => esc_html_x( 'USSD Only', 'payment_options', 'rave-woocommerce-payment-gateway' ), 333 | 'qr' => esc_html_x( 'QR Only', 'payment_options', 'rave-woocommerce-payment-gateway' ), 334 | 'mpesa' => esc_html_x( 'Mpesa Only', 'payment_options', 'rave-woocommerce-payment-gateway' ), 335 | 'mobilemoneyghana' => esc_html_x( 'Ghana MM Only', 'payment_options', 'rave-woocommerce-payment-gateway' ), 336 | 'mobilemoneyrwanda' => esc_html_x( 'Rwanda MM Only', 'payment_options', 'rave-woocommerce-payment-gateway' ), 337 | 'mobilemoneyzambia' => esc_html_x( 'Zambia MM Only', 'payment_options', 'rave-woocommerce-payment-gateway' ), 338 | 'mobilemoneytanzania' => esc_html_x( 'Tanzania MM Only', 'payment_options', 'rave-woocommerce-payment-gateway' ), 339 | ), 340 | 'default' => 'card,ussd,account,mpesa,banktransfer,mobilemoneyghana,mobilemoneyfranco,mobilemoneyrwanda, mobilemoneyzambia,mobilemoneyuganda,ussd', 341 | ), 342 | 'go_live' => array( 343 | 'title' => __( 'Mode', 'rave-woocommerce-payment-gateway' ), 344 | 'label' => __( 'Live mode', 'rave-woocommerce-payment-gateway' ), 345 | 'type' => 'checkbox', 346 | 'description' => __( 'Check this box if you\'re using your live keys.', 'rave-woocommerce-payment-gateway' ), 347 | 'default' => 'no', 348 | 'desc_tip' => true, 349 | ), 350 | 'logging_option' => array( 351 | 'title' => __( 'Disable Logging', 'rave-woocommerce-payment-gateway' ), 352 | 'label' => __( 'Disable Logging', 'rave-woocommerce-payment-gateway' ), 353 | 'type' => 'checkbox', 354 | 'description' => __( 'Check this box if you\'re disabling logging.', 'rave-woocommerce-payment-gateway' ), 355 | 'default' => 'no', 356 | 'desc_tip' => true, 357 | ), 358 | 'barter' => array( 359 | 'title' => __( 'Disable Barter', 'rave-woocommerce-payment-gateway' ), 360 | 'label' => __( 'Disable Barter', 'rave-woocommerce-payment-gateway' ), 361 | 'type' => 'checkbox', 362 | 'description' => __( 'Check the box if you want to disable barter.', 'rave-woocommerce-payment-gateway' ), 363 | 'default' => 'no', 364 | 'desc_tip' => true, 365 | ), 366 | 367 | ); 368 | 369 | } 370 | 371 | /** 372 | * Order id 373 | * 374 | * @param int $order_id Order id. 375 | * 376 | * @return array|void 377 | */ 378 | public function process_payment( $order_id ) { 379 | // For Redirect Checkout. 380 | if ( 'redirect' === $this->payment_style ) { 381 | return $this->process_redirect_payments( $order_id ); 382 | } 383 | 384 | // For inline Checkout. 385 | $order = wc_get_order( $order_id ); 386 | 387 | $custom_nonce = wp_create_nonce(); 388 | 389 | return array( 390 | 'result' => 'success', 391 | 'redirect' => $order->get_checkout_payment_url( true ) . "&_wpnonce=$custom_nonce", 392 | ); 393 | } 394 | 395 | /** 396 | * Order id 397 | * 398 | * @param int $order_id Order id. 399 | * 400 | * @return array|void 401 | */ 402 | public function process_redirect_payments( $order_id ) { 403 | include_once dirname( __FILE__ ) . '/client/class-flw-wc-payment-gateway-request.php'; 404 | 405 | $order = wc_get_order( $order_id ); 406 | 407 | try { 408 | $flutterwave_request = ( new FLW_WC_Payment_Gateway_Request() )->get_prepared_payload( $order, $this->get_secret_key() ); 409 | } catch ( \InvalidArgumentException $flw_e ) { 410 | wc_add_notice( $flw_e, 'error' ); 411 | // redirect user to check out page. 412 | return array( 413 | 'result' => 'fail', 414 | 'redirect' => $order->get_checkout_payment_url( true ), 415 | ); 416 | } 417 | 418 | $flutterwave_request['payment_options'] = $this->payment_options; 419 | $custom_nonce = wp_create_nonce(); 420 | $flutterwave_request['redirect_url'] = $flutterwave_request['redirect_url'] . '&_wpnonce=' . $custom_nonce; 421 | $sdk = $this->sdk->set_event_handler( new FlwEventHandler( $order ) ); 422 | 423 | $response = $sdk->get_client()->request( $this->sdk::$standard_inline_endpoint, 'POST', $flutterwave_request ); 424 | if ( ! is_wp_error( $response ) ) { 425 | $response = json_decode( $response['body'] ); 426 | return array( 427 | 'result' => 'success', 428 | 'redirect' => $response->data->link, 429 | ); 430 | } else { 431 | wc_add_notice( 'Unable to Connect to Flutterwave.', 'error' ); 432 | // redirect user to check out page. 433 | return array( 434 | 'result' => 'fail', 435 | 'redirect' => $order->get_checkout_payment_url( true ), 436 | ); 437 | } 438 | } 439 | 440 | /** 441 | * Handles admin notices 442 | * 443 | * @return void 444 | */ 445 | public function admin_notices(): void { 446 | 447 | if ( 'yes' === $this->enabled ) { 448 | 449 | if ( empty( $this->public_key ) || empty( $this->secret_key ) ) { 450 | 451 | $message = sprintf( 452 | /* translators: %s: url */ 453 | __( 'Flutterwave is enabled, but the API keys are not set. Please set your Flutterwave API keys to be able to accept payments.', 'rave-woocommerce-payment-gateway' ), 454 | esc_url( admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=wc_flw_payment_gateway' ) ) 455 | ); 456 | } 457 | } 458 | 459 | } 460 | 461 | /** 462 | * Checkout receipt page 463 | * 464 | * @param int $order_id Order id. 465 | * 466 | * @return void 467 | */ 468 | public function receipt_page( int $order_id ) { 469 | $order = wc_get_order( $order_id ); 470 | } 471 | 472 | /** 473 | * Loads (enqueue) static files (js & css) for the checkout page 474 | * 475 | * @return void 476 | */ 477 | public function payment_scripts() { 478 | 479 | // Load only on checkout page. 480 | if ( ! is_checkout_pay_page() && ! isset( $_GET['key'] ) ) { 481 | return; 482 | } 483 | 484 | if ( ! isset( $_REQUEST['_wpnonce'] ) ) { 485 | return; 486 | } 487 | 488 | $expiry_message = sprintf( 489 | /* translators: %s: shop cart url */ 490 | __( 'Sorry, your session has expired. Return to shop', 'rave-woocommerce-payment-gateway' ), 491 | esc_url( wc_get_page_permalink( 'shop' ) ) 492 | ); 493 | 494 | $nonce_value = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ); 495 | 496 | $order_key = urldecode( sanitize_text_field( wp_unslash( $_GET['key'] ) ) ); 497 | $order_id = absint( get_query_var( 'order-pay' ) ); 498 | 499 | $order = wc_get_order( $order_id ); 500 | 501 | if ( empty( $nonce_value ) || ! wp_verify_nonce( $nonce_value ) ) { 502 | 503 | WC()->session->set( 'refresh_totals', true ); 504 | wc_add_notice( __( 'We were unable to process your order, please try again.', 'rave-woocommerce-payment-gateway' ) ); 505 | wp_safe_redirect( $order->get_cancel_order_url() ); 506 | return; 507 | } 508 | 509 | if ( $this->id !== $order->get_payment_method() ) { 510 | return; 511 | } 512 | 513 | wp_enqueue_script( 'jquery' ); 514 | 515 | wp_enqueue_script( 'flutterwave', $this->sdk::$checkout_url, array( 'jquery' ), FLW_WC_VERSION, false ); 516 | 517 | $checkout_frontend_script = 'assets/js/checkout.js'; 518 | if ( 'yes' === $this->go_live ) { 519 | $checkout_frontend_script = 'assets/js/checkout.min.js'; 520 | } 521 | 522 | wp_enqueue_script( 'flutterwave_js', plugins_url( $checkout_frontend_script, FLW_WC_PLUGIN_FILE ), array( 'jquery', 'flutterwave' ), FLW_WC_VERSION, false ); 523 | 524 | $payment_args = array(); 525 | 526 | if ( is_checkout_pay_page() && get_query_var( 'order-pay' ) ) { 527 | 528 | $email = $order->get_billing_email(); 529 | $amount = $order->get_total(); 530 | $txnref = 'WOOC_' . $order_id . '_' . time(); 531 | $the_order_id = $order->get_id(); 532 | $the_order_key = $order->get_order_key(); 533 | $currency = $order->get_currency(); 534 | $custom_nonce = wp_create_nonce(); 535 | $redirect_url = ''; 536 | 537 | $flutterwave_woo_url = WC()->api_request_url( 'FLW_WC_Payment_Gateway' ); 538 | 539 | // Parse the base URL to check for existing query parameters. 540 | $url_parts = wp_parse_url( $flutterwave_woo_url ); 541 | 542 | // If the base URL already has query parameters, merge them with new ones. 543 | if ( isset( $url_parts['query'] ) ) { 544 | // Convert the query string to an array. 545 | parse_str( $url_parts['query'], $query_array ); 546 | 547 | // Add the new parameters to the existing query array. 548 | $query_array['order_id'] = $order_id; 549 | 550 | // Rebuild the query string with the new parameters. 551 | $new_query_string = http_build_query( $query_array ); 552 | 553 | // Rebuild the final URL with the new query string. 554 | $redirect_url = $url_parts['scheme'] . '://' . $url_parts['host'] . $url_parts['path'] . '?' . $new_query_string; 555 | } else { 556 | // If no existing query parameters, simply append the new ones. 557 | $redirect_url = add_query_arg( 558 | array( 559 | 'order_id' => $order_id, 560 | '_wpnonce' => $custom_nonce, 561 | ), 562 | $flutterwave_woo_url 563 | ); 564 | } 565 | 566 | if ( $the_order_id === $order_id && $the_order_key === $order_key ) { 567 | $payment_args['email'] = $email; 568 | $payment_args['amount'] = $amount; 569 | $payment_args['tx_ref'] = $txnref; 570 | $payment_args['currency'] = $currency; 571 | $payment_args['public_key'] = $this->public_key; 572 | $payment_args['redirect_url'] = $redirect_url; 573 | $payment_args['payment_options'] = $this->payment_options; 574 | $payment_args['phone_number'] = $order->get_billing_phone(); 575 | $payment_args['first_name'] = $order->get_billing_first_name(); 576 | $payment_args['last_name'] = $order->get_billing_last_name(); 577 | $payment_args['consumer_id'] = $order->get_customer_id(); 578 | $payment_args['ip_address'] = $order->get_customer_ip_address(); 579 | $payment_args['title'] = esc_html__( 'Order Payment', 'rave-woocommerce-payment-gateway' ); 580 | $payment_args['description'] = 'Payment for Order: ' . $order_id; 581 | $payment_args['logo'] = wp_get_attachment_url( get_theme_mod( 'custom_logo' ) ); 582 | $payment_args['checkout_url'] = wc_get_checkout_url(); 583 | $payment_args['cancel_url'] = $order->get_cancel_order_url(); 584 | } 585 | update_post_meta( $order_id, '_flw_payment_txn_ref', $txnref ); 586 | } 587 | wp_localize_script( 'flutterwave_js', 'flw_payment_args', $payment_args ); 588 | } 589 | 590 | /** 591 | * Verify payment made on the checkout page. 592 | * 593 | * @return void 594 | */ 595 | public function flw_verify_payment() { 596 | $public_key = $this->public_key; 597 | $secret_key = $this->secret_key; 598 | $logging_option = $this->logging_option; 599 | $sdk = $this->sdk; 600 | 601 | if ( ! isset( $_GET['_wpnonce'] ) && ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) ) ) { 602 | if ( isset( $_GET['status'] ) && 'cancelled' === $_GET['status'] ) { 603 | $sdk->set_event_handler( new FlwEventHandler( $order ) )->cancel_payment( $txn_ref ); 604 | header( 'Location: ' . wc_get_cart_url() ); 605 | die(); 606 | } 607 | } 608 | 609 | if ( isset( $_POST['tx_ref'] ) || isset( $_GET['tx_ref'] ) ) { 610 | $txn_ref = urldecode( sanitize_text_field( wp_unslash( $_GET['tx_ref'] ) ) ) ?? sanitize_text_field( wp_unslash( $_POST['tx_ref'] ) ); 611 | $o = explode( '_', sanitize_text_field( $txn_ref ) ); 612 | $order_id = intval( $o[1] ); 613 | $order = wc_get_order( $order_id ); 614 | 615 | if ( isset( $_GET['status'] ) && 'cancelled' === $_GET['status'] ) { 616 | $sdk->set_event_handler( new FlwEventHandler( $order ) )->cancel_payment( $txn_ref ); 617 | header( 'Location: ' . wc_get_cart_url() ); 618 | die(); 619 | } 620 | 621 | $sdk->set_event_handler( new FlwEventHandler( $order ) )->requery_transaction( $txn_ref ); 622 | 623 | $redirect_url = $this->get_return_url( $order ); 624 | header( 'Location: ' . $redirect_url ); 625 | die(); 626 | } 627 | } 628 | 629 | /** 630 | * Process Webhook. 631 | */ 632 | public function flutterwave_webhooks() { 633 | $public_key = $this->public_key; 634 | $secret_key = $this->secret_key; 635 | $logging_option = $this->logging_option; 636 | $sdk = $this->sdk; 637 | 638 | $event = file_get_contents( 'php://input' ); 639 | 640 | if ( ! isset( $_SERVER['HTTP_VERIF_HASH'] ) ) { 641 | // redirect to the home page. 642 | wp_safe_redirect( home_url() ); 643 | exit(); 644 | } 645 | 646 | // retrieve the signature sent in the request header's. 647 | $signature = ( sanitize_text_field( wp_unslash( $_SERVER['HTTP_VERIF_HASH'] ) ) ?? '' ); 648 | 649 | if ( ! $signature ) { 650 | // redirect to the home page. 651 | wp_safe_redirect( home_url() ); 652 | exit(); 653 | } 654 | 655 | $local_signature = $this->get_option( 'secret_hash' ); 656 | 657 | if ( $signature !== $local_signature ) { 658 | wp_send_json( 659 | array( 660 | 'status' => 'error', 661 | 'message' => 'Access Denied Hash does not match', 662 | ), 663 | WP_Http::UNAUTHORIZED 664 | ); 665 | } 666 | 667 | http_response_code( 200 ); 668 | $event = json_decode( $event ); 669 | 670 | if ( empty( $event->event ) && empty( $event->data ) ) { 671 | wp_send_json( 672 | array( 673 | 'status' => 'error', 674 | 'message' => 'Webhook sent is deformed. missing data object.', 675 | ), 676 | WP_Http::NO_CONTENT 677 | ); 678 | } 679 | 680 | if ( 'test_assess' === $event->event ) { 681 | wp_send_json( 682 | array( 683 | 'status' => 'success', 684 | 'message' => 'Webhook Test Successful. handler is accessible', 685 | ), 686 | WP_Http::OK 687 | ); 688 | } 689 | 690 | if ( 'charge.completed' === $event->event ) { 691 | sleep( 6 ); 692 | 693 | $event_type = $event->event; 694 | $event_data = $event->data; 695 | 696 | // check if transaction reference starts with WOOC on hpos enabled. 697 | if ( substr( $event_data->tx_ref, 0, 4 ) !== 'WOOC' ) { 698 | wp_send_json( 699 | array( 700 | 'status' => 'failed', 701 | 'message' => 'The transaction reference ' . $event_data->tx_ref . ' is not a Flutterwave WooCommerce Generated transaction', 702 | ), 703 | WP_Http::OK 704 | ); 705 | } 706 | 707 | $txn_ref = sanitize_text_field( $event_data->tx_ref ); 708 | $o = explode( '_', $txn_ref ); 709 | $order_id = intval( $o[1] ); 710 | $order = wc_get_order( $order_id ); 711 | 712 | if ( ! $order ) { 713 | wp_send_json( 714 | array( 715 | 'status' => 'error', 716 | 'message' => 'Invalid Reference', 717 | 'reason' => 'Order does not belong to store', 718 | ), 719 | WP_Http::BAD_REQUEST 720 | ); 721 | } 722 | 723 | // get order status. 724 | $current_order_status = $order->get_status(); 725 | 726 | /** 727 | * Fires after the webhook has been processed. 728 | * 729 | * @param string $event The webhook event. 730 | * @since 2.3.0 731 | */ 732 | do_action( 'flw_webhook_after_action', wp_json_encode( $event, true ) ); 733 | // TODO: Handle Checkout draft status for WooCommerce Blocks users. 734 | $statuses_in_question = array( 'pending', 'on-hold' ); 735 | if ( 'failed' === $current_order_status ) { 736 | // NOTE: customer must have tried to make payment again in the same session. 737 | // TODO: add timeline to order notes to brief merchant as to why the order status changed. 738 | $statuses_in_question[] = 'failed'; 739 | } 740 | if ( ! in_array( $current_order_status, $statuses_in_question, true ) ) { 741 | wp_send_json( 742 | array( 743 | 'status' => 'error', 744 | 'message' => 'Order already processed', 745 | ), 746 | WP_Http::CREATED 747 | ); 748 | } 749 | 750 | $sdk->set_event_handler( new FlwEventHandler( $order ) )->webhook_verify( $event_type, $event_data ); 751 | wp_send_json( 752 | array( 753 | 'status' => 'success', 754 | 'message' => 'Order Processed Successfully', 755 | ), 756 | WP_Http::CREATED 757 | ); 758 | } 759 | 760 | wp_safe_redirect( home_url() ); 761 | exit(); 762 | } 763 | 764 | /** 765 | * Save Customer Card Details 766 | * 767 | * @param object $rave_response The response from Rave. 768 | * @param int $user_id The user ID. 769 | * @param string $order_id The order ID. 770 | */ 771 | public static function save_card_details( object $rave_response, int $user_id, string $order_id ) { 772 | 773 | $token_code = $rave_response->card->card_tokens[0]->embedtoken ?? ''; 774 | 775 | // save payment token to the order. 776 | self::save_subscription_payment_token( $order_id, $token_code ); 777 | } 778 | 779 | /** 780 | * Save payment token to the order for automatic renewal for further subscription payment 781 | * 782 | * @param mixed|string $order_id The order ID. 783 | * @param string $payment_token The payment token. 784 | */ 785 | public static function save_subscription_payment_token( string $order_id, string $payment_token ) { 786 | 787 | if ( ! function_exists( 'wcs_order_contains_subscription' ) ) { 788 | return; 789 | } 790 | 791 | if ( WC_Subscriptions_Order::order_contains_subscription( $order_id ) && ! empty( $payment_token ) ) { 792 | 793 | // Also store it on the subscriptions being purchased or paid for in the order. 794 | if ( function_exists( 'wcs_order_contains_subscription' ) && wcs_order_contains_subscription( $order_id ) ) { 795 | 796 | $subscriptions = wcs_get_subscriptions_for_order( $order_id ); 797 | 798 | } elseif ( function_exists( 'wcs_order_contains_renewal' ) && wcs_order_contains_renewal( $order_id ) ) { 799 | 800 | $subscriptions = wcs_get_subscriptions_for_renewal_order( $order_id ); 801 | 802 | } else { 803 | 804 | $subscriptions = array(); 805 | 806 | } 807 | 808 | foreach ( $subscriptions as $subscription ) { 809 | 810 | $subscription_id = $subscription->get_id(); 811 | 812 | update_post_meta( $subscription_id, '_rave_wc_token', $payment_token ); 813 | 814 | } 815 | } 816 | } 817 | } 818 | 819 | 820 | 821 | -------------------------------------------------------------------------------- /includes/client/class-flw-wc-payment-gateway-client.php: -------------------------------------------------------------------------------- 1 | logger = \wc_get_logger(); 69 | $this->secret_key = $secret_key; 70 | $this->setup(); 71 | if ( $is_logging_enabled ) { 72 | $this->logger->log( 'info', 'Logging enabled', array( 'source' => 'flutterwave' ) ); 73 | } 74 | } 75 | 76 | /** 77 | * Set up the headers for the request. 78 | * 79 | * @return void - Sets up the headers for the request. 80 | */ 81 | private function setup() { 82 | $this->headers = array( 83 | 'Content-Type' => 'application/json', 84 | 'Authorization' => 'Bearer ' . $this->secret_key, 85 | ); 86 | } 87 | 88 | /** 89 | * Request to the Flutterwave API. 90 | * 91 | * @param string $url - The url to make the request to. 92 | * @param string $method - The method to use for the request. 93 | * @param array|null $body - The body of the request. 94 | * 95 | * @return array|\WP_Error - The response from the request. 96 | */ 97 | public function request( $url, string $method = 'GET', $body = null ) { 98 | $body = wp_json_encode( $body, JSON_UNESCAPED_SLASHES ); 99 | 100 | $args = array( 101 | 'method' => $method, 102 | 'headers' => $this->headers, 103 | ); 104 | 105 | if ( 'GET' !== $method ) { 106 | $args['body'] = $body; 107 | } 108 | 109 | return wp_safe_remote_request( $url, $args ); // return the body of the response json. 110 | } 111 | 112 | /** 113 | * Handle the error from the request. 114 | * 115 | * @param \WP_Error $response - The response from the request. 116 | * 117 | * @return void - Logs the error and adds a notice to the cart. 118 | */ 119 | public function handle_error( \WP_Error $response ) { 120 | if ( is_wp_error( $response ) ) { 121 | $this->logger->log( 'error', $response->get_error_message(), array( 'source' => 'flutterwave' ) ); 122 | wc_add_notice( 'Unable to connect to Flutterwave. Please Try Again', 'error' ); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /includes/client/class-flw-wc-payment-gateway-request.php: -------------------------------------------------------------------------------- 1 | notify_url = WC()->api_request_url( 'FLW_WC_Payment_Gateway' ); 50 | } 51 | 52 | /** 53 | * Generate a request hash for the request. 54 | * 55 | * @param array $data This is the request data. 56 | * 57 | * @return string 58 | */ 59 | private function generate_checkout_hash( array $data ): string { 60 | // format: sha256(amount+currency+customeremail+txref+sha256(secretkey)). 61 | $complete_hash = ''; 62 | foreach ( $data as $key => $value ) { 63 | if ( 'secret_key' === $key ) { 64 | $complete_hash .= hash( 'sha256', $value ); 65 | } else { 66 | $complete_hash .= $value; 67 | } 68 | } 69 | return hash( 'sha256', $complete_hash ); 70 | } 71 | 72 | /** 73 | * This method prepares the payload for the request 74 | * 75 | * @param \WC_Order $order Order object. 76 | * @param string $secret_key APi key. 77 | * @param bool $testing is ci. 78 | * @throws \InvalidArgumentException When the secret key is not spplied. 79 | * 80 | * @return array 81 | */ 82 | public function get_prepared_payload( \WC_Order $order, string $secret_key, bool $testing = false ): array { 83 | $order_id = $order->get_id(); 84 | $txnref = 'WOOC_' . $order_id . '_' . time(); 85 | $amount = (float) $order->get_total(); 86 | $currency = $order->get_currency(); 87 | $email = $order->get_billing_email(); 88 | 89 | if ( $testing ) { 90 | $txnref = 'WOOC_' . $order_id . '_TEST'; 91 | } 92 | 93 | if ( empty( $secret_key ) ) { 94 | // let admin know that the secret key is not set. 95 | throw new \InvalidArgumentException( 'This Payment Method is current unavailable as Administrator is yet to Configure it.Please contact Administrator for more information.' ); 96 | } 97 | 98 | $data_to_hash = array( 99 | 'amount' => $amount, 100 | 'currency' => $currency, 101 | 'email' => $email, 102 | 'tx_ref' => $txnref, 103 | 'secret_key' => $secret_key, 104 | ); 105 | 106 | $checkout_hash = $this->generate_checkout_hash( $data_to_hash ); 107 | 108 | // Parse the base URL to check for existing query parameters. 109 | $url_parts = wp_parse_url( $this->notify_url ); 110 | 111 | // If the base URL already has query parameters, merge them with new ones. 112 | if ( isset( $url_parts['query'] ) ) { 113 | // Convert the query string to an array. 114 | parse_str( $url_parts['query'], $query_array ); 115 | 116 | // Add the new parameters to the existing query array. 117 | $query_array['order_id'] = $order_id; 118 | 119 | // Rebuild the query string with the new parameters. 120 | $new_query_string = http_build_query( $query_array ); 121 | 122 | // Rebuild the final URL with the new query string. 123 | $callback_url = $url_parts['scheme'] . '://' . $url_parts['host'] . $url_parts['path'] . '?' . $new_query_string; 124 | } else { 125 | // If no existing query parameters, simply append the new ones. 126 | $callback_url = add_query_arg( 127 | array( 128 | 'order_id' => $order_id, 129 | ), 130 | $this->notify_url 131 | ); 132 | } 133 | 134 | return array( 135 | 'amount' => $amount, 136 | 'tx_ref' => $txnref, 137 | 'currency' => $currency, 138 | 'payment_options' => 'card', 139 | 'redirect_url' => $callback_url, 140 | 'payload_hash' => $checkout_hash, 141 | 'customer' => array( 142 | 'email' => $email, 143 | 'phone_number' => $order->get_billing_phone(), 144 | 'name' => $order->get_billing_first_name() . ' ' . $order->get_billing_last_name(), 145 | ), 146 | 'meta' => array( 147 | 'consumer_id' => $order->get_customer_id(), 148 | 'ip_address' => $order->get_customer_ip_address(), 149 | 'user-agent' => $order->get_customer_user_agent(), 150 | ), 151 | 'customizations' => array( 152 | 'title' => get_bloginfo( 'name' ), 153 | 'description' => __( 'Payment for order ', 'rave-woocommerce-payment-gateway' ) . $order->get_order_number(), 154 | ), 155 | ); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /includes/client/class-flw-wc-payment-gateway-sdk.php: -------------------------------------------------------------------------------- 1 | client = new FLW_WC_Payment_Gateway_Client( $secret_key, $log_enabled ); 89 | $this->logger = wc_get_logger(); 90 | return $this; 91 | } 92 | 93 | /** 94 | * Clone method. 95 | * Prevents cloning of this class. 96 | * 97 | * @since 2.3.2 98 | * 99 | * @return null The clone method. 100 | */ 101 | public function __clone() { 102 | return null; 103 | } 104 | 105 | /** 106 | * Get the client. 107 | * Returns the client. 108 | * 109 | * @since 2.3.2 110 | * 111 | * @return FLW_WC_Payment_Gateway_Client The client. 112 | */ 113 | public function get_client(): FLW_WC_Payment_Gateway_Client { 114 | return $this->client; 115 | } 116 | 117 | /** 118 | * Set the event handler. 119 | * 120 | * @param FLW_WC_Payment_Gateway_Event_Handler_Interface $event_handler The event handler. 121 | * 122 | * @return FLW_WC_Payment_Gateway_Sdk The sdk. 123 | */ 124 | public function set_event_handler( FLW_WC_Payment_Gateway_Event_Handler_Interface $event_handler ): FLW_WC_Payment_Gateway_Sdk { 125 | $this->event_handler = $event_handler; 126 | 127 | return $this; 128 | } 129 | 130 | /** 131 | * Prepares the html to be rendered. 132 | * 133 | * @param array $clean_data This is the data that will be sent to the server. 134 | * @param int $order_id The order id. 135 | * 136 | * @return string The html to be rendered. 137 | */ 138 | private function prepare_html( array $clean_data, int $order_id ): string { 139 | // TODO: enqueue inline checkout script. 140 | 141 | $html_array = array( 142 | '', 143 | '', 144 | ' ', 145 | ' ', 178 | '', 179 | '', 180 | ); 181 | 182 | return implode( '', $html_array ); 183 | } 184 | 185 | /** 186 | * Render the modal. 187 | * 188 | * @param array $data This is the data to be sent to the payment gateway. 189 | * @param int $order_id This is the order id. 190 | * 191 | * @return false|string The json encoded data. 192 | */ 193 | public function render_modal( array $data, int $order_id ) { 194 | $clean_data = $data; 195 | $html = $this->prepare_html( $clean_data, $order_id ); 196 | $this->logger->notice( 'Loading Payment Modal for order:' . $order_id ); 197 | echo wp_kses_post( $html ); 198 | return wp_json_encode( $clean_data ); 199 | } 200 | 201 | /** 202 | * Transaction Reference. 203 | * 204 | * @param string $tx_ref This is the unique reference for the transaction. 205 | * 206 | * @return void The requery transaction. 207 | */ 208 | public function requery_transaction( string $tx_ref ) { 209 | $this->requery_count ++; 210 | $this->logger->notice( 'Requerying Transaction....' . $tx_ref ); 211 | 212 | if ( isset( $this->event_handler ) ) { 213 | $this->event_handler->on_requery( $tx_ref ); 214 | } 215 | 216 | $url = $this->client::API_BASE_URL . '/' . $this->client::API_VERSION . '/transactions/verify_by_reference?tx_ref=' . $tx_ref; 217 | 218 | $response = $this->client->request( $url ); 219 | 220 | if ( ! is_wp_error( $response ) ) { 221 | $response = json_decode( $response['body'] ); 222 | if ( 'success' === $response->status ) { 223 | $this->logger->notice( 'Transaction Requeried Successfully' ); 224 | 225 | if ( isset( $this->event_handler ) ) { 226 | $this->event_handler->on_successful( $response->data ); 227 | } 228 | } else { 229 | $this->logger->notice( 'Transaction Requeried Failed' ); 230 | $this->event_handler->on_failure( $response ); 231 | } 232 | } else { 233 | // TODO: handle request errors. 234 | $this->logger->notice( 'Transaction Requeried Failed. Awaiting Webhook Verification...' ); 235 | } 236 | 237 | } 238 | 239 | /** 240 | * Webhook Verification. 241 | * 242 | * @param string $event_type The event type. 243 | * @param object $event_data The event data. 244 | */ 245 | public function webhook_verify( string $event_type, object $event_data ) { 246 | $this->logger->notice( 'Webhook Verification Started' ); 247 | $this->logger->notice( 'Event Type: ' . $event_type ); 248 | 249 | $event_type = strtolower( $event_type ); 250 | 251 | if ( isset( $this->event_handler ) ) { 252 | $this->event_handler->on_webhook( $event_type, $event_data ); 253 | } 254 | 255 | if ( method_exists( $this, 'requery_transaction' ) ) { 256 | $this->requery_transaction( $event_data->tx_ref ); 257 | } else { 258 | $this->logger->notice( 'Webhook Verification Failed' ); 259 | } 260 | 261 | } 262 | 263 | /** 264 | * Cancel Payment. 265 | * 266 | * @param string $tx_ref This is the transaction ref to be cancelled. 267 | * 268 | * @return void 269 | */ 270 | public function cancel_payment( string $tx_ref ) { 271 | if ( isset( $this->event_handler ) ) { 272 | $this->event_handler->on_cancel( $tx_ref ); 273 | } 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /includes/contracts/class-flw-wc-payment-gateway-event-handler-interface.php: -------------------------------------------------------------------------------- 1 |

' . sprintf( esc_html__( 'Flutterwave WooCommerce requires WooCommerce %1$s or greater to be installed and active. kindly upgrade to a higher version of WooCommerce or downgrade to a lower version of Flutterwave WooCommerce that supports WooCommerce version %2$s.', 'rave-woocommerce-payment-gateway' ), esc_attr( FLW_WC_MIN_WC_VER ), esc_attr( WC_VERSION ) ) . '

'; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /includes/views/html-admin-missing-woocommerce.php: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 |

13 | ' . esc_html__( 'Flutterwave WooCommerce', 'rave-woocommerce-payment-gateway' ) . '' ); 16 | ?> 17 |

18 | 19 | 20 |

21 | 25 | 26 | 27 | 28 | 29 | 30 |

31 | 32 | 39 |

40 | 41 | 42 | 43 | 44 |

45 | 46 |
47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rave-woocommerce-payment-gateway", 3 | "title": "flutterwave-woocommerce", 4 | "version": "2.3.6", 5 | "description": "Official WooCommerce payment gateway for Flutterwave", 6 | "scripts": { 7 | "postinstall": "composer install", 8 | "prebuild": "npm install && composer install", 9 | "build": "npm run uglify && composer run makepot && npm run build:webpack && npm run plugin-zip", 10 | "build:webpack": "wp-scripts build", 11 | "i18n:merge": "php bin/update-pot-file-references.php i18n/languages/rave-woocommerce-payment-gateway.pot", 12 | "start": "npm run start:webpack", 13 | "start:webpack": "rimraf build/* && wp-scripts start", 14 | "preuglify": "rm -f $npm_package_config_assets_js_min", 15 | "uglify": "for f in $npm_package_config_assets_js_js; do file=${f%.js}; node_modules/.bin/uglifyjs $f -c -m > $file.min.js; done", 16 | "check-engines": "wp-scripts check-engines", 17 | "check-licenses": "wp-scripts check-licenses", 18 | "format": "wp-scripts format", 19 | "lint:woocommerce": "vendor/bin/phpcs --standard=WooCommerce-Core --extensions=php --ignore=vendor/* .", 20 | "lint:php": "vendor/bin/phpcs --standard=WordPress --extensions=php --ignore=vendor/* .", 21 | "lint:php:fix": "vendor/bin/phpcbf", 22 | "lint:css": "wp-scripts lint-style", 23 | "lint:js": "wp-scripts lint-js", 24 | "lint:md:docs": "wp-scripts lint-md-docs", 25 | "lint:pkg-json": "wp-scripts lint-pkg-json", 26 | "packages-update": "wp-scripts packages-update", 27 | "plugin-zip": "wp-scripts plugin-zip", 28 | "test:e2e": "wp-scripts test-e2e", 29 | "test:unit": "wp-scripts test-unit-js", 30 | "test:php": "./bin/run-tests.sh", 31 | "docs:dev": "vitepress dev docs", 32 | "docs:build": "vitepress build docs", 33 | "docs:preview": "vitepress preview docs", 34 | "wp-env:clean": "wp-env clean", 35 | "wp-env:start": "wp-env start", 36 | "pw-install": "npm playwright install --with-deps", 37 | "pw-tests": "npm playwright test", 38 | "pw-tests-headed": "npm playwright test --headed" 39 | }, 40 | "engines": { 41 | "node": ">=16.17.0", 42 | "npm": ">=8.15.0" 43 | }, 44 | "config": { 45 | "wp_org_slug": "rave-woocommerce-payment-gateway", 46 | "assets": { 47 | "js": { 48 | "min": "assets/js/*.min.js", 49 | "js": "assets/js/*.js" 50 | }, 51 | "styles": { 52 | "css": "assets/css/*.css", 53 | "sass": "assets/css/*.scss", 54 | "cssfolder": "assets/css/" 55 | } 56 | } 57 | }, 58 | "repository": { 59 | "type": "git", 60 | "url": "git+https://github.com/bajoski34/Woocommerce-v2.git" 61 | }, 62 | "keywords": [ 63 | "payment", 64 | "applepay", 65 | "googlepay", 66 | "woocommerce", 67 | "flutterwave", 68 | "rave", 69 | "fintech", 70 | "africa", 71 | "payments", 72 | "payout" 73 | ], 74 | "author": "Flutterwave Developers", 75 | "license": "MIT", 76 | "bugs": { 77 | "url": "https://github.com/bajoski34/Woocommerce-v2/issues" 78 | }, 79 | "files": [ 80 | "assets", 81 | "i18n", 82 | "build", 83 | "includes", 84 | "rave-woocommerce-payment-gateway.php", 85 | "README.md", 86 | "LICENSE", 87 | "CHANGELOG.md", 88 | "changelog.txt", 89 | "readme.txt" 90 | ], 91 | "homepage": "https://github.com/bajoski34/Woocommerce-v2#readme", 92 | "devDependencies": { 93 | "@automattic/color-studio": "^2.5.0", 94 | "@playwright/test": "^1.32.0", 95 | "@svgr/cli": "^6.5.1", 96 | "@woocommerce/dependency-extraction-webpack-plugin": "^2.2.0", 97 | "@woocommerce/eslint-plugin": "^2.2.0", 98 | "@wordpress/env": "^5.15.0", 99 | "@wordpress/prettier-config": "^2.12.0", 100 | "@wordpress/scripts": "^26.0.0", 101 | "cross-env": "^7.0.3", 102 | "dotenv": "^16.0.3", 103 | "rimraf": "^5.0.0", 104 | "uglify-js": "^3.17.4", 105 | "vitepress": "^1.0.0-alpha.61", 106 | "webpack": "^5.76.3", 107 | "webpack-cli": "^5.0.1", 108 | "wp-textdomain": "^1.0.1" 109 | }, 110 | "dependencies": { 111 | "@automattic/interpolate-components": "^1.2.1", 112 | "flutterwave-react-v3": "^1.3.0", 113 | "gridicons": "^3.4.2" 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /rave-woocommerce-payment-gateway.php: -------------------------------------------------------------------------------- 1 | register( new Flutterwave_WC_Gateway_Blocks_Support() ); 54 | } 55 | ); 56 | } 57 | } 58 | 59 | // add woocommerce block support. 60 | add_action( 'woocommerce_blocks_loaded', 'flutterwave_woocommerce_blocks_support' ); 61 | 62 | 63 | add_action( 64 | 'before_woocommerce_init', 65 | function() { 66 | if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) { 67 | \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true ); 68 | } 69 | } 70 | ); 71 | 72 | /** 73 | * Add the Settings link to the plugin 74 | * 75 | * @param array $links Existing links on the plugin page. 76 | * 77 | * @return array Existing links with our settings link added 78 | */ 79 | function flw_plugin_action_links( array $links ): array { 80 | 81 | $rave_settings_url = esc_url( get_admin_url( null, 'admin.php?page=wc-settings&tab=checkout§ion=rave' ) ); 82 | array_unshift( $links, "Settings" ); 83 | 84 | return $links; 85 | 86 | } 87 | 88 | add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), 'flw_plugin_action_links' ); 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === Flutterwave WooCommerce === 2 | Contributors: theflutterwave 3 | Tags: fintech,flutterwave, woocommerce, payments, nigeria, mastercard, visa, target,Naira,payments,verve,donation,church,shop,store, ghana, kenya, international, mastercard, visa 4 | Requires at least: 3.1 5 | Tested up to: 6.7.1 6 | Stable tag: 2.3.6 7 | License: MIT 8 | License URI: https://github.com/Flutterwave/Woocommerce/blob/master/LICENSE 9 | 10 | The WooCommerce Plugin makes it very easy and quick to add Flutterwave Payment option on Checkout for your online store. Accept Credit card, Debit card and Bank account payment directly on your store with the Flutterwave Plugin for WooCommerce. 11 | 12 | == Description == 13 | 14 | Accept Credit card, Debit card and Bank account payment directly on your store with the official Flutterwave Plugin for WooCommerce. 15 | 16 | = Plugin Features = 17 | 18 | * Collections: Card, Account, Mobile money, Bank Transfers, USSD, Barter, 1voucher. 19 | * Recurring payments: Tokenization and Subscriptions. 20 | * Split payments: Split payments between multiple recipients. 21 | 22 | = Requirements = 23 | 24 | 1. Flutterwave for business [API Keys](https://developer.flutterwave.com/docs/integration-guides/authentication) 25 | 2. [WooCommerce](https://woocommerce.com/) 26 | 6. Supported PHP version: 7.4.0 - 8.1.0 27 | 28 | == Installation == 29 | 30 | = Automatic Installation = 31 | * Login to your WordPress Dashboard. 32 | * Click on "Plugins > Add New" from the left menu. 33 | * In the search box type __Flutterwave Woocommerce__. 34 | * Click on __Install Now__ on __Flutterwave Woocommerce__ to install the plugin on your site. 35 | * Confirm the installation. 36 | * Activate the plugin. 37 | * Click on "WooCommerce > Settings" from the left menu and click the "Checkout" tab. 38 | * Click on the __Flutterwave__ link from the available Checkout Options 39 | * Configure your __Flutterwave Woocommerce__ settings accordingly. 40 | 41 | = Manual Installation = 42 | 1. Download the plugin zip file. 43 | 2. Login to your WordPress Admin. Click on "Plugins > Add New" from the left menu. 44 | 3. Click on the "Upload" option, then click "Choose File" to select the zip file you downloaded. Click "OK" and "Install Now" to complete the installation. 45 | 4. Activate the plugin. 46 | 5. Click on "WooCommerce > Settings" from the left menu and click the "Checkout" tab. 47 | 6. Click on the __Flutterwave__ link from the available Checkout Options 48 | 7. Configure your __Flutterwave WooCommerce__ settings accordingly. 49 | 50 | For FTP manual installation, [check here](http://codex.wordpress.org/Managing_Plugins#Manual_Plugin_Installation). 51 | 52 | = Configure the plugin = 53 | To configure the plugin, go to __WooCommerce > Settings__ from the left menu, click __Checkout__ tab. Click on __Flutterwave__. 54 | 55 | * __Enable/Disable__ - check the box to enable Flutterwave WooCommerce. 56 | * __Pay Button Public Key__ - Enter your public key which can be retrieved from the "Pay Buttons" page on your Flutterwave account dashboard. 57 | * __Modal Title__ - (Optional) customize the title of the Pay Modal. Default is Flutterwave. 58 | * Click __Save Changes__ to save your changes. 59 | 60 | = Webhooks = 61 | Handle Webhooks from Flutterwave with two new actions in WooCommerce. 62 | * flw_webhook_after_action : This action is fired after a transaction is completed and returns the transaction details (json). 63 | * flw_webhook_transaction_failure_action : This action is fired when a transaction fails and returns the transaction details (json). 64 | 65 | = Best Practices = 66 | 1. When in doubt about a transaction, always check the Flutterwave Dashboard to confirm the status of a transaction. 67 | 2. Always ensure you keep your API keys securely and privately. Do not share with anyone 68 | 3. Ensure you change from the default secret hash on the Wordpress admin and apply same on the Flutterwave Dashboard 69 | 4. Always ensure you install the most recent version of the Flutterwave Wordpress plugin 70 | 71 | = Debugging Errors = 72 | 73 | We understand that you may run into some errors while integrating our plugin. You can read more about our error messages [here](https://developer.flutterwave.com/docs/integration-guides/errors). 74 | 75 | For `authorization` and `validation` error responses, double-check your API keys and request. If you get a `server` error, kindly engage the team for support. 76 | 77 | = Support = 78 | 79 | For additional assistance using this library, contact the developer experience (DX) team via [email](mailto:developers@flutterwavego.com) or on [slack](https://bit.ly/34Vkzcg). 80 | 81 | You can also follow us [@FlutterwaveEng](https://twitter.com/FlutterwaveEng) and let us know what you think 😊. 82 | 83 | = Contribution guidelines = 84 | 85 | We love to get your input. Read more about our community contribution guidelines [here](/CONTRIBUTING.md) 86 | 87 | = License = 88 | 89 | By contributing to the Flutterwave WooCommerce, you agree that your contributions will be licensed under its [MIT license](/LICENSE). 90 | 91 | 92 | == Frequently Asked Questions == 93 | 94 | = What Do I Need To Use The Plugin = 95 | 96 | 1. You need to open an account on [Flutterwave for Business](https://dashboard.flutterwave.com) 97 | 98 | == Changelog == 99 | = 2.3.6 = 100 | * Fixed: Dynamic Adjustment to Custom Permalink Set by Merchant. 101 | * Fixed: Redirect Payment option return a Payment Mismatch Error. 102 | = 2.3.5 = 103 | * Added: Support for HPOS. 104 | * Fixed: compatibility with WooCommerce 7.1 to 6.9.1 105 | = 2.3.4 = 106 | * Fix: Webhook Handler Acknowledgement. 107 | = 2.3.2 = 108 | * Added: Support for WooCommerce Blocks. 109 | * Updated: WooCommerce Checkout Process. 110 | = 2.3.0 = 111 | * Fix: Handled MobileMoney Payment Handler Error. 112 | = 2.2.9 = 113 | * Fixed: PHP 8 support for v3 Webhook Handler. 114 | = 2.2.8 = 115 | * Fixed: Woocommerce Subscription processing function error. 116 | * New Feat: Switched to WC-Logger class for logging. 117 | = 2.2.7 = 118 | * fix: on payment completion redirect to order reciept page (redirect Method) 119 | * fix: PHP 8.0 compatibility ( optional method parameter ) 120 | = 2.2.0 = 121 | * Use one base URL for live and test mode. 122 | 123 | * Merchants can get their [test and live](https://developer.flutterwave.com/docs/api-keys) keys [here](https://rave.flutterwave.com/dashboard/settings/apis) 124 | 125 | * Using test keys keeps you in test mode, to move to live mode add live keys. 126 | 127 | * Support for Woocommerce recurring, this allows merchants to collect recurring payments in woocommerce. 128 | 129 | = 2.1.0 = 130 | * Support for Woocommerce recurring, this allows merchants to collect recurring payments in woocommerce. 131 | 132 | = 2.0.0 = 133 | * Support for new currencies (ZMW, UGX, RWF, TZS, SLL). 134 | 135 | = 1.0.1 = 136 | * Add redirect style with admin toogle for redirect or popup payment style 137 | * Custom gateway name 138 | * Bug fixes for country 139 | 140 | = 1.0.0 = 141 | * First release 142 | 143 | == Screenshots == 144 | 145 | 146 | 147 | == Other Notes == 148 | --------------------------------------------------------------------------------