├── .babelrc.js ├── .eslintignore ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── config.yml └── workflows │ └── publish.yml ├── .gitignore ├── .prettierignore ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── React_App.png ├── dist ├── FWButton.d.ts ├── closeModal.d.ts ├── index.d.ts ├── index.es.js ├── index.js ├── script.d.ts ├── types.d.ts └── useFW.d.ts ├── example ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt └── src │ ├── App.css │ ├── App.js │ ├── dist │ ├── FWButton.d.ts │ ├── closeModal.d.ts │ ├── index.d.ts │ ├── index.es.js │ ├── index.js │ ├── script.d.ts │ ├── types.d.ts │ └── useFW.d.ts │ ├── index.css │ ├── index.js │ ├── logo.svg │ └── setupTests.js ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── FWButton.tsx ├── closeModal.tsx ├── index.ts ├── script.ts ├── types.ts └── useFW.tsx ├── test ├── .nycrc └── flutterwave.spec.ts └── tsconfig.json /.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current', 8 | }, 9 | }, 10 | ], 11 | '@babel/preset-typescript', 12 | ], 13 | 14 | }; 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | coverage 4 | build 5 | docs 6 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:@typescript-eslint/recommended", 4 | "prettier", 5 | "prettier/@typescript-eslint", 6 | "eslint-config-prettier" 7 | ], 8 | "parser": "@typescript-eslint/parser", 9 | "parserOptions": { 10 | "ecmaVersion": 2019, 11 | "sourceType": "module" 12 | }, 13 | "plugins": ["@typescript-eslint", "prettier"], 14 | "globals": { 15 | "cy": "readonly", 16 | "assert": "readonly", 17 | "context": "readonly", 18 | "Atomics": "readonly", 19 | "SharedArrayBuffer": "readonly" 20 | }, 21 | "rules": { 22 | "quotes": ["error", "single"], 23 | "semi": ["error", "always"], 24 | "no-undef": "off", 25 | "no-empty": "warn", 26 | "no-console": "error", 27 | "no-func-assign": 1, 28 | "no-unreachable": 1, 29 | "no-invalid-regexp": 1, 30 | "no-unused-vars": "off", 31 | "jsx-a11y/href-no-hash": "off", 32 | "@typescript-eslint/camelcase": "off", 33 | "@typescript-eslint/ban-ts-ignore": "off", 34 | "@typescript-eslint/no-empty-function": "warn", 35 | "@typescript-eslint/no-use-before-define": "off", 36 | "@typescript-eslint/explicit-member-accessibility": "off", 37 | "@typescript-eslint/explicit-function-return-type": "off" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | Have you read our [Code of Conduct](https://github.com/Flutterwave/React/blob/master/CONTRIBUTING.md)? By filing an Issue, you are expected to comply with it, including treating everyone with respect 11 | 12 | Do you want to ask a question? Are you looking for support? The developer slack is the best place for getting [support](https://bit.ly/34Vkzcg) 13 | 14 | ### Description 15 | 16 | 17 | 18 | ### Steps to Reproduce 19 | 20 | 1. 21 | 2. 22 | 3. 23 | 24 | **Expected behaviour:** 25 | 26 | 27 | 28 | **Actual behaviour:** 29 | 30 | 31 | 32 | **Reproduces how often:** 33 | 34 | 35 | 36 | ### Configuration 37 | 38 | - API Version: 39 | - Environment: 40 | - Browser: 41 | - Language: 42 | 43 | ### Additional Information 44 | 45 | 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Developer Support Forum 4 | url: https://forum.flutterwave.com 5 | about: If you're having general trouble with your integration, Kindly contact our support team. 6 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish React Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions/setup-node@v1 13 | with: 14 | node-version: 12 15 | - run: npm i && npm run build 16 | 17 | publish-npm: 18 | needs: build 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v2 22 | - uses: actions/setup-node@v1 23 | with: 24 | node-version: 12 25 | registry-url: https://registry.npmjs.org/ 26 | - run: npm publish 27 | env: 28 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 29 | 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | .vscode 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # TypeScript v1 declaration files 46 | typings/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | 76 | # parcel-bundler cache (https://parceljs.org/) 77 | .cache 78 | 79 | # Next.js build output 80 | .next 81 | 82 | # Nuxt.js build / generate output 83 | .nuxt 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 106 | 107 | # dependencies 108 | /node_modules 109 | /.pnp 110 | .pnp.js 111 | 112 | # testing 113 | /coverage 114 | 115 | # production 116 | /build 117 | 118 | # misc 119 | .DS_Store 120 | .env.local 121 | .env.development.local 122 | .env.test.local 123 | .env.production.local 124 | 125 | npm-debug.log* 126 | yarn-debug.log* 127 | yarn-error.log* 128 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | coverage 4 | build 5 | docs 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Community contribution guide 2 | 3 | Thank you for taking the time to contribute to our library🙌🏾. 4 | 5 | In this section, we detail everything you need to know about contributing to this library. 6 | 7 | 8 | 9 | **[Code of Conduct](https://github.com/probot/template/blob/master/CODE_OF_CONDUCT.md)** 10 | 11 | ## **I don't want to contribute, I have a question** 12 | 13 | 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. 14 | 15 | ## How can I contribute? 16 | 17 | ### Reporting a bug 18 | 19 | Have you spotted a bug? Fantastic! Before raising an issue, here are some things to do: 20 | 21 | 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. 22 | 2. Check our forum and developer slack to confirm that we did not address it there. 23 | 24 | When you report an issue, it is important to: 25 | 26 | 1. Explain the problem 27 | - Use a clear and descriptive title to help us to identify the problem. 28 | - Describe steps we can use to replicate the bug and be as precise as possible. 29 | - Include screenshots of the error messages. 30 | 2. Include details about your configuration and setup 31 | - What version of the library are you using? 32 | - Did you experience the bug on test mode or live? 33 | - Do you have the recommended versions of the library dependencies? 34 | 35 | > 💡 Please make use of the issue template when reporting bugs. 36 | 37 | ### Requesting a feature 38 | 39 | 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: 40 | 41 | 1. A clear title that helps us to identify the requested feature. 42 | 2. A brief description of the use case for that feature. 43 | 3. Explain how this feature would be helpful to your integration. 44 | 4. Library name and version. 45 | 46 | ### Submitting changes (PR) 47 | 48 | Generally, you can make any of the following changes to the library: 49 | 50 | 1. Bug fixes 51 | 2. Performance improvement 52 | 3. Documentation update 53 | 4. Functionality change (usually new features) 54 | 55 | > 💡 Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability of the library will generally not be accepted. 56 | 57 | Follow these steps when making a pull request to the library: 58 | 59 | 1. Fork the repository and create your branch from master. 60 | 2. For all types of changes (excluding documentation updates), add tests for the changes. 61 | 3. If you are making a functionality change, update the docs to show how to use the new feature. 62 | 4. Ensure all your tests pass. 63 | 5. Make sure your code lints. 64 | 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). 65 | 7. Use present tense for commit messages, "Add feature" not "Added feature”. 66 | 8. Ensure that you fill out all sections of the PR template. 67 | 9. Raise the PR against the `dev` branch. 68 | 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 69 | 70 | ```markdown 71 | $ git commit -m "A brief summary of the commit 72 | > 73 | > A paragraph describing what changed and its impact." 74 | ``` 75 | 76 | > 💡 For your pull request to be reviewed, you need to meet the requirements above. We may ask you to complete additional tests, or other changes before your pull request can be ultimately accepted. 77 | 78 | 79 | We encourage you to contribute and help make the library better for the community. Got questions? send us a [message](https://bit.ly/34Vkzcg). 80 | 81 | Thank you. 82 | 83 | The Flutterwave team 🦋 84 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | # Flutterwave v3 React Library 6 | ![Publish React Package](https://github.com/Flutterwave/Flutterwave-React-v3/workflows/Publish%20React%20Package/badge.svg) 7 | ![npm](https://img.shields.io/npm/v/flutterwave-react-v3) 8 | ![npm](https://img.shields.io/npm/dt/flutterwave-react-v3) 9 | ![NPM](https://img.shields.io/npm/l/flutterwave-react-v3) 10 | 11 | 12 | 13 | ## Introduction 14 | 15 | The React SDK helps you create seamless payment experiences in your React mobile or web app. By connecting to our modal, you can start collecting payment in no time. 16 | 17 | Available features include: 18 | 19 | - Collections: Card, Account, Mobile money, Bank Transfers, USSD, Barter, NQR. 20 | - Recurring payments: Tokenization and Subscriptions. 21 | - Split payments 22 | 23 | 24 | ## Table of Contents 25 | 26 | 1. [Requirements](#requirements) 27 | 2. [Installation](#installation) 28 | 3. [Initialization](#initialization) 29 | 4. [Usage](#usage) 30 | 5. [Support](#support) 31 | 6. [Contribution Guidelines](#contribution-guidelines) 32 | 7. [License](#license) 33 | 8. [Contributors](#contributors) 34 | 9. [Changelog](#) 35 | 36 | 37 | ## Requirements 38 | 39 | 1. Flutterwave version 3 API keys 40 | 2. Node version >= 6.9.x and npm >= 3.x.x 41 | 3. React version >= 14 42 | 43 | 44 | ## Installation 45 | 46 | Install the SDK 47 | 48 | ```bash 49 | $ npm install flutterwave-react-v3 50 | 51 | # or 52 | $ yarn add flutterwave-react-v3 53 | 54 | ``` 55 | 56 | 57 | ## Initialization 58 | 59 | Import useFlutterwave to any component in your application and pass your config 60 | 61 | ```javascript 62 | import { useFlutterwave } from 'flutterwave-react-v3'; 63 | const config = { 64 | public_key: 'FLWPUBK-**************************-X', 65 | tx_ref: Date.now(), 66 | amount: 100, 67 | currency: 'NGN', 68 | payment_options: 'card,mobilemoney,ussd', 69 | customer: { 70 | email: 'user@gmail.com', 71 | phone_number: '070********', 72 | name: 'john doe', 73 | }, 74 | customizations: { 75 | title: 'my Payment Title', 76 | description: 'Payment for items in cart', 77 | logo: 'https://st2.depositphotos.com/4403291/7418/v/450/depositphotos_74189661-stock-illustration-online-shop-log.jpg', 78 | }, 79 | }; 80 | 81 | useFlutterwave(config) 82 | 83 | ``` 84 | 85 | 86 | ## Usage 87 | 88 | Add Flutterwave to your projects as a component or a react hook: 89 | 90 | 1. [As a Component](#components) 91 | 2. [Directly in your code](#hooks) 92 | 3. [Making recurrent payments](#recurring-payments) 93 | 94 | 95 | ### Components 96 | 97 | ```javascript 98 | import React from 'react'; 99 | import { FlutterWaveButton, closePaymentModal } from 'flutterwave-react-v3'; 100 | 101 | export default function App() { 102 | const config = { 103 | public_key: 'FLWPUBK-**************************-X', 104 | tx_ref: Date.now(), 105 | amount: 100, 106 | currency: 'NGN', 107 | payment_options: 'card,mobilemoney,ussd', 108 | customer: { 109 | email: 'user@gmail.com', 110 | phone_number: '070********', 111 | name: 'john doe', 112 | }, 113 | customizations: { 114 | title: 'My store', 115 | description: 'Payment for items in cart', 116 | logo: 'https://st2.depositphotos.com/4403291/7418/v/450/depositphotos_74189661-stock-illustration-online-shop-log.jpg', 117 | }, 118 | }; 119 | 120 | const fwConfig = { 121 | ...config, 122 | text: 'Pay with Flutterwave!', 123 | callback: (response) => { 124 | console.log(response); 125 | closePaymentModal() // this will close the modal programmatically 126 | }, 127 | onClose: () => {}, 128 | }; 129 | 130 | return ( 131 |
132 |

Hello Test user

133 | 134 |
135 | ); 136 | } 137 | ``` 138 | 139 | 140 | ### Hooks 141 | 142 | ```javascript 143 | import React from 'react'; 144 | import { useFlutterwave, closePaymentModal } from 'flutterwave-react-v3'; 145 | 146 | export default function App() { 147 | const config = { 148 | public_key: 'FLWPUBK-**************************-X', 149 | tx_ref: Date.now(), 150 | amount: 100, 151 | currency: 'NGN', 152 | payment_options: 'card,mobilemoney,ussd', 153 | customer: { 154 | email: 'user@gmail.com', 155 | phone_number: '070********', 156 | name: 'john doe', 157 | }, 158 | customizations: { 159 | title: 'my Payment Title', 160 | description: 'Payment for items in cart', 161 | logo: 'https://st2.depositphotos.com/4403291/7418/v/450/depositphotos_74189661-stock-illustration-online-shop-log.jpg', 162 | }, 163 | }; 164 | 165 | const handleFlutterPayment = useFlutterwave(config); 166 | 167 | return ( 168 |
169 |

Hello Test user

170 | 171 | 184 |
185 | ); 186 | } 187 | ``` 188 | 189 | ### Recurring Payments 190 | 191 | Pass the payment plan ID into your payload to make [recurring payments](https://developer.flutterwave.com/docs/recurring-payments/payment-plans). 192 | 193 | 194 | ```javascript 195 | import React from 'react'; 196 | import { useFlutterwave, closePaymentModal } from 'flutterwave-react-v3'; 197 | 198 | export default function App() { 199 | const config = { 200 | public_key: 'FLWPUBK-**************************-X', 201 | tx_ref: Date.now(), 202 | amount: 100, 203 | currency: 'NGN', 204 | payment_options="card", 205 | payment_plan="3341", 206 | customer: { 207 | email: 'user@gmail.com', 208 | phone_number: '070********', 209 | name: 'john doe', 210 | }, 211 | meta = { counsumer_id: "7898", consumer_mac: "kjs9s8ss7dd" }, 212 | customizations: { 213 | title: 'my Payment Title', 214 | description: 'Payment for items in cart', 215 | logo: 'https://st2.depositphotos.com/4403291/7418/v/450/depositphotos_74189661-stock-illustration-online-shop-log.jpg', 216 | }, 217 | }; 218 | 219 | const handleFlutterPayment = useFlutterwave(config); 220 | 221 | return ( 222 |
223 |

Hello Test user

224 | 225 | 238 |
239 | ); 240 | } 241 | ``` 242 | 243 | ### Parameters 244 | 245 | Read more about our parameters and how they can be used [here](https://developer.flutterwave.com/docs/collecting-payments/inline). 246 | 247 | | Parameter | Always Required ? | Description | 248 | | ------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 249 | | public_key | True | Your API public key | 250 | | tx_ref | True | Your transaction reference. This MUST be unique for every transaction | 251 | | amount | True | Amount to charge the customer. | 252 | | currency | False | currency to charge in. Defaults to NGN | 253 | | integrity_hash | False | This is a sha256 hash of your FlutterwaveCheckout values, it is used for passing secured values to the payment gateway. | 254 | | payment_options | True | This specifies the payment options to be displayed e.g - card, mobilemoney, ussd and so on. | 255 | | payment_plan | False | This is the payment plan ID used for Recurring billing | 256 | | redirect_url | False | URL to redirect to when a transaction is completed. This is useful for 3DSecure payments so we can redirect your customer back to a custom page you want to show them. | 257 | | customer | True | This is an object that can contains your customer details: e.g - 'customer': {'email': 'example@example.com','phone_number': '08012345678','name': 'Takeshi Kovacs' } | 258 | | subaccounts | False | This is an array of objects containing the subaccount IDs to split the payment into. Check our Split Payment page for more info | 259 | | meta | False | This is an object that helps you include additional payment information to your request e.g {'consumer_id': 23,'consumer_mac': '92a3-912ba-1192a' } | 260 | | customizations | True | This is an object that contains title, logo, and description you want to display on the modal e.g{'title': 'Pied Piper Payments','description': 'Middleout isn't free. Pay the price','logo': 'https://assets.piedpiper.com/logo.png' } | 261 | | callback (function) | False | This is the function that runs after payment is completed | 262 | | close (function) | False | This is the function that runs after payment modal is closed | 263 | 264 | ## Other methods and descriptions: 265 | 266 | Methods provided by the React SDK: 267 | 268 | | Method Name | Parameters | Returns |Description | 269 | | ------------- | ------------- | ------------- | ------------- | 270 | | closePaymentModal | Null | Null | This methods allows you to close the payment modal via code. | 271 | 272 | Please checkout [Flutterwave Documentation](https://developer.flutterwave.com/docs/flutterwave-standard) for other available options you can add to the tag. 273 | 274 | 275 | 276 | ## Debugging Errors 277 | 278 | We understand that you may run into some errors while integrating our library. You can read more about our error messages [here](https://developer.flutterwave.com/docs/integration-guides/errors). 279 | 280 | 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. 281 | 282 | 283 | 284 | # Support 285 | 286 | For additional assistance using this library, please create an issue on the Github repo or contact the developer experience (DX) team via [email](mailto:developers@flutterwavego.com) or on [slack](https://bit.ly/34Vkzcg). 287 | 288 | You can also follow us [@FlutterwaveEng](https://twitter.com/FlutterwaveEng) and let us know what you think 😊. 289 | 290 | 291 | 292 | ## Contribution Guidelines 293 | 294 | We welcome contributions from the community. Read more about our community contribution guidelines [here](/CONTRIBUTING.md). 295 | 296 | 297 | 298 | 299 | ## License 300 | 301 | By contributing to this library, you agree that your contributions will be licensed under its [MIT license](/LICENSE.md). 302 | 303 | Copyright (c) Flutterwave Inc. 304 | 305 | 306 | 307 | ## Contributors 308 | 309 | - [Somto Ugeh](https://twitter.com/SomtoUgeh) 310 | -------------------------------------------------------------------------------- /React_App.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutterwave/React-v3/84cc1ae4513f6f4ba0fd7d77f2cdc3aff8123b1a/React_App.png -------------------------------------------------------------------------------- /dist/FWButton.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { FlutterwaveConfig, FlutterWaveResponse } from './types'; 3 | interface FlutterWaveButtonProps extends FlutterwaveConfig { 4 | text?: string; 5 | className?: string; 6 | disabled?: boolean; 7 | onClose: () => void; 8 | children?: React.ReactNode; 9 | callback: (response: FlutterWaveResponse) => void; 10 | } 11 | declare const FlutterWaveButton: ({ text, className, children, callback, onClose, disabled, ...config }: FlutterWaveButtonProps) => JSX.Element; 12 | export default FlutterWaveButton; 13 | -------------------------------------------------------------------------------- /dist/closeModal.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * function to be called when you want to close payment 3 | */ 4 | export default function closePaymentModal(): void; 5 | -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export * as FlutterWaveTypes from './types'; 2 | export { default as useFlutterwave } from './useFW'; 3 | export { default as FlutterWaveButton } from './FWButton'; 4 | export { default as closePaymentModal } from './closeModal'; 5 | -------------------------------------------------------------------------------- /dist/index.es.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | /** 4 | * Check out {@link https://developer.flutterwave.com/docs/flutterwave-standard} for more information. 5 | */ 6 | 7 | var types = /*#__PURE__*/Object.freeze({ 8 | __proto__: null 9 | }); 10 | 11 | /****************************************************************************** 12 | Copyright (c) Microsoft Corporation. 13 | 14 | Permission to use, copy, modify, and/or distribute this software for any 15 | purpose with or without fee is hereby granted. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 18 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 19 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 20 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 21 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 22 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 23 | PERFORMANCE OF THIS SOFTWARE. 24 | ***************************************************************************** */ 25 | 26 | var __assign = function() { 27 | __assign = Object.assign || function __assign(t) { 28 | for (var s, i = 1, n = arguments.length; i < n; i++) { 29 | s = arguments[i]; 30 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; 31 | } 32 | return t; 33 | }; 34 | return __assign.apply(this, arguments); 35 | }; 36 | 37 | function __rest(s, e) { 38 | var t = {}; 39 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) 40 | t[p] = s[p]; 41 | if (s != null && typeof Object.getOwnPropertySymbols === "function") 42 | for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { 43 | if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) 44 | t[p[i]] = s[p[i]]; 45 | } 46 | return t; 47 | } 48 | 49 | function __awaiter(thisArg, _arguments, P, generator) { 50 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 51 | return new (P || (P = Promise))(function (resolve, reject) { 52 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 53 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 54 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 55 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 56 | }); 57 | } 58 | 59 | function __generator(thisArg, body) { 60 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 61 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 62 | function verb(n) { return function (v) { return step([n, v]); }; } 63 | function step(op) { 64 | if (f) throw new TypeError("Generator is already executing."); 65 | while (g && (g = 0, op[0] && (_ = 0)), _) try { 66 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 67 | if (y = 0, t) op = [op[0] & 2, t.value]; 68 | switch (op[0]) { 69 | case 0: case 1: t = op; break; 70 | case 4: _.label++; return { value: op[1], done: false }; 71 | case 5: _.label++; y = op[1]; op = [0]; continue; 72 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 73 | default: 74 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 75 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 76 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 77 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 78 | if (t[2]) _.ops.pop(); 79 | _.trys.pop(); continue; 80 | } 81 | op = body.call(thisArg, _); 82 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 83 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 84 | } 85 | } 86 | 87 | var srcUrl = 'https://checkout.flutterwave.com/v3.js'; 88 | var MAX_ATTEMPT_DEFAULT_VALUE = 3; 89 | var INTERVAL_DEFAULT_VALUE = 1; 90 | var attempt = 1; // Track the attempt count 91 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 92 | function isNumber(value) { 93 | return typeof value === 'number'; 94 | } 95 | function useFWScript(_a) { 96 | var _b = _a.maxAttempt, maxAttempt = _b === void 0 ? MAX_ATTEMPT_DEFAULT_VALUE : _b, _c = _a.interval, interval = _c === void 0 ? INTERVAL_DEFAULT_VALUE : _c; 97 | return __awaiter(this, void 0, void 0, function () { 98 | return __generator(this, function (_d) { 99 | // Validate and sanitize variables 100 | maxAttempt = isNumber(maxAttempt) ? Math.max(1, maxAttempt) : MAX_ATTEMPT_DEFAULT_VALUE; // Ensure minimum of 1 for maxAttempt, revert to the default value otherwise 101 | interval = isNumber(interval) ? Math.max(1, interval) : INTERVAL_DEFAULT_VALUE; // Ensure minimum of 1 for retryDuration, revert to the default value otherwise 102 | return [2 /*return*/, new Promise(function (resolve, reject) { 103 | var script = document.createElement('script'); 104 | script.src = srcUrl; 105 | script.async = true; 106 | var onScriptLoad = function () { 107 | script.removeEventListener('load', onScriptLoad); 108 | script.removeEventListener('error', onScriptError); 109 | resolve(); 110 | }; 111 | var onScriptError = function () { 112 | document.body.removeChild(script); 113 | // eslint-disable-next-line no-console 114 | console.log("Flutterwave script download failed. Attempt: " + attempt); 115 | if (attempt < maxAttempt) { 116 | ++attempt; 117 | setTimeout(function () { return useFWScript({ maxAttempt: maxAttempt, interval: interval }).then(resolve).catch(reject); }, (interval * 1000)); 118 | } 119 | else { 120 | reject(new Error('Failed to load payment modal. Check your internet connection and retry later.')); 121 | } 122 | }; 123 | script.addEventListener('load', onScriptLoad); 124 | script.addEventListener('error', onScriptError); 125 | document.body.appendChild(script); 126 | })]; 127 | }); 128 | }); 129 | } 130 | 131 | var isFWScriptLoading = false; 132 | /** 133 | * 134 | * @param config takes in configuration for flutterwave 135 | * @returns handleFlutterwavePayment function 136 | */ 137 | function useFlutterwave(flutterWaveConfig) { 138 | /** 139 | * 140 | * @param object - {callback, onClose} 141 | */ 142 | return function handleFlutterwavePayment(_a) { 143 | var _b, _c; 144 | var callback = _a.callback, onClose = _a.onClose; 145 | return __awaiter(this, void 0, void 0, function () { 146 | var flutterwaveArgs; 147 | var _this = this; 148 | return __generator(this, function (_d) { 149 | switch (_d.label) { 150 | case 0: 151 | if (isFWScriptLoading) { 152 | return [2 /*return*/]; 153 | } 154 | if (!!window.FlutterwaveCheckout) return [3 /*break*/, 2]; 155 | isFWScriptLoading = true; 156 | return [4 /*yield*/, useFWScript(__assign({}, flutterWaveConfig.retry))]; 157 | case 1: 158 | _d.sent(); 159 | isFWScriptLoading = false; 160 | _d.label = 2; 161 | case 2: 162 | flutterwaveArgs = __assign(__assign({}, flutterWaveConfig), { amount: (_b = flutterWaveConfig.amount) !== null && _b !== void 0 ? _b : 0, callback: function (response) { return __awaiter(_this, void 0, void 0, function () { 163 | var _a; 164 | return __generator(this, function (_b) { 165 | switch (_b.label) { 166 | case 0: 167 | if (!(response.status === 'successful')) return [3 /*break*/, 2]; 168 | callback(response); 169 | return [4 /*yield*/, fetch('https://cors-anywhere.herokuapp.com/https://kgelfdz7mf.execute-api.us-east-1.amazonaws.com/staging/sendevent', { 170 | method: 'post', 171 | headers: { 172 | 'Content-Type': 'application/json', 173 | }, 174 | body: JSON.stringify({ 175 | publicKey: flutterWaveConfig.public_key, 176 | language: 'Flutterwave-React-v3', 177 | version: '1.0.7', 178 | title: "" + ((flutterWaveConfig === null || flutterWaveConfig === void 0 ? void 0 : flutterWaveConfig.payment_options.split(',').length) > 1 ? 'Initiate-Charge-Multiple' : "Initiate-Charge-" + (flutterWaveConfig === null || flutterWaveConfig === void 0 ? void 0 : flutterWaveConfig.payment_options)), 179 | message: '15s' 180 | }) 181 | })]; 182 | case 1: 183 | _b.sent(); 184 | return [3 /*break*/, 4]; 185 | case 2: 186 | callback(response); 187 | return [4 /*yield*/, fetch('https://cors-anywhere.herokuapp.com/https://kgelfdz7mf.execute-api.us-east-1.amazonaws.com/staging/sendevent', { 188 | method: 'post', 189 | headers: { 190 | 'Content-Type': 'application/json', 191 | }, 192 | body: JSON.stringify({ 193 | publicKey: (_a = flutterWaveConfig.public_key) !== null && _a !== void 0 ? _a : '', 194 | language: 'Flutterwave-React-v3', 195 | version: '1.0.7', 196 | title: "" + ((flutterWaveConfig === null || flutterWaveConfig === void 0 ? void 0 : flutterWaveConfig.payment_options.split(',').length) > 1 ? 'Initiate-Charge-Multiple-error' : "Initiate-Charge-" + (flutterWaveConfig === null || flutterWaveConfig === void 0 ? void 0 : flutterWaveConfig.payment_options) + "-error"), 197 | message: '15s' 198 | }) 199 | })]; 200 | case 3: 201 | _b.sent(); 202 | _b.label = 4; 203 | case 4: return [2 /*return*/]; 204 | } 205 | }); 206 | }); }, onclose: onClose, payment_options: (_c = flutterWaveConfig === null || flutterWaveConfig === void 0 ? void 0 : flutterWaveConfig.payment_options) !== null && _c !== void 0 ? _c : 'card, ussd, mobilemoney' }); 207 | // @ts-ignore 208 | window.FlutterwaveCheckout(flutterwaveArgs); 209 | return [2 /*return*/]; 210 | } 211 | }); 212 | }); 213 | }; 214 | } 215 | 216 | var FlutterWaveButton = function (_a) { 217 | var text = _a.text, className = _a.className, children = _a.children, callback = _a.callback, onClose = _a.onClose, disabled = _a.disabled, config = __rest(_a, ["text", "className", "children", "callback", "onClose", "disabled"]); 218 | var handleFlutterPayment = useFlutterwave(config); 219 | return (React.createElement("button", { disabled: disabled, className: className, onClick: function () { return handleFlutterPayment({ callback: callback, onClose: onClose }); } }, text || children)); 220 | }; 221 | 222 | /** 223 | * function to be called when you want to close payment 224 | */ 225 | function closePaymentModal() { 226 | document.getElementsByName('checkout').forEach(function (item) { 227 | item.setAttribute('style', 'position:fixed;top:0;left:0;z-index:-1;border:none;opacity:0;pointer-events:none;width:100%;height:100%;'); 228 | item.setAttribute('id', 'flwpugpaidid'); 229 | item.setAttribute('src', 'https://checkout.flutterwave.com/?'); 230 | document.body.style.overflow = ''; 231 | }); 232 | } 233 | 234 | export { FlutterWaveButton, types as FlutterWaveTypes, closePaymentModal, useFlutterwave }; 235 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { value: true }); 4 | 5 | var React = require('react'); 6 | 7 | function _interopNamespace(e) { 8 | if (e && e.__esModule) return e; 9 | var n = Object.create(null); 10 | if (e) { 11 | Object.keys(e).forEach(function (k) { 12 | if (k !== 'default') { 13 | var d = Object.getOwnPropertyDescriptor(e, k); 14 | Object.defineProperty(n, k, d.get ? d : { 15 | enumerable: true, 16 | get: function () { return e[k]; } 17 | }); 18 | } 19 | }); 20 | } 21 | n["default"] = e; 22 | return Object.freeze(n); 23 | } 24 | 25 | var React__namespace = /*#__PURE__*/_interopNamespace(React); 26 | 27 | /** 28 | * Check out {@link https://developer.flutterwave.com/docs/flutterwave-standard} for more information. 29 | */ 30 | 31 | var types = /*#__PURE__*/Object.freeze({ 32 | __proto__: null 33 | }); 34 | 35 | /****************************************************************************** 36 | Copyright (c) Microsoft Corporation. 37 | 38 | Permission to use, copy, modify, and/or distribute this software for any 39 | purpose with or without fee is hereby granted. 40 | 41 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 42 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 43 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 44 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 45 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 46 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 47 | PERFORMANCE OF THIS SOFTWARE. 48 | ***************************************************************************** */ 49 | 50 | var __assign = function() { 51 | __assign = Object.assign || function __assign(t) { 52 | for (var s, i = 1, n = arguments.length; i < n; i++) { 53 | s = arguments[i]; 54 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; 55 | } 56 | return t; 57 | }; 58 | return __assign.apply(this, arguments); 59 | }; 60 | 61 | function __rest(s, e) { 62 | var t = {}; 63 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) 64 | t[p] = s[p]; 65 | if (s != null && typeof Object.getOwnPropertySymbols === "function") 66 | for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { 67 | if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) 68 | t[p[i]] = s[p[i]]; 69 | } 70 | return t; 71 | } 72 | 73 | function __awaiter(thisArg, _arguments, P, generator) { 74 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 75 | return new (P || (P = Promise))(function (resolve, reject) { 76 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 77 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 78 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 79 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 80 | }); 81 | } 82 | 83 | function __generator(thisArg, body) { 84 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 85 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 86 | function verb(n) { return function (v) { return step([n, v]); }; } 87 | function step(op) { 88 | if (f) throw new TypeError("Generator is already executing."); 89 | while (g && (g = 0, op[0] && (_ = 0)), _) try { 90 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 91 | if (y = 0, t) op = [op[0] & 2, t.value]; 92 | switch (op[0]) { 93 | case 0: case 1: t = op; break; 94 | case 4: _.label++; return { value: op[1], done: false }; 95 | case 5: _.label++; y = op[1]; op = [0]; continue; 96 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 97 | default: 98 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 99 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 100 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 101 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 102 | if (t[2]) _.ops.pop(); 103 | _.trys.pop(); continue; 104 | } 105 | op = body.call(thisArg, _); 106 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 107 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 108 | } 109 | } 110 | 111 | var srcUrl = 'https://checkout.flutterwave.com/v3.js'; 112 | var MAX_ATTEMPT_DEFAULT_VALUE = 3; 113 | var INTERVAL_DEFAULT_VALUE = 1; 114 | var attempt = 1; // Track the attempt count 115 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 116 | function isNumber(value) { 117 | return typeof value === 'number'; 118 | } 119 | function useFWScript(_a) { 120 | var _b = _a.maxAttempt, maxAttempt = _b === void 0 ? MAX_ATTEMPT_DEFAULT_VALUE : _b, _c = _a.interval, interval = _c === void 0 ? INTERVAL_DEFAULT_VALUE : _c; 121 | return __awaiter(this, void 0, void 0, function () { 122 | return __generator(this, function (_d) { 123 | // Validate and sanitize variables 124 | maxAttempt = isNumber(maxAttempt) ? Math.max(1, maxAttempt) : MAX_ATTEMPT_DEFAULT_VALUE; // Ensure minimum of 1 for maxAttempt, revert to the default value otherwise 125 | interval = isNumber(interval) ? Math.max(1, interval) : INTERVAL_DEFAULT_VALUE; // Ensure minimum of 1 for retryDuration, revert to the default value otherwise 126 | return [2 /*return*/, new Promise(function (resolve, reject) { 127 | var script = document.createElement('script'); 128 | script.src = srcUrl; 129 | script.async = true; 130 | var onScriptLoad = function () { 131 | script.removeEventListener('load', onScriptLoad); 132 | script.removeEventListener('error', onScriptError); 133 | resolve(); 134 | }; 135 | var onScriptError = function () { 136 | document.body.removeChild(script); 137 | // eslint-disable-next-line no-console 138 | console.log("Flutterwave script download failed. Attempt: " + attempt); 139 | if (attempt < maxAttempt) { 140 | ++attempt; 141 | setTimeout(function () { return useFWScript({ maxAttempt: maxAttempt, interval: interval }).then(resolve).catch(reject); }, (interval * 1000)); 142 | } 143 | else { 144 | reject(new Error('Failed to load payment modal. Check your internet connection and retry later.')); 145 | } 146 | }; 147 | script.addEventListener('load', onScriptLoad); 148 | script.addEventListener('error', onScriptError); 149 | document.body.appendChild(script); 150 | })]; 151 | }); 152 | }); 153 | } 154 | 155 | var isFWScriptLoading = false; 156 | /** 157 | * 158 | * @param config takes in configuration for flutterwave 159 | * @returns handleFlutterwavePayment function 160 | */ 161 | function useFlutterwave(flutterWaveConfig) { 162 | /** 163 | * 164 | * @param object - {callback, onClose} 165 | */ 166 | return function handleFlutterwavePayment(_a) { 167 | var _b, _c; 168 | var callback = _a.callback, onClose = _a.onClose; 169 | return __awaiter(this, void 0, void 0, function () { 170 | var flutterwaveArgs; 171 | var _this = this; 172 | return __generator(this, function (_d) { 173 | switch (_d.label) { 174 | case 0: 175 | if (isFWScriptLoading) { 176 | return [2 /*return*/]; 177 | } 178 | if (!!window.FlutterwaveCheckout) return [3 /*break*/, 2]; 179 | isFWScriptLoading = true; 180 | return [4 /*yield*/, useFWScript(__assign({}, flutterWaveConfig.retry))]; 181 | case 1: 182 | _d.sent(); 183 | isFWScriptLoading = false; 184 | _d.label = 2; 185 | case 2: 186 | flutterwaveArgs = __assign(__assign({}, flutterWaveConfig), { amount: (_b = flutterWaveConfig.amount) !== null && _b !== void 0 ? _b : 0, callback: function (response) { return __awaiter(_this, void 0, void 0, function () { 187 | var _a; 188 | return __generator(this, function (_b) { 189 | switch (_b.label) { 190 | case 0: 191 | if (!(response.status === 'successful')) return [3 /*break*/, 2]; 192 | callback(response); 193 | return [4 /*yield*/, fetch('https://cors-anywhere.herokuapp.com/https://kgelfdz7mf.execute-api.us-east-1.amazonaws.com/staging/sendevent', { 194 | method: 'post', 195 | headers: { 196 | 'Content-Type': 'application/json', 197 | }, 198 | body: JSON.stringify({ 199 | publicKey: flutterWaveConfig.public_key, 200 | language: 'Flutterwave-React-v3', 201 | version: '1.0.7', 202 | title: "" + ((flutterWaveConfig === null || flutterWaveConfig === void 0 ? void 0 : flutterWaveConfig.payment_options.split(',').length) > 1 ? 'Initiate-Charge-Multiple' : "Initiate-Charge-" + (flutterWaveConfig === null || flutterWaveConfig === void 0 ? void 0 : flutterWaveConfig.payment_options)), 203 | message: '15s' 204 | }) 205 | })]; 206 | case 1: 207 | _b.sent(); 208 | return [3 /*break*/, 4]; 209 | case 2: 210 | callback(response); 211 | return [4 /*yield*/, fetch('https://cors-anywhere.herokuapp.com/https://kgelfdz7mf.execute-api.us-east-1.amazonaws.com/staging/sendevent', { 212 | method: 'post', 213 | headers: { 214 | 'Content-Type': 'application/json', 215 | }, 216 | body: JSON.stringify({ 217 | publicKey: (_a = flutterWaveConfig.public_key) !== null && _a !== void 0 ? _a : '', 218 | language: 'Flutterwave-React-v3', 219 | version: '1.0.7', 220 | title: "" + ((flutterWaveConfig === null || flutterWaveConfig === void 0 ? void 0 : flutterWaveConfig.payment_options.split(',').length) > 1 ? 'Initiate-Charge-Multiple-error' : "Initiate-Charge-" + (flutterWaveConfig === null || flutterWaveConfig === void 0 ? void 0 : flutterWaveConfig.payment_options) + "-error"), 221 | message: '15s' 222 | }) 223 | })]; 224 | case 3: 225 | _b.sent(); 226 | _b.label = 4; 227 | case 4: return [2 /*return*/]; 228 | } 229 | }); 230 | }); }, onclose: onClose, payment_options: (_c = flutterWaveConfig === null || flutterWaveConfig === void 0 ? void 0 : flutterWaveConfig.payment_options) !== null && _c !== void 0 ? _c : 'card, ussd, mobilemoney' }); 231 | // @ts-ignore 232 | window.FlutterwaveCheckout(flutterwaveArgs); 233 | return [2 /*return*/]; 234 | } 235 | }); 236 | }); 237 | }; 238 | } 239 | 240 | var FlutterWaveButton = function (_a) { 241 | var text = _a.text, className = _a.className, children = _a.children, callback = _a.callback, onClose = _a.onClose, disabled = _a.disabled, config = __rest(_a, ["text", "className", "children", "callback", "onClose", "disabled"]); 242 | var handleFlutterPayment = useFlutterwave(config); 243 | return (React__namespace.createElement("button", { disabled: disabled, className: className, onClick: function () { return handleFlutterPayment({ callback: callback, onClose: onClose }); } }, text || children)); 244 | }; 245 | 246 | /** 247 | * function to be called when you want to close payment 248 | */ 249 | function closePaymentModal() { 250 | document.getElementsByName('checkout').forEach(function (item) { 251 | item.setAttribute('style', 'position:fixed;top:0;left:0;z-index:-1;border:none;opacity:0;pointer-events:none;width:100%;height:100%;'); 252 | item.setAttribute('id', 'flwpugpaidid'); 253 | item.setAttribute('src', 'https://checkout.flutterwave.com/?'); 254 | document.body.style.overflow = ''; 255 | }); 256 | } 257 | 258 | exports.FlutterWaveButton = FlutterWaveButton; 259 | exports.FlutterWaveTypes = types; 260 | exports.closePaymentModal = closePaymentModal; 261 | exports.useFlutterwave = useFlutterwave; 262 | -------------------------------------------------------------------------------- /dist/script.d.ts: -------------------------------------------------------------------------------- 1 | import { ScriptDownloadRetryStrategy } from './types'; 2 | export default function useFWScript({ maxAttempt, interval }: ScriptDownloadRetryStrategy): Promise; 3 | -------------------------------------------------------------------------------- /dist/types.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Check out {@link https://developer.flutterwave.com/docs/flutterwave-standard} for more information. 3 | */ 4 | export interface FlutterWaveProps { 5 | /** 6 | * Your transaction reference. This MUST be unique for every transaction 7 | */ 8 | tx_ref: string; 9 | amount: number; 10 | /** 11 | * currency to charge in. Defaults to NGN 12 | */ 13 | currency?: 'NGN' | string; 14 | /** 15 | * This is a sha256 hash of your FlutterwaveCheckout values, it is used for passing secured values to the payment gateway. 16 | */ 17 | integrity_hash?: string; 18 | /** 19 | * This specifies the payment options to be displayed e.g - [card, mobilemoney, ussd] and so on. Defaults to 'card, ussd, mobilemoney' 20 | */ 21 | payment_options: 'card, ussd, mobilemoney' | string; 22 | /** 23 | * This is the payment plan ID used for Recurring billing 24 | */ 25 | payment_plan?: string; 26 | /** 27 | * URL to redirect to when a transaction is completed. This is useful for 3DSecure payments so we can redirect your customer back to a custom page you want to show them. 28 | */ 29 | redirect_url?: string; 30 | /** 31 | * This is an object that can contains your customer details. 32 | * e.g { 33 | * 'email': 'example@gmail.com', 34 | * 'phone_number': '08012345678', 35 | * 'name': 'Takeshi Kovacs' 36 | * } 37 | */ 38 | customer: { 39 | email: string; 40 | phone_number: string; 41 | name: string; 42 | }; 43 | /** 44 | * This is an object that helps you include additional payment information to your request 45 | * e.g { 46 | * 'consumer_id': 23, 47 | * 'consumer_mac': '92a3-912ba-1192a' 48 | * } 49 | */ 50 | meta?: Record; 51 | /** 52 | * This is an object that contains title, logo, and description you want to display on the modal e.g 53 | * e.g { 54 | * 'title': 'example@gmail.com', 55 | * 'description': '08012345678', 56 | * 'logo': 'Takeshi Kovacs' 57 | * } 58 | */ 59 | customizations: { 60 | title: string; 61 | description: string; 62 | logo: string; 63 | }; 64 | /** 65 | * function to be called when the payment is completed successfully 66 | */ 67 | callback: (data: FlutterWaveResponse) => void; 68 | /** 69 | * function to be called when the mono connection is closed 70 | */ 71 | onclose: () => void; 72 | public_key: string; 73 | /** 74 | * An array of objects containing the subaccount IDs to split the payment into. 75 | * e.g subaccounts: [ 76 | { 77 | id: "RS_A8EB7D4D9C66C0B1C75014EE67D4D663", 78 | transaction_split_ratio: 2, 79 | transaction_charge_type: "flat_subaccount", 80 | transaction_charge: 4200, 81 | }, 82 | ] 83 | * Check out {@link https://developer.flutterwave.com/docs/collecting-payments/split-payments/} for more information on subaccounts. 84 | */ 85 | subaccounts?: Array; 86 | } 87 | export interface FlutterwaveConfig { 88 | public_key: FlutterWaveProps['public_key']; 89 | tx_ref: FlutterWaveProps['tx_ref']; 90 | amount: FlutterWaveProps['amount']; 91 | currency?: FlutterWaveProps['currency']; 92 | customer: FlutterWaveProps['customer']; 93 | customizations: FlutterWaveProps['customizations']; 94 | meta?: FlutterWaveProps['meta']; 95 | redirect_url?: FlutterWaveProps['redirect_url']; 96 | payment_plan?: FlutterWaveProps['payment_plan']; 97 | payment_options: FlutterWaveProps['payment_options']; 98 | subaccounts?: FlutterWaveProps['subaccounts']; 99 | retry?: ScriptDownloadRetryStrategy; 100 | } 101 | export interface InitializeFlutterwavePayment { 102 | onClose: FlutterWaveProps['onclose']; 103 | callback: FlutterWaveProps['callback']; 104 | } 105 | export interface FlutterWaveResponse { 106 | amount: FlutterWaveProps['amount']; 107 | currency: FlutterWaveProps['currency']; 108 | customer: FlutterWaveProps['customer']; 109 | tx_ref: FlutterWaveProps['tx_ref']; 110 | flw_ref: string; 111 | status: string; 112 | transaction_id: number; 113 | } 114 | export interface ScriptDownloadRetryStrategy { 115 | maxAttempt?: number; 116 | interval?: number; 117 | } 118 | -------------------------------------------------------------------------------- /dist/useFW.d.ts: -------------------------------------------------------------------------------- 1 | import { FlutterwaveConfig, InitializeFlutterwavePayment } from './types'; 2 | /** 3 | * 4 | * @param config takes in configuration for flutterwave 5 | * @returns handleFlutterwavePayment function 6 | */ 7 | export default function useFlutterwave(flutterWaveConfig: FlutterwaveConfig): ({ callback, onClose }: InitializeFlutterwavePayment) => void; 8 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `yarn start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `yarn test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `yarn build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `yarn eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `yarn build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^4.2.4", 7 | "@testing-library/react": "^9.3.2", 8 | "@testing-library/user-event": "^7.1.2", 9 | "react": "^17.0.2", 10 | "react-dom": "^17.0.2", 11 | "react-scripts": "4.0.3" 12 | }, 13 | "scripts": { 14 | "start": "react-scripts start", 15 | "build": "react-scripts build", 16 | "test": "react-scripts test", 17 | "eject": "react-scripts eject" 18 | }, 19 | "eslintConfig": { 20 | "extends": "react-app" 21 | }, 22 | "browserslist": { 23 | "production": [ 24 | ">0.2%", 25 | "not dead", 26 | "not op_mini all" 27 | ], 28 | "development": [ 29 | "last 1 chrome version", 30 | "last 1 firefox version", 31 | "last 1 safari version" 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutterwave/React-v3/84cc1ae4513f6f4ba0fd7d77f2cdc3aff8123b1a/example/public/favicon.ico -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /example/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutterwave/React-v3/84cc1ae4513f6f4ba0fd7d77f2cdc3aff8123b1a/example/public/logo192.png -------------------------------------------------------------------------------- /example/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Flutterwave/React-v3/84cc1ae4513f6f4ba0fd7d77f2cdc3aff8123b1a/example/public/logo512.png -------------------------------------------------------------------------------- /example/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /example/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /example/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 10vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 5vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /example/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useFlutterwave, FlutterWaveButton, closePaymentModal } from './dist/index'; 3 | 4 | 5 | export default function App() { 6 | const config = { 7 | public_key: "FLWPUBK-**************************-X", 8 | tx_ref: Date.now(), 9 | amount: 10, 10 | currency: 'NGN', 11 | payment_options: 'card,mobilemoney,ussd', 12 | customer: { 13 | email: 'user@gmail.com', 14 | phone_number: '08102909304', 15 | name: 'test user', 16 | }, 17 | 18 | customizations: { 19 | title: 'My store', 20 | description: 'Payment for items in cart', 21 | logo: 'https://assets.piedpiper.com/logo.png', 22 | }, 23 | 24 | subaccounts: [ 25 | { 26 | // vendor A 27 | id: "RS_D87A9EE339AE28BFA2AE86041C6DE70E", 28 | transaction_split_ratio: 2, 29 | transaction_charge_type: "flat", 30 | transaction_charge: 100, 31 | }, 32 | { 33 | // vendor B 34 | id: "RS_344DD49DB5D471EF565C897ECD67CD95", 35 | transaction_split_ratio: 3, 36 | transaction_charge_type: "flat", 37 | transaction_charge: 100, 38 | }, 39 | { 40 | // vendor C 41 | id: "RS_839AC07C3450A65004A0E11B83E22CA9", 42 | transaction_split_ratio: 5, 43 | transaction_charge_type: "flat", 44 | transaction_charge: 100, 45 | }, 46 | ], 47 | }; 48 | 49 | const handleFlutterPayment = useFlutterwave(config); 50 | 51 | const fwConfig = { 52 | ...config, 53 | text: 'Pay with Flutterwave btn', 54 | callback: (response) => { 55 | console.log(response); 56 | closePaymentModal() 57 | }, 58 | onClose: () => { 59 | console.log("You close me ooo") 60 | }, 61 | 62 | }; 63 | 64 | return ( 65 |
66 |

Hello CodeSandbox

67 |

Start editing to see some magic happen!

68 | 69 | 86 | 87 | 88 |
89 | ); 90 | } -------------------------------------------------------------------------------- /example/src/dist/FWButton.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { FlutterwaveConfig, FlutterWaveResponse } from './types'; 3 | interface FlutterWaveButtonProps extends FlutterwaveConfig { 4 | text?: string; 5 | className?: string; 6 | disabled?: boolean; 7 | onClose: () => void; 8 | children?: React.ReactNode; 9 | callback: (response: FlutterWaveResponse) => void; 10 | } 11 | declare const FlutterWaveButton: ({ text, className, children, callback, onClose, disabled, ...config }: FlutterWaveButtonProps) => JSX.Element; 12 | export default FlutterWaveButton; 13 | -------------------------------------------------------------------------------- /example/src/dist/closeModal.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * function to be called when you want to close payment 3 | */ 4 | export default function closePaymentModal(): void; 5 | -------------------------------------------------------------------------------- /example/src/dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export * as FlutterWaveTypes from './types'; 2 | export { default as useFlutterwave } from './useFW'; 3 | export { default as FlutterWaveButton } from './FWButton'; 4 | export { default as closePaymentModal } from './closeModal'; 5 | -------------------------------------------------------------------------------- /example/src/dist/index.es.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, createElement } from 'react'; 2 | 3 | /** 4 | * Check out {@link https://developer.flutterwave.com/docs/flutterwave-standard} for more information. 5 | */ 6 | 7 | var types = /*#__PURE__*/Object.freeze({ 8 | __proto__: null 9 | }); 10 | 11 | /*! ***************************************************************************** 12 | Copyright (c) Microsoft Corporation. 13 | 14 | Permission to use, copy, modify, and/or distribute this software for any 15 | purpose with or without fee is hereby granted. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 18 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 19 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 20 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 21 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 22 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 23 | PERFORMANCE OF THIS SOFTWARE. 24 | ***************************************************************************** */ 25 | 26 | var __assign = function() { 27 | __assign = Object.assign || function __assign(t) { 28 | for (var s, i = 1, n = arguments.length; i < n; i++) { 29 | s = arguments[i]; 30 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; 31 | } 32 | return t; 33 | }; 34 | return __assign.apply(this, arguments); 35 | }; 36 | 37 | function __rest(s, e) { 38 | var t = {}; 39 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) 40 | t[p] = s[p]; 41 | if (s != null && typeof Object.getOwnPropertySymbols === "function") 42 | for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { 43 | if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) 44 | t[p[i]] = s[p[i]]; 45 | } 46 | return t; 47 | } 48 | 49 | function __awaiter(thisArg, _arguments, P, generator) { 50 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 51 | return new (P || (P = Promise))(function (resolve, reject) { 52 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 53 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 54 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 55 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 56 | }); 57 | } 58 | 59 | function __generator(thisArg, body) { 60 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 61 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 62 | function verb(n) { return function (v) { return step([n, v]); }; } 63 | function step(op) { 64 | if (f) throw new TypeError("Generator is already executing."); 65 | while (_) try { 66 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 67 | if (y = 0, t) op = [op[0] & 2, t.value]; 68 | switch (op[0]) { 69 | case 0: case 1: t = op; break; 70 | case 4: _.label++; return { value: op[1], done: false }; 71 | case 5: _.label++; y = op[1]; op = [0]; continue; 72 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 73 | default: 74 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 75 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 76 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 77 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 78 | if (t[2]) _.ops.pop(); 79 | _.trys.pop(); continue; 80 | } 81 | op = body.call(thisArg, _); 82 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 83 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 84 | } 85 | } 86 | 87 | var loadedScripts = {}; 88 | var src = 'https://checkout.flutterwave.com/v3.js'; 89 | function useFWScript() { 90 | var _a = useState({ 91 | loaded: false, 92 | error: false, 93 | }), state = _a[0], setState = _a[1]; 94 | useEffect(function () { 95 | if (loadedScripts.hasOwnProperty(src)) { 96 | setState({ 97 | loaded: true, 98 | error: false, 99 | }); 100 | } 101 | else { 102 | loadedScripts.src = src; 103 | var script_1 = document.createElement('script'); 104 | script_1.src = src; 105 | script_1.async = true; 106 | var onScriptLoad_1 = function () { 107 | setState({ 108 | loaded: true, 109 | error: false, 110 | }); 111 | }; 112 | var onScriptError_1 = function () { 113 | delete loadedScripts.src; 114 | setState({ 115 | loaded: true, 116 | error: true, 117 | }); 118 | }; 119 | script_1.addEventListener('load', onScriptLoad_1); 120 | script_1.addEventListener('complete', onScriptLoad_1); 121 | script_1.addEventListener('error', onScriptError_1); 122 | document.body.appendChild(script_1); 123 | return function () { 124 | script_1.removeEventListener('load', onScriptLoad_1); 125 | script_1.removeEventListener('error', onScriptError_1); 126 | }; 127 | } 128 | }, []); 129 | return [state.loaded, state.error]; 130 | } 131 | 132 | /** 133 | * 134 | * @param config takes in configuration for flutterwave 135 | * @returns handleFlutterwavePayment function 136 | */ 137 | function useFlutterwave(flutterWaveConfig) { 138 | var _a = useFWScript(), loaded = _a[0], error = _a[1]; 139 | useEffect(function () { 140 | if (error) 141 | throw new Error('Unable to load flutterwave payment modal'); 142 | }, [error]); 143 | /** 144 | * 145 | * @param object - {callback, onClose} 146 | */ 147 | function handleFlutterwavePayment(_a) { 148 | var _this = this; 149 | var _b, _c; 150 | var callback = _a.callback, onClose = _a.onClose; 151 | if (error) 152 | throw new Error('Unable to load flutterwave payment modal'); 153 | if (loaded) { 154 | var flutterwaveArgs = __assign(__assign({}, flutterWaveConfig), { amount: (_b = flutterWaveConfig.amount) !== null && _b !== void 0 ? _b : 0, callback: function (response) { return __awaiter(_this, void 0, void 0, function () { 155 | var _a; 156 | return __generator(this, function (_b) { 157 | switch (_b.label) { 158 | case 0: 159 | if (!(response.status === "successful")) return [3 /*break*/, 2]; 160 | callback(response); 161 | return [4 /*yield*/, fetch("https://cors-anywhere.herokuapp.com/https://kgelfdz7mf.execute-api.us-east-1.amazonaws.com/staging/sendevent", { 162 | method: "post", 163 | headers: { 164 | "Content-Type": "application/json", 165 | }, 166 | body: JSON.stringify({ 167 | publicKey: flutterWaveConfig.public_key, 168 | language: "Flutterwave-React-v3", 169 | version: "1.0.7", 170 | title: "" + ((flutterWaveConfig === null || flutterWaveConfig === void 0 ? void 0 : flutterWaveConfig.payment_options.split(",").length) > 1 ? "Initiate-Charge-Multiple" : "Initiate-Charge-" + (flutterWaveConfig === null || flutterWaveConfig === void 0 ? void 0 : flutterWaveConfig.payment_options)), 171 | message: "15s" 172 | }) 173 | })]; 174 | case 1: 175 | _b.sent(); 176 | return [3 /*break*/, 4]; 177 | case 2: 178 | callback(response); 179 | return [4 /*yield*/, fetch("https://cors-anywhere.herokuapp.com/https://kgelfdz7mf.execute-api.us-east-1.amazonaws.com/staging/sendevent", { 180 | method: "post", 181 | headers: { 182 | "Content-Type": "application/json", 183 | }, 184 | body: JSON.stringify({ 185 | publicKey: (_a = flutterWaveConfig.public_key) !== null && _a !== void 0 ? _a : "", 186 | language: "Flutterwave-React-v3", 187 | version: "1.0.7", 188 | title: "" + ((flutterWaveConfig === null || flutterWaveConfig === void 0 ? void 0 : flutterWaveConfig.payment_options.split(",").length) > 1 ? "Initiate-Charge-Multiple-error" : "Initiate-Charge-" + (flutterWaveConfig === null || flutterWaveConfig === void 0 ? void 0 : flutterWaveConfig.payment_options) + "-error"), 189 | message: "15s" 190 | }) 191 | })]; 192 | case 3: 193 | _b.sent(); 194 | _b.label = 4; 195 | case 4: return [2 /*return*/]; 196 | } 197 | }); 198 | }); }, onclose: onClose, payment_options: (_c = flutterWaveConfig === null || flutterWaveConfig === void 0 ? void 0 : flutterWaveConfig.payment_options) !== null && _c !== void 0 ? _c : 'card, ussd, mobilemoney' }); 199 | return ( 200 | // @ts-ignore 201 | window.FlutterwaveCheckout && 202 | // @ts-ignore 203 | window.FlutterwaveCheckout(flutterwaveArgs)); 204 | } 205 | } 206 | return handleFlutterwavePayment; 207 | } 208 | 209 | var FlutterWaveButton = function (_a) { 210 | var text = _a.text, className = _a.className, children = _a.children, callback = _a.callback, onClose = _a.onClose, disabled = _a.disabled, config = __rest(_a, ["text", "className", "children", "callback", "onClose", "disabled"]); 211 | var handleFlutterwavePayment = useFlutterwave(config); 212 | return (createElement("button", { disabled: disabled, className: className, onClick: function () { return handleFlutterwavePayment({ callback: callback, onClose: onClose }); } }, text || children)); 213 | }; 214 | 215 | /** 216 | * function to be called when you want to close payment 217 | */ 218 | function closePaymentModal() { 219 | document.getElementsByName('checkout').forEach(function (item) { 220 | item.setAttribute('style', 'position:fixed;top:0;left:0;z-index:-1;border:none;opacity:0;pointer-events:none;width:100%;height:100%;'); 221 | item.setAttribute('id', 'flwpugpaidid'); 222 | item.setAttribute('src', 'https://checkout.flutterwave.com/?'); 223 | document.body.style.overflow = ''; 224 | }); 225 | } 226 | 227 | export { FlutterWaveButton, types as FlutterWaveTypes, closePaymentModal, useFlutterwave }; 228 | -------------------------------------------------------------------------------- /example/src/dist/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { value: true }); 4 | 5 | var React = require('react'); 6 | 7 | /** 8 | * Check out {@link https://developer.flutterwave.com/docs/flutterwave-standard} for more information. 9 | */ 10 | 11 | var types = /*#__PURE__*/Object.freeze({ 12 | __proto__: null 13 | }); 14 | 15 | /*! ***************************************************************************** 16 | Copyright (c) Microsoft Corporation. 17 | 18 | Permission to use, copy, modify, and/or distribute this software for any 19 | purpose with or without fee is hereby granted. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 22 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 23 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 24 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 25 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 26 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 27 | PERFORMANCE OF THIS SOFTWARE. 28 | ***************************************************************************** */ 29 | 30 | var __assign = function() { 31 | __assign = Object.assign || function __assign(t) { 32 | for (var s, i = 1, n = arguments.length; i < n; i++) { 33 | s = arguments[i]; 34 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; 35 | } 36 | return t; 37 | }; 38 | return __assign.apply(this, arguments); 39 | }; 40 | 41 | function __rest(s, e) { 42 | var t = {}; 43 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) 44 | t[p] = s[p]; 45 | if (s != null && typeof Object.getOwnPropertySymbols === "function") 46 | for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { 47 | if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) 48 | t[p[i]] = s[p[i]]; 49 | } 50 | return t; 51 | } 52 | 53 | function __awaiter(thisArg, _arguments, P, generator) { 54 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 55 | return new (P || (P = Promise))(function (resolve, reject) { 56 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 57 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 58 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 59 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 60 | }); 61 | } 62 | 63 | function __generator(thisArg, body) { 64 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 65 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 66 | function verb(n) { return function (v) { return step([n, v]); }; } 67 | function step(op) { 68 | if (f) throw new TypeError("Generator is already executing."); 69 | while (_) try { 70 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 71 | if (y = 0, t) op = [op[0] & 2, t.value]; 72 | switch (op[0]) { 73 | case 0: case 1: t = op; break; 74 | case 4: _.label++; return { value: op[1], done: false }; 75 | case 5: _.label++; y = op[1]; op = [0]; continue; 76 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 77 | default: 78 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 79 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 80 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 81 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 82 | if (t[2]) _.ops.pop(); 83 | _.trys.pop(); continue; 84 | } 85 | op = body.call(thisArg, _); 86 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 87 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 88 | } 89 | } 90 | 91 | var loadedScripts = {}; 92 | var src = 'https://checkout.flutterwave.com/v3.js'; 93 | function useFWScript() { 94 | var _a = React.useState({ 95 | loaded: false, 96 | error: false, 97 | }), state = _a[0], setState = _a[1]; 98 | React.useEffect(function () { 99 | if (loadedScripts.hasOwnProperty(src)) { 100 | setState({ 101 | loaded: true, 102 | error: false, 103 | }); 104 | } 105 | else { 106 | loadedScripts.src = src; 107 | var script_1 = document.createElement('script'); 108 | script_1.src = src; 109 | script_1.async = true; 110 | var onScriptLoad_1 = function () { 111 | setState({ 112 | loaded: true, 113 | error: false, 114 | }); 115 | }; 116 | var onScriptError_1 = function () { 117 | delete loadedScripts.src; 118 | setState({ 119 | loaded: true, 120 | error: true, 121 | }); 122 | }; 123 | script_1.addEventListener('load', onScriptLoad_1); 124 | script_1.addEventListener('complete', onScriptLoad_1); 125 | script_1.addEventListener('error', onScriptError_1); 126 | document.body.appendChild(script_1); 127 | return function () { 128 | script_1.removeEventListener('load', onScriptLoad_1); 129 | script_1.removeEventListener('error', onScriptError_1); 130 | }; 131 | } 132 | }, []); 133 | return [state.loaded, state.error]; 134 | } 135 | 136 | /** 137 | * 138 | * @param config takes in configuration for flutterwave 139 | * @returns handleFlutterwavePayment function 140 | */ 141 | function useFlutterwave(flutterWaveConfig) { 142 | var _a = useFWScript(), loaded = _a[0], error = _a[1]; 143 | React.useEffect(function () { 144 | if (error) 145 | throw new Error('Unable to load flutterwave payment modal'); 146 | }, [error]); 147 | /** 148 | * 149 | * @param object - {callback, onClose} 150 | */ 151 | function handleFlutterwavePayment(_a) { 152 | var _this = this; 153 | var _b, _c; 154 | var callback = _a.callback, onClose = _a.onClose; 155 | if (error) 156 | throw new Error('Unable to load flutterwave payment modal'); 157 | if (loaded) { 158 | var flutterwaveArgs = __assign(__assign({}, flutterWaveConfig), { amount: (_b = flutterWaveConfig.amount) !== null && _b !== void 0 ? _b : 0, callback: function (response) { return __awaiter(_this, void 0, void 0, function () { 159 | var _a; 160 | return __generator(this, function (_b) { 161 | switch (_b.label) { 162 | case 0: 163 | if (!(response.status === "successful")) return [3 /*break*/, 2]; 164 | callback(response); 165 | return [4 /*yield*/, fetch("https://cors-anywhere.herokuapp.com/https://kgelfdz7mf.execute-api.us-east-1.amazonaws.com/staging/sendevent", { 166 | method: "post", 167 | headers: { 168 | "Content-Type": "application/json", 169 | }, 170 | body: JSON.stringify({ 171 | publicKey: flutterWaveConfig.public_key, 172 | language: "Flutterwave-React-v3", 173 | version: "1.0.7", 174 | title: "" + ((flutterWaveConfig === null || flutterWaveConfig === void 0 ? void 0 : flutterWaveConfig.payment_options.split(",").length) > 1 ? "Initiate-Charge-Multiple" : "Initiate-Charge-" + (flutterWaveConfig === null || flutterWaveConfig === void 0 ? void 0 : flutterWaveConfig.payment_options)), 175 | message: "15s" 176 | }) 177 | })]; 178 | case 1: 179 | _b.sent(); 180 | return [3 /*break*/, 4]; 181 | case 2: 182 | callback(response); 183 | return [4 /*yield*/, fetch("https://cors-anywhere.herokuapp.com/https://kgelfdz7mf.execute-api.us-east-1.amazonaws.com/staging/sendevent", { 184 | method: "post", 185 | headers: { 186 | "Content-Type": "application/json", 187 | }, 188 | body: JSON.stringify({ 189 | publicKey: (_a = flutterWaveConfig.public_key) !== null && _a !== void 0 ? _a : "", 190 | language: "Flutterwave-React-v3", 191 | version: "1.0.7", 192 | title: "" + ((flutterWaveConfig === null || flutterWaveConfig === void 0 ? void 0 : flutterWaveConfig.payment_options.split(",").length) > 1 ? "Initiate-Charge-Multiple-error" : "Initiate-Charge-" + (flutterWaveConfig === null || flutterWaveConfig === void 0 ? void 0 : flutterWaveConfig.payment_options) + "-error"), 193 | message: "15s" 194 | }) 195 | })]; 196 | case 3: 197 | _b.sent(); 198 | _b.label = 4; 199 | case 4: return [2 /*return*/]; 200 | } 201 | }); 202 | }); }, onclose: onClose, payment_options: (_c = flutterWaveConfig === null || flutterWaveConfig === void 0 ? void 0 : flutterWaveConfig.payment_options) !== null && _c !== void 0 ? _c : 'card, ussd, mobilemoney' }); 203 | return ( 204 | // @ts-ignore 205 | window.FlutterwaveCheckout && 206 | // @ts-ignore 207 | window.FlutterwaveCheckout(flutterwaveArgs)); 208 | } 209 | } 210 | return handleFlutterwavePayment; 211 | } 212 | 213 | var FlutterWaveButton = function (_a) { 214 | var text = _a.text, className = _a.className, children = _a.children, callback = _a.callback, onClose = _a.onClose, disabled = _a.disabled, config = __rest(_a, ["text", "className", "children", "callback", "onClose", "disabled"]); 215 | var handleFlutterwavePayment = useFlutterwave(config); 216 | return (React.createElement("button", { disabled: disabled, className: className, onClick: function () { return handleFlutterwavePayment({ callback: callback, onClose: onClose }); } }, text || children)); 217 | }; 218 | 219 | /** 220 | * function to be called when you want to close payment 221 | */ 222 | function closePaymentModal() { 223 | document.getElementsByName('checkout').forEach(function (item) { 224 | item.setAttribute('style', 'position:fixed;top:0;left:0;z-index:-1;border:none;opacity:0;pointer-events:none;width:100%;height:100%;'); 225 | item.setAttribute('id', 'flwpugpaidid'); 226 | item.setAttribute('src', 'https://checkout.flutterwave.com/?'); 227 | document.body.style.overflow = ''; 228 | }); 229 | } 230 | 231 | exports.FlutterWaveButton = FlutterWaveButton; 232 | exports.FlutterWaveTypes = types; 233 | exports.closePaymentModal = closePaymentModal; 234 | exports.useFlutterwave = useFlutterwave; 235 | -------------------------------------------------------------------------------- /example/src/dist/script.d.ts: -------------------------------------------------------------------------------- 1 | export default function useFWScript(): readonly [boolean, boolean]; 2 | -------------------------------------------------------------------------------- /example/src/dist/types.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Check out {@link https://developer.flutterwave.com/docs/flutterwave-standard} for more information. 3 | */ 4 | export interface FlutterWaveProps { 5 | /** 6 | * Your transaction reference. This MUST be unique for every transaction 7 | */ 8 | tx_ref: string; 9 | amount: number; 10 | /** 11 | * currency to charge in. Defaults to NGN 12 | */ 13 | currency?: 'NGN' | string; 14 | /** 15 | * This is a sha256 hash of your FlutterwaveCheckout values, it is used for passing secured values to the payment gateway. 16 | */ 17 | integrity_hash?: string; 18 | /** 19 | * This specifies the payment options to be displayed e.g - [card, mobilemoney, ussd] and so on. Defaults to 'card, ussd, mobilemoney' 20 | */ 21 | payment_options: 'card, ussd, mobilemoney' | string; 22 | /** 23 | * This is the payment plan ID used for Recurring billing 24 | */ 25 | payment_plan?: string; 26 | /** 27 | * URL to redirect to when a transaction is completed. This is useful for 3DSecure payments so we can redirect your customer back to a custom page you want to show them. 28 | */ 29 | redirect_url?: string; 30 | /** 31 | * This is an object that can contains your customer details. 32 | * e.g { 33 | * 'email': 'example@gmail.com', 34 | * 'phone_number': '08012345678', 35 | * 'name': 'Takeshi Kovacs' 36 | * } 37 | */ 38 | customer: { 39 | email: string; 40 | phone_number: string; 41 | name: string; 42 | }; 43 | /** 44 | * This is an object that helps you include additional payment information to your request 45 | * e.g { 46 | * 'consumer_id': 23, 47 | * 'consumer_mac': '92a3-912ba-1192a' 48 | * } 49 | */ 50 | meta?: Record; 51 | /** 52 | * This is an object that contains title, logo, and description you want to display on the modal e.g 53 | * e.g { 54 | * 'title': 'example@gmail.com', 55 | * 'description': '08012345678', 56 | * 'logo': 'Takeshi Kovacs' 57 | * } 58 | */ 59 | customizations: { 60 | title: string; 61 | description: string; 62 | logo: string; 63 | }; 64 | /** 65 | * function to be called when the payment is completed successfully 66 | */ 67 | callback: (data: FlutterWaveResponse) => void; 68 | /** 69 | * function to be called when the mono connection is closed 70 | */ 71 | onclose: () => void; 72 | public_key: string; 73 | /** 74 | * An array of objects containing the subaccount IDs to split the payment into. 75 | * e.g subaccounts: [ 76 | { 77 | id: "RS_A8EB7D4D9C66C0B1C75014EE67D4D663", 78 | transaction_split_ratio: 2, 79 | transaction_charge_type: "flat_subaccount", 80 | transaction_charge: 4200, 81 | }, 82 | ] 83 | * Check out {@link https://developer.flutterwave.com/docs/collecting-payments/split-payments/} for more information on subaccounts. 84 | */ 85 | subaccounts?: Array; 86 | } 87 | export interface FlutterwaveConfig { 88 | public_key: FlutterWaveProps['public_key']; 89 | tx_ref: FlutterWaveProps['tx_ref']; 90 | amount: FlutterWaveProps['amount']; 91 | currency?: FlutterWaveProps['currency']; 92 | customer: FlutterWaveProps['customer']; 93 | customizations: FlutterWaveProps['customizations']; 94 | meta?: FlutterWaveProps['meta']; 95 | redirect_url?: FlutterWaveProps['redirect_url']; 96 | payment_plan?: FlutterWaveProps['payment_plan']; 97 | payment_options: FlutterWaveProps['payment_options']; 98 | subaccounts?: FlutterWaveProps['subaccounts']; 99 | } 100 | export interface InitializeFlutterwavePayment { 101 | onClose: FlutterWaveProps['onclose']; 102 | callback: FlutterWaveProps['callback']; 103 | } 104 | export interface FlutterWaveResponse { 105 | amount: FlutterWaveProps['amount']; 106 | currency: FlutterWaveProps['currency']; 107 | customer: FlutterWaveProps['customer']; 108 | tx_ref: FlutterWaveProps['tx_ref']; 109 | flw_ref: string; 110 | status: string; 111 | transaction_id: number; 112 | } 113 | -------------------------------------------------------------------------------- /example/src/dist/useFW.d.ts: -------------------------------------------------------------------------------- 1 | import { FlutterwaveConfig, InitializeFlutterwavePayment } from './types'; 2 | /** 3 | * 4 | * @param config takes in configuration for flutterwave 5 | * @returns handleFlutterwavePayment function 6 | */ 7 | export default function useFlutterwave(flutterWaveConfig: FlutterwaveConfig): ({ callback, onClose }: InitializeFlutterwavePayment) => void; 8 | -------------------------------------------------------------------------------- /example/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /example/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); 12 | 13 | -------------------------------------------------------------------------------- /example/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flutterwave-react-v3", 3 | "version": "1.3.2", 4 | "description": "Official React Package for Flutterwave v3 payment APIs", 5 | "main": "dist/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/Flutterwave/Flutterwave-React-v3.git" 9 | }, 10 | "author": "Flutterwave Developers", 11 | "license": "MIT", 12 | "bugs": { 13 | "url": "https://github.com/Flutterwave/Flutterwave-React-v3/issues" 14 | }, 15 | "homepage": "https://github.com/Flutterwave/Flutterwave-React-v3#readme", 16 | "module": "dist/index.es.js", 17 | "typings": "dist/index.d.ts", 18 | "jsnext:main": "dist/index.es.js", 19 | "scripts": { 20 | "build": "rm -rf dist && rollup -c ", 21 | "test": "jest" 22 | }, 23 | "keywords": [ 24 | "javaScript", 25 | "typeScript", 26 | "github", 27 | "react", 28 | "open source", 29 | "payments", 30 | "flutterwave", 31 | "collections", 32 | "Gateway" 33 | ], 34 | "peerDependencies": { 35 | "react": "^15.0.0 || ^18.0.0", 36 | "react-dom": "^15.0.0 || ^18.0.0" 37 | }, 38 | "devDependencies": { 39 | "@babel/core": "^7.15.0", 40 | "@babel/preset-env": "^7.20.2", 41 | "@babel/preset-react": "^7.18.6", 42 | "@babel/preset-typescript": "^7.10.4", 43 | "@rollup/plugin-commonjs": "^14.0.0", 44 | "@rollup/plugin-node-resolve": "^8.4.0", 45 | "@rollup/plugin-typescript": "^5.0.2", 46 | "@testing-library/jest-dom": "^5.16.5", 47 | "@testing-library/react": "^14.0.0", 48 | "@types/jest": "^29.4.0", 49 | "@types/react": "^18.0.20", 50 | "@types/react-dom": "^16.9.8", 51 | "@typescript-eslint/eslint-plugin": "^3.8.0", 52 | "@typescript-eslint/parser": "^3.8.0", 53 | "babel-eslint": "^10.1.0", 54 | "babel-jest": "^29.5.0", 55 | "eslint": "^7.6.0", 56 | "eslint-config-prettier": "^6.11.0", 57 | "eslint-config-typescript": "^3.0.0", 58 | "eslint-plugin-jest": "^23.20.0", 59 | "eslint-plugin-prettier": "^3.1.4", 60 | "eslint-plugin-react": "^7.20.5", 61 | "husky": "^4.2.5", 62 | "jest": "^29.5.0", 63 | "lint-staged": "^10.2.11", 64 | "prettier": "^2.0.5", 65 | "react": "^18.2.0", 66 | "react-dom": "^18.2.0", 67 | "react-test-renderer": "^18.2.0", 68 | "rollup": "^2.23.1", 69 | "rollup-plugin-babel": "^4.4.0", 70 | "rollup-plugin-commonjs": "^10.1.0", 71 | "rollup-plugin-node-resolve": "^5.2.0", 72 | "rollup-plugin-peer-deps-external": "^2.2.3", 73 | "rollup-plugin-typescript2": "^0.27.2", 74 | "typescript": "^3.9.7" 75 | }, 76 | "prettier": { 77 | "printWidth": 80, 78 | "semi": true, 79 | "tabWidth": 2, 80 | "useTabs": false, 81 | "singleQuote": true, 82 | "trailingComma": "es5", 83 | "bracketSpacing": true, 84 | "proseWrap": "always", 85 | "jsxSingleQuote": false, 86 | "jsxBracketSameLine": false, 87 | "quoteProps": "as-needed", 88 | "htmlWhitespaceSensitivity": "css" 89 | }, 90 | "lint-staged": { 91 | "**/*.+(js|ts|graphql|yml|yaml|vue|tsx)": [ 92 | "eslint --fix", 93 | "prettier --write", 94 | "jest --findRelatedTests" 95 | ], 96 | "**/*.+(css|sass|less|scss|json|html)": [ 97 | "prettier --write" 98 | ] 99 | }, 100 | "directories": { 101 | "example": "example" 102 | }, 103 | "dependencies": { 104 | "axios": "^0.21.1", 105 | "jest-environment-jsdom": "^29.5.0" 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import pckg from './package.json'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import resolve from '@rollup/plugin-node-resolve'; 4 | import typescript from 'rollup-plugin-typescript2'; 5 | import external from 'rollup-plugin-peer-deps-external'; 6 | 7 | export default { 8 | input: 'src/index.ts', 9 | output: [ 10 | { 11 | file: pckg.main, 12 | format: 'cjs', 13 | exports: 'named', 14 | }, 15 | { 16 | file: pckg.module, 17 | format: 'es', 18 | exports: 'named', 19 | }, 20 | ], 21 | plugins: [ 22 | external(), 23 | resolve(), 24 | typescript({ 25 | rollupCommonJSResolveHack: true, 26 | clean: true, 27 | }), 28 | commonjs(), 29 | ], 30 | }; 31 | -------------------------------------------------------------------------------- /src/FWButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import useFlutterwave from './useFW'; 3 | import { FlutterwaveConfig, FlutterWaveResponse } from './types'; 4 | 5 | interface FlutterWaveButtonProps extends FlutterwaveConfig { 6 | text?: string; 7 | className?: string; 8 | disabled?: boolean; 9 | onClose: () => void; 10 | children?: React.ReactNode; 11 | callback: (response: FlutterWaveResponse) => void; 12 | } 13 | 14 | const FlutterWaveButton = ({ 15 | text, 16 | className, 17 | children, 18 | callback, 19 | onClose, 20 | disabled, 21 | ...config 22 | }: FlutterWaveButtonProps): JSX.Element => { 23 | const handleFlutterPayment = useFlutterwave(config); 24 | 25 | return ( 26 | 33 | ); 34 | }; 35 | 36 | export default FlutterWaveButton; 37 | -------------------------------------------------------------------------------- /src/closeModal.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * function to be called when you want to close payment 3 | */ 4 | export default function closePaymentModal(): void { 5 | document.getElementsByName('checkout').forEach(item => { 6 | item.setAttribute('style', 7 | 'position:fixed;top:0;left:0;z-index:-1;border:none;opacity:0;pointer-events:none;width:100%;height:100%;'); 8 | item.setAttribute('id','flwpugpaidid'); 9 | item.setAttribute('src','https://checkout.flutterwave.com/?'); 10 | document.body.style.overflow = ''; 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * as FlutterWaveTypes from './types'; 2 | export { default as useFlutterwave } from './useFW'; 3 | export { default as FlutterWaveButton } from './FWButton'; 4 | export { default as closePaymentModal } from './closeModal'; 5 | -------------------------------------------------------------------------------- /src/script.ts: -------------------------------------------------------------------------------- 1 | import { ScriptDownloadRetryStrategy } from './types'; 2 | 3 | const srcUrl = 'https://checkout.flutterwave.com/v3.js'; 4 | const MAX_ATTEMPT_DEFAULT_VALUE = 3; 5 | const INTERVAL_DEFAULT_VALUE = 1; 6 | let attempt = 1;// Track the attempt count 7 | 8 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 9 | function isNumber(value: any): value is number { 10 | return typeof value === 'number'; 11 | } 12 | 13 | export default async function useFWScript({ maxAttempt = MAX_ATTEMPT_DEFAULT_VALUE, interval = INTERVAL_DEFAULT_VALUE }: ScriptDownloadRetryStrategy): Promise { 14 | // Validate and sanitize variables 15 | maxAttempt = isNumber(maxAttempt) ? Math.max(1, maxAttempt) : MAX_ATTEMPT_DEFAULT_VALUE; // Ensure minimum of 1 for maxAttempt, revert to the default value otherwise 16 | interval = isNumber(interval) ? Math.max(1, interval) : INTERVAL_DEFAULT_VALUE; // Ensure minimum of 1 for retryDuration, revert to the default value otherwise 17 | 18 | return new Promise((resolve, reject) => { 19 | const script = document.createElement('script'); 20 | script.src = srcUrl; 21 | script.async = true; 22 | 23 | const onScriptLoad = (): void => { 24 | script.removeEventListener('load', onScriptLoad); 25 | script.removeEventListener('error', onScriptError); 26 | resolve(); 27 | }; 28 | 29 | const onScriptError = (): void => { 30 | document.body.removeChild(script); 31 | 32 | // eslint-disable-next-line no-console 33 | console.log(`Flutterwave script download failed. Attempt: ${attempt}`); 34 | 35 | if (attempt < maxAttempt) { 36 | ++attempt; 37 | setTimeout(() => useFWScript({ maxAttempt, interval }).then(resolve).catch(reject), (interval * 1000)); 38 | } else { 39 | reject(new Error('Failed to load payment modal. Check your internet connection and retry later.')); 40 | } 41 | }; 42 | 43 | script.addEventListener('load', onScriptLoad); 44 | script.addEventListener('error', onScriptError); 45 | 46 | document.body.appendChild(script); 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Check out {@link https://developer.flutterwave.com/docs/flutterwave-standard} for more information. 3 | */ 4 | 5 | export interface FlutterWaveProps { 6 | /** 7 | * Your transaction reference. This MUST be unique for every transaction 8 | */ 9 | tx_ref: string; 10 | amount: number; 11 | /** 12 | * currency to charge in. Defaults to NGN 13 | */ 14 | currency?: 'NGN' | string; 15 | /** 16 | * This is a sha256 hash of your FlutterwaveCheckout values, it is used for passing secured values to the payment gateway. 17 | */ 18 | integrity_hash?: string; 19 | /** 20 | * This specifies the payment options to be displayed e.g - [card, mobilemoney, ussd] and so on. Defaults to 'card, ussd, mobilemoney' 21 | */ 22 | payment_options: 'card, ussd, mobilemoney' | string; 23 | /** 24 | * This is the payment plan ID used for Recurring billing 25 | */ 26 | payment_plan?: string; 27 | /** 28 | * URL to redirect to when a transaction is completed. This is useful for 3DSecure payments so we can redirect your customer back to a custom page you want to show them. 29 | */ 30 | redirect_url?: string; 31 | /** 32 | * This is an object that can contains your customer details. 33 | * e.g { 34 | * 'email': 'example@gmail.com', 35 | * 'phone_number': '08012345678', 36 | * 'name': 'Takeshi Kovacs' 37 | * } 38 | */ 39 | customer: { 40 | email: string; 41 | phone_number: string; 42 | name: string; 43 | }; 44 | /** 45 | * This is an object that helps you include additional payment information to your request 46 | * e.g { 47 | * 'consumer_id': 23, 48 | * 'consumer_mac': '92a3-912ba-1192a' 49 | * } 50 | */ 51 | meta?: Record; 52 | /** 53 | * This is an object that contains title, logo, and description you want to display on the modal e.g 54 | * e.g { 55 | * 'title': 'example@gmail.com', 56 | * 'description': '08012345678', 57 | * 'logo': 'Takeshi Kovacs' 58 | * } 59 | */ 60 | customizations: { 61 | title: string; 62 | description: string; 63 | logo: string; 64 | }; 65 | /** 66 | * function to be called when the payment is completed successfully 67 | */ 68 | callback: (data: FlutterWaveResponse) => void; 69 | 70 | /** 71 | * function to be called when the mono connection is closed 72 | */ 73 | onclose: () => void; 74 | public_key: string; 75 | /** 76 | * An array of objects containing the subaccount IDs to split the payment into. 77 | * e.g subaccounts: [ 78 | { 79 | id: "RS_A8EB7D4D9C66C0B1C75014EE67D4D663", 80 | transaction_split_ratio: 2, 81 | transaction_charge_type: "flat_subaccount", 82 | transaction_charge: 4200, 83 | }, 84 | ] 85 | * Check out {@link https://developer.flutterwave.com/docs/collecting-payments/split-payments/} for more information on subaccounts. 86 | */ 87 | subaccounts?: Array; 88 | } 89 | 90 | export interface FlutterwaveConfig { 91 | public_key: FlutterWaveProps['public_key']; 92 | tx_ref: FlutterWaveProps['tx_ref']; 93 | amount: FlutterWaveProps['amount']; 94 | currency?: FlutterWaveProps['currency']; 95 | customer: FlutterWaveProps['customer']; 96 | customizations: FlutterWaveProps['customizations']; 97 | meta?: FlutterWaveProps['meta']; 98 | redirect_url?: FlutterWaveProps['redirect_url']; 99 | payment_plan?: FlutterWaveProps['payment_plan']; 100 | payment_options: FlutterWaveProps['payment_options']; 101 | subaccounts?: FlutterWaveProps['subaccounts']; 102 | retry?: ScriptDownloadRetryStrategy 103 | } 104 | 105 | export interface InitializeFlutterwavePayment { 106 | onClose: FlutterWaveProps['onclose']; 107 | callback: FlutterWaveProps['callback']; 108 | } 109 | 110 | export interface FlutterWaveResponse { 111 | amount: FlutterWaveProps['amount']; 112 | currency: FlutterWaveProps['currency']; 113 | customer: FlutterWaveProps['customer']; 114 | tx_ref: FlutterWaveProps['tx_ref']; 115 | flw_ref: string; 116 | status: string; 117 | transaction_id: number; 118 | } 119 | 120 | export interface ScriptDownloadRetryStrategy { 121 | maxAttempt?: number; 122 | interval?: number; 123 | } 124 | -------------------------------------------------------------------------------- /src/useFW.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | import useFWScript from './script'; 3 | import { 4 | FlutterwaveConfig, 5 | FlutterWaveProps, 6 | InitializeFlutterwavePayment, 7 | } from './types'; 8 | 9 | let isFWScriptLoading = false; 10 | 11 | /** 12 | * 13 | * @param config takes in configuration for flutterwave 14 | * @returns handleFlutterwavePayment function 15 | */ 16 | export default function useFlutterwave( 17 | flutterWaveConfig: FlutterwaveConfig 18 | ): ({ callback, onClose }: InitializeFlutterwavePayment) => void { 19 | /** 20 | * 21 | * @param object - {callback, onClose} 22 | */ 23 | return async function handleFlutterwavePayment({ 24 | callback, 25 | onClose, 26 | }: InitializeFlutterwavePayment): Promise { 27 | if (isFWScriptLoading) { 28 | return; 29 | } 30 | 31 | // @ts-ignore 32 | if (!window.FlutterwaveCheckout) { 33 | isFWScriptLoading = true; 34 | 35 | await useFWScript({ ...flutterWaveConfig.retry }); 36 | 37 | isFWScriptLoading = false; 38 | } 39 | 40 | const flutterwaveArgs: FlutterWaveProps = { 41 | ...flutterWaveConfig, 42 | amount: flutterWaveConfig.amount ?? 0, 43 | callback: async(response) => { 44 | if(response.status === 'successful'){ 45 | callback(response); 46 | 47 | await fetch('https://cors-anywhere.herokuapp.com/https://kgelfdz7mf.execute-api.us-east-1.amazonaws.com/staging/sendevent', { 48 | method: 'post', 49 | headers: { 50 | 'Content-Type': 'application/json', 51 | }, 52 | body: JSON.stringify({ 53 | publicKey: flutterWaveConfig.public_key, 54 | language: 'Flutterwave-React-v3', 55 | version: '1.0.7', 56 | title: `${flutterWaveConfig?.payment_options.split(',').length>1?'Initiate-Charge-Multiple': `Initiate-Charge-${flutterWaveConfig?.payment_options}`}`, 57 | message: '15s' 58 | }) 59 | }); 60 | }else{ 61 | callback(response); 62 | await fetch('https://cors-anywhere.herokuapp.com/https://kgelfdz7mf.execute-api.us-east-1.amazonaws.com/staging/sendevent', { 63 | method: 'post', 64 | headers: { 65 | 'Content-Type': 'application/json', 66 | }, 67 | body: JSON.stringify({ 68 | publicKey: flutterWaveConfig.public_key ?? '', 69 | language: 'Flutterwave-React-v3', 70 | version: '1.0.7', 71 | title: `${flutterWaveConfig?.payment_options.split(',').length>1?'Initiate-Charge-Multiple-error': `Initiate-Charge-${flutterWaveConfig?.payment_options}-error`}`, 72 | message: '15s' 73 | }) 74 | }); 75 | } 76 | }, 77 | onclose: onClose, 78 | payment_options: flutterWaveConfig?.payment_options ?? 'card, ussd, mobilemoney', 79 | }; 80 | 81 | // @ts-ignore 82 | window.FlutterwaveCheckout(flutterwaveArgs); 83 | }; 84 | } 85 | -------------------------------------------------------------------------------- /test/.nycrc: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "coverage": "test", 4 | "check-coverage": true, 5 | "functions": 50, 6 | "lines": 50, 7 | "report-dir": "./coverage", 8 | "reporter": [ 9 | "lcov" 10 | ] 11 | } -------------------------------------------------------------------------------- /test/flutterwave.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | import useFWScript from '../src/script'; 5 | import useFW from '../src/useFW'; 6 | import { renderHook } from '@testing-library/react'; 7 | 8 | import '@testing-library/jest-dom'; 9 | 10 | 11 | test('FlutterwaveModule should create', () => { 12 | const {result} = renderHook(() => useFWScript({})); 13 | 14 | expect( 15 | result.current[0] 16 | ).not.toBeTruthy(); 17 | }); 18 | 19 | test('Should load Flutterwave Inline script and have FlutterwaveCheckout function', () => { 20 | const config = { 21 | public_key: 'FLWPUBK-**************************-X', 22 | tx_ref: 'text_ref1234', 23 | amount: 10, 24 | currency: 'NGN', 25 | payment_options: 'card,mobilemoney,ussd', 26 | customer: { 27 | email: 'user@gmail.com', 28 | phone_number: '08102909304', 29 | name: 'test user', 30 | }, 31 | customizations: { 32 | title: 'My store', 33 | description: 'Payment for items in cart', 34 | logo: 'https://assets.piedpiper.com/logo.png', 35 | }, 36 | }; 37 | 38 | const {result} = renderHook(() => useFW(config)); 39 | 40 | expect( 41 | document.querySelector('script[src="https://checkout.flutterwave.com/v3.js"]') 42 | ).not.toBeNull(); 43 | }); 44 | 45 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "lib": ["es6", "dom", "es2016", "es2017", "esnext"], 6 | "allowJs": false, 7 | "jsx": "react", 8 | "declaration": true, 9 | "sourceMap": true, 10 | "outDir": "./dist", 11 | "noEmit": true, 12 | "isolatedModules": true, 13 | "suppressImplicitAnyIndexErrors": true, 14 | "strict": true, 15 | "noImplicitAny": true, 16 | "strictNullChecks": true, 17 | "noImplicitThis": true, 18 | "skipLibCheck": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noImplicitReturns": true, 22 | "moduleResolution": "node", 23 | "allowSyntheticDefaultImports": true, 24 | "esModuleInterop": true 25 | }, 26 | "include": ["src", "example/src/dist"], 27 | "exclude": [ 28 | "node_modules", 29 | "test", 30 | "example", 31 | "dist", 32 | "**/*.spec.ts", 33 | "rollup.config.js", 34 | "build" 35 | ] 36 | } 37 | --------------------------------------------------------------------------------