From c13f949921667d3e8f8e3c6dcbd6426efde06ccb Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Wed, 15 Jul 2020 10:53:52 +0100 Subject: [PATCH 001/129] feat(configs.ts): point to v3 standard init endpoint --- src/configs.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/configs.ts b/src/configs.ts index 793308e..26bbc01 100644 --- a/src/configs.ts +++ b/src/configs.ts @@ -1,8 +1,7 @@ /** * Flutterwaves standard init url. */ -export const STANDARD_URL: string = - 'https://api.ravepay.co/flwv3-pug/getpaidx/api/v2/hosted/pay'; +export const STANDARD_URL = 'https://api.flutterwave.com/v3/payments'; export const colors = { primary: '#f5a623', From 3e3ca84a4ea72cee3ddeb8090036ecbd7066ca07 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Wed, 15 Jul 2020 10:55:00 +0100 Subject: [PATCH 002/129] feat(configs.ts): update payment options with v3 payment options --- src/configs.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/configs.ts b/src/configs.ts index 26bbc01..e01eb39 100644 --- a/src/configs.ts +++ b/src/configs.ts @@ -10,17 +10,21 @@ export const colors = { }; export const PAYMENT_OPTIONS = [ - 'card', 'account', - 'ussd', - 'qr', + 'card', + 'banktransfer', 'mpesa', - 'mobilemoneyghana', - 'mobilemoneyuganda', 'mobilemoneyrwanda', 'mobilemoneyzambia', - 'mobilemoneytanzania', + 'qr', + 'mobilemoneyuganda', + 'ussd', + 'credit', 'barter', - 'bank transfer', - 'wechat', + 'mobilemoneyghana', + 'payattitude', + 'mobilemoneyfranco', + 'paga', + '1voucher', + 'mobilemoneytanzania', ]; From 0ed91574b8b5b5ce6ebc9775d5704d121268a497 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Wed, 15 Jul 2020 11:48:41 +0100 Subject: [PATCH 003/129] feat(flutterwaveinit): model options after v3 api standard initialization options --- src/FlutterwaveInit.ts | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/FlutterwaveInit.ts b/src/FlutterwaveInit.ts index acad79e..ad5ea24 100644 --- a/src/FlutterwaveInit.ts +++ b/src/FlutterwaveInit.ts @@ -1,29 +1,34 @@ import {STANDARD_URL} from './configs'; interface FlutterwavePaymentMeta { - metaname: string; - metavalue: string; + [k: string]: any; +} + +export interface InitCustomer { + email: string; + phonenumber?: string; + name?: string; +} + +export interface InitCustomizations { + title?: string; + logo?: string; + description?: string; } export interface FlutterwaveInitOptions { - txref: string; - PBFPubKey: string; - customer_firstname?: string; - customer_lastname?: string; - customer_phone?: string; - customer_email: string; + public_key: string; + tx_ref: string; amount: number; currency?: string; - redirect_url?: string; - payment_options?: string; + integrity_hash?: string; + payment_options: string; payment_plan?: number; + redirect_url: string; + customer: InitCustomer; subaccounts?: Array; - country?: string; - pay_button_text?: string; - custom_title?: string; - custom_description?: string; - custom_logo?: string; meta?: Array; + customizations: InitCustomizations; } interface FlutterwaveInitConfig { From 9b9eafd2eb802d5f67a0e9c79f0c4064ebffef87 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Wed, 15 Jul 2020 11:51:35 +0100 Subject: [PATCH 004/129] feat(flutterwaveinit): add public_key as Authorization header and remove from request body --- src/FlutterwaveInit.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/FlutterwaveInit.ts b/src/FlutterwaveInit.ts index ad5ea24..21368df 100644 --- a/src/FlutterwaveInit.ts +++ b/src/FlutterwaveInit.ts @@ -80,11 +80,12 @@ export default async function FlutterwaveInit( ): Promise { try { // make request body - const body = {...options}; + const {public_key, ...body} = options; // make request headers const headers = new Headers; headers.append('Content-Type', 'application/json'); + headers.append('Authorization', `Bearer ${public_key}`) // make fetch options const fetchOptions: FetchOptions = { From 30e4f7eb5247cd3f9eb9e365104a841744722f39 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Wed, 15 Jul 2020 14:06:57 +0100 Subject: [PATCH 005/129] feat(flutterwaveiniterror): add Flutterwave init error class --- src/utils/FlutterwaveInitError.ts | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/utils/FlutterwaveInitError.ts diff --git a/src/utils/FlutterwaveInitError.ts b/src/utils/FlutterwaveInitError.ts new file mode 100644 index 0000000..72cf6ea --- /dev/null +++ b/src/utils/FlutterwaveInitError.ts @@ -0,0 +1,34 @@ +/** + * Flutterwave Init Error + */ +export default class FlutterwaveInitError extends Error { + /** + * Error code + * @var string + */ + code: string; + + /** + * Error code + * @var string + */ + errorId?: string; + + /** + * Error code + * @var string + */ + errors?: Array; + + /** + * Constructor Method + * @param props {message?: string; code?: string} + */ + constructor(props: {message: string; code: string, errorId?: string, errors?: Array}) { + super(props.message); + this.code = props.code; + this.errorId = props.errorId; + this.errors = props.errors; + } +} + From 1a502439dac235a35ed0c8155d4143adbb43dec1 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Wed, 15 Jul 2020 14:12:19 +0100 Subject: [PATCH 006/129] test(flutterwaveiniterror): write tests for flutterwave init error class --- __tests__/FlutterwaveInitError.spec.ts | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 __tests__/FlutterwaveInitError.spec.ts diff --git a/__tests__/FlutterwaveInitError.spec.ts b/__tests__/FlutterwaveInitError.spec.ts new file mode 100644 index 0000000..a4ca9a2 --- /dev/null +++ b/__tests__/FlutterwaveInitError.spec.ts @@ -0,0 +1,27 @@ +import FlutterwaveInitError from '../src/utils/FlutterwaveInitError'; + +describe('', () => { + it('initializes with the the message and code passed', () => { + const message = 'Hello, World!'; + const code = 'TEST'; + const error = new FlutterwaveInitError({message, code}); + expect(error.message).toEqual(message); + expect(error.code).toEqual(code); + }); + + it('has errors if specified', () => { + const message = 'Hello, World!'; + const code = 'TEST'; + const errors = ['Error 1', 'Error 2']; + const error = new FlutterwaveInitError({message, code, errors}); + expect(Array.isArray(error.errors)).toBeTruthy(); + }); + + it('has an error ID if specified', () => { + const message = 'Hello, World!'; + const code = 'TEST'; + const errorId = 'N/F39040830498039434'; + const error = new FlutterwaveInitError({message, code, errorId}); + expect(typeof error.errorId === 'string').toBeTruthy(); + }); +}); From b673c0b531f429d1fa540713cd11a871b999d533 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Wed, 15 Jul 2020 14:13:03 +0100 Subject: [PATCH 007/129] feat(responseparser): add init request response parser --- src/utils/ResponseParser.ts | 63 +++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/utils/ResponseParser.ts diff --git a/src/utils/ResponseParser.ts b/src/utils/ResponseParser.ts new file mode 100644 index 0000000..3592bac --- /dev/null +++ b/src/utils/ResponseParser.ts @@ -0,0 +1,63 @@ +import { ResponseData } from "../FlutterwaveInit"; +import FlutterwaveInitError from "./FlutterwaveInitError"; + +type ParsedSuccess = string; + +/** + * The purpose of this function is to parse the response message gotten from a + * payment initialization error. + * @param message string + * @param code string (optional) + * @returns {message: string; code: string} + */ +export default function ResponseParser( + { + status, + errors, + message, + data, + code, + error_id, + }: ResponseData +): FlutterwaveInitError | ParsedSuccess { + // return success message + if (status === 'success') { + // check if data or data link is missing + if (!data || !data.link) { + return new FlutterwaveInitError({ + code: 'MALFORMED_RESPONSE', + message, + }) + } + // return the payment link + return data.link; + } + // missing authorization + if (/authorization/i.test(message) && /required/i.test(message)) { + return new FlutterwaveInitError({ + code: 'AUTH_MISSING', + message, + }); + } + // invalid authorization + if (/authorization/i.test(message) && /invalid/i.test(message)) { + return new FlutterwaveInitError({ + code: 'AUTH_INVALID', + message, + }); + } + // field errors + if (errors) { + return new FlutterwaveInitError({ + code: 'INVALID_OPTIONS', + message, + errors: errors.map(i => i.message), + }); + } + // defaults to the initially passed message + return new FlutterwaveInitError({ + code: String(code || 'UNKNOWN').toUpperCase(), + message, + errorId: error_id + }); +} From ecb8485e0463a639fc210aca205bdaa5c8ddb3f7 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Wed, 15 Jul 2020 14:21:48 +0100 Subject: [PATCH 008/129] fix(flutterwaveinit): remove public key prop and replace with authorization --- src/FlutterwaveInit.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/FlutterwaveInit.ts b/src/FlutterwaveInit.ts index 21368df..d5f2f05 100644 --- a/src/FlutterwaveInit.ts +++ b/src/FlutterwaveInit.ts @@ -17,7 +17,7 @@ export interface InitCustomizations { } export interface FlutterwaveInitOptions { - public_key: string; + authorization: string; tx_ref: string; amount: number; currency?: string; @@ -79,13 +79,13 @@ export default async function FlutterwaveInit( config: FlutterwaveInitConfig = {}, ): Promise { try { - // make request body - const {public_key, ...body} = options; + // get request body and authorization + const {authorization, ...body} = options; // make request headers const headers = new Headers; headers.append('Content-Type', 'application/json'); - headers.append('Authorization', `Bearer ${public_key}`) + headers.append('Authorization', `Bearer ${authorization}`); // make fetch options const fetchOptions: FetchOptions = { From 33fe5bf102aebe351ce4440c92d8424132a18954 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Wed, 15 Jul 2020 14:25:37 +0100 Subject: [PATCH 009/129] fix(responseparser): default to "STANDARD_INIT_ERROR" if code is not provided --- src/utils/ResponseParser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/ResponseParser.ts b/src/utils/ResponseParser.ts index 3592bac..ddd2d1d 100644 --- a/src/utils/ResponseParser.ts +++ b/src/utils/ResponseParser.ts @@ -56,7 +56,7 @@ export default function ResponseParser( } // defaults to the initially passed message return new FlutterwaveInitError({ - code: String(code || 'UNKNOWN').toUpperCase(), + code: String(code || 'STANDARD_INIT_ERROR').toUpperCase(), message, errorId: error_id }); From 85e22bf189a77339e29f346ff50d9d8bb3102d2f Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Wed, 15 Jul 2020 15:16:25 +0100 Subject: [PATCH 010/129] test(responseparser): write tests for standard init response parser --- __tests__/ResponseParser.spec.ts | 77 ++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 __tests__/ResponseParser.spec.ts diff --git a/__tests__/ResponseParser.spec.ts b/__tests__/ResponseParser.spec.ts new file mode 100644 index 0000000..125c17e --- /dev/null +++ b/__tests__/ResponseParser.spec.ts @@ -0,0 +1,77 @@ +import ResponseParser from '../src/utils/ResponseParser'; +import FlutterwaveInitError from '../src/utils/FlutterwaveInitError'; + +describe('', () => { + it('returns missing auth error', () => { + const code = 'AUTH_MISSING'; + // parse response + const error = ResponseParser({ + message: 'Authorization is required' + }); + // run assertions + expect(error instanceof FlutterwaveInitError).toBeTruthy() + expect(typeof error !== 'string' && error.code === code).toBeTruthy(); + }); + + it('returns invalid auth error', () => { + const code = 'AUTH_INVALID'; + // parse response + const error = ResponseParser({ + message: 'Authorization is invalid' + }); + // run assertions + expect(error instanceof FlutterwaveInitError).toBeTruthy() + expect(typeof error !== 'string' && error.code === code).toBeTruthy(); + }); + + it('return object with errors property', () => { + const code = 'INVALID_OPTIONS'; + const errors = [{field: 'tx_ref', message: 'An error has occured'}]; + // parse response + const error = ResponseParser({ + message: 'Missing fields error.', + errors + }); + // run assertions + expect(error instanceof FlutterwaveInitError).toBeTruthy() + expect(typeof error !== 'string' && error.code === code).toBeTruthy(); + expect(typeof error !== 'string' && Array.isArray(error.errors)).toBeTruthy(); + expect(typeof error !== 'string' && error.errors[0] === errors[0].message).toBeTruthy(); + }); + + it('returns standard init error code as default code if none is specified', () => { + const code = 'STANDARD_INIT_ERROR'; + // parse response + const error = ResponseParser({ + message: 'A server error occurred.' + }); + // run assertions + expect(error instanceof FlutterwaveInitError).toBeTruthy() + expect(typeof error !== 'string' && error.code === code).toBeTruthy(); + }); + + it('returns malformed response if status is success and data or link is missing', () => { + const code = 'MALFORMED_RESPONSE'; + // parse response + const error = ResponseParser({ + message: 'Great, it worked!!!', + status: 'success' + }); + // run assertions + expect(error instanceof FlutterwaveInitError).toBeTruthy() + expect(typeof error !== 'string' && error.code === code).toBeTruthy(); + }); + + it('returns a payment link', () => { + const link = 'http://checkout.flutterwave.com/380340934u093u403' + // parse response + const data = ResponseParser({ + message: 'Hosted link.', + status: 'success', + data: {link} + }); + // run assertions + expect(data instanceof FlutterwaveInitError).toBeFalsy() + expect(data === link).toBeTruthy(); + }); +}); From 19d408503018ab70640941b93c8797bdfae1db16 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Wed, 15 Jul 2020 15:23:17 +0100 Subject: [PATCH 011/129] fix(flutterwavinit): restructure flutterwave init result --- src/FlutterwaveInit.ts | 101 +++++++++++++---------------------------- 1 file changed, 31 insertions(+), 70 deletions(-) diff --git a/src/FlutterwaveInit.ts b/src/FlutterwaveInit.ts index d5f2f05..e8e31c1 100644 --- a/src/FlutterwaveInit.ts +++ b/src/FlutterwaveInit.ts @@ -1,4 +1,6 @@ import {STANDARD_URL} from './configs'; +import ResponseParser from './utils/ResponseParser'; +import FlutterwaveInitError from './utils/FlutterwaveInitError'; interface FlutterwavePaymentMeta { [k: string]: any; @@ -34,29 +36,26 @@ export interface FlutterwaveInitOptions { interface FlutterwaveInitConfig { canceller?: AbortController; } +export type FlutterwaveInitResult = FlutterwaveInitError | string | null; -export interface FlutterwaveInitError { - code: string; +export interface FieldError { + field: string; message: string; } -interface FlutterwaveInitResult { - error?: FlutterwaveInitError | null; - link?: string | null; -} - -interface ResponseJSON { - status: 'success' | 'error'; +export interface ResponseData { + status?: 'success' | 'error'; message: string; - data: { - link?: string; - code?: string; - message?: string; + error_id?: string; + errors?: Array; + code?: string; + data?: { + link: string; }; } interface FetchOptions { - method: 'POST' | 'GET' | 'PUT' | 'DELETE'; + method: 'POST'; body: string; headers: Headers; signal?: AbortSignal; @@ -66,13 +65,7 @@ interface FetchOptions { * This function is responsible for making the request to * initialize a Flutterwave payment. * @param options FlutterwaveInitOptions - * @return Promise<{ - * error: { - * code: string; - * message: string; - * } | null; - * link?: string | null; - * }> + * @return Promise */ export default async function FlutterwaveInit( options: FlutterwaveInitOptions, @@ -99,62 +92,30 @@ export default async function FlutterwaveInit( fetchOptions.signal = config.canceller.signal }; - // make http request + // initialize payment const response = await fetch(STANDARD_URL, fetchOptions); + // get response data + const responseData: ResponseData = await response.json(); + // get response json - const responseJSON: ResponseJSON = await response.json(); - - // check if data is missing from response - if (!responseJSON.data) { - throw new FlutterwaveInitException({ - code: 'NODATA', - message: responseJSON.message || 'An unknown error occured!', - }); - } + const parsedResponse = ResponseParser(responseData); - // check if the link is missing in data - if (!responseJSON.data.link) { - throw new FlutterwaveInitException({ - code: responseJSON.data.code || 'UNKNOWN', - message: responseJSON.data.message || 'An unknown error occured!', - }); + // thow error if parsed response is instance of Flutterwave Init Error + if (parsedResponse instanceof FlutterwaveInitError) { + throw parsedResponse; } // resolve with the payment link - return Promise.resolve({ - link: responseJSON.data.link, - }); - } catch (e) { + return Promise.resolve(parsedResponse); + } catch (error) { + // user error name as code if code is available + const code = error.code || error.name.toUpperCase(); + // resolve to null if fetch was aborted + if (code === 'ABORTERROR') { + return Promise.resolve(null); + } // resolve with error - return Promise.resolve({ - error: { - code: - e instanceof FlutterwaveInitException - ? e.code - : String(e.name).toUpperCase(), - message: e.message, - } - }); - } -} - -/** - * Flutterwave Init Error - */ -export class FlutterwaveInitException extends Error { - /** - * Error code - * @var string - */ - code: string; - - /** - * Constructor Method - * @param props {message?: string; code?: string} - */ - constructor(props: {message: string; code: string}) { - super(props.message); - this.code = props.code; + return Promise.resolve({...error, code: code}); } } From 175aa951ed76e3d752685b4160d6dd5b12898ed1 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Wed, 15 Jul 2020 15:24:03 +0100 Subject: [PATCH 012/129] feat(flutterwaveinit): make second argument abort controller --- src/FlutterwaveInit.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/FlutterwaveInit.ts b/src/FlutterwaveInit.ts index e8e31c1..ca7651e 100644 --- a/src/FlutterwaveInit.ts +++ b/src/FlutterwaveInit.ts @@ -33,9 +33,6 @@ export interface FlutterwaveInitOptions { customizations: InitCustomizations; } -interface FlutterwaveInitConfig { - canceller?: AbortController; -} export type FlutterwaveInitResult = FlutterwaveInitError | string | null; export interface FieldError { @@ -65,11 +62,12 @@ interface FetchOptions { * This function is responsible for making the request to * initialize a Flutterwave payment. * @param options FlutterwaveInitOptions + * @param abortController AbortController * @return Promise */ export default async function FlutterwaveInit( options: FlutterwaveInitOptions, - config: FlutterwaveInitConfig = {}, + abortController?: AbortController, ): Promise { try { // get request body and authorization @@ -87,9 +85,9 @@ export default async function FlutterwaveInit( headers: headers, } - // add canceller if defined - if (config.canceller) { - fetchOptions.signal = config.canceller.signal + // add abortController if defined + if (abortController) { + fetchOptions.signal = abortController.signal }; // initialize payment From 319f249272b495421518385c2f3bfef3c2389221 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Wed, 15 Jul 2020 15:30:52 +0100 Subject: [PATCH 013/129] feat(flutterwaveinit): resolve only to string and reject if there is an error --- src/FlutterwaveInit.ts | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/FlutterwaveInit.ts b/src/FlutterwaveInit.ts index ca7651e..e97e75c 100644 --- a/src/FlutterwaveInit.ts +++ b/src/FlutterwaveInit.ts @@ -33,8 +33,6 @@ export interface FlutterwaveInitOptions { customizations: InitCustomizations; } -export type FlutterwaveInitResult = FlutterwaveInitError | string | null; - export interface FieldError { field: string; message: string; @@ -63,57 +61,45 @@ interface FetchOptions { * initialize a Flutterwave payment. * @param options FlutterwaveInitOptions * @param abortController AbortController - * @return Promise + * @return Promise */ export default async function FlutterwaveInit( options: FlutterwaveInitOptions, abortController?: AbortController, -): Promise { +): Promise { try { // get request body and authorization const {authorization, ...body} = options; - // make request headers const headers = new Headers; headers.append('Content-Type', 'application/json'); headers.append('Authorization', `Bearer ${authorization}`); - // make fetch options const fetchOptions: FetchOptions = { method: 'POST', body: JSON.stringify(body), headers: headers, } - // add abortController if defined if (abortController) { fetchOptions.signal = abortController.signal }; - // initialize payment const response = await fetch(STANDARD_URL, fetchOptions); - // get response data const responseData: ResponseData = await response.json(); - // get response json const parsedResponse = ResponseParser(responseData); - // thow error if parsed response is instance of Flutterwave Init Error if (parsedResponse instanceof FlutterwaveInitError) { throw parsedResponse; } - // resolve with the payment link return Promise.resolve(parsedResponse); } catch (error) { // user error name as code if code is available - const code = error.code || error.name.toUpperCase(); - // resolve to null if fetch was aborted - if (code === 'ABORTERROR') { - return Promise.resolve(null); - } + error.code = error.code || error.name.toUpperCase(); // resolve with error - return Promise.resolve({...error, code: code}); + return Promise.reject(error); } } From 185ea28368ecc0ab8ab4498fb1e09101ed520787 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Wed, 15 Jul 2020 16:54:41 +0100 Subject: [PATCH 014/129] feat(responseparser): promisify; resolve to link else reject to FlutterwaveInitError --- src/utils/ResponseParser.ts | 82 +++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/src/utils/ResponseParser.ts b/src/utils/ResponseParser.ts index ddd2d1d..41ae26b 100644 --- a/src/utils/ResponseParser.ts +++ b/src/utils/ResponseParser.ts @@ -1,8 +1,6 @@ import { ResponseData } from "../FlutterwaveInit"; import FlutterwaveInitError from "./FlutterwaveInitError"; -type ParsedSuccess = string; - /** * The purpose of this function is to parse the response message gotten from a * payment initialization error. @@ -19,45 +17,49 @@ export default function ResponseParser( code, error_id, }: ResponseData -): FlutterwaveInitError | ParsedSuccess { - // return success message - if (status === 'success') { - // check if data or data link is missing - if (!data || !data.link) { - return new FlutterwaveInitError({ - code: 'MALFORMED_RESPONSE', +): Promise { + return new Promise((resolve, reject) => { + // return success message + if (status === 'success') { + // check if data or data link is missing + if (!data || !data.link) { + return reject(new FlutterwaveInitError({ + code: 'MALFORMED_RESPONSE', + message, + })) + } + // return the payment link + return resolve(data.link); + } + // missing authorization + if (/authorization/i.test(message) && /required/i.test(message)) { + reject( + new FlutterwaveInitError({ + code: 'AUTH_MISSING', + message, + }) + ); + } + // invalid authorization + if (/authorization/i.test(message) && /invalid/i.test(message)) { + reject(new FlutterwaveInitError({ + code: 'AUTH_INVALID', message, - }) + })); } - // return the payment link - return data.link; - } - // missing authorization - if (/authorization/i.test(message) && /required/i.test(message)) { - return new FlutterwaveInitError({ - code: 'AUTH_MISSING', - message, - }); - } - // invalid authorization - if (/authorization/i.test(message) && /invalid/i.test(message)) { - return new FlutterwaveInitError({ - code: 'AUTH_INVALID', - message, - }); - } - // field errors - if (errors) { - return new FlutterwaveInitError({ - code: 'INVALID_OPTIONS', + // field errors + if (errors) { + reject(new FlutterwaveInitError({ + code: 'INVALID_OPTIONS', + message: message, + errors: errors.map(i => i.message), + })); + } + // defaults to the initially passed message + reject(new FlutterwaveInitError({ + code: String(code || 'STANDARD_INIT_ERROR').toUpperCase(), message, - errors: errors.map(i => i.message), - }); - } - // defaults to the initially passed message - return new FlutterwaveInitError({ - code: String(code || 'STANDARD_INIT_ERROR').toUpperCase(), - message, - errorId: error_id - }); + errorId: error_id + })); + }) } From a851b8645973c64773328697f7f2e38633e13034 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Wed, 15 Jul 2020 16:56:19 +0100 Subject: [PATCH 015/129] test(responseparser): rewrite test to use as a promise --- __tests__/ResponseParser.spec.ts | 145 ++++++++++++++++++------------- 1 file changed, 85 insertions(+), 60 deletions(-) diff --git a/__tests__/ResponseParser.spec.ts b/__tests__/ResponseParser.spec.ts index 125c17e..faeff40 100644 --- a/__tests__/ResponseParser.spec.ts +++ b/__tests__/ResponseParser.spec.ts @@ -2,76 +2,101 @@ import ResponseParser from '../src/utils/ResponseParser'; import FlutterwaveInitError from '../src/utils/FlutterwaveInitError'; describe('', () => { - it('returns missing auth error', () => { - const code = 'AUTH_MISSING'; - // parse response - const error = ResponseParser({ - message: 'Authorization is required' - }); - // run assertions - expect(error instanceof FlutterwaveInitError).toBeTruthy() - expect(typeof error !== 'string' && error.code === code).toBeTruthy(); + it('expect error to be instance of FlutterwaveInitError', async () => { + try { + // parse response + await ResponseParser({ + message: 'Error occurred!' + }); + } catch (error) { + // run assertions + expect(error).toBeInstanceOf(FlutterwaveInitError); + } + }); + + it('returns missing auth error', async () => { + try { + // parse response + await ResponseParser({ + message: 'Authorization is required' + }); + } catch (error) { + // run assertions + expect(error).toBeInstanceOf(FlutterwaveInitError); + expect(error.code).toEqual('AUTH_MISSING'); + } }); - it('returns invalid auth error', () => { - const code = 'AUTH_INVALID'; - // parse response - const error = ResponseParser({ - message: 'Authorization is invalid' - }); - // run assertions - expect(error instanceof FlutterwaveInitError).toBeTruthy() - expect(typeof error !== 'string' && error.code === code).toBeTruthy(); + it('returns invalid auth error', async () => { + try { + // parse response + await ResponseParser({ + message: 'Authorization is invalid' + }); + } catch (error) { + // run assertions + expect(error).toBeInstanceOf(FlutterwaveInitError); + expect(error.code).toEqual('AUTH_INVALID'); + } }); - it('return object with errors property', () => { - const code = 'INVALID_OPTIONS'; + it('return object with errors property', async () => { const errors = [{field: 'tx_ref', message: 'An error has occured'}]; - // parse response - const error = ResponseParser({ - message: 'Missing fields error.', - errors - }); - // run assertions - expect(error instanceof FlutterwaveInitError).toBeTruthy() - expect(typeof error !== 'string' && error.code === code).toBeTruthy(); - expect(typeof error !== 'string' && Array.isArray(error.errors)).toBeTruthy(); - expect(typeof error !== 'string' && error.errors[0] === errors[0].message).toBeTruthy(); + try { + // parse response + await ResponseParser({ + message: 'Missing fields error.', + errors + }); + } catch (error) { + // run assertions + expect(error).toBeInstanceOf(FlutterwaveInitError); + expect(error.code).toEqual('INVALID_OPTIONS'); + expect(Array.isArray(error.errors)).toBeTruthy(); + expect(error.errors[0]).toEqual(errors[0].message); + } }); - it('returns standard init error code as default code if none is specified', () => { - const code = 'STANDARD_INIT_ERROR'; - // parse response - const error = ResponseParser({ - message: 'A server error occurred.' - }); - // run assertions - expect(error instanceof FlutterwaveInitError).toBeTruthy() - expect(typeof error !== 'string' && error.code === code).toBeTruthy(); + it('returns standard init error code as default code if none is specified', async () => { + try { + // parse response + ResponseParser({ + message: 'A server error occurred.' + }); + } catch (error) { + // run assertions + expect(error).toBeInstanceOf(FlutterwaveInitError); + expect(error.code).toEqual('STANDARD_INIT_ERROR'); + } }); - it('returns malformed response if status is success and data or link is missing', () => { - const code = 'MALFORMED_RESPONSE'; - // parse response - const error = ResponseParser({ - message: 'Great, it worked!!!', - status: 'success' - }); - // run assertions - expect(error instanceof FlutterwaveInitError).toBeTruthy() - expect(typeof error !== 'string' && error.code === code).toBeTruthy(); + it('returns malformed response if status is success and data or link is missing', async () => { + try { + // parse response + await ResponseParser({ + message: 'Great, it worked!!!', + status: 'success' + }); + } catch (error) { + // run assertions + expect(error).toBeInstanceOf(FlutterwaveInitError); + expect(error.code).toEqual('MALFORMED_RESPONSE'); + } }); - it('returns a payment link', () => { - const link = 'http://checkout.flutterwave.com/380340934u093u403' - // parse response - const data = ResponseParser({ - message: 'Hosted link.', - status: 'success', - data: {link} - }); - // run assertions - expect(data instanceof FlutterwaveInitError).toBeFalsy() - expect(data === link).toBeTruthy(); + it('returns a payment link', async () => { + try { + const link = 'http://checkout.flutterwave.com/380340934u093u403' + // parse response + const data = await ResponseParser({ + message: 'Hosted link.', + status: 'success', + data: {link} + }); + // run assertions + expect(data).toEqual(link); + } catch (err) { + // no need to run this + } }); }); From c8eec391a922fddb905e8f7a93c633d5d0466bd8 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Wed, 15 Jul 2020 17:08:19 +0100 Subject: [PATCH 016/129] fix(flutterwaveinit): use promisified response parser --- src/FlutterwaveInit.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/FlutterwaveInit.ts b/src/FlutterwaveInit.ts index e97e75c..0f1892d 100644 --- a/src/FlutterwaveInit.ts +++ b/src/FlutterwaveInit.ts @@ -88,17 +88,13 @@ export default async function FlutterwaveInit( const response = await fetch(STANDARD_URL, fetchOptions); // get response data const responseData: ResponseData = await response.json(); - // get response json - const parsedResponse = ResponseParser(responseData); - // thow error if parsed response is instance of Flutterwave Init Error - if (parsedResponse instanceof FlutterwaveInitError) { - throw parsedResponse; - } // resolve with the payment link - return Promise.resolve(parsedResponse); - } catch (error) { - // user error name as code if code is available - error.code = error.code || error.name.toUpperCase(); + return Promise.resolve(await ResponseParser(responseData)); + } catch (e) { + // always return a flutterwave init error + const error = e instanceof FlutterwaveInitError + ? e + : new FlutterwaveInitError({message: e.message, code: e.name.toUpperCase()}) // resolve with error return Promise.reject(error); } From d8dfedb8f2cd2bee56e870c33ec25dc3a11832f8 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Wed, 15 Jul 2020 17:09:53 +0100 Subject: [PATCH 017/129] test(flutterwaveinit): refactor test suite and handle resolve and reject calls --- __tests__/FlutterwaveInit.spec.ts | 272 ++++++++++++++++++------------ 1 file changed, 163 insertions(+), 109 deletions(-) diff --git a/__tests__/FlutterwaveInit.spec.ts b/__tests__/FlutterwaveInit.spec.ts index 6a06e68..b9099fe 100644 --- a/__tests__/FlutterwaveInit.spec.ts +++ b/__tests__/FlutterwaveInit.spec.ts @@ -1,117 +1,172 @@ import FlutterwaveInit, {FlutterwaveInitOptions} from '../src/FlutterwaveInit'; import {STANDARD_URL} from '../src/configs'; +import FlutterwaveInitError from '../src/utils/FlutterwaveInitError'; -// default fetch header -const DefaultFetchHeader = new Headers(); -DefaultFetchHeader.append('Content-Type', 'application/json'); +const AUTHORIZATION = '[AUTHORIZATION]'; + +// fetch header +const FETCH_HEADER = new Headers(); +FETCH_HEADER.append('Content-Type', 'application/json'); +FETCH_HEADER.append('Authorization', `Bearer ${AUTHORIZATION}`); + +// fetch body +const FETCH_BODY = { + redirect_url: 'http://flutterwave.com', + amount: 50, + currency: 'NGN', + customer: { + email: 'email@example.com', + }, + tx_ref: Date.now() + '-txref', + payment_options: 'card', + customizations: { + title: 'Hello World', + } +} + +// payment options +const INIT_OPTIONS: FlutterwaveInitOptions = { + ...FETCH_BODY, + authorization: AUTHORIZATION +}; describe('', () => { + it('runs a fetch request', async () => { + // mock next fetch request + fetchMock.mockOnce(JSON.stringify({ + status: 'success', + message: 'Payment link generated.', + data: {link: 'http://example.com'}, + })); + try { + // initialize payment + await FlutterwaveInit(INIT_OPTIONS); + // run assertions + expect(global.fetch).toHaveBeenCalledTimes(1); + expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL, { + body: JSON.stringify(FETCH_BODY), + headers: FETCH_HEADER, + method: 'POST', + }); + } catch (e) { + // no error occurred + } + }); + it('returns a payment link after initialization', async () => { + const link = 'http://payment-link.com/checkout'; // mock next fetch request fetchMock.mockOnce(JSON.stringify({ status: 'success', message: 'Payment link generated.', - data: { - link: 'http://payment-link.com/checkout', - }, + data: {link}, })); - // payment information - const paymentInfo: FlutterwaveInitOptions = { - redirect_url: 'http://flutterwave.com', - PBFPubKey: '[PUB Key]', - amount: 50, - currency: 'NGN', - customer_email: 'email@example.com', - txref: Date.now() + '-txref', - }; - // flutterwave init test - const response = await FlutterwaveInit(paymentInfo); - - // expect fetch to have been called once - expect(global.fetch).toHaveBeenCalledTimes(1); - // expect fetch to have been called to the standard init url - expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL, { - body: JSON.stringify(paymentInfo), - headers: DefaultFetchHeader, - method: 'POST', - }); - expect(typeof response.link === 'string').toBeTruthy(); + try { + // initialize payment + const response = await FlutterwaveInit(INIT_OPTIONS) + // run assertions + expect(response).toEqual(link) + } catch (e) { + // no error occurred + } }); it('includes error code and message of init error', async () => { - // payment information - const paymentInfo: FlutterwaveInitOptions = { - redirect_url: 'http://flutterwave.com', - PBFPubKey: 'FLWPUBK_TEST-c761fb7f0e443f5704a796781b621875-X44', - amount: 50, - currency: 'NGN', - customer_email: 'email@example.com', - txref: Date.now() + '-txref', - }; + const message = 'An error has occurred.'; // reject next fetch - fetchMock.mockRejectOnce(new Error('An error occured!')); - // flutterwave init test - const response = await FlutterwaveInit(paymentInfo); - // expect fetch to have been called - expect(global.fetch).toHaveBeenCalledTimes(1); - expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL, { - body: JSON.stringify(paymentInfo), - headers: DefaultFetchHeader, - method: 'POST', - }); - // expect error and error code to be defined - expect(typeof response.error.code === 'string').toBeTruthy(); - expect(typeof response.error.message === 'string').toBeTruthy(); + fetchMock.mockOnce(JSON.stringify({ + status: 'error', + message: message, + })); + try { + // initialize payment + await FlutterwaveInit(INIT_OPTIONS); + } catch (error) { + // run assertions + expect(error.message).toEqual(message); + expect(error.code).toEqual('STANDARD_INIT_ERROR'); + } }); - it('returns unknown error if the error response has no code or message', async () => { - // payment information - const paymentInfo: FlutterwaveInitOptions = { - redirect_url: 'http://flutterwave.com', - PBFPubKey: 'FLWPUBK_TEST-c761fb7f0e443f5704a796781b621875-X44', - amount: 50, - currency: 'NGN', - customer_email: 'email@example.com', - txref: Date.now() + '-txref', - }; - // mock next fetch - fetchMock.mockOnce(JSON.stringify({status: 'error', data: {}})); - // flutterwave init test - const response = await FlutterwaveInit(paymentInfo); - // expect fetch to have been called - expect(global.fetch).toHaveBeenCalledTimes(1); - expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL, { - body: JSON.stringify(paymentInfo), - headers: DefaultFetchHeader, - method: 'POST', - }); - // expect unkown error from from response - expect(/unknown/i.test(response.error.code)).toBeTruthy(); + it('returns missing authorization request error code', async () => { + const message = 'Authorization is required.'; + // reject next fetch + fetchMock.mockOnce(JSON.stringify({ + status: 'error', + message: message, + })); + try { + // initialize payment + await FlutterwaveInit(INIT_OPTIONS); + } catch (error) { + // run assertions + expect(error.message).toEqual(message); + expect(error.code).toEqual('AUTH_MISSING'); + } + }); + + it('returns invalid authorization request error code', async () => { + const message = 'Authorization is invalid.'; + // reject next fetch + fetchMock.mockOnce(JSON.stringify({ + status: 'error', + message: message, + })); + try { + // initialize payment + await FlutterwaveInit(INIT_OPTIONS); + } catch (error) { + // run assertions + expect(error.message).toEqual(message); + expect(error.code).toEqual('AUTH_INVALID'); + } + }); + + it('returns field errors from request errors', async () => { + const message = 'Missin fields.'; + const errors = [{field: 'tx_ref', message: 'tx_ref is required'}]; + // reject next fetch + fetchMock.mockOnce(JSON.stringify({ + status: 'error', + message: message, + errors: errors, + })); + try { + // initialize payment + await FlutterwaveInit(INIT_OPTIONS); + } catch (error) { + // run assertions + expect(error.message).toEqual(message); + expect(error.code).toEqual('INVALID_OPTIONS'); + expect(error.errors).toBeDefined(); + expect(error.errors[0]).toEqual(errors[0].message); + } + }); + + it('always has an error code property on error object', async () => { + const message = 'An error has occurred.'; + // reject next fetch + fetchMock.mockRejectOnce(new Error(message)); + try { + // initialize payment + await FlutterwaveInit(INIT_OPTIONS); + } catch (error) { + // run assertions + expect(error.code).toBeDefined(); + } }); it('catches missing response data error', async () => { - // payment information - const paymentInfo: FlutterwaveInitOptions = { - redirect_url: 'http://flutterwave.com', - PBFPubKey: 'FLWPUBK_TEST-c761fb7f0e443f5704a796781b621875-X44', - amount: 50, - currency: 'NGN', - customer_email: 'email@example.com', - txref: Date.now() + '-txref', - }; + const message = 'Hello, World!'; // mock next fetch - fetchMock.mockOnce(JSON.stringify({status: 'error'})); - // flutterwave init test - const response = await FlutterwaveInit(paymentInfo); - // expect fetch to have been called - expect(global.fetch).toHaveBeenCalledTimes(1); - expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL, { - body: JSON.stringify(paymentInfo), - headers: DefaultFetchHeader, - method: 'POST', - }); - // expect a no response error - expect(/nodata/i.test(response.error.code)).toBeTruthy(); + fetchMock.mockOnce(JSON.stringify({status: 'success', message})); + try { + // initialize payment + await FlutterwaveInit(INIT_OPTIONS); + } catch (error) { + // run assertions + expect(error.code).toEqual('MALFORMED_RESPONSE'); + } }); it('is abortable', async () => { @@ -120,26 +175,25 @@ describe('', () => { // mock fetch response fetchMock.mockResponse(async () => { jest.advanceTimersByTime(60) - return '' + return JSON.stringify({ + status: 'error', + message: 'Error!', + }) }); // create abort controller const abortController = new AbortController; - // payment information - const paymentInfo: FlutterwaveInitOptions = { - redirect_url: 'http://flutterwave.com', - PBFPubKey: 'FLWPUBK_TEST-c761fb7f0e443f5704a796781b621875-X44', - amount: 50, - currency: 'NGN', - customer_email: 'email@example.com', - txref: Date.now() + '-txref', - }; // abort next fetch setTimeout(() => abortController.abort(), 50); - // expect a no response error - await expect(FlutterwaveInit(paymentInfo, {canceller: abortController})).resolves.toMatchObject({ - error: { - code: 'ABORTERROR' - } - }); + try { + // initialize payment + await FlutterwaveInit( + INIT_OPTIONS, + abortController + ) + } catch(error) { + // run assertions + expect(error).toBeInstanceOf(FlutterwaveInitError); + expect(error.code).toEqual('ABORTERROR'); + } }); }); From c33843c98c8666fc85a44e4cf8f25fde269233af Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Thu, 16 Jul 2020 14:46:04 +0100 Subject: [PATCH 018/129] fix(flutterwaveinit): specify required options on interface --- src/FlutterwaveInit.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/FlutterwaveInit.ts b/src/FlutterwaveInit.ts index 0f1892d..a681e73 100644 --- a/src/FlutterwaveInit.ts +++ b/src/FlutterwaveInit.ts @@ -22,15 +22,15 @@ export interface FlutterwaveInitOptions { authorization: string; tx_ref: string; amount: number; - currency?: string; + currency: string; integrity_hash?: string; - payment_options: string; + payment_options?: string; payment_plan?: number; redirect_url: string; customer: InitCustomer; subaccounts?: Array; meta?: Array; - customizations: InitCustomizations; + customizations?: InitCustomizations; } export interface FieldError { From 2b0954226ed50bbfd2b42a187f5560afafae4e69 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Thu, 16 Jul 2020 14:47:59 +0100 Subject: [PATCH 019/129] feat(config): specify redirect url --- src/configs.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/configs.ts b/src/configs.ts index e01eb39..f643f96 100644 --- a/src/configs.ts +++ b/src/configs.ts @@ -3,6 +3,8 @@ */ export const STANDARD_URL = 'https://api.flutterwave.com/v3/payments'; +export const REDIRECT_URL = 'https://flutterwave.com/rn-redirect'; + export const colors = { primary: '#f5a623', secondary: '#12122C', From d3e12ca8265fdb5e90e72029da094d980ea22249 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Thu, 16 Jul 2020 14:50:21 +0100 Subject: [PATCH 020/129] feat(flutterwavebutton): implement v3 api flutterwave init --- src/FlutterwaveButton.tsx | 153 ++++++++++++++++++-------------------- 1 file changed, 71 insertions(+), 82 deletions(-) diff --git a/src/FlutterwaveButton.tsx b/src/FlutterwaveButton.tsx index 345f722..8104dd5 100644 --- a/src/FlutterwaveButton.tsx +++ b/src/FlutterwaveButton.tsx @@ -16,13 +16,11 @@ import { import WebView from 'react-native-webview'; import PropTypes from 'prop-types'; import {WebViewNavigation} from 'react-native-webview/lib/WebViewTypes'; -import FlutterwaveInit, { - FlutterwaveInitOptions, - FlutterwaveInitError, -} from './FlutterwaveInit'; -import {colors} from './configs'; +import FlutterwaveInit, {FlutterwaveInitOptions} from './FlutterwaveInit'; +import {colors, REDIRECT_URL} from './configs'; import {PaymentOptionsPropRule} from './utils/CustomPropTypesRules'; import DefaultButton from './DefaultButton'; +import FlutterwaveInitError from './utils/FlutterwaveInitError'; const loader = require('./loader.gif'); const pryContent = require('./pry-button-content.png'); const contentWidthPercentage = 0.6549707602; @@ -40,22 +38,15 @@ interface CustomButtonProps { onPress: () => void; } -interface OnCompleteData { - canceled: boolean; - flwref?: string; - txref: string; -} - interface RedirectParams { - canceled: 'true' | 'false'; - flwref?: string; - txref?: string; - response?: string; + status: 'successful' | 'cancelled', + transaction_id?: string; + tx_ref?: string; } export interface FlutterwaveButtonProps { style?: ViewStyle; - onComplete: (data: OnCompleteData) => void; + onComplete: (data: RedirectParams) => void; onWillInitialize?: () => void; onDidInitialize?: () => void; onInitializeError?: (error: FlutterwaveInitError) => void; @@ -70,7 +61,7 @@ interface FlutterwaveButtonState { isPending: boolean; showDialog: boolean; animation: Animated.Value; - txref: string | null; + tx_ref: string | null; resetLink: boolean; buttonSize: { width: number; @@ -90,23 +81,25 @@ class FlutterwaveButton extends React.Component< onDidInitialize: PropTypes.func, onInitializeError: PropTypes.func, options: PropTypes.shape({ - txref: PropTypes.string.isRequired, - PBFPubKey: PropTypes.string.isRequired, - customer_email: PropTypes.string.isRequired, + authorization: PropTypes.string.isRequired, + tx_ref: PropTypes.string.isRequired, amount: PropTypes.number.isRequired, - currency: PropTypes.oneOf(['NGN', 'USD', 'GHS', 'KES', 'ZAR', 'TZS']), + currency: PropTypes.oneOf(['NGN', 'USD', 'GBP', 'GHS', 'KES', 'ZAR', 'TZS']).isRequired, + integrity_hash: PropTypes.string, payment_options: PaymentOptionsPropRule, payment_plan: PropTypes.number, + customer: PropTypes.shape({ + name: PropTypes.string, + phonenumber: PropTypes.string, + email: PropTypes.string.isRequired, + }).isRequired, subaccounts: PropTypes.arrayOf(PropTypes.number), - country: PropTypes.string, - pay_button_text: PropTypes.string, - custom_title: PropTypes.string, - custom_description: PropTypes.string, - custom_logo: PropTypes.string, - meta: PropTypes.arrayOf(PropTypes.shape({ - metaname: PropTypes.string, - metavalue: PropTypes.string, - })), + meta: PropTypes.arrayOf(PropTypes.object), + customizations: PropTypes.shape({ + title: PropTypes.string, + logo: PropTypes.string, + description: PropTypes.string, + }), }).isRequired, customButton: PropTypes.func, }; @@ -117,7 +110,7 @@ class FlutterwaveButton extends React.Component< resetLink: false, showDialog: false, animation: new Animated.Value(0), - txref: null, + tx_ref: null, buttonSize: { width: 0, height: 0, @@ -126,7 +119,7 @@ class FlutterwaveButton extends React.Component< webviewRef: WebView | null = null; - canceller?: AbortController; + abortController?: AbortController; componentDidUpdate(prevProps: FlutterwaveButtonProps) { if (JSON.stringify(prevProps.options) !== JSON.stringify(this.props.options)) { @@ -135,14 +128,14 @@ class FlutterwaveButton extends React.Component< } componentWillUnmount() { - if (this.canceller) { - this.canceller.abort(); + if (this.abortController) { + this.abortController.abort(); } } reset = () => { - if (this.canceller) { - this.canceller.abort(); + if (this.abortController) { + this.abortController.abort(); } // reset the necessaries this.setState(({resetLink, link}) => ({ @@ -161,7 +154,7 @@ class FlutterwaveButton extends React.Component< if (!showDialog) { return this.setState({ link: null, - txref: null, + tx_ref: null, }) } this.setState({resetLink: true}) @@ -169,7 +162,7 @@ class FlutterwaveButton extends React.Component< handleNavigationStateChange = (ev: WebViewNavigation) => { // cregex to check if redirect has occured on completion/cancel - const rx = /\/hosted\/pay\/undefined|\/api\/hosted_pay\/undefined/; + const rx = /\/flutterwave\.com\/rn-redirect/; // Don't end payment if not redirected back if (!rx.test(ev.url)) { return @@ -178,22 +171,18 @@ class FlutterwaveButton extends React.Component< this.handleComplete(this.getRedirectParams(ev.url)); }; - handleComplete(data: any) { + handleComplete(data: RedirectParams) { const {onComplete} = this.props; // reset payment link - this.setState(({resetLink, txref}) => ({ - txref: data.flref && !data.canceled ? null : txref, - resetLink: data.flwref && !data.canceled ? true : resetLink + this.setState(({resetLink, tx_ref}) => ({ + tx_ref: data.status === 'successful' ? null : tx_ref, + resetLink: data.status === 'successful' ? true : resetLink }), () => { // reset this.dismiss(); // fire onComplete handler - onComplete({ - flwref: data.flwref, - txref: data.txref, - canceled: /true/i.test(data.canceled || '') ? true : false - }); + onComplete(data); } ); } @@ -211,7 +200,7 @@ class FlutterwaveButton extends React.Component< if (onAbort) { onAbort(); } - // remove txref and dismiss + // remove tx_ref and dismiss this.dismiss(); }; @@ -274,7 +263,7 @@ class FlutterwaveButton extends React.Component< handleInit = () => { const {options, onWillInitialize, onInitializeError, onDidInitialize} = this.props; - const {isPending, txref, link} = this.state; + const {isPending, tx_ref, link} = this.state; // just show the dialod if the link is already set if (link) { @@ -282,11 +271,11 @@ class FlutterwaveButton extends React.Component< } // throw error if transaction reference has not changed - if (txref === options.txref) { - return onInitializeError ? onInitializeError({ + if (tx_ref === options.tx_ref) { + return onInitializeError ? onInitializeError(new FlutterwaveInitError({ message: 'Please generate a new transaction reference.', code: 'SAME_TXREF', - }) : null; + })) : null; } // stop if currently in pending mode @@ -295,7 +284,7 @@ class FlutterwaveButton extends React.Component< } // initialize abort controller if not set - this.canceller = new AbortController; + this.abortController = new AbortController; // fire will initialize handler if available if (onWillInitialize) { @@ -311,26 +300,31 @@ class FlutterwaveButton extends React.Component< { isPending: true, link: null, - txref: options.txref, + tx_ref: options.tx_ref, }, async () => { - // make init request - const result = await FlutterwaveInit(options, {canceller: this.canceller}); - // stop if request was canceled - if (result.error && /aborterror/i.test(result.error.code)) { - return; - } - // call onInitializeError handler if an error occured - if (!result.link) { - if (onInitializeError && result.error) { - onInitializeError(result.error); + try { + // initialis payment + const link = await FlutterwaveInit( + {...options, redirect_url: REDIRECT_URL}, + this.abortController + ); + // resent pending mode + this.setState({link, isPending: false}, this.show); + // fire did initialize handler if available + if (onDidInitialize) { + onDidInitialize(); + } + } catch (error) { + // stop if request was canceled + if (/aborterror/i.test(error.code)) { + return; + } + if (onInitializeError) { + onInitializeError(error); } return this.dismiss(); - } - this.setState({link: result.link, isPending: false}, this.show); - // fire did initialize handler if available - if (onDidInitialize) { - onDidInitialize(); + } }, ); @@ -448,21 +442,16 @@ class FlutterwaveButton extends React.Component< } renderError = () => { - const {link} = this.state; return ( - {link ? ( - <> - - The page failed to load, please try again. - - - - Try Again - - - - ) : null} + + The page failed to load, please try again. + + + + Try Again + + ); }; From 7dcfa10f16f23c23701e121dc17928e81ed1a436 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Thu, 16 Jul 2020 14:51:10 +0100 Subject: [PATCH 021/129] test(flutterwavebutton): refactor test with v3 implementation --- __tests__/FlutterwaveButton.spec.tsx | 186 ++++++++++++--------------- 1 file changed, 80 insertions(+), 106 deletions(-) diff --git a/__tests__/FlutterwaveButton.spec.tsx b/__tests__/FlutterwaveButton.spec.tsx index e356e5c..3e53b45 100644 --- a/__tests__/FlutterwaveButton.spec.tsx +++ b/__tests__/FlutterwaveButton.spec.tsx @@ -4,9 +4,10 @@ import {TouchableWithoutFeedback, Text, Alert} from 'react-native'; import renderer from 'react-test-renderer'; import FlutterwaveButton from '../src/FlutterwaveButton'; import {FlutterwaveInitOptions} from '../src/FlutterwaveInit'; -import {STANDARD_URL} from '../src/configs'; +import {STANDARD_URL, REDIRECT_URL} from '../src/configs'; import WebView from 'react-native-webview'; import DefaultButton from '../src/DefaultButton'; +import FlutterwaveInitError from '../src/utils/FlutterwaveInitError'; const BtnTestID = 'flw-default-button'; const SuccessResponse = { status: 'success', @@ -15,19 +16,29 @@ const SuccessResponse = { link: 'http://payment-link.com/checkout', }, }; -const PaymentOptions: Omit = { - txref: '34h093h09h034034', - customer_email: 'customer-email@example.com', - PBFPubKey: '[Public Key]', + +const PAYMENT_INFO: Omit = { + tx_ref: '34h093h09h034034', + customer: { + email: 'customer-email@example.com', + }, + authorization: '[Authorization]', amount: 50, currency: 'NGN', }; +const REQUEST_BODY = {...PAYMENT_INFO, redirect_url: REDIRECT_URL}; +delete REQUEST_BODY.authorization; + +const HEADERS = new Headers +HEADERS.append('Content-Type', 'application/json'); +HEADERS.append('Authorization', `Bearer ${PAYMENT_INFO.authorization}`); + describe('', () => { it('renders component correctly', () => { const Renderer = renderer.create(); expect(Renderer.toJSON()).toMatchSnapshot(); }); @@ -36,7 +47,7 @@ describe('', () => { // get create instance of flutterwave button const Renderer = renderer.create(); fetchMock.mockOnce(JSON.stringify(SuccessResponse)); Renderer.root.findByProps({testID: BtnTestID}).props.onPress(); @@ -51,7 +62,7 @@ describe('', () => { // get create instance of flutterwave button const Renderer = renderer.create(); fetchMock.mockOnce(JSON.stringify(SuccessResponse)); Renderer.root.findByProps({testID: BtnTestID}).props.onPress(); @@ -68,7 +79,7 @@ describe('', () => { it('renders custom button correctly', () => { const TestRenderer = renderer.create(', () => { it('renders webview loading correctly', () => { const TestRenderer = renderer.create(); // get webview const webView = TestRenderer.root.findByType(WebView); @@ -100,7 +111,7 @@ describe('', () => { it('renders webview error correctly', () => { const TestRenderer = renderer.create(); // get webview const webView = TestRenderer.root.findByType(WebView); @@ -114,11 +125,10 @@ describe('', () => { const customButton = jest.fn(); const TestRenderer = renderer.create(); TestRenderer.root.instance.handleInit(); - expect(customButton).toHaveBeenCalledTimes(2); expect(customButton).toHaveBeenLastCalledWith({ disabled: true, isInitializing: true, @@ -131,12 +141,11 @@ describe('', () => { const customButton = jest.fn(); const TestRenderer = renderer.create(); TestRenderer.root.instance.handleInit(); setTimeout(() => { - expect(customButton).toHaveBeenCalledTimes(4); expect(customButton).toHaveBeenLastCalledWith({ disabled: true, isInitializing: false, @@ -149,7 +158,7 @@ describe('', () => { it('asks user to confirm abort when pressed backdrop', () => { const TestRenderer = renderer.create(); // get backdrop const Backdrop = TestRenderer.root.findByProps({testID: 'flw-backdrop'}); @@ -169,7 +178,7 @@ describe('', () => { // get create instance of flutterwave button const FlwButton = renderer.create(); // fire handle abort confirm @@ -183,7 +192,7 @@ describe('', () => { // get create instance of flutterwave button const FlwButton = renderer.create(); // fire handle abort confirm FlwButton.root.instance.handleAbortConfirm(); @@ -195,7 +204,7 @@ describe('', () => { // get create instance of flutterwave button const FlwButton = renderer.create(); // mock next fetch request @@ -216,14 +225,11 @@ describe('', () => { it('makes call to standard endpoint when button is pressed', async () => { const Renderer = renderer.create(); const Button = Renderer.root.findByProps({testID: BtnTestID}); const c = new AbortController; - const headers = new Headers - - headers.append('Content-Type', 'application/json'); fetchMock.mockOnce(JSON.stringify(SuccessResponse)); Button.props.onPress(); @@ -232,10 +238,10 @@ describe('', () => { global.timeTravel(); jest.useRealTimers(); - expect(global.fetch).toBeCalledTimes(1); + // run assertions expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL, { - body: JSON.stringify(PaymentOptions), - headers: headers, + body: JSON.stringify(REQUEST_BODY), + headers: HEADERS, method: 'POST', signal: c.signal, }); @@ -251,7 +257,7 @@ describe('', () => { // create test renderer const TestRenderer = renderer.create(); // spy on component methods @@ -274,40 +280,12 @@ describe('', () => { expect(setState).toHaveBeenCalledWith({buttonSize: onSizeChangeEv}) }); - it('initialized without a redirect url', () => { - // get create instance of flutterwave button - const FlwButton = renderer.create(); - const abortController = new AbortController - // default fetch header - const FetchHeader = new Headers(); - FetchHeader.append('Content-Type', 'application/json'); - // mock next fetch request - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - // fire on press - FlwButton.root.findByProps({testID: BtnTestID}).props.onPress(); - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - // expect fetch to have been called - expect(global.fetch).toHaveBeenCalledTimes(1); - expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL, { - body: JSON.stringify({...PaymentOptions, redirect_url: undefined}), - headers: FetchHeader, - method: 'POST', - signal: abortController.signal - }); - }); - it('fires onDidInitialize if available', (done) => { const onDidInitialize = jest.fn(); // get create instance of flutterwave button const FlwButton = renderer.create(); // mock next fetch request @@ -331,7 +309,7 @@ describe('', () => { // get create instance of flutterwave button const FlwButton = renderer.create(); // mock next fetch request @@ -356,7 +334,7 @@ describe('', () => { // get create instance of flutterwave button const FlwButton = renderer.create(); // mock next fetch request @@ -370,10 +348,10 @@ describe('', () => { // wait for request to be made setTimeout(() => { expect(onInitializeError).toHaveBeenCalledTimes(1); - expect(onInitializeError).toHaveBeenCalledWith({ - code: err.name.toUpperCase(), + expect(onInitializeError).toHaveBeenCalledWith(new FlutterwaveInitError({ + code: 'STANDARD_INIT_ERROR', message: err.message - }); + })); // end test done(); }, 50); @@ -383,7 +361,7 @@ describe('', () => { // get create instance of flutterwave button const FlwButton = renderer.create(); // spy on set state const setState = jest.spyOn(FlwButton.root.instance, 'setState'); @@ -407,19 +385,16 @@ describe('', () => { it("gets redirect params and returns them on redirect", (done) => { // define response const response = { - flwref: 'erinf930rnf09', - txref: 'nfeinr09erss', + transaction_id: 'erinf930rnf09', + tx_ref: 'nfeinr09erss', } - // define url - const url = "http://redirect-url.com/api/hosted_pay/undefined" - - const urlWithParams = url + '?flwref=' + response.flwref + '&txref=' + response.txref; + const urlWithParams = REDIRECT_URL + '?transaction_id=' + response.transaction_id + '&tx_ref=' + response.tx_ref; // get create instance of flutterwave button const TestRenderer = renderer.create(); // spy on getRedirectParams method @@ -459,7 +434,7 @@ describe('', () => { // get create instance of flutterwave button const TestRenderer = renderer.create(); // spy on getRedirectParams method @@ -496,22 +471,24 @@ describe('', () => { it("fires onComplete when redirected", (done) => { // define response const response = { - flwref: 'erinf930rnf09', - txref: 'nfeinr09erss', + status: 'successful', + transaction_id: 'erinf930rnf09', + tx_ref: 'nfeinr09erss', } + // on complete const onComplete = jest.fn(); // define url - const url = "http://redirect-url.com/api/hosted_pay/undefined?flwref=" + - response.flwref + - "&txref=" + - response.txref + const url = REDIRECT_URL + + "?status=" + response.status + + "&tx_ref=" + response.tx_ref + + "&transaction_id=" + response.transaction_id // get create instance of flutterwave button const TestRenderer = renderer.create(); // spy on getRedirectParams method @@ -543,10 +520,7 @@ describe('', () => { expect(handleComplete).toHaveBeenCalledTimes(1); expect(handleComplete).toHaveBeenCalledWith(response); expect(onComplete).toHaveBeenCalledTimes(1); - expect(onComplete).toHaveBeenCalledWith({ - ...response, - canceled: false - }); + expect(onComplete).toHaveBeenCalledWith(response); // end test done(); @@ -557,7 +531,7 @@ describe('', () => { // get create instance of flutterwave button const TestRenderer = renderer.create(); fetchMock.mockOnce(JSON.stringify(SuccessResponse)); TestRenderer @@ -566,7 +540,7 @@ describe('', () => { .props .onPress(); // spy on abort method - const abort = jest.spyOn(TestRenderer.root.instance.canceller, 'abort'); + const abort = jest.spyOn(TestRenderer.root.instance.abortController, 'abort'); // call component will unmount TestRenderer.root.instance.componentWillUnmount(); // run checks @@ -578,21 +552,21 @@ describe('', () => { // get create instance of flutterwave button const TestRenderer = renderer.create(); const willUnmount = jest.spyOn(TestRenderer.root.instance, 'componentWillUnmount'); // call component will unmount TestRenderer.root.instance.componentWillUnmount(); // run checks expect(willUnmount).toHaveBeenCalledTimes(1); - expect(TestRenderer.root.instance.canceller).toBeUndefined(); + expect(TestRenderer.root.instance.abortController).toBeUndefined(); }); it('can reload webview if webview ref is set', (done) => { // create renderer const TestRender = renderer.create(); // mock next fetch request fetchMock.mockOnce(JSON.stringify(SuccessResponse)); @@ -619,7 +593,7 @@ describe('', () => { // create renderer const TestRender = renderer.create(); Object.defineProperty(TestRender.root.instance, 'webviewRef', {value: null}); const handleReload = jest.spyOn(TestRender.root.instance, 'handleReload'); @@ -631,7 +605,7 @@ describe('', () => { it("handles DefaultButton onSizeChange", () => { const TestRenderer = renderer.create(); const size = {width: 1200, height: 0}; const handleButtonResize = jest.spyOn(TestRenderer.root.instance, 'handleButtonResize'); @@ -647,7 +621,7 @@ describe('', () => { const url = new String('http://example.com'); const TestRenderer = renderer.create(); const split = jest.spyOn(url, 'split'); TestRenderer.root.instance.getRedirectParams(url);; @@ -658,7 +632,7 @@ describe('', () => { const url = new String('http://example.com?foo=bar'); const TestRenderer = renderer.create(); const split = jest.spyOn(url, 'split'); TestRenderer.root.instance.getRedirectParams(url);; @@ -669,21 +643,21 @@ describe('', () => { // get create instance of flutterwave button const TestRenderer = renderer.create(); const setState = jest.spyOn(TestRenderer.root.instance, 'setState'); // call component will unmount TestRenderer.root.instance.reset(); // run checks expect(setState).toHaveBeenCalledTimes(1); - expect(TestRenderer.root.instance.canceller).toBeUndefined(); + expect(TestRenderer.root.instance.abortController).toBeUndefined(); }); it("cancels fetch if reset is called and abort controller is set.", () => { // get create instance of flutterwave button const TestRenderer = renderer.create(); fetchMock.mockOnce(JSON.stringify(SuccessResponse)); TestRenderer @@ -692,7 +666,7 @@ describe('', () => { .props .onPress(); // spy on abort method - const abort = jest.spyOn(TestRenderer.root.instance.canceller, 'abort'); + const abort = jest.spyOn(TestRenderer.root.instance.abortController, 'abort'); // call component will unmount TestRenderer.root.instance.reset(); // run checks @@ -704,14 +678,14 @@ describe('', () => { // get create instance of flutterwave button const TestRenderer = renderer.create(); // spy on handleOptionsChanged method const handleOptionsChanged = jest.spyOn(TestRenderer.root.instance, 'handleOptionsChanged'); // update component TestRenderer.update() // run checks expect(handleOptionsChanged).toHaveBeenCalledTimes(1); @@ -722,14 +696,14 @@ describe('', () => { // get create instance of flutterwave button const TestRenderer = renderer.create(); // spy on setState method const setState = jest.spyOn(TestRenderer.root.instance, 'setState'); // update component TestRenderer.update() // run checks expect(setState).toHaveBeenCalledTimes(0); @@ -740,7 +714,7 @@ describe('', () => { // get create instance of flutterwave button const TestRenderer = renderer.create(); // mock next fetch fetchMock.mockOnce(JSON.stringify(SuccessResponse)); @@ -759,13 +733,13 @@ describe('', () => { // update component TestRenderer.update() // run checks expect(setState).toHaveBeenCalledTimes(1); expect(setState).toHaveBeenCalledWith({ link: null, - txref: null, + tx_ref: null, }); // end test done(); @@ -776,7 +750,7 @@ describe('', () => { // get create instance of flutterwave button const TestRenderer = renderer.create(); // mock next fetch fetchMock.mockOnce(JSON.stringify(SuccessResponse)); @@ -793,7 +767,7 @@ describe('', () => { // update component TestRenderer.update() // run checks expect(setState).toHaveBeenCalledTimes(1); @@ -807,7 +781,7 @@ describe('', () => { // get create instance of flutterwave button const TestRenderer = renderer.create(); // set a payment link TestRenderer.root.instance.setState({link: 'http://payment-link.com'}); @@ -832,7 +806,7 @@ describe('', () => { // get create instance of flutterwave button const TestRenderer = renderer.create(); // set a payment link TestRenderer.root.instance.setState({isPending: true}); From bd16231c84eecac479daa6cbe4d059889e84e141 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Thu, 16 Jul 2020 14:52:51 +0100 Subject: [PATCH 022/129] test(flutterwavebutton): updates webview error snapshot --- .../FlutterwaveButton.spec.tsx.snap | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/__tests__/__snapshots__/FlutterwaveButton.spec.tsx.snap b/__tests__/__snapshots__/FlutterwaveButton.spec.tsx.snap index 74c8839..dff5c82 100644 --- a/__tests__/__snapshots__/FlutterwaveButton.spec.tsx.snap +++ b/__tests__/__snapshots__/FlutterwaveButton.spec.tsx.snap @@ -714,7 +714,44 @@ exports[` renders webview error correctly 1`] = ` "top": 0, } } -/> +> + + The page failed to load, please try again. + + + + Try Again + + + `; exports[` renders webview loading correctly 1`] = ` From 72a2b78aa7d32e210e4dc6713d54fe4b77f05dd5 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Thu, 16 Jul 2020 16:24:12 +0100 Subject: [PATCH 023/129] chore(dist): build v3 library --- dist/FlutterwaveButton.d.ts | 51 +++++---- dist/FlutterwaveButton.d.ts.map | 2 +- dist/FlutterwaveButton.js | 117 ++++++++++++--------- dist/FlutterwaveInit.d.ts | 81 ++++++-------- dist/FlutterwaveInit.d.ts.map | 2 +- dist/FlutterwaveInit.js | 128 +++++++---------------- dist/configs.d.ts | 3 +- dist/configs.d.ts.map | 2 +- dist/configs.js | 23 ++-- dist/utils/FlutterwaveInitError.d.ts | 31 ++++++ dist/utils/FlutterwaveInitError.d.ts.map | 1 + dist/utils/FlutterwaveInitError.js | 32 ++++++ dist/utils/ResponseParser.d.ts | 10 ++ dist/utils/ResponseParser.d.ts.map | 1 + dist/utils/ResponseParser.js | 53 ++++++++++ src/FlutterwaveButton.tsx | 21 ++-- 16 files changed, 320 insertions(+), 238 deletions(-) create mode 100644 dist/utils/FlutterwaveInitError.d.ts create mode 100644 dist/utils/FlutterwaveInitError.d.ts.map create mode 100644 dist/utils/FlutterwaveInitError.js create mode 100644 dist/utils/ResponseParser.d.ts create mode 100644 dist/utils/ResponseParser.d.ts.map create mode 100644 dist/utils/ResponseParser.js diff --git a/dist/FlutterwaveButton.d.ts b/dist/FlutterwaveButton.d.ts index e256e63..fbc9bf8 100644 --- a/dist/FlutterwaveButton.d.ts +++ b/dist/FlutterwaveButton.d.ts @@ -3,26 +3,21 @@ import { Animated, ViewStyle } from 'react-native'; import WebView from 'react-native-webview'; import PropTypes from 'prop-types'; import { WebViewNavigation } from 'react-native-webview/lib/WebViewTypes'; -import { FlutterwaveInitOptions, FlutterwaveInitError } from './FlutterwaveInit'; +import { FlutterwaveInitOptions } from './FlutterwaveInit'; +import FlutterwaveInitError from './utils/FlutterwaveInitError'; interface CustomButtonProps { disabled: boolean; isInitializing: boolean; onPress: () => void; } -interface OnCompleteData { - canceled: boolean; - flwref?: string; - txref: string; -} interface RedirectParams { - canceled: 'true' | 'false'; - flwref?: string; - txref?: string; - response?: string; + status: 'successful' | 'cancelled'; + transaction_id?: string; + tx_ref?: string; } export interface FlutterwaveButtonProps { style?: ViewStyle; - onComplete: (data: OnCompleteData) => void; + onComplete: (data: RedirectParams) => void; onWillInitialize?: () => void; onDidInitialize?: () => void; onInitializeError?: (error: FlutterwaveInitError) => void; @@ -36,7 +31,7 @@ interface FlutterwaveButtonState { isPending: boolean; showDialog: boolean; animation: Animated.Value; - txref: string | null; + tx_ref: string | null; resetLink: boolean; buttonSize: { width: number; @@ -52,37 +47,39 @@ declare class FlutterwaveButton extends React.Component any>; onInitializeError: PropTypes.Requireable<(...args: any[]) => any>; options: PropTypes.Validator; - PBFPubKey: PropTypes.Validator; - customer_email: PropTypes.Validator; + authorization: PropTypes.Validator; + tx_ref: PropTypes.Validator; amount: PropTypes.Validator; - currency: PropTypes.Requireable; + currency: PropTypes.Validator; + integrity_hash: PropTypes.Requireable; payment_options: (props: { [k: string]: any; }, propName: string) => Error | null; payment_plan: PropTypes.Requireable; + customer: PropTypes.Validator; + phonenumber: PropTypes.Requireable; + email: PropTypes.Validator; + }>>; subaccounts: PropTypes.Requireable<(number | null | undefined)[]>; - country: PropTypes.Requireable; - pay_button_text: PropTypes.Requireable; - custom_title: PropTypes.Requireable; - custom_description: PropTypes.Requireable; - custom_logo: PropTypes.Requireable; - meta: PropTypes.Requireable<(PropTypes.InferProps<{ - metaname: PropTypes.Requireable; - metavalue: PropTypes.Requireable; - }> | null | undefined)[]>; + meta: PropTypes.Requireable<(object | null | undefined)[]>; + customizations: PropTypes.Requireable; + logo: PropTypes.Requireable; + description: PropTypes.Requireable; + }>>; }>>; customButton: PropTypes.Requireable<(...args: any[]) => any>; }; state: FlutterwaveButtonState; webviewRef: WebView | null; - canceller?: AbortController; + abortController?: AbortController; componentDidUpdate(prevProps: FlutterwaveButtonProps): void; componentWillUnmount(): void; reset: () => void; handleOptionsChanged: () => void; handleNavigationStateChange: (ev: WebViewNavigation) => void; - handleComplete(data: any): void; + handleComplete(data: RedirectParams): void; handleReload: () => void; handleAbortConfirm: () => void; handleAbort: () => void; diff --git a/dist/FlutterwaveButton.d.ts.map b/dist/FlutterwaveButton.d.ts.map index ccdfc4b..8d82f89 100644 --- a/dist/FlutterwaveButton.d.ts.map +++ b/dist/FlutterwaveButton.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"FlutterwaveButton.d.ts","sourceRoot":"","sources":["../src/FlutterwaveButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAIL,QAAQ,EAGR,SAAS,EAMV,MAAM,cAAc,CAAC;AACtB,OAAO,OAAO,MAAM,sBAAsB,CAAC;AAC3C,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,EAAC,iBAAiB,EAAC,MAAM,uCAAuC,CAAC;AACxE,OAAwB,EACtB,sBAAsB,EACtB,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAe3B,UAAU,iBAAiB;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,UAAU,cAAc;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,UAAU,cAAc;IACtB,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,UAAU,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAC3C,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC1D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,IAAI,CAAC,sBAAsB,EAAE,cAAc,CAAC,CAAC;IACtD,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,KAAK,CAAC,SAAS,CAAC;IAC9D,SAAS,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;CACnC;AAED,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,cAAM,iBAAkB,SAAQ,KAAK,CAAC,SAAS,CAC7C,sBAAsB,EACtB,sBAAsB,CACvB;IACC,MAAM,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA2Bd;IAEF,KAAK,EAAE,sBAAsB,CAW3B;IAEF,UAAU,EAAE,OAAO,GAAG,IAAI,CAAQ;IAElC,SAAS,CAAC,EAAE,eAAe,CAAC;IAE5B,kBAAkB,CAAC,SAAS,EAAE,sBAAsB;IAMpD,oBAAoB;IAMpB,KAAK,aAWH;IAEF,oBAAoB,aAYnB;IAED,2BAA2B,kCASzB;IAEF,cAAc,CAAC,IAAI,EAAE,GAAG;IAoBxB,YAAY,aAKV;IAEF,kBAAkB,aAQhB;IAEF,WAAW,aAST;IAEF,kBAAkB;;;eAKhB;IAEF,iBAAiB,kCAgBf;IAEF,IAAI,aAUF;IAEF,OAAO,aAOL;IAEF,UAAU,oBA8DR;IAEF,MAAM;IAsCN,YAAY;IA+CZ,cAAc;IAcd,aAAa;IAYb,WAAW,oBAkBT;CACH;AAuED,eAAe,iBAAiB,CAAC"} \ No newline at end of file +{"version":3,"file":"FlutterwaveButton.d.ts","sourceRoot":"","sources":["../src/FlutterwaveButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAIL,QAAQ,EAGR,SAAS,EAMV,MAAM,cAAc,CAAC;AACtB,OAAO,OAAO,MAAM,sBAAsB,CAAC;AAC3C,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,EAAC,iBAAiB,EAAC,MAAM,uCAAuC,CAAC;AACxE,OAAwB,EAAC,sBAAsB,EAAC,MAAM,mBAAmB,CAAC;AAI1E,OAAO,oBAAoB,MAAM,8BAA8B,CAAC;AAYhE,UAAU,iBAAiB;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,UAAU,cAAc;IACtB,MAAM,EAAE,YAAY,GAAG,WAAW,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,UAAU,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAC3C,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC1D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,IAAI,CAAC,sBAAsB,EAAE,cAAc,CAAC,CAAC;IACtD,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,KAAK,CAAC,SAAS,CAAC;IAC9D,SAAS,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;CACnC;AAED,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC;IAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,cAAM,iBAAkB,SAAQ,KAAK,CAAC,SAAS,CAC7C,sBAAsB,EACtB,sBAAsB,CACvB;IACC,MAAM,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA6Bd;IAEF,KAAK,EAAE,sBAAsB,CAW3B;IAEF,UAAU,EAAE,OAAO,GAAG,IAAI,CAAQ;IAElC,eAAe,CAAC,EAAE,eAAe,CAAC;IAElC,kBAAkB,CAAC,SAAS,EAAE,sBAAsB;IAMpD,oBAAoB;IAMpB,KAAK,aAWH;IAEF,oBAAoB,aAYnB;IAED,2BAA2B,kCASzB;IAEF,cAAc,CAAC,IAAI,EAAE,cAAc;IAgBnC,YAAY,aAKV;IAEF,kBAAkB,aAQhB;IAEF,WAAW,aAST;IAEF,kBAAkB;;;eAKhB;IAEF,iBAAiB,kCAgBf;IAEF,IAAI,aAUF;IAEF,OAAO,aAOL;IAEF,UAAU,oBAmER;IAEF,MAAM;IAsCN,YAAY;IA+CZ,cAAc;IAcd,aAAa;IAYb,WAAW,oBAkBT;CACH;AAuED,eAAe,iBAAiB,CAAC"} \ No newline at end of file diff --git a/dist/FlutterwaveButton.js b/dist/FlutterwaveButton.js index b5dbcb4..1797a83 100644 --- a/dist/FlutterwaveButton.js +++ b/dist/FlutterwaveButton.js @@ -11,6 +11,17 @@ var __extends = (this && this.__extends) || (function () { d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { @@ -52,9 +63,10 @@ import { StyleSheet, Modal, View, Animated, TouchableWithoutFeedback, Text, Aler import WebView from 'react-native-webview'; import PropTypes from 'prop-types'; import FlutterwaveInit from './FlutterwaveInit'; -import { colors } from './configs'; +import { colors, REDIRECT_URL } from './configs'; import { PaymentOptionsPropRule } from './utils/CustomPropTypesRules'; import DefaultButton from './DefaultButton'; +import FlutterwaveInitError from './utils/FlutterwaveInitError'; var loader = require('./loader.gif'); var pryContent = require('./pry-button-content.png'); var contentWidthPercentage = 0.6549707602; @@ -75,7 +87,7 @@ var FlutterwaveButton = /** @class */ (function (_super) { resetLink: false, showDialog: false, animation: new Animated.Value(0), - txref: null, + tx_ref: null, buttonSize: { width: 0, height: 0 @@ -83,8 +95,8 @@ var FlutterwaveButton = /** @class */ (function (_super) { }; _this.webviewRef = null; _this.reset = function () { - if (_this.canceller) { - _this.canceller.abort(); + if (_this.abortController) { + _this.abortController.abort(); } // reset the necessaries _this.setState(function (_a) { @@ -105,14 +117,14 @@ var FlutterwaveButton = /** @class */ (function (_super) { if (!showDialog) { return _this.setState({ link: null, - txref: null + tx_ref: null }); } _this.setState({ resetLink: true }); }; _this.handleNavigationStateChange = function (ev) { // cregex to check if redirect has occured on completion/cancel - var rx = /\/hosted\/pay\/undefined|\/api\/hosted_pay\/undefined/; + var rx = /\/flutterwave\.com\/rn-redirect/; // Don't end payment if not redirected back if (!rx.test(ev.url)) { return; @@ -132,7 +144,7 @@ var FlutterwaveButton = /** @class */ (function (_super) { if (onAbort) { onAbort(); } - // remove txref and dismiss + // remove tx_ref and dismiss _this.dismiss(); }; _this.handleAbort = function () { @@ -189,24 +201,24 @@ var FlutterwaveButton = /** @class */ (function (_super) { }; _this.handleInit = function () { var _a = _this.props, options = _a.options, onWillInitialize = _a.onWillInitialize, onInitializeError = _a.onInitializeError, onDidInitialize = _a.onDidInitialize; - var _b = _this.state, isPending = _b.isPending, txref = _b.txref, link = _b.link; + var _b = _this.state, isPending = _b.isPending, tx_ref = _b.tx_ref, link = _b.link; // just show the dialod if the link is already set if (link) { return _this.show(); } // throw error if transaction reference has not changed - if (txref === options.txref) { - return onInitializeError ? onInitializeError({ + if (tx_ref === options.tx_ref) { + return onInitializeError ? onInitializeError(new FlutterwaveInitError({ message: 'Please generate a new transaction reference.', code: 'SAME_TXREF' - }) : null; + })) : null; } // stop if currently in pending mode if (isPending) { return; } // initialize abort controller if not set - _this.canceller = new AbortController; + _this.abortController = new AbortController; // fire will initialize handler if available if (onWillInitialize) { onWillInitialize(); @@ -218,31 +230,34 @@ var FlutterwaveButton = /** @class */ (function (_super) { _this.setState({ isPending: true, link: null, - txref: options.txref + tx_ref: options.tx_ref }, function () { return __awaiter(_this, void 0, void 0, function () { - var result; + var link_1, error_1; return __generator(this, function (_a) { switch (_a.label) { - case 0: return [4 /*yield*/, FlutterwaveInit(options, { canceller: this.canceller })]; + case 0: + _a.trys.push([0, 2, , 3]); + return [4 /*yield*/, FlutterwaveInit(__assign(__assign({}, options), { redirect_url: REDIRECT_URL }), this.abortController)]; case 1: - result = _a.sent(); - // stop if request was canceled - if (result.error && /aborterror/i.test(result.error.code)) { - return [2 /*return*/]; - } - // call onInitializeError handler if an error occured - if (!result.link) { - if (onInitializeError && result.error) { - onInitializeError(result.error); - } - return [2 /*return*/, this.dismiss()]; - } - this.setState({ link: result.link, isPending: false }, this.show); + link_1 = _a.sent(); + // resent pending mode + this.setState({ link: link_1, isPending: false }, this.show); // fire did initialize handler if available if (onDidInitialize) { onDidInitialize(); } - return [2 /*return*/]; + return [3 /*break*/, 3]; + case 2: + error_1 = _a.sent(); + // stop if request was canceled + if (/aborterror/i.test(error_1.code)) { + return [2 /*return*/]; + } + if (onInitializeError) { + onInitializeError(error_1); + } + return [2 /*return*/, this.dismiss()]; + case 3: return [2 /*return*/]; } }); }); }); @@ -270,8 +285,8 @@ var FlutterwaveButton = /** @class */ (function (_super) { } }; FlutterwaveButton.prototype.componentWillUnmount = function () { - if (this.canceller) { - this.canceller.abort(); + if (this.abortController) { + this.abortController.abort(); } }; FlutterwaveButton.prototype.handleComplete = function (data) { @@ -279,20 +294,16 @@ var FlutterwaveButton = /** @class */ (function (_super) { var onComplete = this.props.onComplete; // reset payment link this.setState(function (_a) { - var resetLink = _a.resetLink, txref = _a.txref; + var resetLink = _a.resetLink, tx_ref = _a.tx_ref; return ({ - txref: data.flref && !data.canceled ? null : txref, - resetLink: data.flwref && !data.canceled ? true : resetLink + tx_ref: data.status === 'successful' ? null : tx_ref, + resetLink: data.status === 'successful' ? true : resetLink }); }, function () { // reset _this.dismiss(); // fire onComplete handler - onComplete({ - flwref: data.flwref, - txref: data.txref, - canceled: /true/i.test(data.canceled || '') ? true : false - }); + onComplete(data); }); }; FlutterwaveButton.prototype.render = function () { @@ -371,23 +382,25 @@ var FlutterwaveButton = /** @class */ (function (_super) { onDidInitialize: PropTypes.func, onInitializeError: PropTypes.func, options: PropTypes.shape({ - txref: PropTypes.string.isRequired, - PBFPubKey: PropTypes.string.isRequired, - customer_email: PropTypes.string.isRequired, + authorization: PropTypes.string.isRequired, + tx_ref: PropTypes.string.isRequired, amount: PropTypes.number.isRequired, - currency: PropTypes.oneOf(['NGN', 'USD', 'GHS', 'KES', 'ZAR', 'TZS']), + currency: PropTypes.oneOf(['NGN', 'USD', 'GBP', 'GHS', 'KES', 'ZAR', 'TZS']).isRequired, + integrity_hash: PropTypes.string, payment_options: PaymentOptionsPropRule, payment_plan: PropTypes.number, + customer: PropTypes.shape({ + name: PropTypes.string, + phonenumber: PropTypes.string, + email: PropTypes.string.isRequired + }).isRequired, subaccounts: PropTypes.arrayOf(PropTypes.number), - country: PropTypes.string, - pay_button_text: PropTypes.string, - custom_title: PropTypes.string, - custom_description: PropTypes.string, - custom_logo: PropTypes.string, - meta: PropTypes.arrayOf(PropTypes.shape({ - metaname: PropTypes.string, - metavalue: PropTypes.string - })) + meta: PropTypes.arrayOf(PropTypes.object), + customizations: PropTypes.shape({ + title: PropTypes.string, + logo: PropTypes.string, + description: PropTypes.string + }) }).isRequired, customButton: PropTypes.func }; diff --git a/dist/FlutterwaveInit.d.ts b/dist/FlutterwaveInit.d.ts index 67e3489..343e8da 100644 --- a/dist/FlutterwaveInit.d.ts +++ b/dist/FlutterwaveInit.d.ts @@ -1,69 +1,52 @@ /// interface FlutterwavePaymentMeta { - metaname: string; - metavalue: string; + [k: string]: any; +} +export interface InitCustomer { + email: string; + phonenumber?: string; + name?: string; +} +export interface InitCustomizations { + title?: string; + logo?: string; + description?: string; } export interface FlutterwaveInitOptions { - txref: string; - PBFPubKey: string; - customer_firstname?: string; - customer_lastname?: string; - customer_phone?: string; - customer_email: string; + authorization: string; + tx_ref: string; amount: number; - currency?: string; - redirect_url?: string; + currency: string; + integrity_hash?: string; payment_options?: string; payment_plan?: number; + redirect_url: string; + customer: InitCustomer; subaccounts?: Array; - country?: string; - pay_button_text?: string; - custom_title?: string; - custom_description?: string; - custom_logo?: string; meta?: Array; + customizations?: InitCustomizations; } -interface FlutterwaveInitConfig { - canceller?: AbortController; -} -export interface FlutterwaveInitError { - code: string; +export interface FieldError { + field: string; message: string; } -interface FlutterwaveInitResult { - error?: FlutterwaveInitError | null; - link?: string | null; +export interface ResponseData { + status?: 'success' | 'error'; + message: string; + error_id?: string; + errors?: Array; + code?: string; + data?: { + link: string; + }; } /** * This function is responsible for making the request to * initialize a Flutterwave payment. * @param options FlutterwaveInitOptions - * @return Promise<{ - * error: { - * code: string; - * message: string; - * } | null; - * link?: string | null; - * }> + * @param abortController AbortController + * @return Promise */ -export default function FlutterwaveInit(options: FlutterwaveInitOptions, config?: FlutterwaveInitConfig): Promise; -/** - * Flutterwave Init Error - */ -export declare class FlutterwaveInitException extends Error { - /** - * Error code - * @var string - */ - code: string; - /** - * Constructor Method - * @param props {message?: string; code?: string} - */ - constructor(props: { - message: string; - code: string; - }); -} +export default function FlutterwaveInit(options: FlutterwaveInitOptions, abortController?: AbortController): Promise; export {}; //# sourceMappingURL=FlutterwaveInit.d.ts.map \ No newline at end of file diff --git a/dist/FlutterwaveInit.d.ts.map b/dist/FlutterwaveInit.d.ts.map index 96d26c4..b3c0a89 100644 --- a/dist/FlutterwaveInit.d.ts.map +++ b/dist/FlutterwaveInit.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"FlutterwaveInit.d.ts","sourceRoot":"","sources":["../src/FlutterwaveInit.ts"],"names":[],"mappings":";AAEA,UAAU,sBAAsB;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,KAAK,CAAC,sBAAsB,CAAC,CAAC;CACtC;AAED,UAAU,qBAAqB;IAC7B,SAAS,CAAC,EAAE,eAAe,CAAC;CAC7B;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,qBAAqB;IAC7B,KAAK,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACpC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAmBD;;;;;;;;;;;GAWG;AACH,wBAA8B,eAAe,CAC3C,OAAO,EAAE,sBAAsB,EAC/B,MAAM,GAAE,qBAA0B,GACjC,OAAO,CAAC,qBAAqB,CAAC,CA2DhC;AAED;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;IACjD;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;OAGG;gBACS,KAAK,EAAE;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC;CAInD"} \ No newline at end of file +{"version":3,"file":"FlutterwaveInit.d.ts","sourceRoot":"","sources":["../src/FlutterwaveInit.ts"],"names":[],"mappings":";AAIA,UAAU,sBAAsB;IAC9B,CAAC,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,sBAAsB;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,YAAY,CAAC;IACvB,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC5B,IAAI,CAAC,EAAE,KAAK,CAAC,sBAAsB,CAAC,CAAC;IACrC,cAAc,CAAC,EAAE,kBAAkB,CAAC;CACrC;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AASD;;;;;;GAMG;AACH,wBAA8B,eAAe,CAC3C,OAAO,EAAE,sBAAsB,EAC/B,eAAe,CAAC,EAAE,eAAe,GAChC,OAAO,CAAC,MAAM,CAAC,CAgCjB"} \ No newline at end of file diff --git a/dist/FlutterwaveInit.js b/dist/FlutterwaveInit.js index 9a44a70..13d7f87 100644 --- a/dist/FlutterwaveInit.js +++ b/dist/FlutterwaveInit.js @@ -1,27 +1,3 @@ -var __extends = (this && this.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -var __assign = (this && this.__assign) || function () { - __assign = Object.assign || function(t) { - for (var s, i = 1, n = arguments.length; i < n; i++) { - s = arguments[i]; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) - t[p] = s[p]; - } - return t; - }; - return __assign.apply(this, arguments); -}; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { @@ -58,94 +34,68 @@ var __generator = (this && this.__generator) || function (thisArg, body) { if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; import { STANDARD_URL } from './configs'; +import ResponseParser from './utils/ResponseParser'; +import FlutterwaveInitError from './utils/FlutterwaveInitError'; /** * This function is responsible for making the request to * initialize a Flutterwave payment. * @param options FlutterwaveInitOptions - * @return Promise<{ - * error: { - * code: string; - * message: string; - * } | null; - * link?: string | null; - * }> + * @param abortController AbortController + * @return Promise */ -export default function FlutterwaveInit(options, config) { - if (config === void 0) { config = {}; } +export default function FlutterwaveInit(options, abortController) { return __awaiter(this, void 0, void 0, function () { - var body, headers, fetchOptions, response, responseJSON, e_1; - return __generator(this, function (_a) { - switch (_a.label) { + var authorization, body, headers, fetchOptions, response, responseData, _a, _b, e_1, error; + return __generator(this, function (_c) { + switch (_c.label) { case 0: - _a.trys.push([0, 3, , 4]); - body = __assign({}, options); + _c.trys.push([0, 4, , 5]); + authorization = options.authorization, body = __rest(options, ["authorization"]); headers = new Headers; headers.append('Content-Type', 'application/json'); + headers.append('Authorization', "Bearer " + authorization); fetchOptions = { method: 'POST', body: JSON.stringify(body), headers: headers }; - // add canceller if defined - if (config.canceller) { - fetchOptions.signal = config.canceller.signal; + // add abortController if defined + if (abortController) { + fetchOptions.signal = abortController.signal; } ; return [4 /*yield*/, fetch(STANDARD_URL, fetchOptions)]; case 1: - response = _a.sent(); + response = _c.sent(); return [4 /*yield*/, response.json()]; case 2: - responseJSON = _a.sent(); - // check if data is missing from response - if (!responseJSON.data) { - throw new FlutterwaveInitException({ - code: 'NODATA', - message: responseJSON.message || 'An unknown error occured!' - }); - } - // check if the link is missing in data - if (!responseJSON.data.link) { - throw new FlutterwaveInitException({ - code: responseJSON.data.code || 'UNKNOWN', - message: responseJSON.data.message || 'An unknown error occured!' - }); - } - // resolve with the payment link - return [2 /*return*/, Promise.resolve({ - link: responseJSON.data.link - })]; - case 3: - e_1 = _a.sent(); + responseData = _c.sent(); + _b = (_a = Promise).resolve; + return [4 /*yield*/, ResponseParser(responseData)]; + case 3: + // resolve with the payment link + return [2 /*return*/, _b.apply(_a, [_c.sent()])]; + case 4: + e_1 = _c.sent(); + error = e_1 instanceof FlutterwaveInitError + ? e_1 + : new FlutterwaveInitError({ message: e_1.message, code: e_1.name.toUpperCase() }); // resolve with error - return [2 /*return*/, Promise.resolve({ - error: { - code: e_1 instanceof FlutterwaveInitException - ? e_1.code - : String(e_1.name).toUpperCase(), - message: e_1.message - } - })]; - case 4: return [2 /*return*/]; + return [2 /*return*/, Promise.reject(error)]; + case 5: return [2 /*return*/]; } }); }); } -/** - * Flutterwave Init Error - */ -var FlutterwaveInitException = /** @class */ (function (_super) { - __extends(FlutterwaveInitException, _super); - /** - * Constructor Method - * @param props {message?: string; code?: string} - */ - function FlutterwaveInitException(props) { - var _this = _super.call(this, props.message) || this; - _this.code = props.code; - return _this; - } - return FlutterwaveInitException; -}(Error)); -export { FlutterwaveInitException }; diff --git a/dist/configs.d.ts b/dist/configs.d.ts index 89a0592..624273a 100644 --- a/dist/configs.d.ts +++ b/dist/configs.d.ts @@ -1,7 +1,8 @@ /** * Flutterwaves standard init url. */ -export declare const STANDARD_URL: string; +export declare const STANDARD_URL = "https://api.flutterwave.com/v3/payments"; +export declare const REDIRECT_URL = "https://flutterwave.com/rn-redirect"; export declare const colors: { primary: string; secondary: string; diff --git a/dist/configs.d.ts.map b/dist/configs.d.ts.map index 3e9399f..54c6658 100644 --- a/dist/configs.d.ts.map +++ b/dist/configs.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"configs.d.ts","sourceRoot":"","sources":["../src/configs.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,MACoC,CAAC;AAEhE,eAAO,MAAM,MAAM;;;;CAIlB,CAAC;AAEF,eAAO,MAAM,eAAe,UAc3B,CAAC"} \ No newline at end of file +{"version":3,"file":"configs.d.ts","sourceRoot":"","sources":["../src/configs.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,YAAY,4CAA4C,CAAC;AAEtE,eAAO,MAAM,YAAY,wCAAwC,CAAC;AAElE,eAAO,MAAM,MAAM;;;;CAIlB,CAAC;AAEF,eAAO,MAAM,eAAe,UAkB3B,CAAC"} \ No newline at end of file diff --git a/dist/configs.js b/dist/configs.js index 62da89d..3f84bee 100644 --- a/dist/configs.js +++ b/dist/configs.js @@ -1,24 +1,29 @@ /** * Flutterwaves standard init url. */ -export var STANDARD_URL = 'https://api.ravepay.co/flwv3-pug/getpaidx/api/v2/hosted/pay'; +export var STANDARD_URL = 'https://api.flutterwave.com/v3/payments'; +export var REDIRECT_URL = 'https://flutterwave.com/rn-redirect'; export var colors = { primary: '#f5a623', secondary: '#12122C', transparent: 'rgba(0,0,0,0)' }; export var PAYMENT_OPTIONS = [ - 'card', 'account', - 'ussd', - 'qr', + 'card', + 'banktransfer', 'mpesa', - 'mobilemoneyghana', - 'mobilemoneyuganda', 'mobilemoneyrwanda', 'mobilemoneyzambia', - 'mobilemoneytanzania', + 'qr', + 'mobilemoneyuganda', + 'ussd', + 'credit', 'barter', - 'bank transfer', - 'wechat', + 'mobilemoneyghana', + 'payattitude', + 'mobilemoneyfranco', + 'paga', + '1voucher', + 'mobilemoneytanzania', ]; diff --git a/dist/utils/FlutterwaveInitError.d.ts b/dist/utils/FlutterwaveInitError.d.ts new file mode 100644 index 0000000..1f71182 --- /dev/null +++ b/dist/utils/FlutterwaveInitError.d.ts @@ -0,0 +1,31 @@ +/** + * Flutterwave Init Error + */ +export default class FlutterwaveInitError extends Error { + /** + * Error code + * @var string + */ + code: string; + /** + * Error code + * @var string + */ + errorId?: string; + /** + * Error code + * @var string + */ + errors?: Array; + /** + * Constructor Method + * @param props {message?: string; code?: string} + */ + constructor(props: { + message: string; + code: string; + errorId?: string; + errors?: Array; + }); +} +//# sourceMappingURL=FlutterwaveInitError.d.ts.map \ No newline at end of file diff --git a/dist/utils/FlutterwaveInitError.d.ts.map b/dist/utils/FlutterwaveInitError.d.ts.map new file mode 100644 index 0000000..994f8be --- /dev/null +++ b/dist/utils/FlutterwaveInitError.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"FlutterwaveInitError.d.ts","sourceRoot":"","sources":["../../src/utils/FlutterwaveInitError.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,oBAAqB,SAAQ,KAAK;IACrD;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;MAGE;IACF,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;MAGE;IACF,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAEvB;;;OAGG;gBACS,KAAK,EAAE;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;KAAC;CAM7F"} \ No newline at end of file diff --git a/dist/utils/FlutterwaveInitError.js b/dist/utils/FlutterwaveInitError.js new file mode 100644 index 0000000..ec1d9ef --- /dev/null +++ b/dist/utils/FlutterwaveInitError.js @@ -0,0 +1,32 @@ +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +/** + * Flutterwave Init Error + */ +var FlutterwaveInitError = /** @class */ (function (_super) { + __extends(FlutterwaveInitError, _super); + /** + * Constructor Method + * @param props {message?: string; code?: string} + */ + function FlutterwaveInitError(props) { + var _this = _super.call(this, props.message) || this; + _this.code = props.code; + _this.errorId = props.errorId; + _this.errors = props.errors; + return _this; + } + return FlutterwaveInitError; +}(Error)); +export default FlutterwaveInitError; diff --git a/dist/utils/ResponseParser.d.ts b/dist/utils/ResponseParser.d.ts new file mode 100644 index 0000000..62c8308 --- /dev/null +++ b/dist/utils/ResponseParser.d.ts @@ -0,0 +1,10 @@ +import { ResponseData } from "../FlutterwaveInit"; +/** + * The purpose of this function is to parse the response message gotten from a + * payment initialization error. + * @param message string + * @param code string (optional) + * @returns {message: string; code: string} + */ +export default function ResponseParser({ status, errors, message, data, code, error_id, }: ResponseData): Promise; +//# sourceMappingURL=ResponseParser.d.ts.map \ No newline at end of file diff --git a/dist/utils/ResponseParser.d.ts.map b/dist/utils/ResponseParser.d.ts.map new file mode 100644 index 0000000..b50336e --- /dev/null +++ b/dist/utils/ResponseParser.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"ResponseParser.d.ts","sourceRoot":"","sources":["../../src/utils/ResponseParser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,cAAc,CACpC,EACE,MAAM,EACN,MAAM,EACN,OAAO,EACP,IAAI,EACJ,IAAI,EACJ,QAAQ,GACT,EAAE,YAAY,GACd,OAAO,CAAC,MAAM,CAAC,CA6CjB"} \ No newline at end of file diff --git a/dist/utils/ResponseParser.js b/dist/utils/ResponseParser.js new file mode 100644 index 0000000..9c8db5f --- /dev/null +++ b/dist/utils/ResponseParser.js @@ -0,0 +1,53 @@ +import FlutterwaveInitError from "./FlutterwaveInitError"; +/** + * The purpose of this function is to parse the response message gotten from a + * payment initialization error. + * @param message string + * @param code string (optional) + * @returns {message: string; code: string} + */ +export default function ResponseParser(_a) { + var status = _a.status, errors = _a.errors, message = _a.message, data = _a.data, code = _a.code, error_id = _a.error_id; + return new Promise(function (resolve, reject) { + // return success message + if (status === 'success') { + // check if data or data link is missing + if (!data || !data.link) { + return reject(new FlutterwaveInitError({ + code: 'MALFORMED_RESPONSE', + message: message + })); + } + // return the payment link + return resolve(data.link); + } + // missing authorization + if (/authorization/i.test(message) && /required/i.test(message)) { + reject(new FlutterwaveInitError({ + code: 'AUTH_MISSING', + message: message + })); + } + // invalid authorization + if (/authorization/i.test(message) && /invalid/i.test(message)) { + reject(new FlutterwaveInitError({ + code: 'AUTH_INVALID', + message: message + })); + } + // field errors + if (errors) { + reject(new FlutterwaveInitError({ + code: 'INVALID_OPTIONS', + message: message, + errors: errors.map(function (i) { return i.message; }) + })); + } + // defaults to the initially passed message + reject(new FlutterwaveInitError({ + code: String(code || 'STANDARD_INIT_ERROR').toUpperCase(), + message: message, + errorId: error_id + })); + }); +} diff --git a/src/FlutterwaveButton.tsx b/src/FlutterwaveButton.tsx index 8104dd5..6fcb4b7 100644 --- a/src/FlutterwaveButton.tsx +++ b/src/FlutterwaveButton.tsx @@ -442,16 +442,21 @@ class FlutterwaveButton extends React.Component< } renderError = () => { + const {link} = this.state; return ( - - The page failed to load, please try again. - - - - Try Again - - + {link ? ( + <> + + The page failed to load, please try again. + + + + Try Again + + + + ) : null} ); }; From 18559be0c37b0584f99c5ab6cf0e6616f54fc1ff Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Thu, 16 Jul 2020 18:10:43 +0100 Subject: [PATCH 024/129] fix(flutterwavebutton): reset link and tx_ref on standard init failure --- dist/FlutterwaveButton.d.ts.map | 2 +- dist/FlutterwaveButton.js | 5 ++++- src/FlutterwaveButton.tsx | 6 ++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/dist/FlutterwaveButton.d.ts.map b/dist/FlutterwaveButton.d.ts.map index 8d82f89..90e1145 100644 --- a/dist/FlutterwaveButton.d.ts.map +++ b/dist/FlutterwaveButton.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"FlutterwaveButton.d.ts","sourceRoot":"","sources":["../src/FlutterwaveButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAIL,QAAQ,EAGR,SAAS,EAMV,MAAM,cAAc,CAAC;AACtB,OAAO,OAAO,MAAM,sBAAsB,CAAC;AAC3C,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,EAAC,iBAAiB,EAAC,MAAM,uCAAuC,CAAC;AACxE,OAAwB,EAAC,sBAAsB,EAAC,MAAM,mBAAmB,CAAC;AAI1E,OAAO,oBAAoB,MAAM,8BAA8B,CAAC;AAYhE,UAAU,iBAAiB;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,UAAU,cAAc;IACtB,MAAM,EAAE,YAAY,GAAG,WAAW,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,UAAU,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAC3C,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC1D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,IAAI,CAAC,sBAAsB,EAAE,cAAc,CAAC,CAAC;IACtD,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,KAAK,CAAC,SAAS,CAAC;IAC9D,SAAS,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;CACnC;AAED,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC;IAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,cAAM,iBAAkB,SAAQ,KAAK,CAAC,SAAS,CAC7C,sBAAsB,EACtB,sBAAsB,CACvB;IACC,MAAM,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA6Bd;IAEF,KAAK,EAAE,sBAAsB,CAW3B;IAEF,UAAU,EAAE,OAAO,GAAG,IAAI,CAAQ;IAElC,eAAe,CAAC,EAAE,eAAe,CAAC;IAElC,kBAAkB,CAAC,SAAS,EAAE,sBAAsB;IAMpD,oBAAoB;IAMpB,KAAK,aAWH;IAEF,oBAAoB,aAYnB;IAED,2BAA2B,kCASzB;IAEF,cAAc,CAAC,IAAI,EAAE,cAAc;IAgBnC,YAAY,aAKV;IAEF,kBAAkB,aAQhB;IAEF,WAAW,aAST;IAEF,kBAAkB;;;eAKhB;IAEF,iBAAiB,kCAgBf;IAEF,IAAI,aAUF;IAEF,OAAO,aAOL;IAEF,UAAU,oBAmER;IAEF,MAAM;IAsCN,YAAY;IA+CZ,cAAc;IAcd,aAAa;IAYb,WAAW,oBAkBT;CACH;AAuED,eAAe,iBAAiB,CAAC"} \ No newline at end of file +{"version":3,"file":"FlutterwaveButton.d.ts","sourceRoot":"","sources":["../src/FlutterwaveButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAIL,QAAQ,EAGR,SAAS,EAMV,MAAM,cAAc,CAAC;AACtB,OAAO,OAAO,MAAM,sBAAsB,CAAC;AAC3C,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,EAAC,iBAAiB,EAAC,MAAM,uCAAuC,CAAC;AACxE,OAAwB,EAAC,sBAAsB,EAAC,MAAM,mBAAmB,CAAC;AAI1E,OAAO,oBAAoB,MAAM,8BAA8B,CAAC;AAYhE,UAAU,iBAAiB;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,UAAU,cAAc;IACtB,MAAM,EAAE,YAAY,GAAG,WAAW,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,UAAU,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAC3C,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC1D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,IAAI,CAAC,sBAAsB,EAAE,cAAc,CAAC,CAAC;IACtD,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,KAAK,CAAC,SAAS,CAAC;IAC9D,SAAS,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;CACnC;AAED,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC;IAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,cAAM,iBAAkB,SAAQ,KAAK,CAAC,SAAS,CAC7C,sBAAsB,EACtB,sBAAsB,CACvB;IACC,MAAM,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA6Bd;IAEF,KAAK,EAAE,sBAAsB,CAW3B;IAEF,UAAU,EAAE,OAAO,GAAG,IAAI,CAAQ;IAElC,eAAe,CAAC,EAAE,eAAe,CAAC;IAElC,kBAAkB,CAAC,SAAS,EAAE,sBAAsB;IAMpD,oBAAoB;IAMpB,KAAK,aAWH;IAEF,oBAAoB,aAYnB;IAED,2BAA2B,kCASzB;IAEF,cAAc,CAAC,IAAI,EAAE,cAAc;IAgBnC,YAAY,aAKV;IAEF,kBAAkB,aAQhB;IAEF,WAAW,aAST;IAEF,kBAAkB;;;eAKhB;IAEF,iBAAiB,kCAgBf;IAEF,IAAI,aAUF;IAEF,OAAO,aAOL;IAEF,UAAU,oBAqER;IAEF,MAAM;IAsCN,YAAY;IA+CZ,cAAc;IAcd,aAAa;IAYb,WAAW,oBAkBT;CACH;AAuED,eAAe,iBAAiB,CAAC"} \ No newline at end of file diff --git a/dist/FlutterwaveButton.js b/dist/FlutterwaveButton.js index 1797a83..2394b99 100644 --- a/dist/FlutterwaveButton.js +++ b/dist/FlutterwaveButton.js @@ -256,7 +256,10 @@ var FlutterwaveButton = /** @class */ (function (_super) { if (onInitializeError) { onInitializeError(error_1); } - return [2 /*return*/, this.dismiss()]; + return [2 /*return*/, this.setState({ + resetLink: true, + tx_ref: null + }, this.dismiss)]; case 3: return [2 /*return*/]; } }); diff --git a/src/FlutterwaveButton.tsx b/src/FlutterwaveButton.tsx index 6fcb4b7..717810b 100644 --- a/src/FlutterwaveButton.tsx +++ b/src/FlutterwaveButton.tsx @@ -323,8 +323,10 @@ class FlutterwaveButton extends React.Component< if (onInitializeError) { onInitializeError(error); } - return this.dismiss(); - + return this.setState({ + resetLink: true, + tx_ref: null + }, this.dismiss); } }, ); From c1fef5f1a6919750af2cd58a33c3f486b5911fd3 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Thu, 16 Jul 2020 18:13:01 +0100 Subject: [PATCH 025/129] test(flutterwavebutton): add snapshot for webview render error view without error --- __tests__/FlutterwaveButton.spec.tsx | 26 ++++++++++++++++++- .../FlutterwaveButton.spec.tsx.snap | 18 +++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/__tests__/FlutterwaveButton.spec.tsx b/__tests__/FlutterwaveButton.spec.tsx index 3e53b45..48b5e4d 100644 --- a/__tests__/FlutterwaveButton.spec.tsx +++ b/__tests__/FlutterwaveButton.spec.tsx @@ -108,7 +108,31 @@ describe('', () => { expect(LoadingRenderer).toMatchSnapshot(); }); - it('renders webview error correctly', () => { + it('renders webview error correctly', (done) => { + const TestRenderer = renderer.create(); + // mock next fetch request + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + // press button + TestRenderer.root.findByProps({testID: BtnTestID}).props.onPress(); + // simulate animated animation + jest.useFakeTimers(); + global.timeTravel(); + jest.useRealTimers(); + // checks + setTimeout(() => { + // get webview + const webView = TestRenderer.root.findByType(WebView); + // create error renderer + const ErrorRenderer = renderer.create(webView.props.renderError()); + expect(ErrorRenderer).toMatchSnapshot(); + done(); + }, 50); + }); + + it('does not render webview error if there is no link', () => { const TestRenderer = renderer.create( does not render webview error if there is no link 1`] = ` + +`; + exports[` renders busy button if isPending 1`] = ` Array [ Date: Mon, 20 Jul 2020 13:09:59 +0100 Subject: [PATCH 026/129] fix(flutterwavebutton): define tx_ref in redirect params as always available --- src/FlutterwaveButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FlutterwaveButton.tsx b/src/FlutterwaveButton.tsx index 717810b..bf3d604 100644 --- a/src/FlutterwaveButton.tsx +++ b/src/FlutterwaveButton.tsx @@ -41,7 +41,7 @@ interface CustomButtonProps { interface RedirectParams { status: 'successful' | 'cancelled', transaction_id?: string; - tx_ref?: string; + tx_ref: string; } export interface FlutterwaveButtonProps { From be9c85039a206e67e36ceac5dfa6d87bad985c0a Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 20 Jul 2020 15:47:02 +0100 Subject: [PATCH 027/129] docs(readme): reference version 2 api library version --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 4092515..2a5c30c 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,9 @@ Easily implement Flutterwave for payments in your React Native appliction. This - Standard payment initialization function. - Flutterwave designed button. +## :warning: If Using Version 2 API :warning: +This version of the library uses Version 3 of Flutterwave's API, if you are still using the Version 2 API please use [this documentation](/README.md) instead. + ## Installation This library is available on npm, you can install it by running `npm install --save react-native-flutterwave` or `yarn add react-native-flutterwave` From db9536c51c88fc665a896353d609f3f41e93b99d Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 20 Jul 2020 15:49:46 +0100 Subject: [PATCH 028/129] docs(readme): redefine init meta interface --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 2a5c30c..b38ba99 100644 --- a/README.md +++ b/README.md @@ -259,8 +259,7 @@ interface FlutterwaveInitResult { #### FlutterwavePaymentMeta ````typescript interface FlutterwavePaymentMeta { - metaname: string; - metavalue: string; + [k: string]: any; } ```` From 7355b6c4e64ba360453803981260c7841c3abd0c Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 20 Jul 2020 15:53:27 +0100 Subject: [PATCH 029/129] docs(readme): update on complete data --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b38ba99..dd04539 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Easily implement Flutterwave for payments in your React Native appliction. This ## Table Of Content - Getting Started + - [V2 API](#if-using-version-2-api) - [Installation](#installation) - [Dependencies](#dependencies) - [Activity Indicator (Android)](#activity-indicator-only-needed-for-android) @@ -30,7 +31,7 @@ Easily implement Flutterwave for payments in your React Native appliction. This - [Flutterwave Init Options](#flutterwaveinitoptions-interface) - [Flutterwave Init Error](#flutterwaveiniterror) - [FlutterwavePaymentMeta](#flutterwavepaymentmeta) - - [OnCompleteData](#oncompletedata) + - [RedirectParams](#redirectparams) - [CustomButtonProps](#custombuttonprops) - [Contributing](./CONTRIBUTING.md) @@ -198,7 +199,7 @@ Hi :wave:, so there are cases where you have already initialized a payment with | Name | Required | Type | Default | Description | | --------- | --------- | ---- | ------- | ----------- | | style | No | object | undefined | Used to apply styling to the button.| -| onComplete | Yes | function | **REQUIRED** | Called when a payment is completed successfully or is canceled. The function will receive [on complete data](#oncompletedata)| +| onComplete | Yes | function | **REQUIRED** | Called when a payment is completed successfully or is canceled. The function will receive [redirect params](#redirectparams) as an argument.| | onWillInitialize | No | function | undefined | This will be called before a payment link is generated.| | onDidInitialize | No | function | undefined | This is called when a new payment link has been successfully initialized.| | onInitializeError | No | function | undefined | This is called if an error occurred while initializing a new pyment link. The function will receive [FlutterwaveInitError](#flutterwaveiniterror) | @@ -229,12 +230,12 @@ interface CustomButtonProps { } ```` -#### OnCompleteData +#### RedirectParams ````typescript -interface OnCompleteData { - canceled: boolean; - flwref?: string; - txref: string; +interface RedirectParams { + status: 'successful' | 'cancelled', + transaction_id?: string; + tx_ref: string; } ```` @@ -291,7 +292,7 @@ export interface FlutterwaveInitOptions { ````typescript interface FlutterwaveButtonProps { style?: ViewStyle; - onComplete: (data: OnCompleteData) => void; + onComplete: (data: RedirectParams) => void; onWillInitialize?: () => void; onDidInitialize?: () => void; onInitializeError?: (error: FlutterwaveInitError) => void; From eae57efd513009a297a0c7d5c4dea77fd665b7ed Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 20 Jul 2020 15:56:35 +0100 Subject: [PATCH 030/129] docs(readme): add InitCustomer interface to interfaces --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index dd04539..b4882a0 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Easily implement Flutterwave for payments in your React Native appliction. This - Types - [Flutterwave Button Props](#flutterwavebuttonprops-interface) - [Default Button Props](#defaultbuttonprops-interface) + - [Init Customer](#initcustomer) - [Flutterwave Init Options](#flutterwaveinitoptions-interface) - [Flutterwave Init Error](#flutterwaveiniterror) - [FlutterwavePaymentMeta](#flutterwavepaymentmeta) @@ -264,11 +265,21 @@ interface FlutterwavePaymentMeta { } ```` +### InitCustomer +```typescript +interface InitCustomer { + email: string; + phonenumber?: string; + name?: string; +} +``` + #### FlutterwaveInitOptions Interface ````typescript export interface FlutterwaveInitOptions { txref: string; PBFPubKey: string; + customer: InitCustomer; customer_firstname?: string; customer_lastname?: string; customer_phone?: string; From 4d76709ad9b9bbb868046e016cfa36282577d1b3 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 20 Jul 2020 15:58:28 +0100 Subject: [PATCH 031/129] docs(readme): add InitCusomizations interface to interfaces --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index b4882a0..836c2f9 100644 --- a/README.md +++ b/README.md @@ -274,12 +274,22 @@ interface InitCustomer { } ``` +### InitCustomizations +```typescript +interface InitCustomizations { + title?: string; + logo?: string; + description?: string; +} +``` + #### FlutterwaveInitOptions Interface ````typescript export interface FlutterwaveInitOptions { txref: string; PBFPubKey: string; customer: InitCustomer; + customizations: InitCustomizations; customer_firstname?: string; customer_lastname?: string; customer_phone?: string; From 0721924f6bc6114a0db1460fd50ee7a36352437c Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 20 Jul 2020 16:12:47 +0100 Subject: [PATCH 032/129] docs(readme): update FlutterwaveInitOptions interface --- README.md | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 836c2f9..0c775ac 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,8 @@ interface RedirectParams { interface FlutterwaveInitError { code: string; message: string; + errorId?: string; + errors?: Array; } ```` @@ -286,26 +288,18 @@ interface InitCustomizations { #### FlutterwaveInitOptions Interface ````typescript export interface FlutterwaveInitOptions { - txref: string; - PBFPubKey: string; - customer: InitCustomer; - customizations: InitCustomizations; - customer_firstname?: string; - customer_lastname?: string; - customer_phone?: string; - customer_email: string; + authorization: string; + tx_ref: string; amount: number; - currency?: string; - redirect_url?: string; + currency: string; + integrity_hash?: string; payment_options?: string; payment_plan?: number; + redirect_url: string; + customer: InitCustomer; subaccounts?: Array; - country?: string; - pay_button_text?: string; - custom_title?: string; - custom_description?: string; - custom_logo?: string; meta?: Array; + customizations?: InitCustomizations; } ```` From c00c5f705c7220f77d17eba76d750f227afdba9b Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 20 Jul 2020 16:31:42 +0100 Subject: [PATCH 033/129] fix(flutterwaveinit): add subaccount interface --- README.md | 12 +++++++++++- src/FlutterwaveInit.ts | 9 ++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0c775ac..8d3ee16 100644 --- a/README.md +++ b/README.md @@ -285,6 +285,16 @@ interface InitCustomizations { } ``` +### InitCustomizations +```typescript +interface FlutterwaveInitSubAccount { + id: string; + transaction_split_ratio?: number; + transaction_charge_type?: string; + transaction_charge?: number; +} +``` + #### FlutterwaveInitOptions Interface ````typescript export interface FlutterwaveInitOptions { @@ -297,7 +307,7 @@ export interface FlutterwaveInitOptions { payment_plan?: number; redirect_url: string; customer: InitCustomer; - subaccounts?: Array; + subaccounts?: Array; meta?: Array; customizations?: InitCustomizations; } diff --git a/src/FlutterwaveInit.ts b/src/FlutterwaveInit.ts index a681e73..1b78804 100644 --- a/src/FlutterwaveInit.ts +++ b/src/FlutterwaveInit.ts @@ -18,6 +18,13 @@ export interface InitCustomizations { description?: string; } +export interface FlutterwaveInitSubAccount { + id: string; + transaction_split_ratio?: number; + transaction_charge_type?: string; + transaction_charge?: number; +} + export interface FlutterwaveInitOptions { authorization: string; tx_ref: string; @@ -28,7 +35,7 @@ export interface FlutterwaveInitOptions { payment_plan?: number; redirect_url: string; customer: InitCustomer; - subaccounts?: Array; + subaccounts?: Array; meta?: Array; customizations?: InitCustomizations; } From 5fef115eb680be5aab949596a0179a290a2dc258 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 20 Jul 2020 16:37:40 +0100 Subject: [PATCH 034/129] docs(readme): update FlutterwaveInitOptions table --- README.md | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 8d3ee16..7cf9ed6 100644 --- a/README.md +++ b/README.md @@ -176,24 +176,18 @@ Hi :wave:, so there are cases where you have already initialized a payment with [See Interface](#flutterwaveinitoptions-interface) | Name | Required | Type | Default | Description | | --------- | --------- | ---- | ------- | ----------- | -| PBFPubKey | Yes | string | **REQUIRED** | Your merchant public key, see how to get your [API Keys](https://developer.flutterwave.com/v2.0/docs/api-keys)| -| txref | Yes | string | **REQUIRED** | Your Unique transaction reference.| -| customer_email | Yes | string | **REQUIRED** | The customer's email address. | -| customer_phone | No | string | undefined | The customer's phone number. | -| customer_firstname | No | string | undefined | The customer's first name. | -| customer_lastname | No | string | undefined | The customer's last name. | -| amount | Yes | number | undefined | Amount to charge the customer.| -| currency | No | string | NGN | Currency to charge in. Defaults to NGN. Check our [International Payments](https://developer.flutterwave.com/v2.0/docs/multicurrency-payments) section for more on international currencies.| -| redirect_url | No | string | undefined | 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. | -| payment_options | No | string | undefined | This allows you to select the payment option you want for your users, see [Choose Payment Methods](https://developer.flutterwave.com/v2.0/docs/splitting-payment-methods) for more info. | -| payment_plan | No | number | undefined | This is the payment plan ID used for [Recurring billing](https://developer.flutterwave.com/v2.0/docs/recurring-billing). | -| subaccounts | No | array | undefined | This is an array of objects containing the subaccount IDs to split the payment into. | -| country | No | string | NG | Route country. Defaults to NG | -| pay_button_text | No | string | undefined | Text to be displayed on the Rave Checkout Button. | -| custom_title | No | string | undefined | Text to be displayed as the title of the payment modal. | -| custom_description | No | string | undefined | Text to be displayed as a short modal description. | -| custom_logo | No | string | undefined | Link to the Logo image. | -| meta | No | array of [FlutterwavePaymentMeta](#flutterwavepaymentmeta) | undefined | Any other custom data you wish to pass. | +| authorization | Yes | string | **REQUIRED** | Your merchant secret key, see how to get your [API Keys](https://developer.flutterwave.com/v3.0/docs/api-keys)| +| tx_ref | Yes | string | **REQUIRED** | Your transaction reference. This MUST be unique for every transaction.| +| amount | Yes | string | **REQUIRED** | Amount to charge the customer. | +| currency | No | string | NGN | Currency to charge in. Defaults to NGN. | +| integrity_hash | No | string | undefined | This is a sha256 hash of your FlutterwaveCheckout values, it is used for passing secured values to the payment gateway. | +| payment_options | Yes | string | **REQUIRED** | This specifies the payment options to be displayed e.g - card, mobilemoney, ussd and so on. | +| payment_plan | No | number | undefined | This is the payment plan ID used for [Recurring billing](https://developer.flutterwave.com/v3.0/docs/recurring-billing). | +| redirect_url | Yes | string | **REQUIRED** | 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. **IMPORTANT** This only required when you are directly using [FlutterwaveInit](#flutterwave-standard-init) | +| customer | Yes | [FlutterwaveInitCustomer](#flutterwaveinitcustomer) | **REQUIRED** | This is an object that can contains your customer details. `E.g.'customer': { 'email': 'example@example.com', 'phonenumber': '08012345678', 'name': 'Takeshi Kovacs' }.` | +| subaccounts | No | array of [FlutterwaveInitSubAccount](#flutterwaveinitsubaccount) | undefined | This is an array of objects containing the subaccount IDs to split the payment into. Check out the [Split Payment page](https://developer.flutterwave.com/docs/split-payment) for more info | +| meta | No | array of [FlutterwavePaymentMeta](#flutterwavepaymentmeta) | undefined | This is an object that helps you include additional payment information to your request. `E.g. { 'consumer_id': 23, 'consumer_mac': '92a3-912ba-1192a' }` | +| customizations | No | [FlutterwaveInitCustomizations](#flutterwaveinitcustomizations) | undefined | 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'}` | ### FlutterwaveButtonProps [See Interface](#flutterwavebuttonprops-interface) From 5be41b189554cd73d8901071572ec249e2c52cc3 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 20 Jul 2020 16:38:56 +0100 Subject: [PATCH 035/129] docs(readme): update FlutterwaveButton table --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7cf9ed6..ff50aad 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,7 @@ Hi :wave:, so there are cases where you have already initialized a payment with | onDidInitialize | No | function | undefined | This is called when a new payment link has been successfully initialized.| | onInitializeError | No | function | undefined | This is called if an error occurred while initializing a new pyment link. The function will receive [FlutterwaveInitError](#flutterwaveiniterror) | | onAbort | No | function | undefined | This is called if a user aborts a transaction, a user can abort a transaction when they click on the dialog's backdrop and choose cancel when prompted to cancel transaction. | -| options | Yes | **[FlutterwaveInitOptions](#flutterwaveinitoptions)** | **REQUIRED** | The option passed here is used to initialize a payment. | +| options | Yes | [FlutterwaveInitOptions](#flutterwaveinitoptions) | **REQUIRED** | The option passed here is used to initialize a payment. | | customButton | No | function | undefined | This is used to render a custom button. The function a prop argument structured like [CustomButtonProps](#custombuttonprops), this function should return a valid React node. | | alignLeft | No | boolean | undefined | This aligns the content of the button to the left. | From a7f3f474a97d5cc35278173009e1227f14c9b664 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 20 Jul 2020 16:52:31 +0100 Subject: [PATCH 036/129] docs(readme): update FlutterwaveInit example --- README.md | 50 +++++++++++++++++++------------------------------- 1 file changed, 19 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index ff50aad..3a0f7eb 100644 --- a/README.md +++ b/README.md @@ -140,32 +140,30 @@ import {DefaultButton} from 'react-native-flutterwave'; ```` ### Flutterwave Standard Init -[View All Options](#flutterwaveinitioptions) | [Returned Value](#flutterwaveinitresult) +When called, this function returns a Promise which resolves to a string on success and rejects if an error occurs. [See all config options](#flutterwaveinitioptions) Import `FlutterwaveInit` from `react-native-flutterwave` and use it like so. ````javascript -import {FlutterwaveInit} from 'react-native-flutterwave';; - -// initialize a new payment -const payment = await FlutterwaveInit({ - txref: generateTransactionRef(), - PBFPubKey: '[Your Flutterwave Public Key]', - amount: 100, - currency: 'USD', -}); - -// link is available if payment initialized successfully -if (payment.link) { +import {FlutterwaveInit} from 'react-native-flutterwave'; + +try { + // initialize payment + const paymentLink = await FlutterwaveInit({ + tx_ref: generateTransactionRef(), + authorization: '[your merchant secret Key]', + amount: 100, + currency: 'USD', + customer: { + email: 'customer-email@example.com' + }, + payment_options: 'card' + }); // use payment link - return usePaymentLink(payment.link); + usePaymentLink(paymentLink); +} catch (error) { + // handle payment error + displayError(error.message); } - -// handle payment error -handlePaymentError( - payment.error - ? paymet.error.message - : 'Kai, an unknown error occurred!' -); ```` ### Aborting Payment Initialization Hi :wave:, so there are cases where you have already initialized a payment with `FlutterwaveInit` but might also want to be able to cancel the payment initialization should in case your component is being unmounted or you want to allow users cancel the action before the payment is initialized, we have provided a way for you to do this... [continue reading](./docs/AbortingPaymentInitialization.md) @@ -244,16 +242,6 @@ interface FlutterwaveInitError { } ```` - - -#### FlutterwaveInitResult -````typescript -interface FlutterwaveInitResult { - error?: FlutterwaveInitError | null; - link?: string | null; -} -```` - #### FlutterwavePaymentMeta ````typescript interface FlutterwavePaymentMeta { From e4214c015e9a5c5ddf1ffc840c6cbb7131b402f0 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 20 Jul 2020 16:59:16 +0100 Subject: [PATCH 037/129] docs(readme): update FlutterwaveButton example --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3a0f7eb..bfbbbce 100644 --- a/README.md +++ b/README.md @@ -86,11 +86,14 @@ import {FlutterwaveButton} from 'react-native-flutterwave'; ... onComplete={handleOnComplete} options={{ - txref: txref, - PBFPubKey: '[Your Flutterwave Public Key]', - customer_email: 'customer-email@example.com', + tx_ref: transactionReference, + authorization: '[merchant secret key]', + customer: { + email: 'customer-email@example.com' + }, amount: 2000, currency: 'NGN', + payment_options: 'card' }} /> ```` From b2f51d6e8862c2fe22675a0301630b15e01d1166 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 20 Jul 2020 17:07:00 +0100 Subject: [PATCH 038/129] docs(readme): rename InitCustomer interface --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bfbbbce..9fb9bc5 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Easily implement Flutterwave for payments in your React Native appliction. This - Types - [Flutterwave Button Props](#flutterwavebuttonprops-interface) - [Default Button Props](#defaultbuttonprops-interface) - - [Init Customer](#initcustomer) + - [Flutterwave Init Customer](#flutterwaveinitcustomer) - [Flutterwave Init Options](#flutterwaveinitoptions-interface) - [Flutterwave Init Error](#flutterwaveiniterror) - [FlutterwavePaymentMeta](#flutterwavepaymentmeta) @@ -252,9 +252,9 @@ interface FlutterwavePaymentMeta { } ```` -### InitCustomer +### FlutterwaveInitCustomer ```typescript -interface InitCustomer { +interface FlutterwaveInitCustomer { email: string; phonenumber?: string; name?: string; @@ -291,7 +291,7 @@ export interface FlutterwaveInitOptions { payment_options?: string; payment_plan?: number; redirect_url: string; - customer: InitCustomer; + customer: FlutterwaveInitCustomer; subaccounts?: Array; meta?: Array; customizations?: InitCustomizations; From 182e83297a46be5ea62d02146f18388b2939017a Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 20 Jul 2020 17:07:40 +0100 Subject: [PATCH 039/129] docs(readme): rename init customizations --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9fb9bc5..9221a67 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ Easily implement Flutterwave for payments in your React Native appliction. This - [Flutterwave Button Props](#flutterwavebuttonprops-interface) - [Default Button Props](#defaultbuttonprops-interface) - [Flutterwave Init Customer](#flutterwaveinitcustomer) + - [Flutterwave Init Customization](#flutterwaveinitcustomization) - [Flutterwave Init Options](#flutterwaveinitoptions-interface) - [Flutterwave Init Error](#flutterwaveiniterror) - [FlutterwavePaymentMeta](#flutterwavepaymentmeta) @@ -261,9 +262,9 @@ interface FlutterwaveInitCustomer { } ``` -### InitCustomizations +### FlutterwaveInitCustomizations ```typescript -interface InitCustomizations { +interface FlutterwaveInitCustomizations { title?: string; logo?: string; description?: string; @@ -294,7 +295,7 @@ export interface FlutterwaveInitOptions { customer: FlutterwaveInitCustomer; subaccounts?: Array; meta?: Array; - customizations?: InitCustomizations; + customizations?: FlutterwaveInitCustomizations; } ```` From a18207570ee1a5de1f22f917fc26c8c7c7fde789 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 20 Jul 2020 17:08:19 +0100 Subject: [PATCH 040/129] docs(readme): fix xub account interface name and add sub account to TOC --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9221a67..83f842c 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Easily implement Flutterwave for payments in your React Native appliction. This - [Default Button Props](#defaultbuttonprops-interface) - [Flutterwave Init Customer](#flutterwaveinitcustomer) - [Flutterwave Init Customization](#flutterwaveinitcustomization) + - [Flutterwave Init Sub Account](#flutterwaveinitsubaccount) - [Flutterwave Init Options](#flutterwaveinitoptions-interface) - [Flutterwave Init Error](#flutterwaveiniterror) - [FlutterwavePaymentMeta](#flutterwavepaymentmeta) @@ -271,7 +272,7 @@ interface FlutterwaveInitCustomizations { } ``` -### InitCustomizations +### FlutterwaveInitSubAccount ```typescript interface FlutterwaveInitSubAccount { id: string; From b20bce0e3392dca2b5cef7149c7b04c3bbcf1af1 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 20 Jul 2020 17:10:47 +0100 Subject: [PATCH 041/129] fix(flutterwaveinit): fix customer and customizations interface name --- src/FlutterwaveInit.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/FlutterwaveInit.ts b/src/FlutterwaveInit.ts index 1b78804..99ab82d 100644 --- a/src/FlutterwaveInit.ts +++ b/src/FlutterwaveInit.ts @@ -6,13 +6,13 @@ interface FlutterwavePaymentMeta { [k: string]: any; } -export interface InitCustomer { +export interface FlutterwaveInitCustomer { email: string; phonenumber?: string; name?: string; } -export interface InitCustomizations { +export interface FlutterwaveInitCustomizations { title?: string; logo?: string; description?: string; @@ -34,10 +34,10 @@ export interface FlutterwaveInitOptions { payment_options?: string; payment_plan?: number; redirect_url: string; - customer: InitCustomer; + customer: FlutterwaveInitCustomer; subaccounts?: Array; meta?: Array; - customizations?: InitCustomizations; + customizations?: FlutterwaveInitCustomizations; } export interface FieldError { From 0f6d46a7fa029f40fe5253a892270693ea0f05ff Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 20 Jul 2020 17:11:40 +0100 Subject: [PATCH 042/129] feat: build with identified fixes --- dist/FlutterwaveButton.d.ts | 2 +- dist/FlutterwaveButton.d.ts.map | 2 +- dist/FlutterwaveInit.d.ts | 16 +++++++++++----- dist/FlutterwaveInit.d.ts.map | 2 +- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/dist/FlutterwaveButton.d.ts b/dist/FlutterwaveButton.d.ts index fbc9bf8..4fa7254 100644 --- a/dist/FlutterwaveButton.d.ts +++ b/dist/FlutterwaveButton.d.ts @@ -13,7 +13,7 @@ interface CustomButtonProps { interface RedirectParams { status: 'successful' | 'cancelled'; transaction_id?: string; - tx_ref?: string; + tx_ref: string; } export interface FlutterwaveButtonProps { style?: ViewStyle; diff --git a/dist/FlutterwaveButton.d.ts.map b/dist/FlutterwaveButton.d.ts.map index 90e1145..52ec128 100644 --- a/dist/FlutterwaveButton.d.ts.map +++ b/dist/FlutterwaveButton.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"FlutterwaveButton.d.ts","sourceRoot":"","sources":["../src/FlutterwaveButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAIL,QAAQ,EAGR,SAAS,EAMV,MAAM,cAAc,CAAC;AACtB,OAAO,OAAO,MAAM,sBAAsB,CAAC;AAC3C,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,EAAC,iBAAiB,EAAC,MAAM,uCAAuC,CAAC;AACxE,OAAwB,EAAC,sBAAsB,EAAC,MAAM,mBAAmB,CAAC;AAI1E,OAAO,oBAAoB,MAAM,8BAA8B,CAAC;AAYhE,UAAU,iBAAiB;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,UAAU,cAAc;IACtB,MAAM,EAAE,YAAY,GAAG,WAAW,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,UAAU,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAC3C,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC1D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,IAAI,CAAC,sBAAsB,EAAE,cAAc,CAAC,CAAC;IACtD,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,KAAK,CAAC,SAAS,CAAC;IAC9D,SAAS,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;CACnC;AAED,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC;IAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,cAAM,iBAAkB,SAAQ,KAAK,CAAC,SAAS,CAC7C,sBAAsB,EACtB,sBAAsB,CACvB;IACC,MAAM,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA6Bd;IAEF,KAAK,EAAE,sBAAsB,CAW3B;IAEF,UAAU,EAAE,OAAO,GAAG,IAAI,CAAQ;IAElC,eAAe,CAAC,EAAE,eAAe,CAAC;IAElC,kBAAkB,CAAC,SAAS,EAAE,sBAAsB;IAMpD,oBAAoB;IAMpB,KAAK,aAWH;IAEF,oBAAoB,aAYnB;IAED,2BAA2B,kCASzB;IAEF,cAAc,CAAC,IAAI,EAAE,cAAc;IAgBnC,YAAY,aAKV;IAEF,kBAAkB,aAQhB;IAEF,WAAW,aAST;IAEF,kBAAkB;;;eAKhB;IAEF,iBAAiB,kCAgBf;IAEF,IAAI,aAUF;IAEF,OAAO,aAOL;IAEF,UAAU,oBAqER;IAEF,MAAM;IAsCN,YAAY;IA+CZ,cAAc;IAcd,aAAa;IAYb,WAAW,oBAkBT;CACH;AAuED,eAAe,iBAAiB,CAAC"} \ No newline at end of file +{"version":3,"file":"FlutterwaveButton.d.ts","sourceRoot":"","sources":["../src/FlutterwaveButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAIL,QAAQ,EAGR,SAAS,EAMV,MAAM,cAAc,CAAC;AACtB,OAAO,OAAO,MAAM,sBAAsB,CAAC;AAC3C,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,EAAC,iBAAiB,EAAC,MAAM,uCAAuC,CAAC;AACxE,OAAwB,EAAC,sBAAsB,EAAC,MAAM,mBAAmB,CAAC;AAI1E,OAAO,oBAAoB,MAAM,8BAA8B,CAAC;AAYhE,UAAU,iBAAiB;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,UAAU,cAAc;IACtB,MAAM,EAAE,YAAY,GAAG,WAAW,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,UAAU,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAC3C,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC1D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,IAAI,CAAC,sBAAsB,EAAE,cAAc,CAAC,CAAC;IACtD,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,KAAK,CAAC,SAAS,CAAC;IAC9D,SAAS,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;CACnC;AAED,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC;IAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,cAAM,iBAAkB,SAAQ,KAAK,CAAC,SAAS,CAC7C,sBAAsB,EACtB,sBAAsB,CACvB;IACC,MAAM,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA6Bd;IAEF,KAAK,EAAE,sBAAsB,CAW3B;IAEF,UAAU,EAAE,OAAO,GAAG,IAAI,CAAQ;IAElC,eAAe,CAAC,EAAE,eAAe,CAAC;IAElC,kBAAkB,CAAC,SAAS,EAAE,sBAAsB;IAMpD,oBAAoB;IAMpB,KAAK,aAWH;IAEF,oBAAoB,aAYnB;IAED,2BAA2B,kCASzB;IAEF,cAAc,CAAC,IAAI,EAAE,cAAc;IAgBnC,YAAY,aAKV;IAEF,kBAAkB,aAQhB;IAEF,WAAW,aAST;IAEF,kBAAkB;;;eAKhB;IAEF,iBAAiB,kCAgBf;IAEF,IAAI,aAUF;IAEF,OAAO,aAOL;IAEF,UAAU,oBAqER;IAEF,MAAM;IAsCN,YAAY;IA+CZ,cAAc;IAcd,aAAa;IAYb,WAAW,oBAkBT;CACH;AAuED,eAAe,iBAAiB,CAAC"} \ No newline at end of file diff --git a/dist/FlutterwaveInit.d.ts b/dist/FlutterwaveInit.d.ts index 343e8da..063485c 100644 --- a/dist/FlutterwaveInit.d.ts +++ b/dist/FlutterwaveInit.d.ts @@ -2,16 +2,22 @@ interface FlutterwavePaymentMeta { [k: string]: any; } -export interface InitCustomer { +export interface FlutterwaveInitCustomer { email: string; phonenumber?: string; name?: string; } -export interface InitCustomizations { +export interface FlutterwaveInitCustomizations { title?: string; logo?: string; description?: string; } +export interface FlutterwaveInitSubAccount { + id: string; + transaction_split_ratio?: number; + transaction_charge_type?: string; + transaction_charge?: number; +} export interface FlutterwaveInitOptions { authorization: string; tx_ref: string; @@ -21,10 +27,10 @@ export interface FlutterwaveInitOptions { payment_options?: string; payment_plan?: number; redirect_url: string; - customer: InitCustomer; - subaccounts?: Array; + customer: FlutterwaveInitCustomer; + subaccounts?: Array; meta?: Array; - customizations?: InitCustomizations; + customizations?: FlutterwaveInitCustomizations; } export interface FieldError { field: string; diff --git a/dist/FlutterwaveInit.d.ts.map b/dist/FlutterwaveInit.d.ts.map index b3c0a89..726de58 100644 --- a/dist/FlutterwaveInit.d.ts.map +++ b/dist/FlutterwaveInit.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"FlutterwaveInit.d.ts","sourceRoot":"","sources":["../src/FlutterwaveInit.ts"],"names":[],"mappings":";AAIA,UAAU,sBAAsB;IAC9B,CAAC,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,sBAAsB;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,YAAY,CAAC;IACvB,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC5B,IAAI,CAAC,EAAE,KAAK,CAAC,sBAAsB,CAAC,CAAC;IACrC,cAAc,CAAC,EAAE,kBAAkB,CAAC;CACrC;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AASD;;;;;;GAMG;AACH,wBAA8B,eAAe,CAC3C,OAAO,EAAE,sBAAsB,EAC/B,eAAe,CAAC,EAAE,eAAe,GAChC,OAAO,CAAC,MAAM,CAAC,CAgCjB"} \ No newline at end of file +{"version":3,"file":"FlutterwaveInit.d.ts","sourceRoot":"","sources":["../src/FlutterwaveInit.ts"],"names":[],"mappings":";AAIA,UAAU,sBAAsB;IAC9B,CAAC,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,6BAA6B;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,uBAAuB,CAAC;IAClC,WAAW,CAAC,EAAE,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC/C,IAAI,CAAC,EAAE,KAAK,CAAC,sBAAsB,CAAC,CAAC;IACrC,cAAc,CAAC,EAAE,6BAA6B,CAAC;CAChD;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AASD;;;;;;GAMG;AACH,wBAA8B,eAAe,CAC3C,OAAO,EAAE,sBAAsB,EAC/B,eAAe,CAAC,EAAE,eAAe,GAChC,OAAO,CAAC,MAAM,CAAC,CAgCjB"} \ No newline at end of file From b4dd6cb7d2e5c550c9b52d144419bc377f2027eb Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 20 Jul 2020 17:14:28 +0100 Subject: [PATCH 043/129] docs(readme): fix incorrect link for v2 api reference in TOC --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 83f842c..b29c14a 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Easily implement Flutterwave for payments in your React Native appliction. This ## Table Of Content - Getting Started - - [V2 API](#if-using-version-2-api) + - [V2 API](#warning-if-using-version-2-api-warning) - [Installation](#installation) - [Dependencies](#dependencies) - [Activity Indicator (Android)](#activity-indicator-only-needed-for-android) From 1e0cdb9e36a0b5f5746da81e9c52e05c028ab426 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 20 Jul 2020 17:22:59 +0100 Subject: [PATCH 044/129] docs(abortingpaymentinitialization): update example to use v3 api flutterwave init function --- docs/AbortingPaymentInitialization.md | 44 +++++++++++++++------------ 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/docs/AbortingPaymentInitialization.md b/docs/AbortingPaymentInitialization.md index 3f9ebb7..217f5c4 100644 --- a/docs/AbortingPaymentInitialization.md +++ b/docs/AbortingPaymentInitialization.md @@ -1,5 +1,5 @@ # Aborting Payment Initialization -Hi :wave:, so there are cases where you have already initialized a payment with `FlutterwaveInit` but might also want to be able to cancel the payment initialization should in case your component is being unmounted or you want to allow users cancel the action before the payment is initialized, we have provided a way for you to do this, we use `fetch` underneath the hood to make the request to the standard payment endpoint and `fetch` allows you to pass an [abort controller](https://github.com/mo/abortcontroller-polyfill) which you can use to cancel ongoing requests, if your version of React Native does not have the abort functionality for fetch in the Javascript runtime, you will need to [install the polyfill](https://github.com/mo/abortcontroller-polyfill) before moving on. Below is a code snippet showcasing how you can go about cancelling an ongoing payment initialization. +:wave: Hi, so there are cases where you have already initialized a payment with `FlutterwaveInit` but might also want to be able to cancel the payment initialization should in case your component is being unmounted or you want to allow users cancel the action before the payment is initialized, we have provided a way for you to do this, we use `fetch` underneath the hood to make the request to the standard payment endpoint and `fetch` allows you to pass an [abort controller](https://github.com/mo/abortcontroller-polyfill) which you can use to cancel ongoing requests, if your version of React Native does not have the abort functionality for fetch in the Javascript runtime, you will need to [install the polyfill](https://github.com/mo/abortcontroller-polyfill) before moving on. Below is a code snippet showcasing how you can go about cancelling an ongoing payment initialization. **:point_right:`If you have already installed the polyfill or have it already available in the Javascript runtime, this action happens automatically within FlutterwaveButton.`** @@ -23,26 +23,32 @@ class MyCart extends React.Component { }, () => { // set abort controller this.abortController = new AbortController; - // initialize a new payment - const payment = await FlutterwaveInit({ - txref: generateTransactionRef(), - PBFPubKey: '[Your Flutterwave Public Key]', - amount: 100, - currency: 'USD', - }, { - canceller: this.abortController, - }); - // do nothing if our payment initialization was aborted - if (payment.error && payment.error.code === 'ABORTERROR') { - return; - } - // link is available if payment initialized successfully - if (payment.link) { + try { + // initialize payment + const paymentLink = await FlutterwaveInit( + { + tx_ref: generateTransactionRef(), + authorization: '[merchant secret key]', + amount: 100, + currency: 'USD', + customer: { + email: 'customer-email@example.com', + }, + payment_options: 'card', + }, + this.abortController + ); // use payment link - return; + return this.usePaymentLink(paymentLink); + } catch (error) { + // do nothing if our payment initialization was aborted + if (error && error.code === 'ABORTERROR') { + return; + } + // handle other errors + this.displayErrorMessage(error.message); } - // handle other errors - }) + }); } render() { From 9e6b0fc2113978c2f474eeb985693f0b3d4b8baf Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 20 Jul 2020 17:28:19 +0100 Subject: [PATCH 045/129] docs(abortingpaymentinitialization): correct example code --- docs/AbortingPaymentInitialization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/AbortingPaymentInitialization.md b/docs/AbortingPaymentInitialization.md index 217f5c4..ba2221c 100644 --- a/docs/AbortingPaymentInitialization.md +++ b/docs/AbortingPaymentInitialization.md @@ -42,7 +42,7 @@ class MyCart extends React.Component { return this.usePaymentLink(paymentLink); } catch (error) { // do nothing if our payment initialization was aborted - if (error && error.code === 'ABORTERROR') { + if (error.code === 'ABORTERROR') { return; } // handle other errors From 1f4c0a92c31f0a04c7bdf7cdfcf4e353fb2aee1b Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 20 Jul 2020 17:29:10 +0100 Subject: [PATCH 046/129] docs(readme): fix broken links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b29c14a..536bad1 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Easily implement Flutterwave for payments in your React Native appliction. This - Flutterwave designed button. ## :warning: If Using Version 2 API :warning: -This version of the library uses Version 3 of Flutterwave's API, if you are still using the Version 2 API please use [this documentation](/README.md) instead. +This version of the library uses Version 3 of Flutterwave's API, if you are still using the Version 2 API please use [this documentation](https://github.com/thecodecafe/react-native-flutterwave) instead. ## Installation This library is available on npm, you can install it by running `npm install --save react-native-flutterwave` or `yarn add react-native-flutterwave` @@ -66,7 +66,7 @@ dependencies { ```` ### :fire: IMPORTANT INFORMATION :fire: -If the `options` property on the [FlutterwaveButton](flutterwavebuttonprops-interface) changes, when next the user taps on the button a new payment will be initialized whether the last one was successful or not. +If the `options` property on the [FlutterwaveButton](#flutterwavebuttonprops-interface) changes, when next the user taps on the button a new payment will be initialized whether the last one was successful or not. Remember you cannot use the same transaction reference for two different payments, remember to recreate the transaction reference before allowing the user initiate a new payment. From 4ca0c80d9e0eaf415bfc353d89ef6fa4e6a5fe35 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 09:39:22 +0100 Subject: [PATCH 047/129] docs(readme): typo correction --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 536bad1..895f1e7 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ try { } ```` ### Aborting Payment Initialization -Hi :wave:, so there are cases where you have already initialized a payment with `FlutterwaveInit` but might also want to be able to cancel the payment initialization should in case your component is being unmounted or you want to allow users cancel the action before the payment is initialized, we have provided a way for you to do this... [continue reading](./docs/AbortingPaymentInitialization.md) +:wave:Hi, so there are cases where you have already initialized a payment with `FlutterwaveInit` but might also want to be able to cancel the payment initialization should in case your component is being unmounted or you want to allow users cancel the action before the payment is initialized, we have provided a way for you to do this... [continue reading](./docs/AbortingPaymentInitialization.md) ## Props From 00ce4a1cb173c92535ef481f3ba3d631b7730663 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 09:42:20 +0100 Subject: [PATCH 048/129] fix(flutterwavebutton): properly define options subaccount property type --- src/FlutterwaveButton.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/FlutterwaveButton.tsx b/src/FlutterwaveButton.tsx index bf3d604..3a31f0c 100644 --- a/src/FlutterwaveButton.tsx +++ b/src/FlutterwaveButton.tsx @@ -93,7 +93,12 @@ class FlutterwaveButton extends React.Component< phonenumber: PropTypes.string, email: PropTypes.string.isRequired, }).isRequired, - subaccounts: PropTypes.arrayOf(PropTypes.number), + subaccounts: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string.isRequired, + transaction_split_ratio: PropTypes.number, + transaction_charge_type: PropTypes.string, + transaction_charge: PropTypes.number, + })), meta: PropTypes.arrayOf(PropTypes.object), customizations: PropTypes.shape({ title: PropTypes.string, From 5ea95aeddedb2afd2d68696a33cac7a8aafd7386 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 11:01:39 +0100 Subject: [PATCH 049/129] refactor: reorganize v2 specific code --- README.v2.md | 329 +++++++ __tests__/CustomPropTypesRules.spec.ts | 9 +- __tests__/FlutterwaveButton.spec.tsx | 6 +- __tests__/v2/CustomPropTypesRules.spec.ts | 28 + __tests__/v2/FlutterwaveButton.spec.tsx | 844 ++++++++++++++++++ __tests__/v2/FlutterwaveInit.spec.ts | 145 +++ .../FlutterwaveButton.spec.tsx.snap | 751 ++++++++++++++++ docs/v2/AbortingPaymentInitialization.md | 72 ++ src/utils/CustomPropTypesRules.ts | 7 +- src/v2/FlutterwaveButton.tsx | 546 +++++++++++ src/v2/FlutterwaveInit.ts | 161 ++++ src/v2/configs.v2.ts | 21 + src/v2/index.ts | 8 + 13 files changed, 2916 insertions(+), 11 deletions(-) create mode 100644 README.v2.md create mode 100644 __tests__/v2/CustomPropTypesRules.spec.ts create mode 100644 __tests__/v2/FlutterwaveButton.spec.tsx create mode 100644 __tests__/v2/FlutterwaveInit.spec.ts create mode 100644 __tests__/v2/__snapshots__/FlutterwaveButton.spec.tsx.snap create mode 100644 docs/v2/AbortingPaymentInitialization.md create mode 100644 src/v2/FlutterwaveButton.tsx create mode 100644 src/v2/FlutterwaveInit.ts create mode 100644 src/v2/configs.v2.ts create mode 100644 src/v2/index.ts diff --git a/README.v2.md b/README.v2.md new file mode 100644 index 0000000..1c03831 --- /dev/null +++ b/README.v2.md @@ -0,0 +1,329 @@ +# React Native Flutterwave +Easily implement Flutterwave for payments in your React Native appliction. This library has support for both Android and iOS. + +[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) + +

+ ios-preview + android-preview +

+ +## Table Of Content +- Getting Started + - [Installation](#installation) + - [Dependencies](#dependencies) + - [Activity Indicator (Android)](#activity-indicator-only-needed-for-android) + - [Important Information](#fire-important-information-fire) +- Usage + - [Flutterwave Button ](#flutterwave-button) + - [Flutterwave Button (with custom render)](#flutterwave-button-with-custom-render) + - [Default Button (Flutterwave styled button)](#defaultbutton-flutterwave-styled-button) + - [Flutterwave Standard Init](#flutterwave-standard-init) + - [Aborting Payment Initialization](#aborting-payment-initialization) +- Props + - [Flutterwave Button Props](#flutterwavebuttonprops) + - [Default Button Props](#defaultbuttonprops) + - [Flutterwave Init Options](#flutterwaveinitoptions) +- Types + - [Flutterwave Button Props](#flutterwavebuttonprops-interface) + - [Default Button Props](#defaultbuttonprops-interface) + - [Flutterwave Init Options](#flutterwaveinitoptions-interface) + - [Flutterwave Init Error](#flutterwaveiniterror) + - [FlutterwavePaymentMeta](#flutterwavepaymentmeta) + - [OnCompleteData](#oncompletedata) + - [CustomButtonProps](#custombuttonprops) +- [Contributing](./CONTRIBUTING.md) + +## What's Inside? +- Pay with Flutterwave button and checkout dialog. +- Standard payment initialization function. +- Flutterwave designed button. + +## Installation +This library is available on npm, you can install it by running `npm install --save react-native-flutterwave` or `yarn add react-native-flutterwave` + +### Dependencies +In order to render the Flutterwave checkout screen this library depends on [react-native-webview](https://github.com/react-native-community/react-native-webview) ensure you properly install this library before continuing. + +### Activity Indicator (only needed for android) +To display the Flutterwave styled activity indicator when the checkout screen is being loaded on Android you will need to add some modules in `android/app/build.gradle`. +***Skip this if you already have setup your app to support gif images.*** +````javascript +dependencies { + // If your app supports Android versions before Ice Cream Sandwich (API level 14) + implementation 'com.facebook.fresco:animated-base-support:1.3.0' + + // For animated GIF support + implementation 'com.facebook.fresco:animated-gif:2.0.0' +} +```` + +### :fire: IMPORTANT INFORMATION :fire: +If the `options` property on the [FlutterwaveButton](flutterwavebuttonprops-interface) changes, when next the user taps on the button a new payment will be initialized whether the last one was successful or not. + +Remember you cannot use the same transaction reference for two different payments, remember to recreate the transaction reference before allowing the user initiate a new payment. + + +## Usage +Below are a few examples showcasing how you can use the library to implement payment in your React Native app. + +### Flutterwave Button +preview + +[View All Props](#flutterwavebuttonprops) + +Import `FlutterwaveButton` from `react-native-flutterwave` and use it like so. +````jsx +import {FlutterwaveButton} from 'react-native-flutterwave'; +// or import FlutterwaveButton from 'react-native-flutterwave'; + + +```` + +### Flutterwave Button (with custom render) +preview + +[View All Props](#flutterwavebuttonprops) + +Import `FlutterwaveButton` from `react-native-flutterwave` and use it like so. +````jsx +import {FlutterwaveButton} from 'react-native-flutterwave'; +// or import FlutterwaveButton from 'react-native-flutterwave'; + + ( + + Pay $500 + + )} +/> +```` + +### DefaultButton (Flutterwave styled button) +preview + +[View All Props](#defaultbuttonprops) + +Import `DefaultButton` from `react-native-flutterwave` and use it like so. +````jsx +import {DefaultButton} from 'react-native-flutterwave'; + + + Pay $500 + +```` + +### Flutterwave Standard Init +[View All Options](#flutterwaveinitioptions) | [Returned Value](#flutterwaveinitresult) + +Import `FlutterwaveInit` from `react-native-flutterwave` and use it like so. +````javascript +import {FlutterwaveInit} from 'react-native-flutterwave';; + +// initialize a new payment +const payment = await FlutterwaveInit({ + txref: generateTransactionRef(), + PBFPubKey: '[Your Flutterwave Public Key]', + amount: 100, + currency: 'USD', +}); + +// link is available if payment initialized successfully +if (payment.link) { + // use payment link + return usePaymentLink(payment.link); +} + +// handle payment error +handlePaymentError( + payment.error + ? paymet.error.message + : 'Kai, an unknown error occurred!' +); +```` +### Aborting Payment Initialization +Hi :wave:, so there are cases where you have already initialized a payment with `FlutterwaveInit` but might also want to be able to cancel the payment initialization should in case your component is being unmounted or you want to allow users cancel the action before the payment is initialized, we have provided a way for you to do this... [continue reading](./docs/v2/AbortingPaymentInitialization.md) + +## Props + +### FlutterwaveInitOptions +[See Interface](#flutterwaveinitoptions-interface) +| Name | Required | Type | Default | Description | +| --------- | --------- | ---- | ------- | ----------- | +| PBFPubKey | Yes | string | **REQUIRED** | Your merchant public key, see how to get your [API Keys](https://developer.flutterwave.com/v2.0/docs/api-keys)| +| txref | Yes | string | **REQUIRED** | Your Unique transaction reference.| +| customer_email | Yes | string | **REQUIRED** | The customer's email address. | +| customer_phone | No | string | undefined | The customer's phone number. | +| customer_firstname | No | string | undefined | The customer's first name. | +| customer_lastname | No | string | undefined | The customer's last name. | +| amount | Yes | number | undefined | Amount to charge the customer.| +| currency | No | string | NGN | Currency to charge in. Defaults to NGN. Check our [International Payments](https://developer.flutterwave.com/v2.0/docs/multicurrency-payments) section for more on international currencies.| +| redirect_url | No | string | undefined | 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. | +| payment_options | No | string | undefined | This allows you to select the payment option you want for your users, see [Choose Payment Methods](https://developer.flutterwave.com/v2.0/docs/splitting-payment-methods) for more info. | +| payment_plan | No | number | undefined | This is the payment plan ID used for [Recurring billing](https://developer.flutterwave.com/v2.0/docs/recurring-billing). | +| subaccounts | No | array of [FlutterwaveInitSubAccount](#flutterwaveinitsubaccount) | undefined | This is an array of objects containing the subaccount IDs to [split the payment](https://developer.flutterwave.com/v2.0/docs/split-payment) into. | +| country | No | string | NG | Route country. Defaults to NG | +| pay_button_text | No | string | undefined | Text to be displayed on the Rave Checkout Button. | +| custom_title | No | string | undefined | Text to be displayed as the title of the payment modal. | +| custom_description | No | string | undefined | Text to be displayed as a short modal description. | +| custom_logo | No | string | undefined | Link to the Logo image. | +| meta | No | array of [FlutterwavePaymentMeta](#flutterwavepaymentmeta) | undefined | Any other custom data you wish to pass. | + +### FlutterwaveButtonProps +[See Interface](#flutterwavebuttonprops-interface) +| Name | Required | Type | Default | Description | +| --------- | --------- | ---- | ------- | ----------- | +| style | No | object | undefined | Used to apply styling to the button.| +| onComplete | Yes | function | **REQUIRED** | Called when a payment is completed successfully or is canceled. The function will receive [on complete data](#oncompletedata)| +| onWillInitialize | No | function | undefined | This will be called before a payment link is generated.| +| onDidInitialize | No | function | undefined | This is called when a new payment link has been successfully initialized.| +| onInitializeError | No | function | undefined | This is called if an error occurred while initializing a new pyment link. The function will receive [FlutterwaveInitError](#flutterwaveiniterror) | +| onAbort | No | function | undefined | This is called if a user aborts a transaction, a user can abort a transaction when they click on the dialog's backdrop and choose cancel when prompted to cancel transaction. | +| options | Yes | **[FlutterwaveInitOptions](#flutterwaveinitoptions)** | **REQUIRED** | The option passed here is used to initialize a payment. | +| customButton | No | function | undefined | This is used to render a custom button. The function a prop argument structured like [CustomButtonProps](#custombuttonprops), this function should return a valid React node. | +| alignLeft | No | boolean | undefined | This aligns the content of the button to the left. | + +### DefaultButtonProps +[See Interface](#defaultbuttonprops-interface) +| Name | Required | Type | Default | Description | +| --------- | --------- | ---- | ------- | ----------- | +| style | No | object | undefined | Used to apply styling to the button.| +| onPress | Yes | function | undefined | This | +| disabled | No | boolean | undefined | This disables button, and causes onPress not to be fired.| +| isBusy | No | boolean | undefined | This puts the button in a busy state, making the content look faded.| +| onSizeChange | No | (ev: {width: number; height: number}) => void | undefined | If provided this function is fired whenever the size(height or width) of the button changes | +| children | Yes | ReactElement | undefined | This will be the content rendered within the button, if string is to be direct decendant, remember to put string in the Text component. | +| alignLeft | No | boolean | undefined | This aligns the content of the button to the left. | + +## Types +#### CustomButtonProps +````typescript +interface CustomButtonProps { + disabled: boolean; + isInitializing: boolean; + onPress: () => void; +} +```` + +#### OnCompleteData +````typescript +interface OnCompleteData { + canceled: boolean; + flwref?: string; + txref: string; +} +```` + +#### FlutterwaveInitError +````typescript +interface FlutterwaveInitError { + code: string; + message: string; +} +```` + + + +#### FlutterwaveInitResult +````typescript +interface FlutterwaveInitResult { + error?: FlutterwaveInitError | null; + link?: string | null; +} +```` + +### FlutterwaveInitSubAccount +```typescript +interface FlutterwaveInitSubAccount { + id: string; + transaction_split_ratio?: number; + transaction_charge_type?: string; + transaction_charge?: number; +} +``` + +#### FlutterwavePaymentMeta +````typescript +interface FlutterwavePaymentMeta { + metaname: string; + metavalue: string; +} +```` + +#### FlutterwaveInitOptions Interface +````typescript +export interface FlutterwaveInitOptions { + txref: string; + PBFPubKey: string; + customer_firstname?: string; + customer_lastname?: string; + customer_phone?: string; + customer_email: string; + amount: number; + currency?: string; + redirect_url?: string; + payment_options?: string; + payment_plan?: number; + subaccounts?: Array; + country?: string; + pay_button_text?: string; + custom_title?: string; + custom_description?: string; + custom_logo?: string; + meta?: Array; +} +```` + +#### FlutterwaveButtonProps Interface +````typescript +interface FlutterwaveButtonProps { + style?: ViewStyle; + onComplete: (data: OnCompleteData) => void; + onWillInitialize?: () => void; + onDidInitialize?: () => void; + onInitializeError?: (error: FlutterwaveInitError) => void; + onAbort?: () => void; + options: Omit; + customButton?: (props: CustomButtonProps) => React.ReactNode; + alignLeft?: 'alignLeft' | boolean; +} +```` + +#### DefaultButtonProps Interface +````typescript +interface DefaultButtonProps { + style?: ViewStyle; + onPress?: () => void; + disabled?: boolean; + children: React.ReactElement; + isBusy?: boolean; + onSizeChange?: (ev: {width: number; height: number}) => void; + alignLeft?: 'alignLeft' | boolean, +} +```` + +## Contributing +For information on how you can contribute to this repo, simply [go here](./CONTRIBUTING.md), all contributions are greatly appreciated. + +Built with love. :yellow_heart: diff --git a/__tests__/CustomPropTypesRules.spec.ts b/__tests__/CustomPropTypesRules.spec.ts index 2ecf13c..0286a73 100644 --- a/__tests__/CustomPropTypesRules.spec.ts +++ b/__tests__/CustomPropTypesRules.spec.ts @@ -1,27 +1,28 @@ import 'react-native'; import {PaymentOptionsPropRule} from '../src/utils/CustomPropTypesRules'; +import {PAYMENT_OPTIONS} from '../src/configs'; const PropName = 'payment_options'; describe('CustomPropTypes.PaymentOptionsPropRule', () => { it ('returns null if prop is not defined in props', () => { - const result = PaymentOptionsPropRule({}, PropName); + const result = PaymentOptionsPropRule(PAYMENT_OPTIONS)({}, PropName); expect(result).toBe(null); }); it ('returns error if prop is not a string', () => { - const result = PaymentOptionsPropRule({[PropName]: []}, PropName); + const result = PaymentOptionsPropRule(PAYMENT_OPTIONS)({[PropName]: []}, PropName); expect(result !== null).toBe(true); expect(result.message).toContain('should be a string.'); }); it ('returns error if prop includes invalid payment option', () => { - const result = PaymentOptionsPropRule({[PropName]: 'barter, foo'}, PropName); + const result = PaymentOptionsPropRule(PAYMENT_OPTIONS)({[PropName]: 'barter, foo'}, PropName); expect(result !== null).toBe(true); expect(result.message).toContain('must be any of the following values.'); }); it ('returns null if payment options are valid', () => { - const result = PaymentOptionsPropRule({[PropName]: 'barter'}, PropName); + const result = PaymentOptionsPropRule(PAYMENT_OPTIONS)({[PropName]: 'barter'}, PropName); expect(result).toBe(null); }); }) diff --git a/__tests__/FlutterwaveButton.spec.tsx b/__tests__/FlutterwaveButton.spec.tsx index e356e5c..d7e70a8 100644 --- a/__tests__/FlutterwaveButton.spec.tsx +++ b/__tests__/FlutterwaveButton.spec.tsx @@ -2,9 +2,9 @@ import 'react-native'; import React from 'react'; import {TouchableWithoutFeedback, Text, Alert} from 'react-native'; import renderer from 'react-test-renderer'; -import FlutterwaveButton from '../src/FlutterwaveButton'; -import {FlutterwaveInitOptions} from '../src/FlutterwaveInit'; -import {STANDARD_URL} from '../src/configs'; +import FlutterwaveButton from '../src/v2/FlutterwaveButton'; +import {FlutterwaveInitOptions} from '../src/v2/FlutterwaveInit'; +import {STANDARD_URL} from '../src/v2/configs.v2'; import WebView from 'react-native-webview'; import DefaultButton from '../src/DefaultButton'; const BtnTestID = 'flw-default-button'; diff --git a/__tests__/v2/CustomPropTypesRules.spec.ts b/__tests__/v2/CustomPropTypesRules.spec.ts new file mode 100644 index 0000000..7f3aff5 --- /dev/null +++ b/__tests__/v2/CustomPropTypesRules.spec.ts @@ -0,0 +1,28 @@ +import 'react-native'; +import {PaymentOptionsPropRule} from '../../src/utils/CustomPropTypesRules'; +import {PAYMENT_OPTIONS} from '../../src/v2/configs.v2'; +const PropName = 'payment_options'; + +describe('CustomPropTypes.PaymentOptionsPropRule', () => { + it ('returns null if prop is not defined in props', () => { + const result = PaymentOptionsPropRule(PAYMENT_OPTIONS)({}, PropName); + expect(result).toBe(null); + }); + + it ('returns error if prop is not a string', () => { + const result = PaymentOptionsPropRule(PAYMENT_OPTIONS)({[PropName]: []}, PropName); + expect(result !== null).toBe(true); + expect(result.message).toContain('should be a string.'); + }); + + it ('returns error if prop includes invalid payment option', () => { + const result = PaymentOptionsPropRule(PAYMENT_OPTIONS)({[PropName]: 'barter, foo'}, PropName); + expect(result !== null).toBe(true); + expect(result.message).toContain('must be any of the following values.'); + }); + + it ('returns null if payment options are valid', () => { + const result = PaymentOptionsPropRule(PAYMENT_OPTIONS)({[PropName]: 'barter'}, PropName); + expect(result).toBe(null); + }); +}) diff --git a/__tests__/v2/FlutterwaveButton.spec.tsx b/__tests__/v2/FlutterwaveButton.spec.tsx new file mode 100644 index 0000000..97e9949 --- /dev/null +++ b/__tests__/v2/FlutterwaveButton.spec.tsx @@ -0,0 +1,844 @@ +import 'react-native'; +import React from 'react'; +import {TouchableWithoutFeedback, Text, Alert} from 'react-native'; +import renderer from 'react-test-renderer'; +import FlutterwaveButton from '../../src/v2/FlutterwaveButton'; +import {FlutterwaveInitOptions} from '../../src/v2/FlutterwaveInit'; +import {STANDARD_URL} from '../../src/v2/configs.v2'; +import WebView from 'react-native-webview'; +import DefaultButton from '../../src/DefaultButton'; +const BtnTestID = 'flw-default-button'; +const SuccessResponse = { + status: 'success', + message: 'Payment link generated.', + data: { + link: 'http://payment-link.com/checkout', + }, +}; +const PaymentOptions: Omit = { + txref: '34h093h09h034034', + customer_email: 'customer-email@example.com', + PBFPubKey: '[Public Key]', + amount: 50, + currency: 'NGN', +}; + +describe('', () => { + it('renders component correctly', () => { + const Renderer = renderer.create(); + expect(Renderer.toJSON()).toMatchSnapshot(); + }); + + it('renders busy button if isPending', () => { + // get create instance of flutterwave button + const Renderer = renderer.create(); + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + Renderer.root.findByProps({testID: BtnTestID}).props.onPress(); + // simulate animated animation + jest.useFakeTimers(); + global.timeTravel(); + jest.useRealTimers(); + expect(Renderer.toJSON()).toMatchSnapshot(); + }); + + it('renders modal with visibile property as true if show dialog state is true', (done) => { + // get create instance of flutterwave button + const Renderer = renderer.create(); + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + Renderer.root.findByProps({testID: BtnTestID}).props.onPress(); + // simulate animated animation + jest.useFakeTimers(); + global.timeTravel(); + jest.useRealTimers(); + setTimeout(() => { + expect(Renderer.toJSON()).toMatchSnapshot(); + done(); + }, 50); + }); + + it('renders custom button correctly', () => { + const TestRenderer = renderer.create( { + return ( + + {isInitializing ? (Please wait...) : (Pay)} + + ); + }} + />); + expect(TestRenderer.toJSON()).toMatchSnapshot(); + }); + + it('renders webview loading correctly', () => { + const TestRenderer = renderer.create(); + // get webview + const webView = TestRenderer.root.findByType(WebView); + // create loading renderer + const LoadingRenderer = renderer.create(webView.props.renderLoading()); + // checks + expect(LoadingRenderer).toMatchSnapshot(); + }); + + it('renders webview error correctly', () => { + const TestRenderer = renderer.create(); + // get webview + const webView = TestRenderer.root.findByType(WebView); + // create error renderer + const ErrorRenderer = renderer.create(webView.props.renderError()); + // checks + expect(ErrorRenderer).toMatchSnapshot(); + }); + + it('disables custom button and set is initializing to true when initializing payment', () => { + const customButton = jest.fn(); + const TestRenderer = renderer.create(); + TestRenderer.root.instance.handleInit(); + expect(customButton).toHaveBeenCalledTimes(2); + expect(customButton).toHaveBeenLastCalledWith({ + disabled: true, + isInitializing: true, + onPress: expect.any(Function), + }); + }); + + it('disables custom button and set is initializing to false after initializing payment', (done) => { + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + const customButton = jest.fn(); + const TestRenderer = renderer.create(); + TestRenderer.root.instance.handleInit(); + setTimeout(() => { + expect(customButton).toHaveBeenCalledTimes(4); + expect(customButton).toHaveBeenLastCalledWith({ + disabled: true, + isInitializing: false, + onPress: expect.any(Function), + }); + done(); + }, 50); + }); + + it('asks user to confirm abort when pressed backdrop', () => { + const TestRenderer = renderer.create(); + // get backdrop + const Backdrop = TestRenderer.root.findByProps({testID: 'flw-backdrop'}); + // simulate backdrop onPress + Backdrop.props.onPress(); + // checks + expect(Alert.alert).toHaveBeenCalledTimes(1); + expect(Alert.alert).toHaveBeenCalledWith( + expect.any(String), + expect.stringContaining('cancel this payment'), + expect.any(Array), + ); + }); + + it('calls onAbort if available and abort event occurred', () => { + const onAbort = jest.fn(); + // get create instance of flutterwave button + const FlwButton = renderer.create(); + // fire handle abort confirm + FlwButton.root.instance.handleAbortConfirm(); + // called on abort + expect(onAbort).toHaveBeenCalledTimes(1); + }); + + it('does not call onAbort if not available and abort event occurred', () => { + const onAbort = jest.fn(); + // get create instance of flutterwave button + const FlwButton = renderer.create(); + // fire handle abort confirm + FlwButton.root.instance.handleAbortConfirm(); + // called on abort + expect(onAbort).toHaveBeenCalledTimes(0); + }); + + it('does not make standard api call if in pending state', () => { + // get create instance of flutterwave button + const FlwButton = renderer.create(); + + // mock next fetch request + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + + // fire on press + FlwButton.root.findByProps({testID: BtnTestID}).props.onPress(); + FlwButton.root.findByProps({testID: BtnTestID}).props.onPress(); + + // simulate animated animation + jest.useFakeTimers(); + global.timeTravel(); + jest.useRealTimers(); + + // ensure the button is disabled after beign pressed + expect(global.fetch).toHaveBeenCalledTimes(1); + }); + + it('makes call to standard endpoint when button is pressed', async () => { + const Renderer = renderer.create(); + const Button = Renderer.root.findByProps({testID: BtnTestID}); + const c = new AbortController; + const headers = new Headers + + headers.append('Content-Type', 'application/json'); + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + Button.props.onPress(); + + // simulate animated animation + jest.useFakeTimers(); + global.timeTravel(); + jest.useRealTimers(); + + expect(global.fetch).toBeCalledTimes(1); + expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL, { + body: JSON.stringify(PaymentOptions), + headers: headers, + method: 'POST', + signal: c.signal, + }); + }); + + it("updates button size when current and new size don't match", () => { + // on layout event + const onSizeChangeEv = { + width: 100, + height: 100 + }; + + // create test renderer + const TestRenderer = renderer.create(); + + // spy on component methods + const setState = jest.spyOn(TestRenderer.root.instance, 'setState'); + const handleButtonResize = jest.spyOn(TestRenderer.root.instance, 'handleButtonResize'); + + // get default button + const Button = TestRenderer.root.findByProps({testID: BtnTestID}); + + // fire on size change on button + Button.props.onLayout({nativeEvent: {layout: onSizeChangeEv}}); + Button.props.onLayout({nativeEvent: {layout: onSizeChangeEv}}); + + // handle button resize checks + expect(handleButtonResize).toHaveBeenCalledTimes(1); + expect(handleButtonResize).toHaveBeenLastCalledWith(onSizeChangeEv); + + // set state checks + expect(setState).toHaveBeenCalledTimes(1); + expect(setState).toHaveBeenCalledWith({buttonSize: onSizeChangeEv}) + }); + + it('initialized without a redirect url', () => { + // get create instance of flutterwave button + const FlwButton = renderer.create(); + const abortController = new AbortController + // default fetch header + const FetchHeader = new Headers(); + FetchHeader.append('Content-Type', 'application/json'); + // mock next fetch request + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + // fire on press + FlwButton.root.findByProps({testID: BtnTestID}).props.onPress(); + // simulate animated animation + jest.useFakeTimers(); + global.timeTravel(); + jest.useRealTimers(); + // expect fetch to have been called + expect(global.fetch).toHaveBeenCalledTimes(1); + expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL, { + body: JSON.stringify({...PaymentOptions, redirect_url: undefined}), + headers: FetchHeader, + method: 'POST', + signal: abortController.signal + }); + }); + + it('fires onDidInitialize if available', (done) => { + const onDidInitialize = jest.fn(); + // get create instance of flutterwave button + const FlwButton = renderer.create(); + // mock next fetch request + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + // fire on press + FlwButton.root.findByProps({testID: BtnTestID}).props.onPress(); + // simulate animated animation + jest.useFakeTimers(); + global.timeTravel(); + jest.useRealTimers(); + // wait for request to be made + setTimeout(() => { + expect(onDidInitialize).toHaveBeenCalledTimes(1); + // end test + done(); + }, 50); + }); + + it('fires onWillInitialize if available', (done) => { + const onWillInitialize = jest.fn(); + // get create instance of flutterwave button + const FlwButton = renderer.create(); + // mock next fetch request + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + // fire on press + FlwButton.root.findByProps({testID: BtnTestID}).props.onPress(); + // simulate animated animation + jest.useFakeTimers(); + global.timeTravel(); + jest.useRealTimers(); + // wait for request to be made + setTimeout(() => { + expect(onWillInitialize).toHaveBeenCalledTimes(1); + // end test + done(); + }, 50); + }); + + it('fires onInitializeError if available', (done) => { + const err = new Error('Error occurred.'); + const onInitializeError = jest.fn(); + // get create instance of flutterwave button + const FlwButton = renderer.create(); + // mock next fetch request + fetchMock.mockRejectOnce(err); + // fire on press + FlwButton.root.findByProps({testID: BtnTestID}).props.onPress(); + // simulate animated animation + jest.useFakeTimers(); + global.timeTravel(); + jest.useRealTimers(); + // wait for request to be made + setTimeout(() => { + expect(onInitializeError).toHaveBeenCalledTimes(1); + expect(onInitializeError).toHaveBeenCalledWith({ + code: err.name.toUpperCase(), + message: err.message + }); + // end test + done(); + }, 50); + }); + + it('does not update state if init is aborted', (done) => { + // get create instance of flutterwave button + const FlwButton = renderer.create(); + // spy on set state + const setState = jest.spyOn(FlwButton.root.instance, 'setState'); + // mock next fetch request + fetchMock.mockAbortOnce(); + // fire on press + FlwButton.root.findByProps({testID: BtnTestID}).props.onPress(); + // simulate animated animation + jest.useFakeTimers(); + global.timeTravel(); + jest.useRealTimers(); + // wait for request to be made + setTimeout(() => { + expect(setState).toHaveBeenCalledTimes(1); + expect(FlwButton.root.instance.state.isPending).toBe(true); + // end test + done(); + }, 50); + }); + + it("gets redirect params and returns them on redirect", (done) => { + // define response + const response = { + flwref: 'erinf930rnf09', + txref: 'nfeinr09erss', + } + + // define url + const url = "http://redirect-url.com/api/hosted_pay/undefined" + + const urlWithParams = url + '?flwref=' + response.flwref + '&txref=' + response.txref; + + // get create instance of flutterwave button + const TestRenderer = renderer.create(); + + // spy on getRedirectParams method + const getRedirectParams = jest.spyOn(TestRenderer.root.instance, 'getRedirectParams'); + + // mock next fetch request + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + + // find button and + const Button = TestRenderer.root.findByProps({testID: BtnTestID}); + Button.props.onPress(); + + // simulate animated animation + jest.useFakeTimers(); + global.timeTravel(); + jest.useRealTimers(); + + // wait for fetch to complete + setTimeout(() => { + // find webview and fire webview onNavigationStateChange + const webView = TestRenderer.root.findByType(WebView); + webView.props.onNavigationStateChange({url: urlWithParams}); + + // run checks + expect(getRedirectParams).toHaveBeenCalledTimes(1); + expect(getRedirectParams).toHaveBeenCalledWith(urlWithParams); + expect(getRedirectParams).toHaveReturnedWith(response); + // end test + done(); + }, 50); + }); + + it("does not fire complete handle if redirect url does not match", (done) => { + // define url + const url = "http://redirect-url.com"; + + // get create instance of flutterwave button + const TestRenderer = renderer.create(); + + // spy on getRedirectParams method + const handleComplete = jest.spyOn(TestRenderer.root.instance, 'handleComplete'); + const handleNavigationStateChange = jest.spyOn(TestRenderer.root.instance, 'handleNavigationStateChange'); + + // mock next fetch request + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + + // find button and + const Button = TestRenderer.root.findByProps({testID: BtnTestID}); + Button.props.onPress(); + + // simulate animated animation + jest.useFakeTimers(); + global.timeTravel(); + jest.useRealTimers(); + + // wait for fetch to complete + setTimeout(() => { + // find webview and fire webview onNavigationStateChange + const webView = TestRenderer.root.findByType(WebView); + webView.props.onNavigationStateChange({url: url}); + + // run checks + expect(handleNavigationStateChange).toHaveBeenCalledTimes(1); + expect(handleComplete).toHaveBeenCalledTimes(0); + + // end test + done(); + }, 50); + }); + + it("fires onComplete when redirected", (done) => { + // define response + const response = { + flwref: 'erinf930rnf09', + txref: 'nfeinr09erss', + } + + const onComplete = jest.fn(); + + // define url + const url = "http://redirect-url.com/api/hosted_pay/undefined?flwref=" + + response.flwref + + "&txref=" + + response.txref + + // get create instance of flutterwave button + const TestRenderer = renderer.create(); + + // spy on getRedirectParams method + const handleComplete = jest.spyOn(TestRenderer.root.instance, 'handleComplete'); + const handleNavigationStateChange = jest.spyOn( + TestRenderer.root.instance, + 'handleNavigationStateChange' + ); + + // mock next fetch request + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + + // press init button + TestRenderer.root.findByProps({testID: BtnTestID}).props.onPress(); + + // simulate animated animation + jest.useFakeTimers(); + global.timeTravel(); + jest.useRealTimers(); + + // wait for fetch to complete + setTimeout(() => { + // find webview and fire webview onNavigationStateChange + const webView = TestRenderer.root.findByType(WebView); + webView.props.onNavigationStateChange({url: url}); + + // run checks + expect(handleNavigationStateChange).toHaveBeenCalledTimes(1); + expect(handleComplete).toHaveBeenCalledTimes(1); + expect(handleComplete).toHaveBeenCalledWith(response); + expect(onComplete).toHaveBeenCalledTimes(1); + expect(onComplete).toHaveBeenCalledWith({ + ...response, + canceled: false + }); + + // end test + done(); + }, 50); + }); + + it("cancels fetch on will unmount.", () => { + // get create instance of flutterwave button + const TestRenderer = renderer.create(); + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + TestRenderer + .root + .findByProps({testID: BtnTestID}) + .props + .onPress(); + // spy on abort method + const abort = jest.spyOn(TestRenderer.root.instance.canceller, 'abort'); + // call component will unmount + TestRenderer.root.instance.componentWillUnmount(); + // run checks + expect(abort).toHaveBeenCalledTimes(1); + // end test + }); + + it("does not cancel fetch on will unmount if canceller is not set.", () => { + // get create instance of flutterwave button + const TestRenderer = renderer.create(); + const willUnmount = jest.spyOn(TestRenderer.root.instance, 'componentWillUnmount'); + // call component will unmount + TestRenderer.root.instance.componentWillUnmount(); + // run checks + expect(willUnmount).toHaveBeenCalledTimes(1); + expect(TestRenderer.root.instance.canceller).toBeUndefined(); + }); + + it('can reload webview if webview ref is set', (done) => { + // create renderer + const TestRender = renderer.create(); + // mock next fetch request + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + // fire on press + TestRender.root.findByProps({testID: BtnTestID}).props.onPress(); + // simulate animated animation + jest.useFakeTimers(); + global.timeTravel(); + jest.useRealTimers(); + + // wait for standard call to occurr + setTimeout(() => { + const webviewReload = jest.spyOn( + TestRender.root.instance.webviewRef, + 'reload' + ).mockImplementationOnce((() => {})); + TestRender.root.instance.handleReload(); + expect(webviewReload).toHaveBeenCalledTimes(1); + done(); + }, 50); + }); + + it('does not reload if webview ref is not set', () => { + // create renderer + const TestRender = renderer.create(); + Object.defineProperty(TestRender.root.instance, 'webviewRef', {value: null}); + const handleReload = jest.spyOn(TestRender.root.instance, 'handleReload'); + TestRender.root.instance.handleReload(); + expect(handleReload).toHaveBeenCalledTimes(1); + expect(TestRender.root.instance.webviewRef === null).toBe(true); + }); + + it("handles DefaultButton onSizeChange", () => { + const TestRenderer = renderer.create(); + const size = {width: 1200, height: 0}; + const handleButtonResize = jest.spyOn(TestRenderer.root.instance, 'handleButtonResize'); + + TestRenderer.root.findByType(DefaultButton).props.onSizeChange(size); + TestRenderer.root.findByType(DefaultButton).props.onSizeChange(size); + + expect(handleButtonResize).toHaveBeenCalledTimes(1); + expect(handleButtonResize).toHaveBeenCalledWith(size); + }); + + it("does not return query params if non is available with getRedirectParams method", () => { + const url = new String('http://example.com'); + const TestRenderer = renderer.create(); + const split = jest.spyOn(url, 'split'); + TestRenderer.root.instance.getRedirectParams(url);; + expect(split).toHaveBeenCalledTimes(1); + }); + + it("returns query params if avialeble with getRedirectParams method", () => { + const url = new String('http://example.com?foo=bar'); + const TestRenderer = renderer.create(); + const split = jest.spyOn(url, 'split'); + TestRenderer.root.instance.getRedirectParams(url);; + expect(split).toHaveBeenCalledTimes(2); + }); + + it("updates state if reset is called.", () => { + // get create instance of flutterwave button + const TestRenderer = renderer.create(); + const setState = jest.spyOn(TestRenderer.root.instance, 'setState'); + // call component will unmount + TestRenderer.root.instance.reset(); + // run checks + expect(setState).toHaveBeenCalledTimes(1); + expect(TestRenderer.root.instance.canceller).toBeUndefined(); + }); + + it("cancels fetch if reset is called and abort controller is set.", () => { + // get create instance of flutterwave button + const TestRenderer = renderer.create(); + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + TestRenderer + .root + .findByProps({testID: BtnTestID}) + .props + .onPress(); + // spy on abort method + const abort = jest.spyOn(TestRenderer.root.instance.canceller, 'abort'); + // call component will unmount + TestRenderer.root.instance.reset(); + // run checks + expect(abort).toHaveBeenCalledTimes(1); + // end test + }); + + it("calls options change handler if options changed", () => { + // get create instance of flutterwave button + const TestRenderer = renderer.create(); + // spy on handleOptionsChanged method + const handleOptionsChanged = jest.spyOn(TestRenderer.root.instance, 'handleOptionsChanged'); + // update component + TestRenderer.update() + // run checks + expect(handleOptionsChanged).toHaveBeenCalledTimes(1); + // end test + }); + + it("does not set state if link has not been set", () => { + // get create instance of flutterwave button + const TestRenderer = renderer.create(); + // spy on setState method + const setState = jest.spyOn(TestRenderer.root.instance, 'setState'); + // update component + TestRenderer.update() + // run checks + expect(setState).toHaveBeenCalledTimes(0); + // end test + }); + + it("resets link if dialog is not being show", (done) => { + // get create instance of flutterwave button + const TestRenderer = renderer.create(); + // mock next fetch + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + // initialize payment + TestRenderer + .root + .findByProps({testID: BtnTestID}) + .props + .onPress(); + // wait for mocked fetch + setTimeout(() => { + // set dialog to hidden + TestRenderer.root.instance.setState({showDialog: false}); + // spy on setState method + const setState = jest.spyOn(TestRenderer.root.instance, 'setState'); + // update component + TestRenderer.update() + // run checks + expect(setState).toHaveBeenCalledTimes(1); + expect(setState).toHaveBeenCalledWith({ + link: null, + txref: null, + }); + // end test + done(); + }, 50); + }); + + it("schedules a link reset if dialog has already been shown", (done) => { + // get create instance of flutterwave button + const TestRenderer = renderer.create(); + // mock next fetch + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + // initialize payment + TestRenderer + .root + .findByProps({testID: BtnTestID}) + .props + .onPress(); + // wait for mocked fetch + setTimeout(() => { + // spy on setState method + const setState = jest.spyOn(TestRenderer.root.instance, 'setState'); + // update component + TestRenderer.update() + // run checks + expect(setState).toHaveBeenCalledTimes(1); + expect(setState).toHaveBeenCalledWith({resetLink: true}); + // end test + done(); + }, 50); + }); + + it("renders checkout screen if link already exist when init is called", (done) => { + // get create instance of flutterwave button + const TestRenderer = renderer.create(); + // set a payment link + TestRenderer.root.instance.setState({link: 'http://payment-link.com'}); + // spy on show method + const show = jest.spyOn(TestRenderer.root.instance, 'show'); + // initialize payment + TestRenderer + .root + .findByProps({testID: BtnTestID}) + .props + .onPress(); + setTimeout(() => { + // run checks + expect(show).toHaveBeenCalledTimes(1); + expect(global.fetch).toHaveBeenCalledTimes(0); + // end test + done(); + }, 20); + }); + + it("does not generate a new link if already generating one", () => { + // get create instance of flutterwave button + const TestRenderer = renderer.create(); + // set a payment link + TestRenderer.root.instance.setState({isPending: true}); + // set a payment link + TestRenderer.root.instance.handleInit(); + // run checks + expect(global.fetch).toHaveBeenCalledTimes(0); + }); +}); diff --git a/__tests__/v2/FlutterwaveInit.spec.ts b/__tests__/v2/FlutterwaveInit.spec.ts new file mode 100644 index 0000000..67131c2 --- /dev/null +++ b/__tests__/v2/FlutterwaveInit.spec.ts @@ -0,0 +1,145 @@ +import FlutterwaveInit, {FlutterwaveInitOptions} from '../../src/v2/FlutterwaveInit'; +import {STANDARD_URL} from '../../src/v2/configs.v2'; + +// default fetch header +const DefaultFetchHeader = new Headers(); +DefaultFetchHeader.append('Content-Type', 'application/json'); + +describe('', () => { + it('returns a payment link after initialization', async () => { + // mock next fetch request + fetchMock.mockOnce(JSON.stringify({ + status: 'success', + message: 'Payment link generated.', + data: { + link: 'http://payment-link.com/checkout', + }, + })); + // payment information + const paymentInfo: FlutterwaveInitOptions = { + redirect_url: 'http://flutterwave.com', + PBFPubKey: '[PUB Key]', + amount: 50, + currency: 'NGN', + customer_email: 'email@example.com', + txref: Date.now() + '-txref', + }; + // flutterwave init test + const response = await FlutterwaveInit(paymentInfo); + + // expect fetch to have been called once + expect(global.fetch).toHaveBeenCalledTimes(1); + // expect fetch to have been called to the standard init url + expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL, { + body: JSON.stringify(paymentInfo), + headers: DefaultFetchHeader, + method: 'POST', + }); + expect(typeof response.link === 'string').toBeTruthy(); + }); + + it('includes error code and message of init error', async () => { + // payment information + const paymentInfo: FlutterwaveInitOptions = { + redirect_url: 'http://flutterwave.com', + PBFPubKey: 'FLWPUBK_TEST-c761fb7f0e443f5704a796781b621875-X44', + amount: 50, + currency: 'NGN', + customer_email: 'email@example.com', + txref: Date.now() + '-txref', + }; + // reject next fetch + fetchMock.mockRejectOnce(new Error('An error occured!')); + // flutterwave init test + const response = await FlutterwaveInit(paymentInfo); + // expect fetch to have been called + expect(global.fetch).toHaveBeenCalledTimes(1); + expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL, { + body: JSON.stringify(paymentInfo), + headers: DefaultFetchHeader, + method: 'POST', + }); + // expect error and error code to be defined + expect(typeof response.error.code === 'string').toBeTruthy(); + expect(typeof response.error.message === 'string').toBeTruthy(); + }); + + it('returns unknown error if the error response has no code or message', async () => { + // payment information + const paymentInfo: FlutterwaveInitOptions = { + redirect_url: 'http://flutterwave.com', + PBFPubKey: 'FLWPUBK_TEST-c761fb7f0e443f5704a796781b621875-X44', + amount: 50, + currency: 'NGN', + customer_email: 'email@example.com', + txref: Date.now() + '-txref', + }; + // mock next fetch + fetchMock.mockOnce(JSON.stringify({status: 'error', data: {}})); + // flutterwave init test + const response = await FlutterwaveInit(paymentInfo); + // expect fetch to have been called + expect(global.fetch).toHaveBeenCalledTimes(1); + expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL, { + body: JSON.stringify(paymentInfo), + headers: DefaultFetchHeader, + method: 'POST', + }); + // expect unkown error from from response + expect(/unknown/i.test(response.error.code)).toBeTruthy(); + }); + + it('catches missing response data error', async () => { + // payment information + const paymentInfo: FlutterwaveInitOptions = { + redirect_url: 'http://flutterwave.com', + PBFPubKey: 'FLWPUBK_TEST-c761fb7f0e443f5704a796781b621875-X44', + amount: 50, + currency: 'NGN', + customer_email: 'email@example.com', + txref: Date.now() + '-txref', + }; + // mock next fetch + fetchMock.mockOnce(JSON.stringify({status: 'error'})); + // flutterwave init test + const response = await FlutterwaveInit(paymentInfo); + // expect fetch to have been called + expect(global.fetch).toHaveBeenCalledTimes(1); + expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL, { + body: JSON.stringify(paymentInfo), + headers: DefaultFetchHeader, + method: 'POST', + }); + // expect a no response error + expect(/nodata/i.test(response.error.code)).toBeTruthy(); + }); + + it('is abortable', async () => { + // use fake jest timers + jest.useFakeTimers(); + // mock fetch response + fetchMock.mockResponse(async () => { + jest.advanceTimersByTime(60) + return '' + }); + // create abort controller + const abortController = new AbortController; + // payment information + const paymentInfo: FlutterwaveInitOptions = { + redirect_url: 'http://flutterwave.com', + PBFPubKey: 'FLWPUBK_TEST-c761fb7f0e443f5704a796781b621875-X44', + amount: 50, + currency: 'NGN', + customer_email: 'email@example.com', + txref: Date.now() + '-txref', + }; + // abort next fetch + setTimeout(() => abortController.abort(), 50); + // expect a no response error + await expect(FlutterwaveInit(paymentInfo, {canceller: abortController})).resolves.toMatchObject({ + error: { + code: 'ABORTERROR' + } + }); + }); +}); diff --git a/__tests__/v2/__snapshots__/FlutterwaveButton.spec.tsx.snap b/__tests__/v2/__snapshots__/FlutterwaveButton.spec.tsx.snap new file mode 100644 index 0000000..74c8839 --- /dev/null +++ b/__tests__/v2/__snapshots__/FlutterwaveButton.spec.tsx.snap @@ -0,0 +1,751 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders busy button if isPending 1`] = ` +Array [ + + + + , + + + + + + + + + + + , +] +`; + +exports[` renders component correctly 1`] = ` +Array [ + + + , + + + + + + + + + + + , +] +`; + +exports[` renders custom button correctly 1`] = ` +Array [ + + Pay + , + + + + + + + + + + + , +] +`; + +exports[` renders modal with visibile property as true if show dialog state is true 1`] = ` +Array [ + + + , + + + + + + + + + + + , +] +`; + +exports[` renders webview error correctly 1`] = ` + +`; + +exports[` renders webview loading correctly 1`] = ` + + + +`; diff --git a/docs/v2/AbortingPaymentInitialization.md b/docs/v2/AbortingPaymentInitialization.md new file mode 100644 index 0000000..3f9ebb7 --- /dev/null +++ b/docs/v2/AbortingPaymentInitialization.md @@ -0,0 +1,72 @@ +# Aborting Payment Initialization +Hi :wave:, so there are cases where you have already initialized a payment with `FlutterwaveInit` but might also want to be able to cancel the payment initialization should in case your component is being unmounted or you want to allow users cancel the action before the payment is initialized, we have provided a way for you to do this, we use `fetch` underneath the hood to make the request to the standard payment endpoint and `fetch` allows you to pass an [abort controller](https://github.com/mo/abortcontroller-polyfill) which you can use to cancel ongoing requests, if your version of React Native does not have the abort functionality for fetch in the Javascript runtime, you will need to [install the polyfill](https://github.com/mo/abortcontroller-polyfill) before moving on. Below is a code snippet showcasing how you can go about cancelling an ongoing payment initialization. + +**:point_right:`If you have already installed the polyfill or have it already available in the Javascript runtime, this action happens automatically within FlutterwaveButton.`** + +````jsx +import React from 'react'; +import {View, TouchableOpacity} from 'react-native'; +import {FlutterwaveInit} from 'react-native-flutterwave'; + +class MyCart extends React.Component { + abortController = null; + + componentWillUnmout() { + if (this.abortController) { + this.abortController.abort(); + } + } + + handlePaymentInitialization = () => { + this.setState({ + isPending: true, + }, () => { + // set abort controller + this.abortController = new AbortController; + // initialize a new payment + const payment = await FlutterwaveInit({ + txref: generateTransactionRef(), + PBFPubKey: '[Your Flutterwave Public Key]', + amount: 100, + currency: 'USD', + }, { + canceller: this.abortController, + }); + // do nothing if our payment initialization was aborted + if (payment.error && payment.error.code === 'ABORTERROR') { + return; + } + // link is available if payment initialized successfully + if (payment.link) { + // use payment link + return; + } + // handle other errors + }) + } + + render() { + const {isPending} = this.state; + return ( + + ... + + Pay $100 + + + ) + } +} +```` +In the above code we created a component called `MyCart` within that component we have an `abortController` property and in the same component we have two methods that interact with this property, the first is the `handlePaymentInitialization` method, this creates the abort controller before initializing the payment, the second method is `componentWillUnmount`, this is a react lifecycle hook method which is fired when the component is being unmounted, you are expected to unsubscribe from any event here before the component unmounts, so within this method we check to see if the abort controller has been defined and if it has, we call the abort method on the controller, this will abort the ongoing payment initialization and return an error with the error code `ABORTERROR`, if the we check the error code and it is `ABORTERROR` we can then stop the execution of `handlePaymentInitialization` so nothing else happens within our unmounted component. + +And that's all you need to abort an ongoing payment initialization. + +With love from Flutterwave. :yellow_heart: diff --git a/src/utils/CustomPropTypesRules.ts b/src/utils/CustomPropTypesRules.ts index 900c488..78f02b3 100644 --- a/src/utils/CustomPropTypesRules.ts +++ b/src/utils/CustomPropTypesRules.ts @@ -1,6 +1,5 @@ -import { PAYMENT_OPTIONS } from "../configs"; -export const PaymentOptionsPropRule = (props:{[k: string]: any}, propName: string) => { +export const PaymentOptionsPropRule = (options: Array) => (props:{[k: string]: any}, propName: string) => { // skip check if payment options is not defined if (props[propName] === undefined) { return null; @@ -14,14 +13,14 @@ export const PaymentOptionsPropRule = (props:{[k: string]: any}, propName: strin const paymentOptionsList = props[propName].split(','); for (let i = 0; i < paymentOptionsList.length; i++) { if ( - PAYMENT_OPTIONS.findIndex( + options.findIndex( (j) => j.trim() === paymentOptionsList[i].trim(), ) === -1 ) { return new Error( `"payment_options"(${ props[propName] - }) must be any of the following values.\n${PAYMENT_OPTIONS.map( + }) must be any of the following values.\n${options.map( (i, n) => `${n + 1}. ${i}\n`, ).join('')}`, ); diff --git a/src/v2/FlutterwaveButton.tsx b/src/v2/FlutterwaveButton.tsx new file mode 100644 index 0000000..532495d --- /dev/null +++ b/src/v2/FlutterwaveButton.tsx @@ -0,0 +1,546 @@ +import React from 'react'; +import { + StyleSheet, + Modal, + View, + Animated, + TouchableWithoutFeedback, + Text, + ViewStyle, + Alert, + Image, + Platform, + Dimensions, + Easing, +} from 'react-native'; +import WebView from 'react-native-webview'; +import PropTypes from 'prop-types'; +import {WebViewNavigation} from 'react-native-webview/lib/WebViewTypes'; +import FlutterwaveInit, { + FlutterwaveInitOptions, + FlutterwaveInitError, +} from './FlutterwaveInit'; +import {PAYMENT_OPTIONS} from './configs.v2'; +import {colors} from '../configs'; +import {PaymentOptionsPropRule} from '../utils/CustomPropTypesRules'; +import DefaultButton from '../DefaultButton'; +const loader = require('../loader.gif'); +const pryContent = require('../pry-button-content.png'); +const contentWidthPercentage = 0.6549707602; +const contentSizeDimension = 8.2962962963; +const contentMaxWidth = 187.3; +const contentMaxHeight = contentMaxWidth / contentSizeDimension; +const contentMinWidth = 187.3; +const contentMinHeight = contentMinWidth / contentSizeDimension; +const borderRadiusDimension = 24 / 896; +const windowHeight = Dimensions.get('window').height; + +interface CustomButtonProps { + disabled: boolean; + isInitializing: boolean; + onPress: () => void; +} + +interface OnCompleteData { + canceled: boolean; + flwref?: string; + txref: string; +} + +interface RedirectParams { + canceled: 'true' | 'false'; + flwref?: string; + txref?: string; + response?: string; +} + +export interface FlutterwaveButtonProps { + style?: ViewStyle; + onComplete: (data: OnCompleteData) => void; + onWillInitialize?: () => void; + onDidInitialize?: () => void; + onInitializeError?: (error: FlutterwaveInitError) => void; + onAbort?: () => void; + options: Omit; + customButton?: (params: CustomButtonProps) => React.ReactNode; + alignLeft?: 'alignLeft' | boolean; +} + +interface FlutterwaveButtonState { + link: string | null; + isPending: boolean; + showDialog: boolean; + animation: Animated.Value; + txref: string | null; + resetLink: boolean; + buttonSize: { + width: number; + height: number; + }; +} + +class FlutterwaveButton extends React.Component< + FlutterwaveButtonProps, + FlutterwaveButtonState +> { + static propTypes = { + alignLeft: PropTypes.bool, + onAbort: PropTypes.func, + onComplete: PropTypes.func.isRequired, + onWillInitialize: PropTypes.func, + onDidInitialize: PropTypes.func, + onInitializeError: PropTypes.func, + options: PropTypes.shape({ + txref: PropTypes.string.isRequired, + PBFPubKey: PropTypes.string.isRequired, + customer_email: PropTypes.string.isRequired, + amount: PropTypes.number.isRequired, + currency: PropTypes.oneOf(['NGN', 'USD', 'GHS', 'KES', 'ZAR', 'TZS']), + payment_options: PaymentOptionsPropRule(PAYMENT_OPTIONS), + payment_plan: PropTypes.number, + subaccounts: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string.isRequired, + transaction_split_ratio: PropTypes.number, + transaction_charge_type: PropTypes.string, + transaction_charge: PropTypes.number, + })), + country: PropTypes.string, + pay_button_text: PropTypes.string, + custom_title: PropTypes.string, + custom_description: PropTypes.string, + custom_logo: PropTypes.string, + meta: PropTypes.arrayOf(PropTypes.shape({ + metaname: PropTypes.string, + metavalue: PropTypes.string, + })), + }).isRequired, + customButton: PropTypes.func, + }; + + state: FlutterwaveButtonState = { + isPending: false, + link: null, + resetLink: false, + showDialog: false, + animation: new Animated.Value(0), + txref: null, + buttonSize: { + width: 0, + height: 0, + }, + }; + + webviewRef: WebView | null = null; + + canceller?: AbortController; + + componentDidUpdate(prevProps: FlutterwaveButtonProps) { + if (JSON.stringify(prevProps.options) !== JSON.stringify(this.props.options)) { + this.handleOptionsChanged() + } + } + + componentWillUnmount() { + if (this.canceller) { + this.canceller.abort(); + } + } + + reset = () => { + if (this.canceller) { + this.canceller.abort(); + } + // reset the necessaries + this.setState(({resetLink, link}) => ({ + isPending: false, + link: resetLink ? null : link, + resetLink: false, + showDialog: false, + })); + }; + + handleOptionsChanged = () => { + const {showDialog, link} = this.state; + if (!link) { + return; + } + if (!showDialog) { + return this.setState({ + link: null, + txref: null, + }) + } + this.setState({resetLink: true}) + } + + handleNavigationStateChange = (ev: WebViewNavigation) => { + // cregex to check if redirect has occured on completion/cancel + const rx = /\/hosted\/pay\/undefined|\/api\/hosted_pay\/undefined/; + // Don't end payment if not redirected back + if (!rx.test(ev.url)) { + return + } + // fire handle complete + this.handleComplete(this.getRedirectParams(ev.url)); + }; + + handleComplete(data: any) { + const {onComplete} = this.props; + // reset payment link + this.setState(({resetLink, txref}) => ({ + txref: data.flref && !data.canceled ? null : txref, + resetLink: data.flwref && !data.canceled ? true : resetLink + }), + () => { + // reset + this.dismiss(); + // fire onComplete handler + onComplete({ + flwref: data.flwref, + txref: data.txref, + canceled: /true/i.test(data.canceled || '') ? true : false + }); + } + ); + } + + handleReload = () => { + // fire if webview is set + if (this.webviewRef) { + this.webviewRef.reload(); + } + }; + + handleAbortConfirm = () => { + const {onAbort} = this.props; + // abort action + if (onAbort) { + onAbort(); + } + // remove txref and dismiss + this.dismiss(); + }; + + handleAbort = () => { + Alert.alert('', 'Are you sure you want to cancel this payment?', [ + {text: 'No'}, + { + text: 'Yes, Cancel', + style: 'destructive', + onPress: this.handleAbortConfirm, + }, + ]); + }; + + handleButtonResize = (size: {width: number; height: number}) => { + const {buttonSize} = this.state; + if (JSON.stringify(buttonSize) !== JSON.stringify(size)) { + this.setState({buttonSize: size}); + } + }; + + getRedirectParams = (url: string): RedirectParams => { + // initialize result container + const res: any = {}; + // if url has params + if (url.split('?').length > 1) { + // get query params in an array + const params = url.split('?')[1].split('&'); + // add url params to result + for (let i = 0; i < params.length; i++) { + const param: Array = params[i].split('='); + const val = decodeURIComponent(param[1]).trim(); + res[param[0]] = String(val); + } + } + // return result + return res; + }; + + show = () => { + const {animation} = this.state; + this.setState({showDialog: true}, () => { + Animated.timing(animation, { + toValue: 1, + duration: 700, + easing: Easing.in(Easing.elastic(0.72)), + useNativeDriver: false, + }).start(); + }); + }; + + dismiss = () => { + const {animation} = this.state; + Animated.timing(animation, { + toValue: 0, + duration: 400, + useNativeDriver: false, + }).start(this.reset); + }; + + handleInit = () => { + const {options, onWillInitialize, onInitializeError, onDidInitialize} = this.props; + const {isPending, txref, link} = this.state; + + // just show the dialod if the link is already set + if (link) { + return this.show(); + } + + // throw error if transaction reference has not changed + if (txref === options.txref) { + return onInitializeError ? onInitializeError({ + message: 'Please generate a new transaction reference.', + code: 'SAME_TXREF', + }) : null; + } + + // stop if currently in pending mode + if (isPending) { + return; + } + + // initialize abort controller if not set + this.canceller = new AbortController; + + // fire will initialize handler if available + if (onWillInitialize) { + onWillInitialize(); + } + + // @ts-ignore + // delete redirect url if set + delete options.redirect_url; + + // set pending state to true + this.setState( + { + isPending: true, + link: null, + txref: options.txref, + }, + async () => { + // make init request + const result = await FlutterwaveInit(options, {canceller: this.canceller}); + // stop if request was canceled + if (result.error && /aborterror/i.test(result.error.code)) { + return; + } + // call onInitializeError handler if an error occured + if (!result.link) { + if (onInitializeError && result.error) { + onInitializeError(result.error); + } + return this.dismiss(); + } + this.setState({link: result.link, isPending: false}, this.show); + // fire did initialize handler if available + if (onDidInitialize) { + onDidInitialize(); + } + }, + ); + }; + + render() { + const {link, animation, showDialog} = this.state; + const marginTop = animation.interpolate({ + inputRange: [0, 1], + outputRange: [windowHeight, Platform.OS === 'ios' ? 46 : 14], + }); + const opacity = animation.interpolate({ + inputRange: [0, 0.3, 1], + outputRange: [0, 1, 1], + }); + // render UI + return ( + <> + {this.renderButton()} + + {this.renderBackdrop()} + + (this.webviewRef = ref)} + source={{uri: link || ''}} + style={styles.webview} + startInLoadingState={true} + scalesPageToFit={true} + javaScriptEnabled={true} + onNavigationStateChange={this.handleNavigationStateChange} + renderError={this.renderError} + renderLoading={this.renderLoading} + /> + + + + ); + } + + renderButton() { + const {customButton, style, alignLeft} = this.props; + const {isPending, link, showDialog, buttonSize} = this.state; + const contentWidth = buttonSize.width * contentWidthPercentage; + const contentHeight = contentWidth / contentSizeDimension; + const contentSizeStyle = { + width: + contentWidth > contentMaxWidth + ? contentMaxWidth + : contentWidth < contentMinWidth + ? contentMinWidth + : contentWidth, + height: + contentHeight > contentMaxHeight + ? contentMaxHeight + : contentHeight < contentMinHeight + ? contentMinHeight + : contentHeight, + }; + // render custom button + if (customButton) { + return customButton({ + isInitializing: isPending && !link ? true : false, + disabled: isPending || showDialog? true : false, + onPress: this.handleInit, + }); + } + // render primary button + return ( + + + + ); + } + + renderBackdrop() { + const {animation} = this.state; + // Interpolation backdrop animation + const backgroundColor = animation.interpolate({ + inputRange: [0, 0.3, 1], + outputRange: [colors.transparent, colors.transparent, 'rgba(0,0,0,0.5)'], + }); + return ( + + + + ); + } + + renderLoading() { + return ( + + + + ); + } + + renderError = () => { + const {link} = this.state; + return ( + + {link ? ( + <> + + The page failed to load, please try again. + + + + Try Again + + + + ) : null} + + ); + }; +} + +const styles = StyleSheet.create({ + promtActions: { + flexDirection: 'row', + alignItems: 'center', + }, + promptActionText: { + textAlign: 'center', + color: colors.primary, + fontSize: 16, + paddingHorizontal: 16, + paddingVertical: 16, + }, + promptQuestion: { + color: colors.secondary, + textAlign: 'center', + marginBottom: 32, + fontSize: 18, + }, + prompt: { + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + top: 0, + backgroundColor: '#ffffff', + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: 56, + }, + backdrop: { + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + top: 0, + }, + loadingImage: { + width: 64, + height: 64, + resizeMode: 'contain', + }, + loading: { + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 0, + backgroundColor: 'rgba(255, 255, 255, 0.3)', + justifyContent: 'center', + alignItems: 'center', + }, + webviewContainer: { + top: 50, + flex: 1, + backgroundColor: '#efefef', + paddingBottom: 50, + overflow: 'hidden', + borderTopLeftRadius: windowHeight * borderRadiusDimension, + borderTopRightRadius: windowHeight * borderRadiusDimension, + }, + webview: { + flex: 1, + backgroundColor: 'rgba(0,0,0,0)', + }, + buttonContent: { + resizeMode: 'contain', + }, +}); + +export default FlutterwaveButton; diff --git a/src/v2/FlutterwaveInit.ts b/src/v2/FlutterwaveInit.ts new file mode 100644 index 0000000..d1a98fc --- /dev/null +++ b/src/v2/FlutterwaveInit.ts @@ -0,0 +1,161 @@ +import {STANDARD_URL} from './configs.v2'; + +interface FlutterwavePaymentMeta { + metaname: string; + metavalue: string; +} + +export interface FlutterwaveInitSubAccount { + id: string; + transaction_split_ratio?: number; + transaction_charge_type?: string; + transaction_charge?: number; +} + +export interface FlutterwaveInitOptions { + txref: string; + PBFPubKey: string; + customer_firstname?: string; + customer_lastname?: string; + customer_phone?: string; + customer_email: string; + amount: number; + currency?: string; + redirect_url?: string; + payment_options?: string; + payment_plan?: number; + subaccounts?: Array; + country?: string; + pay_button_text?: string; + custom_title?: string; + custom_description?: string; + custom_logo?: string; + meta?: Array; +} + +interface FlutterwaveInitConfig { + canceller?: AbortController; +} + +export interface FlutterwaveInitError { + code: string; + message: string; +} + +interface FlutterwaveInitResult { + error?: FlutterwaveInitError | null; + link?: string | null; +} + +interface ResponseJSON { + status: 'success' | 'error'; + message: string; + data: { + link?: string; + code?: string; + message?: string; + }; +} + +interface FetchOptions { + method: 'POST' | 'GET' | 'PUT' | 'DELETE'; + body: string; + headers: Headers; + signal?: AbortSignal; +} + +/** + * This function is responsible for making the request to + * initialize a Flutterwave payment. + * @param options FlutterwaveInitOptions + * @return Promise<{ + * error: { + * code: string; + * message: string; + * } | null; + * link?: string | null; + * }> + */ +export default async function FlutterwaveInit( + options: FlutterwaveInitOptions, + config: FlutterwaveInitConfig = {}, +): Promise { + try { + // make request body + const body = {...options}; + + // make request headers + const headers = new Headers; + headers.append('Content-Type', 'application/json'); + + // make fetch options + const fetchOptions: FetchOptions = { + method: 'POST', + body: JSON.stringify(body), + headers: headers, + } + + // add canceller if defined + if (config.canceller) { + fetchOptions.signal = config.canceller.signal + }; + + // make http request + const response = await fetch(STANDARD_URL, fetchOptions); + + // get response json + const responseJSON: ResponseJSON = await response.json(); + + // check if data is missing from response + if (!responseJSON.data) { + throw new FlutterwaveInitException({ + code: 'NODATA', + message: responseJSON.message || 'An unknown error occured!', + }); + } + + // check if the link is missing in data + if (!responseJSON.data.link) { + throw new FlutterwaveInitException({ + code: responseJSON.data.code || 'UNKNOWN', + message: responseJSON.data.message || 'An unknown error occured!', + }); + } + + // resolve with the payment link + return Promise.resolve({ + link: responseJSON.data.link, + }); + } catch (e) { + // resolve with error + return Promise.resolve({ + error: { + code: + e instanceof FlutterwaveInitException + ? e.code + : String(e.name).toUpperCase(), + message: e.message, + } + }); + } +} + +/** + * Flutterwave Init Error + */ +export class FlutterwaveInitException extends Error { + /** + * Error code + * @var string + */ + code: string; + + /** + * Constructor Method + * @param props {message?: string; code?: string} + */ + constructor(props: {message: string; code: string}) { + super(props.message); + this.code = props.code; + } +} diff --git a/src/v2/configs.v2.ts b/src/v2/configs.v2.ts new file mode 100644 index 0000000..26498bd --- /dev/null +++ b/src/v2/configs.v2.ts @@ -0,0 +1,21 @@ +/** + * Flutterwaves standard init url. + */ +export const STANDARD_URL: string = + 'https://api.ravepay.co/flwv3-pug/getpaidx/api/v2/hosted/pay'; + +export const PAYMENT_OPTIONS = [ + 'card', + 'account', + 'ussd', + 'qr', + 'mpesa', + 'mobilemoneyghana', + 'mobilemoneyuganda', + 'mobilemoneyrwanda', + 'mobilemoneyzambia', + 'mobilemoneytanzania', + 'barter', + 'bank transfer', + 'wechat', +]; diff --git a/src/v2/index.ts b/src/v2/index.ts new file mode 100644 index 0000000..3737c29 --- /dev/null +++ b/src/v2/index.ts @@ -0,0 +1,8 @@ +import FlutterwaveInit from './FlutterwaveInit'; +import FlutterwaveButton from './FlutterwaveButton'; + +// export modules +export {FlutterwaveInit, FlutterwaveButton}; + +// export init as default +export default FlutterwaveButton; From fe73ed2d404b14f97914b3ed497162e8cf9ae7c4 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 14:40:00 +0100 Subject: [PATCH 050/129] feat(assets): put images in assets folder --- src/{ => assets}/loader.gif | Bin src/{ => assets}/pry-button-content.png | Bin 2 files changed, 0 insertions(+), 0 deletions(-) rename src/{ => assets}/loader.gif (100%) rename src/{ => assets}/pry-button-content.png (100%) diff --git a/src/loader.gif b/src/assets/loader.gif similarity index 100% rename from src/loader.gif rename to src/assets/loader.gif diff --git a/src/pry-button-content.png b/src/assets/pry-button-content.png similarity index 100% rename from src/pry-button-content.png rename to src/assets/pry-button-content.png From d8bbb745bb27b3568b8ce060b6670fbdfb73218c Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 14:41:47 +0100 Subject: [PATCH 051/129] feat(configs): add v2 api configs --- src/configs.ts | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/configs.ts b/src/configs.ts index f643f96..5d4e7ea 100644 --- a/src/configs.ts +++ b/src/configs.ts @@ -1,16 +1,25 @@ /** - * Flutterwaves standard init url. + * V# API Standard initialization endpoint */ export const STANDARD_URL = 'https://api.flutterwave.com/v3/payments'; +/** + * Redirect URL used in V3 FlutterwaveButton + */ export const REDIRECT_URL = 'https://flutterwave.com/rn-redirect'; +/** + * Fluttereave volors + */ export const colors = { primary: '#f5a623', secondary: '#12122C', transparent: 'rgba(0,0,0,0)', }; +/** + * Payment options available in V3 + */ export const PAYMENT_OPTIONS = [ 'account', 'card', @@ -30,3 +39,28 @@ export const PAYMENT_OPTIONS = [ '1voucher', 'mobilemoneytanzania', ]; + +/** + * V2 API standard initialization endpoint + */ +export const STANDARD_URL_V2: string = + 'https://api.ravepay.co/flwv3-pug/getpaidx/api/v2/hosted/pay'; + +/** + * Payment options available in V2 API + */ +export const PAYMENT_OPTIONS_V2 = [ + 'card', + 'account', + 'ussd', + 'qr', + 'mpesa', + 'mobilemoneyghana', + 'mobilemoneyuganda', + 'mobilemoneyrwanda', + 'mobilemoneyzambia', + 'mobilemoneytanzania', + 'barter', + 'bank transfer', + 'wechat', +]; From e4c327fbca88b28526c53d0391b6ce1cd3a35047 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 14:44:44 +0100 Subject: [PATCH 052/129] test(customproptypesrules): remove config dependency for payment options rule --- __tests__/CustomPropTypesRules.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/CustomPropTypesRules.spec.ts b/__tests__/CustomPropTypesRules.spec.ts index 0286a73..35f271b 100644 --- a/__tests__/CustomPropTypesRules.spec.ts +++ b/__tests__/CustomPropTypesRules.spec.ts @@ -1,7 +1,7 @@ import 'react-native'; import {PaymentOptionsPropRule} from '../src/utils/CustomPropTypesRules'; -import {PAYMENT_OPTIONS} from '../src/configs'; const PropName = 'payment_options'; +const PAYMENT_OPTIONS = ['barter', 'card', 'banktransfer'] describe('CustomPropTypes.PaymentOptionsPropRule', () => { it ('returns null if prop is not defined in props', () => { From 886872c9fa9e5dd3ec7fb054f6a74f454dc37d60 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 14:48:49 +0100 Subject: [PATCH 053/129] fix(defaultbutton): fix style and children prop definition and compile style as an array --- src/DefaultButton.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/DefaultButton.tsx b/src/DefaultButton.tsx index cf598ce..0a64bd9 100644 --- a/src/DefaultButton.tsx +++ b/src/DefaultButton.tsx @@ -5,14 +5,15 @@ import { ViewStyle, LayoutChangeEvent, TouchableHighlight, + StyleProp, } from "react-native"; import {colors} from './configs'; interface DefaultButtonProps { - style?: ViewStyle; + style?: StyleProp; onPress?: () => void; disabled?: boolean; - children: React.ReactElement; + children: React.ReactNode; isBusy?: boolean; onSizeChange?: (ev: {width: number; height: number}) => void; alignLeft?: 'alignLeft' | boolean, @@ -57,12 +58,12 @@ const DefaultButton: React.FC = function Button({ underlayColor={colors.primary} disabled={disabled} onPress={onPress} - style={{ - ...styles.button, - ...getBusyStyle(isBusy), - ...getAlginStyle(alignLeft), - ...(style || {} - )}} + style={[ + styles.button, + getBusyStyle(isBusy), + getAlginStyle(alignLeft), + style + ]} activeOpacity={1} onLayout={handleOnLayout} testID='flw-default-button'> From 7084141b00bcde7196fec6c7d6a5635927fa4d4e Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 14:51:14 +0100 Subject: [PATCH 054/129] test(defaultbutton): update snapshot --- .../__snapshots__/DefaultButton.spec.tsx.snap | 97 +++++++++++-------- 1 file changed, 58 insertions(+), 39 deletions(-) diff --git a/__tests__/__snapshots__/DefaultButton.spec.tsx.snap b/__tests__/__snapshots__/DefaultButton.spec.tsx.snap index 830f879..f65a202 100644 --- a/__tests__/__snapshots__/DefaultButton.spec.tsx.snap +++ b/__tests__/__snapshots__/DefaultButton.spec.tsx.snap @@ -13,19 +13,26 @@ exports[` renders overlay if busy 1`] = ` onResponderTerminationRequest={[Function]} onStartShouldSetResponder={[Function]} style={ - Object { - "alignItems": "center", - "backgroundColor": "#f5a623", - "borderColor": "#FBDBA7", - "borderRadius": 6, - "borderWidth": 1, - "flexDirection": "row", - "height": 52, - "justifyContent": "center", - "minWidth": 100, - "overflow": "hidden", - "paddingHorizontal": 16, - } + Array [ + Object { + "alignItems": "center", + "backgroundColor": "#f5a623", + "borderColor": "#f5a623", + "borderRadius": 6, + "borderWidth": 1, + "flexDirection": "row", + "height": 52, + "justifyContent": "center", + "minWidth": 100, + "overflow": "hidden", + "paddingHorizontal": 16, + }, + Object { + "borderColor": "#FBDBA7", + }, + Object {}, + undefined, + ] } testID="flw-default-button" > @@ -60,19 +67,24 @@ exports[` renders primary button correctly 1`] = ` onResponderTerminationRequest={[Function]} onStartShouldSetResponder={[Function]} style={ - Object { - "alignItems": "center", - "backgroundColor": "#f5a623", - "borderColor": "#f5a623", - "borderRadius": 6, - "borderWidth": 1, - "flexDirection": "row", - "height": 52, - "justifyContent": "center", - "minWidth": 100, - "overflow": "hidden", - "paddingHorizontal": 16, - } + Array [ + Object { + "alignItems": "center", + "backgroundColor": "#f5a623", + "borderColor": "#f5a623", + "borderRadius": 6, + "borderWidth": 1, + "flexDirection": "row", + "height": 52, + "justifyContent": "center", + "minWidth": 100, + "overflow": "hidden", + "paddingHorizontal": 16, + }, + Object {}, + Object {}, + undefined, + ] } testID="flw-default-button" > @@ -95,19 +107,26 @@ exports[` renders with align left style 1`] = ` onResponderTerminationRequest={[Function]} onStartShouldSetResponder={[Function]} style={ - Object { - "alignItems": "center", - "backgroundColor": "#f5a623", - "borderColor": "#f5a623", - "borderRadius": 6, - "borderWidth": 1, - "flexDirection": "row", - "height": 52, - "justifyContent": "flex-start", - "minWidth": 100, - "overflow": "hidden", - "paddingHorizontal": 16, - } + Array [ + Object { + "alignItems": "center", + "backgroundColor": "#f5a623", + "borderColor": "#f5a623", + "borderRadius": 6, + "borderWidth": 1, + "flexDirection": "row", + "height": 52, + "justifyContent": "center", + "minWidth": 100, + "overflow": "hidden", + "paddingHorizontal": 16, + }, + Object {}, + Object { + "justifyContent": "flex-start", + }, + undefined, + ] } testID="flw-default-button" > From 18cf3e858f8472cd547ae770748c42f13a8e1aa7 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 14:55:56 +0100 Subject: [PATCH 055/129] refactor(v3): move v3 specific modules to v3 folder and user v3 as default --- src/FlutterwaveButton.tsx | 553 ++--------------------------------- src/FlutterwaveInit.ts | 103 +------ src/utils/ResponseParser.ts | 2 +- src/v3/FlutterwaveButton.tsx | 513 ++++++++++++++++++++++++++++++++ src/v3/FlutterwaveInit.ts | 95 ++++++ src/v3/index.ts | 9 + 6 files changed, 652 insertions(+), 623 deletions(-) create mode 100644 src/v3/FlutterwaveButton.tsx create mode 100644 src/v3/FlutterwaveInit.ts create mode 100644 src/v3/index.ts diff --git a/src/FlutterwaveButton.tsx b/src/FlutterwaveButton.tsx index 3a31f0c..f5ef2d9 100644 --- a/src/FlutterwaveButton.tsx +++ b/src/FlutterwaveButton.tsx @@ -1,541 +1,48 @@ -import React from 'react'; -import { - StyleSheet, - Modal, - View, - Animated, - TouchableWithoutFeedback, - Text, - ViewStyle, - Alert, - Image, - Platform, - Dimensions, - Easing, -} from 'react-native'; -import WebView from 'react-native-webview'; +import {ViewStyle, StyleProp} from 'react-native'; import PropTypes from 'prop-types'; -import {WebViewNavigation} from 'react-native-webview/lib/WebViewTypes'; -import FlutterwaveInit, {FlutterwaveInitOptions} from './FlutterwaveInit'; -import {colors, REDIRECT_URL} from './configs'; -import {PaymentOptionsPropRule} from './utils/CustomPropTypesRules'; -import DefaultButton from './DefaultButton'; +import FlutterwaveButton from './v3/FlutterwaveButton'; import FlutterwaveInitError from './utils/FlutterwaveInitError'; -const loader = require('./loader.gif'); -const pryContent = require('./pry-button-content.png'); -const contentWidthPercentage = 0.6549707602; -const contentSizeDimension = 8.2962962963; -const contentMaxWidth = 187.3; -const contentMaxHeight = contentMaxWidth / contentSizeDimension; -const contentMinWidth = 187.3; -const contentMinHeight = contentMinWidth / contentSizeDimension; -const borderRadiusDimension = 24 / 896; -const windowHeight = Dimensions.get('window').height; -interface CustomButtonProps { - disabled: boolean; - isInitializing: boolean; - onPress: () => void; -} - -interface RedirectParams { +export interface RedirectParams { status: 'successful' | 'cancelled', transaction_id?: string; tx_ref: string; } -export interface FlutterwaveButtonProps { - style?: ViewStyle; - onComplete: (data: RedirectParams) => void; +export interface RedirectParamsV2 { + canceled: 'true' | 'false'; + flwref?: string; + txref?: string; +} + +export interface CustomButtonProps { + disabled: boolean; + isInitializing: boolean; + onPress: () => void; +} + +export interface FlutterwaveButtonPropsBase { + style?: StyleProp; + onComplete: (data: any) => void; onWillInitialize?: () => void; onDidInitialize?: () => void; onInitializeError?: (error: FlutterwaveInitError) => void; onAbort?: () => void; - options: Omit; customButton?: (params: CustomButtonProps) => React.ReactNode; alignLeft?: 'alignLeft' | boolean; } -interface FlutterwaveButtonState { - link: string | null; - isPending: boolean; - showDialog: boolean; - animation: Animated.Value; - tx_ref: string | null; - resetLink: boolean; - buttonSize: { - width: number; - height: number; - }; -} - -class FlutterwaveButton extends React.Component< - FlutterwaveButtonProps, - FlutterwaveButtonState -> { - static propTypes = { - alignLeft: PropTypes.bool, - onAbort: PropTypes.func, - onComplete: PropTypes.func.isRequired, - onWillInitialize: PropTypes.func, - onDidInitialize: PropTypes.func, - onInitializeError: PropTypes.func, - options: PropTypes.shape({ - authorization: PropTypes.string.isRequired, - tx_ref: PropTypes.string.isRequired, - amount: PropTypes.number.isRequired, - currency: PropTypes.oneOf(['NGN', 'USD', 'GBP', 'GHS', 'KES', 'ZAR', 'TZS']).isRequired, - integrity_hash: PropTypes.string, - payment_options: PaymentOptionsPropRule, - payment_plan: PropTypes.number, - customer: PropTypes.shape({ - name: PropTypes.string, - phonenumber: PropTypes.string, - email: PropTypes.string.isRequired, - }).isRequired, - subaccounts: PropTypes.arrayOf(PropTypes.shape({ - id: PropTypes.string.isRequired, - transaction_split_ratio: PropTypes.number, - transaction_charge_type: PropTypes.string, - transaction_charge: PropTypes.number, - })), - meta: PropTypes.arrayOf(PropTypes.object), - customizations: PropTypes.shape({ - title: PropTypes.string, - logo: PropTypes.string, - description: PropTypes.string, - }), - }).isRequired, - customButton: PropTypes.func, - }; - - state: FlutterwaveButtonState = { - isPending: false, - link: null, - resetLink: false, - showDialog: false, - animation: new Animated.Value(0), - tx_ref: null, - buttonSize: { - width: 0, - height: 0, - }, - }; - - webviewRef: WebView | null = null; - - abortController?: AbortController; - - componentDidUpdate(prevProps: FlutterwaveButtonProps) { - if (JSON.stringify(prevProps.options) !== JSON.stringify(this.props.options)) { - this.handleOptionsChanged() - } - } - - componentWillUnmount() { - if (this.abortController) { - this.abortController.abort(); - } - } - - reset = () => { - if (this.abortController) { - this.abortController.abort(); - } - // reset the necessaries - this.setState(({resetLink, link}) => ({ - isPending: false, - link: resetLink ? null : link, - resetLink: false, - showDialog: false, - })); - }; - - handleOptionsChanged = () => { - const {showDialog, link} = this.state; - if (!link) { - return; - } - if (!showDialog) { - return this.setState({ - link: null, - tx_ref: null, - }) - } - this.setState({resetLink: true}) - } - - handleNavigationStateChange = (ev: WebViewNavigation) => { - // cregex to check if redirect has occured on completion/cancel - const rx = /\/flutterwave\.com\/rn-redirect/; - // Don't end payment if not redirected back - if (!rx.test(ev.url)) { - return - } - // fire handle complete - this.handleComplete(this.getRedirectParams(ev.url)); - }; - - handleComplete(data: RedirectParams) { - const {onComplete} = this.props; - // reset payment link - this.setState(({resetLink, tx_ref}) => ({ - tx_ref: data.status === 'successful' ? null : tx_ref, - resetLink: data.status === 'successful' ? true : resetLink - }), - () => { - // reset - this.dismiss(); - // fire onComplete handler - onComplete(data); - } - ); - } - - handleReload = () => { - // fire if webview is set - if (this.webviewRef) { - this.webviewRef.reload(); - } - }; - - handleAbortConfirm = () => { - const {onAbort} = this.props; - // abort action - if (onAbort) { - onAbort(); - } - // remove tx_ref and dismiss - this.dismiss(); - }; - - handleAbort = () => { - Alert.alert('', 'Are you sure you want to cancel this payment?', [ - {text: 'No'}, - { - text: 'Yes, Cancel', - style: 'destructive', - onPress: this.handleAbortConfirm, - }, - ]); - }; - - handleButtonResize = (size: {width: number; height: number}) => { - const {buttonSize} = this.state; - if (JSON.stringify(buttonSize) !== JSON.stringify(size)) { - this.setState({buttonSize: size}); - } - }; - - getRedirectParams = (url: string): RedirectParams => { - // initialize result container - const res: any = {}; - // if url has params - if (url.split('?').length > 1) { - // get query params in an array - const params = url.split('?')[1].split('&'); - // add url params to result - for (let i = 0; i < params.length; i++) { - const param: Array = params[i].split('='); - const val = decodeURIComponent(param[1]).trim(); - res[param[0]] = String(val); - } - } - // return result - return res; - }; - - show = () => { - const {animation} = this.state; - this.setState({showDialog: true}, () => { - Animated.timing(animation, { - toValue: 1, - duration: 700, - easing: Easing.in(Easing.elastic(0.72)), - useNativeDriver: false, - }).start(); - }); - }; - - dismiss = () => { - const {animation} = this.state; - Animated.timing(animation, { - toValue: 0, - duration: 400, - useNativeDriver: false, - }).start(this.reset); - }; - - handleInit = () => { - const {options, onWillInitialize, onInitializeError, onDidInitialize} = this.props; - const {isPending, tx_ref, link} = this.state; - - // just show the dialod if the link is already set - if (link) { - return this.show(); - } - - // throw error if transaction reference has not changed - if (tx_ref === options.tx_ref) { - return onInitializeError ? onInitializeError(new FlutterwaveInitError({ - message: 'Please generate a new transaction reference.', - code: 'SAME_TXREF', - })) : null; - } - - // stop if currently in pending mode - if (isPending) { - return; - } - - // initialize abort controller if not set - this.abortController = new AbortController; - - // fire will initialize handler if available - if (onWillInitialize) { - onWillInitialize(); - } - - // @ts-ignore - // delete redirect url if set - delete options.redirect_url; - - // set pending state to true - this.setState( - { - isPending: true, - link: null, - tx_ref: options.tx_ref, - }, - async () => { - try { - // initialis payment - const link = await FlutterwaveInit( - {...options, redirect_url: REDIRECT_URL}, - this.abortController - ); - // resent pending mode - this.setState({link, isPending: false}, this.show); - // fire did initialize handler if available - if (onDidInitialize) { - onDidInitialize(); - } - } catch (error) { - // stop if request was canceled - if (/aborterror/i.test(error.code)) { - return; - } - if (onInitializeError) { - onInitializeError(error); - } - return this.setState({ - resetLink: true, - tx_ref: null - }, this.dismiss); - } - }, - ); - }; - - render() { - const {link, animation, showDialog} = this.state; - const marginTop = animation.interpolate({ - inputRange: [0, 1], - outputRange: [windowHeight, Platform.OS === 'ios' ? 46 : 14], - }); - const opacity = animation.interpolate({ - inputRange: [0, 0.3, 1], - outputRange: [0, 1, 1], - }); - // render UI - return ( - <> - {this.renderButton()} - - {this.renderBackdrop()} - - (this.webviewRef = ref)} - source={{uri: link || ''}} - style={styles.webview} - startInLoadingState={true} - scalesPageToFit={true} - javaScriptEnabled={true} - onNavigationStateChange={this.handleNavigationStateChange} - renderError={this.renderError} - renderLoading={this.renderLoading} - /> - - - - ); - } - - renderButton() { - const {customButton, style, alignLeft} = this.props; - const {isPending, link, showDialog, buttonSize} = this.state; - const contentWidth = buttonSize.width * contentWidthPercentage; - const contentHeight = contentWidth / contentSizeDimension; - const contentSizeStyle = { - width: - contentWidth > contentMaxWidth - ? contentMaxWidth - : contentWidth < contentMinWidth - ? contentMinWidth - : contentWidth, - height: - contentHeight > contentMaxHeight - ? contentMaxHeight - : contentHeight < contentMinHeight - ? contentMinHeight - : contentHeight, - }; - // render custom button - if (customButton) { - return customButton({ - isInitializing: isPending && !link ? true : false, - disabled: isPending || showDialog? true : false, - onPress: this.handleInit, - }); - } - // render primary button - return ( - - - - ); - } - - renderBackdrop() { - const {animation} = this.state; - // Interpolation backdrop animation - const backgroundColor = animation.interpolate({ - inputRange: [0, 0.3, 1], - outputRange: [colors.transparent, colors.transparent, 'rgba(0,0,0,0.5)'], - }); - return ( - - - - ); - } - - renderLoading() { - return ( - - - - ); - } - - renderError = () => { - const {link} = this.state; - return ( - - {link ? ( - <> - - The page failed to load, please try again. - - - - Try Again - - - - ) : null} - - ); - }; -} - -const styles = StyleSheet.create({ - promtActions: { - flexDirection: 'row', - alignItems: 'center', - }, - promptActionText: { - textAlign: 'center', - color: colors.primary, - fontSize: 16, - paddingHorizontal: 16, - paddingVertical: 16, - }, - promptQuestion: { - color: colors.secondary, - textAlign: 'center', - marginBottom: 32, - fontSize: 18, - }, - prompt: { - position: 'absolute', - left: 0, - right: 0, - bottom: 0, - top: 0, - backgroundColor: '#ffffff', - justifyContent: 'center', - alignItems: 'center', - paddingHorizontal: 56, - }, - backdrop: { - position: 'absolute', - left: 0, - right: 0, - bottom: 0, - top: 0, - }, - loadingImage: { - width: 64, - height: 64, - resizeMode: 'contain', - }, - loading: { - position: 'absolute', - top: 0, - right: 0, - bottom: 0, - left: 0, - backgroundColor: 'rgba(255, 255, 255, 0.3)', - justifyContent: 'center', - alignItems: 'center', - }, - webviewContainer: { - top: 50, - flex: 1, - backgroundColor: '#efefef', - paddingBottom: 50, - overflow: 'hidden', - borderTopLeftRadius: windowHeight * borderRadiusDimension, - borderTopRightRadius: windowHeight * borderRadiusDimension, - }, - webview: { - flex: 1, - backgroundColor: 'rgba(0,0,0,0)', - }, - buttonContent: { - resizeMode: 'contain', - }, -}); +export const OptionsPropTypeBase = { + amount: PropTypes.number.isRequired, + currency: PropTypes.oneOf(['NGN', 'USD', 'GHS', 'KES', 'ZAR', 'TZS']), + payment_plan: PropTypes.number, + subaccounts: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string.isRequired, + transaction_split_ratio: PropTypes.number, + transaction_charge_type: PropTypes.string, + transaction_charge: PropTypes.number, + })), + integrity_hash: PropTypes.string, +}; export default FlutterwaveButton; diff --git a/src/FlutterwaveInit.ts b/src/FlutterwaveInit.ts index e263b48..2f0c4eb 100644 --- a/src/FlutterwaveInit.ts +++ b/src/FlutterwaveInit.ts @@ -1,29 +1,4 @@ -import {STANDARD_URL} from './configs'; -import ResponseParser from './utils/ResponseParser'; -import FlutterwaveInitError from './utils/FlutterwaveInitError'; - -interface FlutterwavePaymentMeta { - [k: string]: any; -} - -export interface FlutterwaveInitCustomer { - email: string; - phonenumber?: string; - name?: string; -} - -export interface FlutterwaveInitCustomizations { - title?: string; - logo?: string; - description?: string; -} - -export interface FlutterwaveInitSubAccount { - id: string; - transaction_split_ratio?: number; - transaction_charge_type?: string; - transaction_charge?: number; -} +import FlutterwaveInit from './v3/FlutterwaveInit'; export interface FlutterwaveInitSubAccount { id: string; @@ -32,84 +7,14 @@ export interface FlutterwaveInitSubAccount { transaction_charge?: number; } -export interface FlutterwaveInitOptions { - authorization: string; - tx_ref: string; +export interface FlutterwaveInitOptionsBase { amount: number; - currency: string; + currency?: string; integrity_hash?: string; payment_options?: string; payment_plan?: number; redirect_url: string; - customer: FlutterwaveInitCustomer; subaccounts?: Array; - meta?: Array; - customizations?: FlutterwaveInitCustomizations; -} - -export interface FieldError { - field: string; - message: string; } -export interface ResponseData { - status?: 'success' | 'error'; - message: string; - error_id?: string; - errors?: Array; - code?: string; - data?: { - link: string; - }; -} - -interface FetchOptions { - method: 'POST'; - body: string; - headers: Headers; - signal?: AbortSignal; -} - -/** - * This function is responsible for making the request to - * initialize a Flutterwave payment. - * @param options FlutterwaveInitOptions - * @param abortController AbortController - * @return Promise - */ -export default async function FlutterwaveInit( - options: FlutterwaveInitOptions, - abortController?: AbortController, -): Promise { - try { - // get request body and authorization - const {authorization, ...body} = options; - // make request headers - const headers = new Headers; - headers.append('Content-Type', 'application/json'); - headers.append('Authorization', `Bearer ${authorization}`); - // make fetch options - const fetchOptions: FetchOptions = { - method: 'POST', - body: JSON.stringify(body), - headers: headers, - } - // add abortController if defined - if (abortController) { - fetchOptions.signal = abortController.signal - }; - // initialize payment - const response = await fetch(STANDARD_URL, fetchOptions); - // get response data - const responseData: ResponseData = await response.json(); - // resolve with the payment link - return Promise.resolve(await ResponseParser(responseData)); - } catch (e) { - // always return a flutterwave init error - const error = e instanceof FlutterwaveInitError - ? e - : new FlutterwaveInitError({message: e.message, code: e.name.toUpperCase()}) - // resolve with error - return Promise.reject(error); - } -} +export default FlutterwaveInit; diff --git a/src/utils/ResponseParser.ts b/src/utils/ResponseParser.ts index 41ae26b..708438c 100644 --- a/src/utils/ResponseParser.ts +++ b/src/utils/ResponseParser.ts @@ -1,4 +1,4 @@ -import { ResponseData } from "../FlutterwaveInit"; +import {ResponseData} from "../v3/FlutterwaveInit"; import FlutterwaveInitError from "./FlutterwaveInitError"; /** diff --git a/src/v3/FlutterwaveButton.tsx b/src/v3/FlutterwaveButton.tsx new file mode 100644 index 0000000..ebf2a87 --- /dev/null +++ b/src/v3/FlutterwaveButton.tsx @@ -0,0 +1,513 @@ +import React from 'react'; +import { + StyleSheet, + Modal, + View, + Animated, + TouchableWithoutFeedback, + Text, + Alert, + Image, + Platform, + Dimensions, + Easing, +} from 'react-native'; +import WebView from 'react-native-webview'; +import PropTypes from 'prop-types'; +import {WebViewNavigation} from 'react-native-webview/lib/WebViewTypes'; +import {FlutterwaveButtonPropsBase, RedirectParams, OptionsPropTypeBase} from '../FlutterwaveButton'; +import FlutterwaveInit, {FlutterwaveInitOptions} from './FlutterwaveInit'; +import DefaultButton from '../DefaultButton'; +import {colors, REDIRECT_URL, PAYMENT_OPTIONS} from '../configs'; +import {PaymentOptionsPropRule} from '../utils/CustomPropTypesRules'; +import FlutterwaveInitError from '../utils/FlutterwaveInitError'; +const loader = require('../assets/loader.gif'); +const pryContent = require('../assets/pry-button-content.png'); +const contentWidthPercentage = 0.6549707602; +const contentSizeDimension = 8.2962962963; +const contentMaxWidth = 187.3; +const contentMaxHeight = contentMaxWidth / contentSizeDimension; +const contentMinWidth = 187.3; +const contentMinHeight = contentMinWidth / contentSizeDimension; +const borderRadiusDimension = 24 / 896; +const windowHeight = Dimensions.get('window').height; + +export type FlutterwaveButtonProps = FlutterwaveButtonPropsBase & { + onComplete: (data: RedirectParams) => void; + options: Omit; +} + +interface FlutterwaveButtonState { + link: string | null; + isPending: boolean; + showDialog: boolean; + animation: Animated.Value; + tx_ref: string | null; + resetLink: boolean; + buttonSize: { + width: number; + height: number; + }; +} + +class FlutterwaveButton extends React.Component< + FlutterwaveButtonProps, + FlutterwaveButtonState +> { + static propTypes = { + alignLeft: PropTypes.bool, + onAbort: PropTypes.func, + onComplete: PropTypes.func.isRequired, + onWillInitialize: PropTypes.func, + onDidInitialize: PropTypes.func, + onInitializeError: PropTypes.func, + options: PropTypes.shape({ + ...OptionsPropTypeBase, + authorization: PropTypes.string.isRequired, + tx_ref: PropTypes.string.isRequired, + payment_options: PaymentOptionsPropRule(PAYMENT_OPTIONS), + customer: PropTypes.shape({ + name: PropTypes.string, + phonenumber: PropTypes.string, + email: PropTypes.string.isRequired, + }).isRequired, + meta: PropTypes.arrayOf(PropTypes.object), + customizations: PropTypes.shape({ + title: PropTypes.string, + logo: PropTypes.string, + description: PropTypes.string, + }), + }).isRequired, + customButton: PropTypes.func, + }; + + state: FlutterwaveButtonState = { + isPending: false, + link: null, + resetLink: false, + showDialog: false, + animation: new Animated.Value(0), + tx_ref: null, + buttonSize: { + width: 0, + height: 0, + }, + }; + + webviewRef: WebView | null = null; + + abortController?: AbortController; + + componentDidUpdate(prevProps: FlutterwaveButtonProps) { + if (JSON.stringify(prevProps.options) !== JSON.stringify(this.props.options)) { + this.handleOptionsChanged() + } + } + + componentWillUnmount() { + if (this.abortController) { + this.abortController.abort(); + } + } + + reset = () => { + if (this.abortController) { + this.abortController.abort(); + } + // reset the necessaries + this.setState(({resetLink, link}) => ({ + isPending: false, + link: resetLink ? null : link, + resetLink: false, + showDialog: false, + })); + }; + + handleOptionsChanged = () => { + const {showDialog, link} = this.state; + if (!link) { + return; + } + if (!showDialog) { + return this.setState({ + link: null, + tx_ref: null, + }) + } + this.setState({resetLink: true}) + } + + handleNavigationStateChange = (ev: WebViewNavigation) => { + // cregex to check if redirect has occured on completion/cancel + const rx = /\/flutterwave\.com\/rn-redirect/; + // Don't end payment if not redirected back + if (!rx.test(ev.url)) { + return + } + // fire handle complete + this.handleComplete(this.getRedirectParams(ev.url)); + }; + + handleComplete(data: RedirectParams) { + const {onComplete} = this.props; + // reset payment link + this.setState(({resetLink, tx_ref}) => ({ + tx_ref: data.status === 'successful' ? null : tx_ref, + resetLink: data.status === 'successful' ? true : resetLink + }), + () => { + // reset + this.dismiss(); + // fire onComplete handler + onComplete(data); + } + ); + } + + handleReload = () => { + // fire if webview is set + if (this.webviewRef) { + this.webviewRef.reload(); + } + }; + + handleAbortConfirm = () => { + const {onAbort} = this.props; + // abort action + if (onAbort) { + onAbort(); + } + // remove tx_ref and dismiss + this.dismiss(); + }; + + handleAbort = () => { + Alert.alert('', 'Are you sure you want to cancel this payment?', [ + {text: 'No'}, + { + text: 'Yes, Cancel', + style: 'destructive', + onPress: this.handleAbortConfirm, + }, + ]); + }; + + handleButtonResize = (size: {width: number; height: number}) => { + const {buttonSize} = this.state; + if (JSON.stringify(buttonSize) !== JSON.stringify(size)) { + this.setState({buttonSize: size}); + } + }; + + getRedirectParams = (url: string): RedirectParams => { + // initialize result container + const res: any = {}; + // if url has params + if (url.split('?').length > 1) { + // get query params in an array + const params = url.split('?')[1].split('&'); + // add url params to result + for (let i = 0; i < params.length; i++) { + const param: Array = params[i].split('='); + const val = decodeURIComponent(param[1]).trim(); + res[param[0]] = String(val); + } + } + // return result + return res; + }; + + show = () => { + const {animation} = this.state; + this.setState({showDialog: true}, () => { + Animated.timing(animation, { + toValue: 1, + duration: 700, + easing: Easing.in(Easing.elastic(0.72)), + useNativeDriver: false, + }).start(); + }); + }; + + dismiss = () => { + const {animation} = this.state; + Animated.timing(animation, { + toValue: 0, + duration: 400, + useNativeDriver: false, + }).start(this.reset); + }; + + handleInit = () => { + const {options, onWillInitialize, onInitializeError, onDidInitialize} = this.props; + const {isPending, tx_ref, link} = this.state; + + // just show the dialod if the link is already set + if (link) { + return this.show(); + } + + // throw error if transaction reference has not changed + if (tx_ref === options.tx_ref) { + return onInitializeError ? onInitializeError(new FlutterwaveInitError({ + message: 'Please generate a new transaction reference.', + code: 'SAME_TXREF', + })) : null; + } + + // stop if currently in pending mode + if (isPending) { + return; + } + + // initialize abort controller if not set + this.abortController = new AbortController; + + // fire will initialize handler if available + if (onWillInitialize) { + onWillInitialize(); + } + + // @ts-ignore + // delete redirect url if set + delete options.redirect_url; + + // set pending state to true + this.setState( + { + isPending: true, + link: null, + tx_ref: options.tx_ref, + }, + async () => { + try { + // initialis payment + const link = await FlutterwaveInit( + {...options, redirect_url: REDIRECT_URL}, + this.abortController + ); + // resent pending mode + this.setState({link, isPending: false}, this.show); + // fire did initialize handler if available + if (onDidInitialize) { + onDidInitialize(); + } + } catch (error) { + // stop if request was canceled + if (/aborterror/i.test(error.code)) { + return; + } + if (onInitializeError) { + onInitializeError(error); + } + return this.setState({ + resetLink: true, + tx_ref: null + }, this.dismiss); + } + }, + ); + }; + + render() { + const {link, animation, showDialog} = this.state; + const marginTop = animation.interpolate({ + inputRange: [0, 1], + outputRange: [windowHeight, Platform.OS === 'ios' ? 46 : 14], + }); + const opacity = animation.interpolate({ + inputRange: [0, 0.3, 1], + outputRange: [0, 1, 1], + }); + // render UI + return ( + <> + {this.renderButton()} + + {this.renderBackdrop()} + + (this.webviewRef = ref)} + source={{uri: link || ''}} + style={styles.webview} + startInLoadingState={true} + scalesPageToFit={true} + javaScriptEnabled={true} + onNavigationStateChange={this.handleNavigationStateChange} + renderError={this.renderError} + renderLoading={this.renderLoading} + /> + + + + ); + } + + renderButton() { + const {customButton, style, alignLeft} = this.props; + const {isPending, link, showDialog, buttonSize} = this.state; + const contentWidth = buttonSize.width * contentWidthPercentage; + const contentHeight = contentWidth / contentSizeDimension; + const contentSizeStyle = { + width: + contentWidth > contentMaxWidth + ? contentMaxWidth + : contentWidth < contentMinWidth + ? contentMinWidth + : contentWidth, + height: + contentHeight > contentMaxHeight + ? contentMaxHeight + : contentHeight < contentMinHeight + ? contentMinHeight + : contentHeight, + }; + // render custom button + if (customButton) { + return customButton({ + isInitializing: isPending && !link ? true : false, + disabled: isPending || showDialog? true : false, + onPress: this.handleInit, + }); + } + // render primary button + return ( + + + + ); + } + + renderBackdrop() { + const {animation} = this.state; + // Interpolation backdrop animation + const backgroundColor = animation.interpolate({ + inputRange: [0, 0.3, 1], + outputRange: [colors.transparent, colors.transparent, 'rgba(0,0,0,0.5)'], + }); + return ( + + + + ); + } + + renderLoading() { + return ( + + + + ); + } + + renderError = () => { + const {link} = this.state; + return ( + + {link ? ( + <> + + The page failed to load, please try again. + + + + Try Again + + + + ) : null} + + ); + }; +} + +const styles = StyleSheet.create({ + promtActions: { + flexDirection: 'row', + alignItems: 'center', + }, + promptActionText: { + textAlign: 'center', + color: colors.primary, + fontSize: 16, + paddingHorizontal: 16, + paddingVertical: 16, + }, + promptQuestion: { + color: colors.secondary, + textAlign: 'center', + marginBottom: 32, + fontSize: 18, + }, + prompt: { + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + top: 0, + backgroundColor: '#ffffff', + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: 56, + }, + backdrop: { + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + top: 0, + }, + loadingImage: { + width: 64, + height: 64, + resizeMode: 'contain', + }, + loading: { + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 0, + backgroundColor: 'rgba(255, 255, 255, 0.3)', + justifyContent: 'center', + alignItems: 'center', + }, + webviewContainer: { + top: 50, + flex: 1, + backgroundColor: '#efefef', + paddingBottom: 50, + overflow: 'hidden', + borderTopLeftRadius: windowHeight * borderRadiusDimension, + borderTopRightRadius: windowHeight * borderRadiusDimension, + }, + webview: { + flex: 1, + backgroundColor: 'rgba(0,0,0,0)', + }, + buttonContent: { + resizeMode: 'contain', + }, +}); + +export default FlutterwaveButton; diff --git a/src/v3/FlutterwaveInit.ts b/src/v3/FlutterwaveInit.ts new file mode 100644 index 0000000..7b5d86a --- /dev/null +++ b/src/v3/FlutterwaveInit.ts @@ -0,0 +1,95 @@ +import {STANDARD_URL} from '../configs'; +import ResponseParser from '../utils/ResponseParser'; +import FlutterwaveInitError from '../utils/FlutterwaveInitError'; +import {FlutterwaveInitOptionsBase} from '../FlutterwaveInit'; + +interface FlutterwavePaymentMeta { + [k: string]: any; +} + +export interface FlutterwaveInitCustomer { + email: string; + phonenumber?: string; + name?: string; +} + +export interface FlutterwaveInitCustomizations { + title?: string; + logo?: string; + description?: string; +} + +export type FlutterwaveInitOptions = FlutterwaveInitOptionsBase & { + authorization: string; + tx_ref: string; + customer: FlutterwaveInitCustomer; + meta?: Array; + customizations?: FlutterwaveInitCustomizations; +}; + +export interface FieldError { + field: string; + message: string; +} + +export interface ResponseData { + status?: 'success' | 'error'; + message: string; + error_id?: string; + errors?: Array; + code?: string; + data?: { + link: string; + }; +} + +interface FetchOptions { + method: 'POST'; + body: string; + headers: Headers; + signal?: AbortSignal; +} + +/** + * This function is responsible for making the request to + * initialize a Flutterwave payment. + * @param options FlutterwaveInitOptions + * @param abortController AbortController + * @return Promise + */ +export default async function FlutterwaveInit( + options: FlutterwaveInitOptions, + abortController?: AbortController, +): Promise { + try { + // get request body and authorization + const {authorization, ...body} = options; + // make request headers + const headers = new Headers; + headers.append('Content-Type', 'application/json'); + headers.append('Authorization', `Bearer ${authorization}`); + // make fetch options + const fetchOptions: FetchOptions = { + method: 'POST', + body: JSON.stringify(body), + headers: headers, + } + // add abortController if defined + if (abortController) { + fetchOptions.signal = abortController.signal + }; + // initialize payment + const response = await fetch(STANDARD_URL, fetchOptions); + // get response data + const responseData: ResponseData = await response.json(); + // resolve with the payment link + return Promise.resolve(await ResponseParser(responseData)); + } catch (e) { + // always return a flutterwave init error + const error = e instanceof FlutterwaveInitError + ? e + : new FlutterwaveInitError({message: e.message, code: e.name.toUpperCase()}) + // resolve with error + return Promise.reject(error); + } +} diff --git a/src/v3/index.ts b/src/v3/index.ts new file mode 100644 index 0000000..1af517d --- /dev/null +++ b/src/v3/index.ts @@ -0,0 +1,9 @@ +import FlutterwaveInit from './FlutterwaveInit'; +import FlutterwaveButton from './FlutterwaveButton'; +import DefaultButton from '../DefaultButton'; + +// export modules +export {FlutterwaveInit, FlutterwaveButton, DefaultButton}; + +// export button as default +export default FlutterwaveButton; From ae58d8c9278c6973acd56d67383b27961ff6288a Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 14:57:56 +0100 Subject: [PATCH 056/129] test(v3): move v3 tests and snapshot to v3 tests subfolder --- __tests__/{ => v3}/FlutterwaveButton.spec.tsx | 10 +- __tests__/{ => v3}/FlutterwaveInit.spec.ts | 6 +- .../FlutterwaveButton.spec.tsx.snap | 111 ++++++++++-------- 3 files changed, 72 insertions(+), 55 deletions(-) rename __tests__/{ => v3}/FlutterwaveButton.spec.tsx (98%) rename __tests__/{ => v3}/FlutterwaveInit.spec.ts (96%) rename __tests__/{ => v3}/__snapshots__/FlutterwaveButton.spec.tsx.snap (90%) diff --git a/__tests__/FlutterwaveButton.spec.tsx b/__tests__/v3/FlutterwaveButton.spec.tsx similarity index 98% rename from __tests__/FlutterwaveButton.spec.tsx rename to __tests__/v3/FlutterwaveButton.spec.tsx index 48b5e4d..47c8ec6 100644 --- a/__tests__/FlutterwaveButton.spec.tsx +++ b/__tests__/v3/FlutterwaveButton.spec.tsx @@ -2,12 +2,12 @@ import 'react-native'; import React from 'react'; import {TouchableWithoutFeedback, Text, Alert} from 'react-native'; import renderer from 'react-test-renderer'; -import FlutterwaveButton from '../src/FlutterwaveButton'; -import {FlutterwaveInitOptions} from '../src/FlutterwaveInit'; -import {STANDARD_URL, REDIRECT_URL} from '../src/configs'; +import FlutterwaveButton from '../../src/v3/FlutterwaveButton'; +import {FlutterwaveInitOptions} from '../../src/v3/FlutterwaveInit'; +import {STANDARD_URL, REDIRECT_URL} from '../../src/configs'; import WebView from 'react-native-webview'; -import DefaultButton from '../src/DefaultButton'; -import FlutterwaveInitError from '../src/utils/FlutterwaveInitError'; +import DefaultButton from '../../src/DefaultButton'; +import FlutterwaveInitError from '../../src/utils/FlutterwaveInitError'; const BtnTestID = 'flw-default-button'; const SuccessResponse = { status: 'success', diff --git a/__tests__/FlutterwaveInit.spec.ts b/__tests__/v3/FlutterwaveInit.spec.ts similarity index 96% rename from __tests__/FlutterwaveInit.spec.ts rename to __tests__/v3/FlutterwaveInit.spec.ts index b9099fe..959fd30 100644 --- a/__tests__/FlutterwaveInit.spec.ts +++ b/__tests__/v3/FlutterwaveInit.spec.ts @@ -1,6 +1,6 @@ -import FlutterwaveInit, {FlutterwaveInitOptions} from '../src/FlutterwaveInit'; -import {STANDARD_URL} from '../src/configs'; -import FlutterwaveInitError from '../src/utils/FlutterwaveInitError'; +import FlutterwaveInit, {FlutterwaveInitOptions} from '../../src/v3/FlutterwaveInit'; +import {STANDARD_URL} from '../../src/configs'; +import FlutterwaveInitError from '../../src/utils/FlutterwaveInitError'; const AUTHORIZATION = '[AUTHORIZATION]'; diff --git a/__tests__/__snapshots__/FlutterwaveButton.spec.tsx.snap b/__tests__/v3/__snapshots__/FlutterwaveButton.spec.tsx.snap similarity index 90% rename from __tests__/__snapshots__/FlutterwaveButton.spec.tsx.snap rename to __tests__/v3/__snapshots__/FlutterwaveButton.spec.tsx.snap index 4669d39..1f5306f 100644 --- a/__tests__/__snapshots__/FlutterwaveButton.spec.tsx.snap +++ b/__tests__/v3/__snapshots__/FlutterwaveButton.spec.tsx.snap @@ -32,19 +32,26 @@ Array [ onResponderTerminationRequest={[Function]} onStartShouldSetResponder={[Function]} style={ - Object { - "alignItems": "center", - "backgroundColor": "#f5a623", - "borderColor": "#FBDBA7", - "borderRadius": 6, - "borderWidth": 1, - "flexDirection": "row", - "height": 52, - "justifyContent": "center", - "minWidth": 100, - "overflow": "hidden", - "paddingHorizontal": 16, - } + Array [ + Object { + "alignItems": "center", + "backgroundColor": "#f5a623", + "borderColor": "#f5a623", + "borderRadius": 6, + "borderWidth": 1, + "flexDirection": "row", + "height": 52, + "justifyContent": "center", + "minWidth": 100, + "overflow": "hidden", + "paddingHorizontal": 16, + }, + Object { + "borderColor": "#FBDBA7", + }, + Object {}, + undefined, + ] } testID="flw-default-button" > @@ -54,7 +61,7 @@ Array [ resizeMode="contain" source={ Object { - "testUri": "../../../src/pry-button-content.png", + "testUri": "../../../src/assets/pry-button-content.png", } } style={ @@ -193,7 +200,7 @@ Array [ resizeMode="contain" source={ Object { - "testUri": "../../../src/loader.gif", + "testUri": "../../../src/assets/loader.gif", } } style={ @@ -225,19 +232,24 @@ Array [ onResponderTerminationRequest={[Function]} onStartShouldSetResponder={[Function]} style={ - Object { - "alignItems": "center", - "backgroundColor": "#f5a623", - "borderColor": "#f5a623", - "borderRadius": 6, - "borderWidth": 1, - "flexDirection": "row", - "height": 52, - "justifyContent": "center", - "minWidth": 100, - "overflow": "hidden", - "paddingHorizontal": 16, - } + Array [ + Object { + "alignItems": "center", + "backgroundColor": "#f5a623", + "borderColor": "#f5a623", + "borderRadius": 6, + "borderWidth": 1, + "flexDirection": "row", + "height": 52, + "justifyContent": "center", + "minWidth": 100, + "overflow": "hidden", + "paddingHorizontal": 16, + }, + Object {}, + Object {}, + undefined, + ] } testID="flw-default-button" > @@ -247,7 +259,7 @@ Array [ resizeMode="contain" source={ Object { - "testUri": "../../../src/pry-button-content.png", + "testUri": "../../../src/assets/pry-button-content.png", } } style={ @@ -374,7 +386,7 @@ Array [ resizeMode="contain" source={ Object { - "testUri": "../../../src/loader.gif", + "testUri": "../../../src/assets/loader.gif", } } style={ @@ -518,7 +530,7 @@ Array [ resizeMode="contain" source={ Object { - "testUri": "../../../src/loader.gif", + "testUri": "../../../src/assets/loader.gif", } } style={ @@ -550,19 +562,24 @@ Array [ onResponderTerminationRequest={[Function]} onStartShouldSetResponder={[Function]} style={ - Object { - "alignItems": "center", - "backgroundColor": "#f5a623", - "borderColor": "#f5a623", - "borderRadius": 6, - "borderWidth": 1, - "flexDirection": "row", - "height": 52, - "justifyContent": "center", - "minWidth": 100, - "overflow": "hidden", - "paddingHorizontal": 16, - } + Array [ + Object { + "alignItems": "center", + "backgroundColor": "#f5a623", + "borderColor": "#f5a623", + "borderRadius": 6, + "borderWidth": 1, + "flexDirection": "row", + "height": 52, + "justifyContent": "center", + "minWidth": 100, + "overflow": "hidden", + "paddingHorizontal": 16, + }, + Object {}, + Object {}, + undefined, + ] } testID="flw-default-button" > @@ -572,7 +589,7 @@ Array [ resizeMode="contain" source={ Object { - "testUri": "../../../src/pry-button-content.png", + "testUri": "../../../src/assets/pry-button-content.png", } } style={ @@ -699,7 +716,7 @@ Array [ resizeMode="contain" source={ Object { - "testUri": "../../../src/loader.gif", + "testUri": "../../../src/assets/loader.gif", } } style={ @@ -791,7 +808,7 @@ exports[` renders webview loading correctly 1`] = ` resizeMode="contain" source={ Object { - "testUri": "../../../src/loader.gif", + "testUri": "../../../src/assets/loader.gif", } } style={ From 2d1f62ab07d4f80bf3f47d97774bc880d1c70900 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 15:00:48 +0100 Subject: [PATCH 057/129] feat(v2/flutterwavebutton): allow users specify button content --- src/v2/FlutterwaveButton.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/v2/FlutterwaveButton.tsx b/src/v2/FlutterwaveButton.tsx index 532495d..43002c6 100644 --- a/src/v2/FlutterwaveButton.tsx +++ b/src/v2/FlutterwaveButton.tsx @@ -381,7 +381,7 @@ class FlutterwaveButton extends React.Component< } renderButton() { - const {customButton, style, alignLeft} = this.props; + const {customButton, style, alignLeft, children} = this.props; const {isPending, link, showDialog, buttonSize} = this.state; const contentWidth = buttonSize.width * contentWidthPercentage; const contentHeight = contentWidth / contentSizeDimension; @@ -416,13 +416,15 @@ class FlutterwaveButton extends React.Component< disabled={isPending || showDialog ? true : false} onPress={this.handleInit} onSizeChange={this.handleButtonResize}> - + {children ? children : ( + + )} ); } From dbf7aa46881782d2c918469623d23a78f96b62c7 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 15:03:01 +0100 Subject: [PATCH 058/129] refactor(v2/flutterwavebutton): reference base types from base flutterwave button module --- src/v2/FlutterwaveButton.tsx | 40 ++++-------------------------------- 1 file changed, 4 insertions(+), 36 deletions(-) diff --git a/src/v2/FlutterwaveButton.tsx b/src/v2/FlutterwaveButton.tsx index 43002c6..00caee3 100644 --- a/src/v2/FlutterwaveButton.tsx +++ b/src/v2/FlutterwaveButton.tsx @@ -6,7 +6,6 @@ import { Animated, TouchableWithoutFeedback, Text, - ViewStyle, Alert, Image, Platform, @@ -16,13 +15,8 @@ import { import WebView from 'react-native-webview'; import PropTypes from 'prop-types'; import {WebViewNavigation} from 'react-native-webview/lib/WebViewTypes'; -import FlutterwaveInit, { - FlutterwaveInitOptions, - FlutterwaveInitError, -} from './FlutterwaveInit'; -import {PAYMENT_OPTIONS} from './configs.v2'; -import {colors} from '../configs'; -import {PaymentOptionsPropRule} from '../utils/CustomPropTypesRules'; +import {FlutterwaveButtonPropsBase, RedirectParamsV2, OptionsPropTypeBase} from '../FlutterwaveButton'; +import FlutterwaveInit, {FlutterwaveInitOptions} from './FlutterwaveInit'; import DefaultButton from '../DefaultButton'; const loader = require('../loader.gif'); const pryContent = require('../pry-button-content.png'); @@ -35,35 +29,9 @@ const contentMinHeight = contentMinWidth / contentSizeDimension; const borderRadiusDimension = 24 / 896; const windowHeight = Dimensions.get('window').height; -interface CustomButtonProps { - disabled: boolean; - isInitializing: boolean; - onPress: () => void; -} - -interface OnCompleteData { - canceled: boolean; - flwref?: string; - txref: string; -} - -interface RedirectParams { - canceled: 'true' | 'false'; - flwref?: string; - txref?: string; - response?: string; -} - -export interface FlutterwaveButtonProps { - style?: ViewStyle; - onComplete: (data: OnCompleteData) => void; - onWillInitialize?: () => void; - onDidInitialize?: () => void; - onInitializeError?: (error: FlutterwaveInitError) => void; - onAbort?: () => void; +export type FlutterwaveButtonProps = FlutterwaveButtonPropsBase & { + onComplete: (data: RedirectParamsV2) => void; options: Omit; - customButton?: (params: CustomButtonProps) => React.ReactNode; - alignLeft?: 'alignLeft' | boolean; } interface FlutterwaveButtonState { From beda6f18d51d70d155c5b23c0904251d3acbcf8a Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 15:04:06 +0100 Subject: [PATCH 059/129] fix(v2/flutterwavebutton): fix require path for button content and loader --- src/v2/FlutterwaveButton.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/v2/FlutterwaveButton.tsx b/src/v2/FlutterwaveButton.tsx index 00caee3..105ba36 100644 --- a/src/v2/FlutterwaveButton.tsx +++ b/src/v2/FlutterwaveButton.tsx @@ -18,8 +18,8 @@ import {WebViewNavigation} from 'react-native-webview/lib/WebViewTypes'; import {FlutterwaveButtonPropsBase, RedirectParamsV2, OptionsPropTypeBase} from '../FlutterwaveButton'; import FlutterwaveInit, {FlutterwaveInitOptions} from './FlutterwaveInit'; import DefaultButton from '../DefaultButton'; -const loader = require('../loader.gif'); -const pryContent = require('../pry-button-content.png'); +const loader = require('../assets/loader.gif'); +const pryContent = require('../assets/pry-button-content.png'); const contentWidthPercentage = 0.6549707602; const contentSizeDimension = 8.2962962963; const contentMaxWidth = 187.3; From 05187bdd0894768451dd016178c0f84af04a901f Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 15:06:55 +0100 Subject: [PATCH 060/129] refactor(v2/flutterwavbutton): use base options proptype and add customer names and phone number --- src/v2/FlutterwaveButton.tsx | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/v2/FlutterwaveButton.tsx b/src/v2/FlutterwaveButton.tsx index 105ba36..0aebec5 100644 --- a/src/v2/FlutterwaveButton.tsx +++ b/src/v2/FlutterwaveButton.tsx @@ -18,6 +18,7 @@ import {WebViewNavigation} from 'react-native-webview/lib/WebViewTypes'; import {FlutterwaveButtonPropsBase, RedirectParamsV2, OptionsPropTypeBase} from '../FlutterwaveButton'; import FlutterwaveInit, {FlutterwaveInitOptions} from './FlutterwaveInit'; import DefaultButton from '../DefaultButton'; +import {PaymentOptionsPropRule} from '../utils/CustomPropTypesRules'; const loader = require('../assets/loader.gif'); const pryContent = require('../assets/pry-button-content.png'); const contentWidthPercentage = 0.6549707602; @@ -59,19 +60,14 @@ class FlutterwaveButton extends React.Component< onDidInitialize: PropTypes.func, onInitializeError: PropTypes.func, options: PropTypes.shape({ + ...OptionsPropTypeBase, + payment_options: PaymentOptionsPropRule(PAYMENT_OPTIONS_V2), txref: PropTypes.string.isRequired, PBFPubKey: PropTypes.string.isRequired, + customer_firstname: PropTypes.string, + customer_lastname: PropTypes.string, customer_email: PropTypes.string.isRequired, - amount: PropTypes.number.isRequired, - currency: PropTypes.oneOf(['NGN', 'USD', 'GHS', 'KES', 'ZAR', 'TZS']), - payment_options: PaymentOptionsPropRule(PAYMENT_OPTIONS), - payment_plan: PropTypes.number, - subaccounts: PropTypes.arrayOf(PropTypes.shape({ - id: PropTypes.string.isRequired, - transaction_split_ratio: PropTypes.number, - transaction_charge_type: PropTypes.string, - transaction_charge: PropTypes.number, - })), + customer_phone: PropTypes.string, country: PropTypes.string, pay_button_text: PropTypes.string, custom_title: PropTypes.string, From 9248b3f195cc08135d97287b92e20e0551487b9f Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 15:07:56 +0100 Subject: [PATCH 061/129] fix(v2/flutterwavebutton): add missing dependencies --- src/v2/FlutterwaveButton.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/v2/FlutterwaveButton.tsx b/src/v2/FlutterwaveButton.tsx index 0aebec5..323521d 100644 --- a/src/v2/FlutterwaveButton.tsx +++ b/src/v2/FlutterwaveButton.tsx @@ -18,6 +18,7 @@ import {WebViewNavigation} from 'react-native-webview/lib/WebViewTypes'; import {FlutterwaveButtonPropsBase, RedirectParamsV2, OptionsPropTypeBase} from '../FlutterwaveButton'; import FlutterwaveInit, {FlutterwaveInitOptions} from './FlutterwaveInit'; import DefaultButton from '../DefaultButton'; +import {PAYMENT_OPTIONS_V2, colors, REDIRECT_URL} from '../configs'; import {PaymentOptionsPropRule} from '../utils/CustomPropTypesRules'; const loader = require('../assets/loader.gif'); const pryContent = require('../assets/pry-button-content.png'); From 6bd1f5383ea79d52b7efea74b7f981868bc279b8 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 15:09:55 +0100 Subject: [PATCH 062/129] refactor(v2/flutterwavebutton): user FlutterwaveInitError as object for init error handler --- src/v2/FlutterwaveButton.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/v2/FlutterwaveButton.tsx b/src/v2/FlutterwaveButton.tsx index 323521d..9187896 100644 --- a/src/v2/FlutterwaveButton.tsx +++ b/src/v2/FlutterwaveButton.tsx @@ -20,6 +20,7 @@ import FlutterwaveInit, {FlutterwaveInitOptions} from './FlutterwaveInit'; import DefaultButton from '../DefaultButton'; import {PAYMENT_OPTIONS_V2, colors, REDIRECT_URL} from '../configs'; import {PaymentOptionsPropRule} from '../utils/CustomPropTypesRules'; +import FlutterwaveInitError from '../utils/FlutterwaveInitError'; const loader = require('../assets/loader.gif'); const pryContent = require('../assets/pry-button-content.png'); const contentWidthPercentage = 0.6549707602; @@ -254,10 +255,10 @@ class FlutterwaveButton extends React.Component< // throw error if transaction reference has not changed if (txref === options.txref) { - return onInitializeError ? onInitializeError({ + return onInitializeError ? onInitializeError(new FlutterwaveInitError({ message: 'Please generate a new transaction reference.', code: 'SAME_TXREF', - }) : null; + })) : null; } // stop if currently in pending mode From a21a3328deeb581bcc75d0e9b5cae51a986d1c72 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 15:11:10 +0100 Subject: [PATCH 063/129] refactor(v2/flutterwavebutton): correctly name abortController property --- src/v2/FlutterwaveButton.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/v2/FlutterwaveButton.tsx b/src/v2/FlutterwaveButton.tsx index 9187896..6ca3823 100644 --- a/src/v2/FlutterwaveButton.tsx +++ b/src/v2/FlutterwaveButton.tsx @@ -98,7 +98,7 @@ class FlutterwaveButton extends React.Component< webviewRef: WebView | null = null; - canceller?: AbortController; + abortController?: AbortController; componentDidUpdate(prevProps: FlutterwaveButtonProps) { if (JSON.stringify(prevProps.options) !== JSON.stringify(this.props.options)) { @@ -107,14 +107,14 @@ class FlutterwaveButton extends React.Component< } componentWillUnmount() { - if (this.canceller) { - this.canceller.abort(); + if (this.abortController) { + this.abortController.abort(); } } reset = () => { - if (this.canceller) { - this.canceller.abort(); + if (this.abortController) { + this.abortController.abort(); } // reset the necessaries this.setState(({resetLink, link}) => ({ @@ -267,7 +267,7 @@ class FlutterwaveButton extends React.Component< } // initialize abort controller if not set - this.canceller = new AbortController; + this.abortController = new AbortController; // fire will initialize handler if available if (onWillInitialize) { From bab4845c46657d26b3c0142715e8cf5008e4d150 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 15:13:19 +0100 Subject: [PATCH 064/129] refactor(v2/flutterwavebutton): set v2 redirect params argument as type for dependant methods --- src/v2/FlutterwaveButton.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/v2/FlutterwaveButton.tsx b/src/v2/FlutterwaveButton.tsx index 6ca3823..1f24dbe 100644 --- a/src/v2/FlutterwaveButton.tsx +++ b/src/v2/FlutterwaveButton.tsx @@ -150,11 +150,11 @@ class FlutterwaveButton extends React.Component< this.handleComplete(this.getRedirectParams(ev.url)); }; - handleComplete(data: any) { + handleComplete(data: RedirectParamsV2) { const {onComplete} = this.props; // reset payment link this.setState(({resetLink, txref}) => ({ - txref: data.flref && !data.canceled ? null : txref, + txref: data.flwref && !data.canceled ? null : txref, resetLink: data.flwref && !data.canceled ? true : resetLink }), () => { @@ -164,7 +164,7 @@ class FlutterwaveButton extends React.Component< onComplete({ flwref: data.flwref, txref: data.txref, - canceled: /true/i.test(data.canceled || '') ? true : false + canceled: data.canceled, }); } ); @@ -205,7 +205,7 @@ class FlutterwaveButton extends React.Component< } }; - getRedirectParams = (url: string): RedirectParams => { + getRedirectParams = (url: string): RedirectParamsV2 => { // initialize result container const res: any = {}; // if url has params From 6685df2dc83daa6f117a58fc5030c5e2793473fa Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 15:19:59 +0100 Subject: [PATCH 065/129] feat(v2/flutterwaveinit): implement new specs and use base flutterwaveinit for base dependencies --- src/v2/FlutterwaveInit.ts | 99 ++++++++------------------------------- 1 file changed, 20 insertions(+), 79 deletions(-) diff --git a/src/v2/FlutterwaveInit.ts b/src/v2/FlutterwaveInit.ts index d1a98fc..c30be6d 100644 --- a/src/v2/FlutterwaveInit.ts +++ b/src/v2/FlutterwaveInit.ts @@ -1,50 +1,25 @@ -import {STANDARD_URL} from './configs.v2'; +import {STANDARD_URL_V2} from '../configs'; +import {FlutterwaveInitOptionsBase} from '../FlutterwaveInit'; +import FlutterwaveInitError from '../utils/FlutterwaveInitError'; -interface FlutterwavePaymentMeta { +interface FlutterwavePaymentMetaV2 { metaname: string; metavalue: string; } -export interface FlutterwaveInitSubAccount { - id: string; - transaction_split_ratio?: number; - transaction_charge_type?: string; - transaction_charge?: number; -} - -export interface FlutterwaveInitOptions { +export type FlutterwaveInitOptions = FlutterwaveInitOptionsBase & { txref: string; PBFPubKey: string; customer_firstname?: string; customer_lastname?: string; customer_phone?: string; customer_email: string; - amount: number; - currency?: string; - redirect_url?: string; - payment_options?: string; - payment_plan?: number; - subaccounts?: Array; country?: string; pay_button_text?: string; custom_title?: string; custom_description?: string; custom_logo?: string; - meta?: Array; -} - -interface FlutterwaveInitConfig { - canceller?: AbortController; -} - -export interface FlutterwaveInitError { - code: string; - message: string; -} - -interface FlutterwaveInitResult { - error?: FlutterwaveInitError | null; - link?: string | null; + meta?: Array; } interface ResponseJSON { @@ -78,84 +53,50 @@ interface FetchOptions { */ export default async function FlutterwaveInit( options: FlutterwaveInitOptions, - config: FlutterwaveInitConfig = {}, -): Promise { + abortController?: AbortController, +): Promise { try { // make request body const body = {...options}; - // make request headers const headers = new Headers; headers.append('Content-Type', 'application/json'); - // make fetch options const fetchOptions: FetchOptions = { method: 'POST', body: JSON.stringify(body), headers: headers, } - - // add canceller if defined - if (config.canceller) { - fetchOptions.signal = config.canceller.signal + // add abort controller if defined + if (abortController) { + fetchOptions.signal = abortController.signal }; - // make http request - const response = await fetch(STANDARD_URL, fetchOptions); - + const response = await fetch(STANDARD_URL_V2, fetchOptions); // get response json const responseJSON: ResponseJSON = await response.json(); - // check if data is missing from response if (!responseJSON.data) { - throw new FlutterwaveInitException({ + throw new FlutterwaveInitError({ code: 'NODATA', message: responseJSON.message || 'An unknown error occured!', }); } - // check if the link is missing in data if (!responseJSON.data.link) { - throw new FlutterwaveInitException({ + throw new FlutterwaveInitError({ code: responseJSON.data.code || 'UNKNOWN', message: responseJSON.data.message || 'An unknown error occured!', }); } - // resolve with the payment link - return Promise.resolve({ - link: responseJSON.data.link, - }); + return Promise.resolve(responseJSON.data.link); } catch (e) { + // always return a flutterwave init error + const error = e instanceof FlutterwaveInitError + ? e + : new FlutterwaveInitError({message: e.message, code: e.name.toUpperCase()}) // resolve with error - return Promise.resolve({ - error: { - code: - e instanceof FlutterwaveInitException - ? e.code - : String(e.name).toUpperCase(), - message: e.message, - } - }); - } -} - -/** - * Flutterwave Init Error - */ -export class FlutterwaveInitException extends Error { - /** - * Error code - * @var string - */ - code: string; - - /** - * Constructor Method - * @param props {message?: string; code?: string} - */ - constructor(props: {message: string; code: string}) { - super(props.message); - this.code = props.code; + return Promise.reject(error); } } From 42ca045c33e903822d1ec35d17430360e2ede946 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 15:21:17 +0100 Subject: [PATCH 066/129] feat(v2/flutterwavebutton): implement new v2 flutterwave init api --- src/v2/FlutterwaveButton.tsx | 42 ++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/v2/FlutterwaveButton.tsx b/src/v2/FlutterwaveButton.tsx index 1f24dbe..97f5e1a 100644 --- a/src/v2/FlutterwaveButton.tsx +++ b/src/v2/FlutterwaveButton.tsx @@ -274,10 +274,6 @@ class FlutterwaveButton extends React.Component< onWillInitialize(); } - // @ts-ignore - // delete redirect url if set - delete options.redirect_url; - // set pending state to true this.setState( { @@ -286,24 +282,32 @@ class FlutterwaveButton extends React.Component< txref: options.txref, }, async () => { - // make init request - const result = await FlutterwaveInit(options, {canceller: this.canceller}); - // stop if request was canceled - if (result.error && /aborterror/i.test(result.error.code)) { - return; - } - // call onInitializeError handler if an error occured - if (!result.link) { - if (onInitializeError && result.error) { - onInitializeError(result.error); + try { + // make init request + const paymentLink = await FlutterwaveInit( + {...options, redirect_url: REDIRECT_URL}, + this.abortController + ); + // set payment link + this.setState({ + link: paymentLink, + isPending: false + }, this.show); + // fire did initialize handler if available + if (onDidInitialize) { + onDidInitialize(); + } + } catch (error) { + // stop if request was canceled + if (error && /aborterror/i.test(error.code)) { + return; + } + // call onInitializeError handler if an error occured + if (onInitializeError) { + onInitializeError(error); } return this.dismiss(); } - this.setState({link: result.link, isPending: false}, this.show); - // fire did initialize handler if available - if (onDidInitialize) { - onDidInitialize(); - } }, ); }; From b285ec95e2e183fa4fece50a1ffafcd68bee8ce3 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 15:22:00 +0100 Subject: [PATCH 067/129] feat(v2/index): add default button to exports --- src/v2/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/v2/index.ts b/src/v2/index.ts index 3737c29..2471f06 100644 --- a/src/v2/index.ts +++ b/src/v2/index.ts @@ -1,8 +1,9 @@ import FlutterwaveInit from './FlutterwaveInit'; import FlutterwaveButton from './FlutterwaveButton'; +import DefaultButton from '../DefaultButton'; // export modules -export {FlutterwaveInit, FlutterwaveButton}; +export {FlutterwaveInit, FlutterwaveButton, DefaultButton}; // export init as default export default FlutterwaveButton; From cbd731e87c113fb7d4f0cef79f138629825db871 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 15:22:50 +0100 Subject: [PATCH 068/129] refactor(v2): code cleaning --- src/v2/configs.v2.ts | 21 --------------------- src/v2/index.ts | 2 +- 2 files changed, 1 insertion(+), 22 deletions(-) delete mode 100644 src/v2/configs.v2.ts diff --git a/src/v2/configs.v2.ts b/src/v2/configs.v2.ts deleted file mode 100644 index 26498bd..0000000 --- a/src/v2/configs.v2.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Flutterwaves standard init url. - */ -export const STANDARD_URL: string = - 'https://api.ravepay.co/flwv3-pug/getpaidx/api/v2/hosted/pay'; - -export const PAYMENT_OPTIONS = [ - 'card', - 'account', - 'ussd', - 'qr', - 'mpesa', - 'mobilemoneyghana', - 'mobilemoneyuganda', - 'mobilemoneyrwanda', - 'mobilemoneyzambia', - 'mobilemoneytanzania', - 'barter', - 'bank transfer', - 'wechat', -]; diff --git a/src/v2/index.ts b/src/v2/index.ts index 2471f06..1af517d 100644 --- a/src/v2/index.ts +++ b/src/v2/index.ts @@ -5,5 +5,5 @@ import DefaultButton from '../DefaultButton'; // export modules export {FlutterwaveInit, FlutterwaveButton, DefaultButton}; -// export init as default +// export button as default export default FlutterwaveButton; From 495ac216eaf0bc76a18d6c9534200d770ad4bf83 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 15:25:04 +0100 Subject: [PATCH 069/129] feat(index): add v2 to base index modules export --- src/index.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 39d543f..2b1b2f0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,17 @@ import FlutterwaveInit from './FlutterwaveInit'; import FlutterwaveButton from './FlutterwaveButton'; +import FlutterwaveInitV2 from './v2/FlutterwaveInit'; +import FlutterwaveButtonV2 from './v2/FlutterwaveButton'; import DefaultButton from './DefaultButton'; // export modules -export {FlutterwaveInit, FlutterwaveButton, DefaultButton}; +export { + FlutterwaveInit, + FlutterwaveButton, + FlutterwaveInitV2, + FlutterwaveButtonV2, + DefaultButton, +}; -// export init as default +// export v3 button as default export default FlutterwaveButton; From 7ed703f6f837eaf379e271be4d922e0d2a9fd8f6 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 15:25:32 +0100 Subject: [PATCH 070/129] refactor: remove redundant test file from v2 tests --- __tests__/v2/CustomPropTypesRules.spec.ts | 28 ----------------------- 1 file changed, 28 deletions(-) delete mode 100644 __tests__/v2/CustomPropTypesRules.spec.ts diff --git a/__tests__/v2/CustomPropTypesRules.spec.ts b/__tests__/v2/CustomPropTypesRules.spec.ts deleted file mode 100644 index 7f3aff5..0000000 --- a/__tests__/v2/CustomPropTypesRules.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import 'react-native'; -import {PaymentOptionsPropRule} from '../../src/utils/CustomPropTypesRules'; -import {PAYMENT_OPTIONS} from '../../src/v2/configs.v2'; -const PropName = 'payment_options'; - -describe('CustomPropTypes.PaymentOptionsPropRule', () => { - it ('returns null if prop is not defined in props', () => { - const result = PaymentOptionsPropRule(PAYMENT_OPTIONS)({}, PropName); - expect(result).toBe(null); - }); - - it ('returns error if prop is not a string', () => { - const result = PaymentOptionsPropRule(PAYMENT_OPTIONS)({[PropName]: []}, PropName); - expect(result !== null).toBe(true); - expect(result.message).toContain('should be a string.'); - }); - - it ('returns error if prop includes invalid payment option', () => { - const result = PaymentOptionsPropRule(PAYMENT_OPTIONS)({[PropName]: 'barter, foo'}, PropName); - expect(result !== null).toBe(true); - expect(result.message).toContain('must be any of the following values.'); - }); - - it ('returns null if payment options are valid', () => { - const result = PaymentOptionsPropRule(PAYMENT_OPTIONS)({[PropName]: 'barter'}, PropName); - expect(result).toBe(null); - }); -}) From fbd51f9ff7e66a55c17f9a45bbe4b92a5d4c9771 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 15:28:02 +0100 Subject: [PATCH 071/129] test(v2): use v2 configs --- __tests__/v2/FlutterwaveButton.spec.tsx | 6 +++--- __tests__/v2/FlutterwaveInit.spec.ts | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/__tests__/v2/FlutterwaveButton.spec.tsx b/__tests__/v2/FlutterwaveButton.spec.tsx index 97e9949..344211d 100644 --- a/__tests__/v2/FlutterwaveButton.spec.tsx +++ b/__tests__/v2/FlutterwaveButton.spec.tsx @@ -4,7 +4,7 @@ import {TouchableWithoutFeedback, Text, Alert} from 'react-native'; import renderer from 'react-test-renderer'; import FlutterwaveButton from '../../src/v2/FlutterwaveButton'; import {FlutterwaveInitOptions} from '../../src/v2/FlutterwaveInit'; -import {STANDARD_URL} from '../../src/v2/configs.v2'; +import {STANDARD_URL_V2} from '../../src/configs'; import WebView from 'react-native-webview'; import DefaultButton from '../../src/DefaultButton'; const BtnTestID = 'flw-default-button'; @@ -233,7 +233,7 @@ describe('', () => { jest.useRealTimers(); expect(global.fetch).toBeCalledTimes(1); - expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL, { + expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL_V2, { body: JSON.stringify(PaymentOptions), headers: headers, method: 'POST', @@ -294,7 +294,7 @@ describe('', () => { jest.useRealTimers(); // expect fetch to have been called expect(global.fetch).toHaveBeenCalledTimes(1); - expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL, { + expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL_V2, { body: JSON.stringify({...PaymentOptions, redirect_url: undefined}), headers: FetchHeader, method: 'POST', diff --git a/__tests__/v2/FlutterwaveInit.spec.ts b/__tests__/v2/FlutterwaveInit.spec.ts index 67131c2..2a95257 100644 --- a/__tests__/v2/FlutterwaveInit.spec.ts +++ b/__tests__/v2/FlutterwaveInit.spec.ts @@ -1,5 +1,5 @@ import FlutterwaveInit, {FlutterwaveInitOptions} from '../../src/v2/FlutterwaveInit'; -import {STANDARD_URL} from '../../src/v2/configs.v2'; +import {STANDARD_URL_V2} from '../../src/configs'; // default fetch header const DefaultFetchHeader = new Headers(); @@ -30,7 +30,7 @@ describe('', () => { // expect fetch to have been called once expect(global.fetch).toHaveBeenCalledTimes(1); // expect fetch to have been called to the standard init url - expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL, { + expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL_V2, { body: JSON.stringify(paymentInfo), headers: DefaultFetchHeader, method: 'POST', @@ -54,7 +54,7 @@ describe('', () => { const response = await FlutterwaveInit(paymentInfo); // expect fetch to have been called expect(global.fetch).toHaveBeenCalledTimes(1); - expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL, { + expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL_V2, { body: JSON.stringify(paymentInfo), headers: DefaultFetchHeader, method: 'POST', @@ -80,7 +80,7 @@ describe('', () => { const response = await FlutterwaveInit(paymentInfo); // expect fetch to have been called expect(global.fetch).toHaveBeenCalledTimes(1); - expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL, { + expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL_V2, { body: JSON.stringify(paymentInfo), headers: DefaultFetchHeader, method: 'POST', @@ -105,7 +105,7 @@ describe('', () => { const response = await FlutterwaveInit(paymentInfo); // expect fetch to have been called expect(global.fetch).toHaveBeenCalledTimes(1); - expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL, { + expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL_V2, { body: JSON.stringify(paymentInfo), headers: DefaultFetchHeader, method: 'POST', From ca4d9d2ee1a433ae30de1d5d79b605299e43e5bf Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 15:28:24 +0100 Subject: [PATCH 072/129] test(v2/flutterwavebutton): update snapshot --- .../FlutterwaveButton.spec.tsx.snap | 111 ++++++++++-------- 1 file changed, 64 insertions(+), 47 deletions(-) diff --git a/__tests__/v2/__snapshots__/FlutterwaveButton.spec.tsx.snap b/__tests__/v2/__snapshots__/FlutterwaveButton.spec.tsx.snap index 74c8839..bd047cc 100644 --- a/__tests__/v2/__snapshots__/FlutterwaveButton.spec.tsx.snap +++ b/__tests__/v2/__snapshots__/FlutterwaveButton.spec.tsx.snap @@ -14,19 +14,26 @@ Array [ onResponderTerminationRequest={[Function]} onStartShouldSetResponder={[Function]} style={ - Object { - "alignItems": "center", - "backgroundColor": "#f5a623", - "borderColor": "#FBDBA7", - "borderRadius": 6, - "borderWidth": 1, - "flexDirection": "row", - "height": 52, - "justifyContent": "center", - "minWidth": 100, - "overflow": "hidden", - "paddingHorizontal": 16, - } + Array [ + Object { + "alignItems": "center", + "backgroundColor": "#f5a623", + "borderColor": "#f5a623", + "borderRadius": 6, + "borderWidth": 1, + "flexDirection": "row", + "height": 52, + "justifyContent": "center", + "minWidth": 100, + "overflow": "hidden", + "paddingHorizontal": 16, + }, + Object { + "borderColor": "#FBDBA7", + }, + Object {}, + undefined, + ] } testID="flw-default-button" > @@ -36,7 +43,7 @@ Array [ resizeMode="contain" source={ Object { - "testUri": "../../../src/pry-button-content.png", + "testUri": "../../../src/assets/pry-button-content.png", } } style={ @@ -175,7 +182,7 @@ Array [ resizeMode="contain" source={ Object { - "testUri": "../../../src/loader.gif", + "testUri": "../../../src/assets/loader.gif", } } style={ @@ -207,19 +214,24 @@ Array [ onResponderTerminationRequest={[Function]} onStartShouldSetResponder={[Function]} style={ - Object { - "alignItems": "center", - "backgroundColor": "#f5a623", - "borderColor": "#f5a623", - "borderRadius": 6, - "borderWidth": 1, - "flexDirection": "row", - "height": 52, - "justifyContent": "center", - "minWidth": 100, - "overflow": "hidden", - "paddingHorizontal": 16, - } + Array [ + Object { + "alignItems": "center", + "backgroundColor": "#f5a623", + "borderColor": "#f5a623", + "borderRadius": 6, + "borderWidth": 1, + "flexDirection": "row", + "height": 52, + "justifyContent": "center", + "minWidth": 100, + "overflow": "hidden", + "paddingHorizontal": 16, + }, + Object {}, + Object {}, + undefined, + ] } testID="flw-default-button" > @@ -229,7 +241,7 @@ Array [ resizeMode="contain" source={ Object { - "testUri": "../../../src/pry-button-content.png", + "testUri": "../../../src/assets/pry-button-content.png", } } style={ @@ -356,7 +368,7 @@ Array [ resizeMode="contain" source={ Object { - "testUri": "../../../src/loader.gif", + "testUri": "../../../src/assets/loader.gif", } } style={ @@ -500,7 +512,7 @@ Array [ resizeMode="contain" source={ Object { - "testUri": "../../../src/loader.gif", + "testUri": "../../../src/assets/loader.gif", } } style={ @@ -532,19 +544,24 @@ Array [ onResponderTerminationRequest={[Function]} onStartShouldSetResponder={[Function]} style={ - Object { - "alignItems": "center", - "backgroundColor": "#f5a623", - "borderColor": "#f5a623", - "borderRadius": 6, - "borderWidth": 1, - "flexDirection": "row", - "height": 52, - "justifyContent": "center", - "minWidth": 100, - "overflow": "hidden", - "paddingHorizontal": 16, - } + Array [ + Object { + "alignItems": "center", + "backgroundColor": "#f5a623", + "borderColor": "#f5a623", + "borderRadius": 6, + "borderWidth": 1, + "flexDirection": "row", + "height": 52, + "justifyContent": "center", + "minWidth": 100, + "overflow": "hidden", + "paddingHorizontal": 16, + }, + Object {}, + Object {}, + undefined, + ] } testID="flw-default-button" > @@ -554,7 +571,7 @@ Array [ resizeMode="contain" source={ Object { - "testUri": "../../../src/pry-button-content.png", + "testUri": "../../../src/assets/pry-button-content.png", } } style={ @@ -681,7 +698,7 @@ Array [ resizeMode="contain" source={ Object { - "testUri": "../../../src/loader.gif", + "testUri": "../../../src/assets/loader.gif", } } style={ @@ -736,7 +753,7 @@ exports[` renders webview loading correctly 1`] = ` resizeMode="contain" source={ Object { - "testUri": "../../../src/loader.gif", + "testUri": "../../../src/assets/loader.gif", } } style={ From c4cdc2d19a9a954973f40b5731aab5d38e94065f Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 16:12:02 +0100 Subject: [PATCH 073/129] fix(v2/flutterwavebutton): use correct regex pattern for redirect url check --- src/v2/FlutterwaveButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v2/FlutterwaveButton.tsx b/src/v2/FlutterwaveButton.tsx index 97f5e1a..39af074 100644 --- a/src/v2/FlutterwaveButton.tsx +++ b/src/v2/FlutterwaveButton.tsx @@ -141,7 +141,7 @@ class FlutterwaveButton extends React.Component< handleNavigationStateChange = (ev: WebViewNavigation) => { // cregex to check if redirect has occured on completion/cancel - const rx = /\/hosted\/pay\/undefined|\/api\/hosted_pay\/undefined/; + const rx = /\/flutterwave\.com\/rn-redirect/; // Don't end payment if not redirected back if (!rx.test(ev.url)) { return From d6a29c3d6ad5d9a9f3a9f944b13ce2c13533b0c5 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 16:17:17 +0100 Subject: [PATCH 074/129] fix(v2/flutterwaveinit): use proper error codes for malformed or missing data --- src/v2/FlutterwaveInit.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/v2/FlutterwaveInit.ts b/src/v2/FlutterwaveInit.ts index c30be6d..b51eead 100644 --- a/src/v2/FlutterwaveInit.ts +++ b/src/v2/FlutterwaveInit.ts @@ -78,14 +78,14 @@ export default async function FlutterwaveInit( // check if data is missing from response if (!responseJSON.data) { throw new FlutterwaveInitError({ - code: 'NODATA', + code: 'STANDARD_INIT_ERROR', message: responseJSON.message || 'An unknown error occured!', }); } // check if the link is missing in data if (!responseJSON.data.link) { throw new FlutterwaveInitError({ - code: responseJSON.data.code || 'UNKNOWN', + code: responseJSON.data.code || 'MALFORMED_RESPONSE', message: responseJSON.data.message || 'An unknown error occured!', }); } From 52f8646fcedac837cb1842a1bdbdf5381f79374c Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 16:18:10 +0100 Subject: [PATCH 075/129] test(v2/flutterwaveinit): write tests for new v2 flutterwave init implementation --- __tests__/v2/FlutterwaveInit.spec.ts | 199 +++++++++++++-------------- 1 file changed, 92 insertions(+), 107 deletions(-) diff --git a/__tests__/v2/FlutterwaveInit.spec.ts b/__tests__/v2/FlutterwaveInit.spec.ts index 2a95257..01c207a 100644 --- a/__tests__/v2/FlutterwaveInit.spec.ts +++ b/__tests__/v2/FlutterwaveInit.spec.ts @@ -1,145 +1,130 @@ import FlutterwaveInit, {FlutterwaveInitOptions} from '../../src/v2/FlutterwaveInit'; import {STANDARD_URL_V2} from '../../src/configs'; +import FlutterwaveInitError from '../../src/utils/FlutterwaveInitError'; -// default fetch header -const DefaultFetchHeader = new Headers(); -DefaultFetchHeader.append('Content-Type', 'application/json'); +const AUTHORIZATION = '[PUB Key]'; -describe('', () => { +// fetch header +const FETCH_HEADER = new Headers(); +FETCH_HEADER.append('Content-Type', 'application/json'); + +// fetch body +const FETCH_BODY = { + redirect_url: 'http://flutterwave.com', + PBFPubKey: AUTHORIZATION, + amount: 50, + currency: 'NGN', + customer_email: 'email@example.com', + txref: Date.now() + '-txref', +} + +// payment options +const INIT_OPTIONS: FlutterwaveInitOptions = { + ...FETCH_BODY, +}; + +const SUCCESS_RESPONSE = { + status: 'success', + message: 'Payment link generated.', + data: { + link: 'http://payment-link.com/checkout', + }, +}; + +describe('', () => { it('returns a payment link after initialization', async () => { // mock next fetch request - fetchMock.mockOnce(JSON.stringify({ - status: 'success', - message: 'Payment link generated.', - data: { - link: 'http://payment-link.com/checkout', - }, - })); - // payment information - const paymentInfo: FlutterwaveInitOptions = { - redirect_url: 'http://flutterwave.com', - PBFPubKey: '[PUB Key]', - amount: 50, - currency: 'NGN', - customer_email: 'email@example.com', - txref: Date.now() + '-txref', - }; + fetchMock.mockOnce(JSON.stringify(SUCCESS_RESPONSE)); // flutterwave init test - const response = await FlutterwaveInit(paymentInfo); - + const link = await FlutterwaveInit(INIT_OPTIONS); // expect fetch to have been called once expect(global.fetch).toHaveBeenCalledTimes(1); // expect fetch to have been called to the standard init url expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL_V2, { - body: JSON.stringify(paymentInfo), - headers: DefaultFetchHeader, + body: JSON.stringify(FETCH_BODY), + headers: FETCH_HEADER, method: 'POST', }); - expect(typeof response.link === 'string').toBeTruthy(); + expect(typeof link === 'string').toBeTruthy(); }); it('includes error code and message of init error', async () => { - // payment information - const paymentInfo: FlutterwaveInitOptions = { - redirect_url: 'http://flutterwave.com', - PBFPubKey: 'FLWPUBK_TEST-c761fb7f0e443f5704a796781b621875-X44', - amount: 50, - currency: 'NGN', - customer_email: 'email@example.com', - txref: Date.now() + '-txref', - }; + const message = 'An error has occurred.'; // reject next fetch - fetchMock.mockRejectOnce(new Error('An error occured!')); - // flutterwave init test - const response = await FlutterwaveInit(paymentInfo); - // expect fetch to have been called - expect(global.fetch).toHaveBeenCalledTimes(1); - expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL_V2, { - body: JSON.stringify(paymentInfo), - headers: DefaultFetchHeader, - method: 'POST', - }); - // expect error and error code to be defined - expect(typeof response.error.code === 'string').toBeTruthy(); - expect(typeof response.error.message === 'string').toBeTruthy(); + fetchMock.mockOnce(JSON.stringify({ + status: 'error', + message: message, + })); + try { + // initialize payment + await FlutterwaveInit(INIT_OPTIONS); + } catch (error) { + // run assertions + expect(error.message).toEqual(message); + expect(error.code).toEqual('STANDARD_INIT_ERROR'); + } }); - it('returns unknown error if the error response has no code or message', async () => { - // payment information - const paymentInfo: FlutterwaveInitOptions = { - redirect_url: 'http://flutterwave.com', - PBFPubKey: 'FLWPUBK_TEST-c761fb7f0e443f5704a796781b621875-X44', - amount: 50, - currency: 'NGN', - customer_email: 'email@example.com', - txref: Date.now() + '-txref', - }; + it('handles missing data error', async () => { // mock next fetch - fetchMock.mockOnce(JSON.stringify({status: 'error', data: {}})); - // flutterwave init test - const response = await FlutterwaveInit(paymentInfo); - // expect fetch to have been called - expect(global.fetch).toHaveBeenCalledTimes(1); - expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL_V2, { - body: JSON.stringify(paymentInfo), - headers: DefaultFetchHeader, - method: 'POST', - }); - // expect unkown error from from response - expect(/unknown/i.test(response.error.code)).toBeTruthy(); + fetchMock.mockOnce(JSON.stringify({status: 'error'})); + try { + // initialize payment + const response = await FlutterwaveInit(INIT_OPTIONS); + } catch (error) { + // run assertions + expect(error.code).toEqual('STANDARD_INIT_ERROR'); + } }); - it('catches missing response data error', async () => { - // payment information - const paymentInfo: FlutterwaveInitOptions = { - redirect_url: 'http://flutterwave.com', - PBFPubKey: 'FLWPUBK_TEST-c761fb7f0e443f5704a796781b621875-X44', - amount: 50, - currency: 'NGN', - customer_email: 'email@example.com', - txref: Date.now() + '-txref', - }; + it('rejects with an error if link is missing in data', async () => { + const errorMessage = 'Missing link test.'; // mock next fetch - fetchMock.mockOnce(JSON.stringify({status: 'error'})); - // flutterwave init test - const response = await FlutterwaveInit(paymentInfo); - // expect fetch to have been called - expect(global.fetch).toHaveBeenCalledTimes(1); - expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL_V2, { - body: JSON.stringify(paymentInfo), - headers: DefaultFetchHeader, - method: 'POST', - }); - // expect a no response error - expect(/nodata/i.test(response.error.code)).toBeTruthy(); + fetchMock.mockOnce( + JSON.stringify({ + status: 'error', + data: { + message: errorMessage, + code: 'MALFORMED_RESPONSE' + } + } + ) + ); + try { + // initialize payment + const response = await FlutterwaveInit(INIT_OPTIONS); + } catch (error) { + // run assertions + expect(error.code).toEqual('MALFORMED_RESPONSE'); + } }); + it('is abortable', async () => { // use fake jest timers jest.useFakeTimers(); // mock fetch response fetchMock.mockResponse(async () => { jest.advanceTimersByTime(60) - return '' + return JSON.stringify({ + status: 'error', + message: 'Error!', + }) }); // create abort controller const abortController = new AbortController; - // payment information - const paymentInfo: FlutterwaveInitOptions = { - redirect_url: 'http://flutterwave.com', - PBFPubKey: 'FLWPUBK_TEST-c761fb7f0e443f5704a796781b621875-X44', - amount: 50, - currency: 'NGN', - customer_email: 'email@example.com', - txref: Date.now() + '-txref', - }; // abort next fetch setTimeout(() => abortController.abort(), 50); - // expect a no response error - await expect(FlutterwaveInit(paymentInfo, {canceller: abortController})).resolves.toMatchObject({ - error: { - code: 'ABORTERROR' - } - }); + try { + // initialize payment + await FlutterwaveInit( + INIT_OPTIONS, + abortController + ) + } catch(error) { + // run assertions + expect(error).toBeInstanceOf(FlutterwaveInitError); + expect(error.code).toEqual('ABORTERROR'); + } }); }); From 7f1319cec8e0e975f4f90610bc4d5c6ea8459903 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 16:21:55 +0100 Subject: [PATCH 076/129] test(v2/flutterwavebutton): rewriter v2 flutterwave button tests --- __tests__/v2/FlutterwaveButton.spec.tsx | 193 ++++++++++++------------ 1 file changed, 93 insertions(+), 100 deletions(-) diff --git a/__tests__/v2/FlutterwaveButton.spec.tsx b/__tests__/v2/FlutterwaveButton.spec.tsx index 344211d..0e1e76c 100644 --- a/__tests__/v2/FlutterwaveButton.spec.tsx +++ b/__tests__/v2/FlutterwaveButton.spec.tsx @@ -4,10 +4,12 @@ import {TouchableWithoutFeedback, Text, Alert} from 'react-native'; import renderer from 'react-test-renderer'; import FlutterwaveButton from '../../src/v2/FlutterwaveButton'; import {FlutterwaveInitOptions} from '../../src/v2/FlutterwaveInit'; -import {STANDARD_URL_V2} from '../../src/configs'; +import {STANDARD_URL_V2, REDIRECT_URL} from '../../src/configs'; import WebView from 'react-native-webview'; import DefaultButton from '../../src/DefaultButton'; +import FlutterwaveInitError from '../../src/utils/FlutterwaveInitError'; const BtnTestID = 'flw-default-button'; + const SuccessResponse = { status: 'success', message: 'Payment link generated.', @@ -15,7 +17,8 @@ const SuccessResponse = { link: 'http://payment-link.com/checkout', }, }; -const PaymentOptions: Omit = { + +const PAYMENT_INFO: Omit = { txref: '34h093h09h034034', customer_email: 'customer-email@example.com', PBFPubKey: '[Public Key]', @@ -23,11 +26,16 @@ const PaymentOptions: Omit = { currency: 'NGN', }; -describe('', () => { +const REQUEST_BODY = {...PAYMENT_INFO, redirect_url: REDIRECT_URL}; + +const HEADERS = new Headers +HEADERS.append('Content-Type', 'application/json'); + +describe('', () => { it('renders component correctly', () => { const Renderer = renderer.create(); expect(Renderer.toJSON()).toMatchSnapshot(); }); @@ -36,7 +44,7 @@ describe('', () => { // get create instance of flutterwave button const Renderer = renderer.create(); fetchMock.mockOnce(JSON.stringify(SuccessResponse)); Renderer.root.findByProps({testID: BtnTestID}).props.onPress(); @@ -51,7 +59,7 @@ describe('', () => { // get create instance of flutterwave button const Renderer = renderer.create(); fetchMock.mockOnce(JSON.stringify(SuccessResponse)); Renderer.root.findByProps({testID: BtnTestID}).props.onPress(); @@ -68,7 +76,7 @@ describe('', () => { it('renders custom button correctly', () => { const TestRenderer = renderer.create(', () => { it('renders webview loading correctly', () => { const TestRenderer = renderer.create(); // get webview const webView = TestRenderer.root.findByType(WebView); @@ -97,10 +105,34 @@ describe('', () => { expect(LoadingRenderer).toMatchSnapshot(); }); - it('renders webview error correctly', () => { + it('renders webview error correctly', (done) => { + const TestRenderer = renderer.create(); + // mock next fetch request + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + // press button + TestRenderer.root.findByProps({testID: BtnTestID}).props.onPress(); + // simulate animated animation + jest.useFakeTimers(); + global.timeTravel(); + jest.useRealTimers(); + // checks + setTimeout(() => { + // get webview + const webView = TestRenderer.root.findByType(WebView); + // create error renderer + const ErrorRenderer = renderer.create(webView.props.renderError()); + expect(ErrorRenderer).toMatchSnapshot(); + done(); + }, 50); + }); + + it('does not render webview error if there is no link', () => { const TestRenderer = renderer.create(); // get webview const webView = TestRenderer.root.findByType(WebView); @@ -114,11 +146,10 @@ describe('', () => { const customButton = jest.fn(); const TestRenderer = renderer.create(); TestRenderer.root.instance.handleInit(); - expect(customButton).toHaveBeenCalledTimes(2); expect(customButton).toHaveBeenLastCalledWith({ disabled: true, isInitializing: true, @@ -131,12 +162,11 @@ describe('', () => { const customButton = jest.fn(); const TestRenderer = renderer.create(); TestRenderer.root.instance.handleInit(); setTimeout(() => { - expect(customButton).toHaveBeenCalledTimes(4); expect(customButton).toHaveBeenLastCalledWith({ disabled: true, isInitializing: false, @@ -149,7 +179,7 @@ describe('', () => { it('asks user to confirm abort when pressed backdrop', () => { const TestRenderer = renderer.create(); // get backdrop const Backdrop = TestRenderer.root.findByProps({testID: 'flw-backdrop'}); @@ -169,7 +199,7 @@ describe('', () => { // get create instance of flutterwave button const FlwButton = renderer.create(); // fire handle abort confirm @@ -183,7 +213,7 @@ describe('', () => { // get create instance of flutterwave button const FlwButton = renderer.create(); // fire handle abort confirm FlwButton.root.instance.handleAbortConfirm(); @@ -195,7 +225,7 @@ describe('', () => { // get create instance of flutterwave button const FlwButton = renderer.create(); // mock next fetch request @@ -216,14 +246,11 @@ describe('', () => { it('makes call to standard endpoint when button is pressed', async () => { const Renderer = renderer.create(); const Button = Renderer.root.findByProps({testID: BtnTestID}); const c = new AbortController; - const headers = new Headers - - headers.append('Content-Type', 'application/json'); fetchMock.mockOnce(JSON.stringify(SuccessResponse)); Button.props.onPress(); @@ -232,10 +259,10 @@ describe('', () => { global.timeTravel(); jest.useRealTimers(); - expect(global.fetch).toBeCalledTimes(1); + // run assertions expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL_V2, { - body: JSON.stringify(PaymentOptions), - headers: headers, + body: JSON.stringify(REQUEST_BODY), + headers: HEADERS, method: 'POST', signal: c.signal, }); @@ -251,7 +278,7 @@ describe('', () => { // create test renderer const TestRenderer = renderer.create(); // spy on component methods @@ -274,40 +301,12 @@ describe('', () => { expect(setState).toHaveBeenCalledWith({buttonSize: onSizeChangeEv}) }); - it('initialized without a redirect url', () => { - // get create instance of flutterwave button - const FlwButton = renderer.create(); - const abortController = new AbortController - // default fetch header - const FetchHeader = new Headers(); - FetchHeader.append('Content-Type', 'application/json'); - // mock next fetch request - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - // fire on press - FlwButton.root.findByProps({testID: BtnTestID}).props.onPress(); - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - // expect fetch to have been called - expect(global.fetch).toHaveBeenCalledTimes(1); - expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL_V2, { - body: JSON.stringify({...PaymentOptions, redirect_url: undefined}), - headers: FetchHeader, - method: 'POST', - signal: abortController.signal - }); - }); - it('fires onDidInitialize if available', (done) => { const onDidInitialize = jest.fn(); // get create instance of flutterwave button const FlwButton = renderer.create(); // mock next fetch request @@ -331,7 +330,7 @@ describe('', () => { // get create instance of flutterwave button const FlwButton = renderer.create(); // mock next fetch request @@ -356,7 +355,7 @@ describe('', () => { // get create instance of flutterwave button const FlwButton = renderer.create(); // mock next fetch request @@ -370,10 +369,10 @@ describe('', () => { // wait for request to be made setTimeout(() => { expect(onInitializeError).toHaveBeenCalledTimes(1); - expect(onInitializeError).toHaveBeenCalledWith({ - code: err.name.toUpperCase(), + expect(onInitializeError).toHaveBeenCalledWith(new FlutterwaveInitError({ + code: 'STANDARD_INIT_ERROR', message: err.message - }); + })); // end test done(); }, 50); @@ -383,7 +382,7 @@ describe('', () => { // get create instance of flutterwave button const FlwButton = renderer.create(); // spy on set state const setState = jest.spyOn(FlwButton.root.instance, 'setState'); @@ -411,15 +410,12 @@ describe('', () => { txref: 'nfeinr09erss', } - // define url - const url = "http://redirect-url.com/api/hosted_pay/undefined" - - const urlWithParams = url + '?flwref=' + response.flwref + '&txref=' + response.txref; + const urlWithParams = REDIRECT_URL + '?flwref=' + response.flwref + '&txref=' + response.txref; // get create instance of flutterwave button const TestRenderer = renderer.create(); // spy on getRedirectParams method @@ -459,7 +455,7 @@ describe('', () => { // get create instance of flutterwave button const TestRenderer = renderer.create(); // spy on getRedirectParams method @@ -500,18 +496,18 @@ describe('', () => { txref: 'nfeinr09erss', } + // on complete const onComplete = jest.fn(); // define url - const url = "http://redirect-url.com/api/hosted_pay/undefined?flwref=" + - response.flwref + - "&txref=" + - response.txref + const url = REDIRECT_URL + + "?txref=" + response.txref + + "&flwref=" + response.flwref // get create instance of flutterwave button const TestRenderer = renderer.create(); // spy on getRedirectParams method @@ -543,10 +539,7 @@ describe('', () => { expect(handleComplete).toHaveBeenCalledTimes(1); expect(handleComplete).toHaveBeenCalledWith(response); expect(onComplete).toHaveBeenCalledTimes(1); - expect(onComplete).toHaveBeenCalledWith({ - ...response, - canceled: false - }); + expect(onComplete).toHaveBeenCalledWith(response); // end test done(); @@ -557,7 +550,7 @@ describe('', () => { // get create instance of flutterwave button const TestRenderer = renderer.create(); fetchMock.mockOnce(JSON.stringify(SuccessResponse)); TestRenderer @@ -566,7 +559,7 @@ describe('', () => { .props .onPress(); // spy on abort method - const abort = jest.spyOn(TestRenderer.root.instance.canceller, 'abort'); + const abort = jest.spyOn(TestRenderer.root.instance.abortController, 'abort'); // call component will unmount TestRenderer.root.instance.componentWillUnmount(); // run checks @@ -578,21 +571,21 @@ describe('', () => { // get create instance of flutterwave button const TestRenderer = renderer.create(); const willUnmount = jest.spyOn(TestRenderer.root.instance, 'componentWillUnmount'); // call component will unmount TestRenderer.root.instance.componentWillUnmount(); // run checks expect(willUnmount).toHaveBeenCalledTimes(1); - expect(TestRenderer.root.instance.canceller).toBeUndefined(); + expect(TestRenderer.root.instance.abortController).toBeUndefined(); }); it('can reload webview if webview ref is set', (done) => { // create renderer const TestRender = renderer.create(); // mock next fetch request fetchMock.mockOnce(JSON.stringify(SuccessResponse)); @@ -619,7 +612,7 @@ describe('', () => { // create renderer const TestRender = renderer.create(); Object.defineProperty(TestRender.root.instance, 'webviewRef', {value: null}); const handleReload = jest.spyOn(TestRender.root.instance, 'handleReload'); @@ -631,7 +624,7 @@ describe('', () => { it("handles DefaultButton onSizeChange", () => { const TestRenderer = renderer.create(); const size = {width: 1200, height: 0}; const handleButtonResize = jest.spyOn(TestRenderer.root.instance, 'handleButtonResize'); @@ -647,7 +640,7 @@ describe('', () => { const url = new String('http://example.com'); const TestRenderer = renderer.create(); const split = jest.spyOn(url, 'split'); TestRenderer.root.instance.getRedirectParams(url);; @@ -658,7 +651,7 @@ describe('', () => { const url = new String('http://example.com?foo=bar'); const TestRenderer = renderer.create(); const split = jest.spyOn(url, 'split'); TestRenderer.root.instance.getRedirectParams(url);; @@ -669,21 +662,21 @@ describe('', () => { // get create instance of flutterwave button const TestRenderer = renderer.create(); const setState = jest.spyOn(TestRenderer.root.instance, 'setState'); // call component will unmount TestRenderer.root.instance.reset(); // run checks expect(setState).toHaveBeenCalledTimes(1); - expect(TestRenderer.root.instance.canceller).toBeUndefined(); + expect(TestRenderer.root.instance.abortController).toBeUndefined(); }); it("cancels fetch if reset is called and abort controller is set.", () => { // get create instance of flutterwave button const TestRenderer = renderer.create(); fetchMock.mockOnce(JSON.stringify(SuccessResponse)); TestRenderer @@ -692,7 +685,7 @@ describe('', () => { .props .onPress(); // spy on abort method - const abort = jest.spyOn(TestRenderer.root.instance.canceller, 'abort'); + const abort = jest.spyOn(TestRenderer.root.instance.abortController, 'abort'); // call component will unmount TestRenderer.root.instance.reset(); // run checks @@ -704,14 +697,14 @@ describe('', () => { // get create instance of flutterwave button const TestRenderer = renderer.create(); // spy on handleOptionsChanged method const handleOptionsChanged = jest.spyOn(TestRenderer.root.instance, 'handleOptionsChanged'); // update component TestRenderer.update() // run checks expect(handleOptionsChanged).toHaveBeenCalledTimes(1); @@ -722,14 +715,14 @@ describe('', () => { // get create instance of flutterwave button const TestRenderer = renderer.create(); // spy on setState method const setState = jest.spyOn(TestRenderer.root.instance, 'setState'); // update component TestRenderer.update() // run checks expect(setState).toHaveBeenCalledTimes(0); @@ -740,7 +733,7 @@ describe('', () => { // get create instance of flutterwave button const TestRenderer = renderer.create(); // mock next fetch fetchMock.mockOnce(JSON.stringify(SuccessResponse)); @@ -759,7 +752,7 @@ describe('', () => { // update component TestRenderer.update() // run checks expect(setState).toHaveBeenCalledTimes(1); @@ -776,7 +769,7 @@ describe('', () => { // get create instance of flutterwave button const TestRenderer = renderer.create(); // mock next fetch fetchMock.mockOnce(JSON.stringify(SuccessResponse)); @@ -793,7 +786,7 @@ describe('', () => { // update component TestRenderer.update() // run checks expect(setState).toHaveBeenCalledTimes(1); @@ -807,7 +800,7 @@ describe('', () => { // get create instance of flutterwave button const TestRenderer = renderer.create(); // set a payment link TestRenderer.root.instance.setState({link: 'http://payment-link.com'}); @@ -832,7 +825,7 @@ describe('', () => { // get create instance of flutterwave button const TestRenderer = renderer.create(); // set a payment link TestRenderer.root.instance.setState({isPending: true}); From 51efe295d0ccd5d686d904f8a5c9b24a7087e35e Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 16:22:37 +0100 Subject: [PATCH 077/129] test(v2/flutterwavebutton): update v2 flutterwave button snapshot --- .../FlutterwaveButton.spec.tsx.snap | 69 +++++++++++++++++-- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/__tests__/v2/__snapshots__/FlutterwaveButton.spec.tsx.snap b/__tests__/v2/__snapshots__/FlutterwaveButton.spec.tsx.snap index bd047cc..b8a0d54 100644 --- a/__tests__/v2/__snapshots__/FlutterwaveButton.spec.tsx.snap +++ b/__tests__/v2/__snapshots__/FlutterwaveButton.spec.tsx.snap @@ -1,6 +1,24 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` renders busy button if isPending 1`] = ` +exports[` does not render webview error if there is no link 1`] = ` + +`; + +exports[` renders busy button if isPending 1`] = ` Array [ renders component correctly 1`] = ` +exports[` renders component correctly 1`] = ` Array [ renders custom button correctly 1`] = ` +exports[` renders custom button correctly 1`] = ` Array [ renders modal with visibile property as true if show dialog state is true 1`] = ` +exports[` renders modal with visibile property as true if show dialog state is true 1`] = ` Array [ renders webview error correctly 1`] = ` +exports[` renders webview error correctly 1`] = ` renders webview error correctly 1`] = ` "top": 0, } } -/> +> + + The page failed to load, please try again. + + + + Try Again + + + `; -exports[` renders webview loading correctly 1`] = ` +exports[` renders webview loading correctly 1`] = ` Date: Tue, 21 Jul 2020 16:27:35 +0100 Subject: [PATCH 078/129] fix(package.json): update assets locations in copy-files script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e92fd4c..b890b66 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "lint": "eslint \"src/**/*.{ts, tsx}\"", "format": "prettier-standard --format", "test": "jest --coverage", - "copy-files": "copyfiles -u 1 src/*.gif src/*.png dist", + "copy-files": "copyfiles -u 1 src/assets/*.gif src/assets/*.png src/assets dist", "commit": "npx git-cz", "release": "./node_modules/.bin/semantic-release", "set-example": "npm run build && node setExample.js" From 3d5d3ccc1c335366e6370806892c9c97004c2ce0 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 16:44:34 +0100 Subject: [PATCH 079/129] fix(v2/flutterwavebutton): fix incorrect spelling of cancelled prop on redirect params --- src/FlutterwaveButton.tsx | 2 +- src/v2/FlutterwaveButton.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/FlutterwaveButton.tsx b/src/FlutterwaveButton.tsx index f5ef2d9..3d17b66 100644 --- a/src/FlutterwaveButton.tsx +++ b/src/FlutterwaveButton.tsx @@ -10,7 +10,7 @@ export interface RedirectParams { } export interface RedirectParamsV2 { - canceled: 'true' | 'false'; + cancelled?: 'true' | 'false'; flwref?: string; txref?: string; } diff --git a/src/v2/FlutterwaveButton.tsx b/src/v2/FlutterwaveButton.tsx index 39af074..d60bacc 100644 --- a/src/v2/FlutterwaveButton.tsx +++ b/src/v2/FlutterwaveButton.tsx @@ -154,8 +154,8 @@ class FlutterwaveButton extends React.Component< const {onComplete} = this.props; // reset payment link this.setState(({resetLink, txref}) => ({ - txref: data.flwref && !data.canceled ? null : txref, - resetLink: data.flwref && !data.canceled ? true : resetLink + txref: data.flwref && !data.cancelled ? null : txref, + resetLink: data.flwref && !data.cancelled ? true : resetLink }), () => { // reset @@ -164,7 +164,7 @@ class FlutterwaveButton extends React.Component< onComplete({ flwref: data.flwref, txref: data.txref, - canceled: data.canceled, + cancelled: data.cancelled, }); } ); From d026c3e291803de9ea42c3d26cd7e1e0157a7444 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 16:45:36 +0100 Subject: [PATCH 080/129] feat(flutterwavebutton): add GBP to base options currency option --- src/FlutterwaveButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FlutterwaveButton.tsx b/src/FlutterwaveButton.tsx index 3d17b66..1014ec0 100644 --- a/src/FlutterwaveButton.tsx +++ b/src/FlutterwaveButton.tsx @@ -34,7 +34,7 @@ export interface FlutterwaveButtonPropsBase { export const OptionsPropTypeBase = { amount: PropTypes.number.isRequired, - currency: PropTypes.oneOf(['NGN', 'USD', 'GHS', 'KES', 'ZAR', 'TZS']), + currency: PropTypes.oneOf(['GBP', 'NGN', 'USD', 'GHS', 'KES', 'ZAR', 'TZS']), payment_plan: PropTypes.number, subaccounts: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string.isRequired, From 4c20a28f64b7ee01a50d6edaa0f3e30d8c5f4d7c Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 16:47:47 +0100 Subject: [PATCH 081/129] feat(dist): create build with v2 and v3 options --- dist/DefaultButton.d.ts | 6 +- dist/DefaultButton.d.ts.map | 2 +- dist/DefaultButton.js | 18 +- dist/FlutterwaveButton.d.ts | 115 ++---- dist/FlutterwaveButton.d.ts.map | 2 +- dist/FlutterwaveButton.js | 491 +---------------------- dist/FlutterwaveInit.d.ts | 48 +-- dist/FlutterwaveInit.d.ts.map | 2 +- dist/FlutterwaveInit.js | 103 +---- dist/{ => assets}/loader.gif | Bin dist/{ => assets}/pry-button-content.png | Bin dist/configs.d.ts | 19 +- dist/configs.d.ts.map | 2 +- dist/configs.js | 33 +- dist/index.d.ts | 4 +- dist/index.d.ts.map | 2 +- dist/index.js | 6 +- dist/utils/CustomPropTypesRules.d.ts | 2 +- dist/utils/CustomPropTypesRules.d.ts.map | 2 +- dist/utils/CustomPropTypesRules.js | 9 +- dist/utils/CustomPropTypesRules.ts | 31 -- dist/utils/ResponseParser.d.ts | 2 +- dist/utils/ResponseParser.d.ts.map | 2 +- dist/v2/FlutterwaveButton.d.ts | 91 +++++ dist/v2/FlutterwaveButton.d.ts.map | 1 + dist/v2/FlutterwaveButton.js | 466 +++++++++++++++++++++ dist/v2/FlutterwaveInit.d.ts | 35 ++ dist/v2/FlutterwaveInit.d.ts.map | 1 + dist/v2/FlutterwaveInit.js | 115 ++++++ dist/v3/FlutterwaveButton.d.ts | 89 ++++ dist/v3/FlutterwaveButton.d.ts.map | 1 + dist/v3/FlutterwaveButton.js | 469 ++++++++++++++++++++++ dist/v3/FlutterwaveInit.d.ts | 46 +++ dist/v3/FlutterwaveInit.d.ts.map | 1 + dist/v3/FlutterwaveInit.js | 101 +++++ 35 files changed, 1543 insertions(+), 774 deletions(-) rename dist/{ => assets}/loader.gif (100%) rename dist/{ => assets}/pry-button-content.png (100%) delete mode 100644 dist/utils/CustomPropTypesRules.ts create mode 100644 dist/v2/FlutterwaveButton.d.ts create mode 100644 dist/v2/FlutterwaveButton.d.ts.map create mode 100644 dist/v2/FlutterwaveButton.js create mode 100644 dist/v2/FlutterwaveInit.d.ts create mode 100644 dist/v2/FlutterwaveInit.d.ts.map create mode 100644 dist/v2/FlutterwaveInit.js create mode 100644 dist/v3/FlutterwaveButton.d.ts create mode 100644 dist/v3/FlutterwaveButton.d.ts.map create mode 100644 dist/v3/FlutterwaveButton.js create mode 100644 dist/v3/FlutterwaveInit.d.ts create mode 100644 dist/v3/FlutterwaveInit.d.ts.map create mode 100644 dist/v3/FlutterwaveInit.js diff --git a/dist/DefaultButton.d.ts b/dist/DefaultButton.d.ts index 99d519e..8c363fa 100644 --- a/dist/DefaultButton.d.ts +++ b/dist/DefaultButton.d.ts @@ -1,10 +1,10 @@ import React from 'react'; -import { ViewStyle } from "react-native"; +import { ViewStyle, StyleProp } from "react-native"; interface DefaultButtonProps { - style?: ViewStyle; + style?: StyleProp; onPress?: () => void; disabled?: boolean; - children: React.ReactElement; + children: React.ReactNode; isBusy?: boolean; onSizeChange?: (ev: { width: number; diff --git a/dist/DefaultButton.d.ts.map b/dist/DefaultButton.d.ts.map index bd84c25..555096f 100644 --- a/dist/DefaultButton.d.ts.map +++ b/dist/DefaultButton.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"DefaultButton.d.ts","sourceRoot":"","sources":["../src/DefaultButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAGL,SAAS,EAGV,MAAM,cAAc,CAAC;AAGtB,UAAU,kBAAkB;IAC1B,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,KAAK,IAAI,CAAC;IAC7D,SAAS,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;CACnC;AAgBD;;;GAGG;AACH,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAsC/C,CAAC;AA0CF,eAAe,aAAa,CAAC"} \ No newline at end of file +{"version":3,"file":"DefaultButton.d.ts","sourceRoot":"","sources":["../src/DefaultButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAGL,SAAS,EAGT,SAAS,EACV,MAAM,cAAc,CAAC;AAGtB,UAAU,kBAAkB;IAC1B,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,KAAK,IAAI,CAAC;IAC7D,SAAS,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;CACnC;AAgBD;;;GAGG;AACH,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAsC/C,CAAC;AA0CF,eAAe,aAAa,CAAC"} \ No newline at end of file diff --git a/dist/DefaultButton.js b/dist/DefaultButton.js index f1967a5..f629ea8 100644 --- a/dist/DefaultButton.js +++ b/dist/DefaultButton.js @@ -1,14 +1,3 @@ -var __assign = (this && this.__assign) || function () { - __assign = Object.assign || function(t) { - for (var s, i = 1, n = arguments.length; i < n; i++) { - s = arguments[i]; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) - t[p] = s[p]; - } - return t; - }; - return __assign.apply(this, arguments); -}; import React from 'react'; import { View, StyleSheet, TouchableHighlight, } from "react-native"; import { colors } from './configs'; @@ -36,7 +25,12 @@ var DefaultButton = function Button(_a) { onSizeChange({ width: width, height: height }); } }; - return ( + return ( <> {children} {isBusy diff --git a/dist/FlutterwaveButton.d.ts b/dist/FlutterwaveButton.d.ts index 4fa7254..2b94bb2 100644 --- a/dist/FlutterwaveButton.d.ts +++ b/dist/FlutterwaveButton.d.ts @@ -1,101 +1,44 @@ -import React from 'react'; -import { Animated, ViewStyle } from 'react-native'; -import WebView from 'react-native-webview'; +/// +import { ViewStyle, StyleProp } from 'react-native'; import PropTypes from 'prop-types'; -import { WebViewNavigation } from 'react-native-webview/lib/WebViewTypes'; -import { FlutterwaveInitOptions } from './FlutterwaveInit'; +import FlutterwaveButton from './v3/FlutterwaveButton'; import FlutterwaveInitError from './utils/FlutterwaveInitError'; -interface CustomButtonProps { - disabled: boolean; - isInitializing: boolean; - onPress: () => void; -} -interface RedirectParams { +export interface RedirectParams { status: 'successful' | 'cancelled'; transaction_id?: string; tx_ref: string; } -export interface FlutterwaveButtonProps { - style?: ViewStyle; - onComplete: (data: RedirectParams) => void; +export interface RedirectParamsV2 { + cancelled?: 'true' | 'false'; + flwref?: string; + txref?: string; +} +export interface CustomButtonProps { + disabled: boolean; + isInitializing: boolean; + onPress: () => void; +} +export interface FlutterwaveButtonPropsBase { + style?: StyleProp; + onComplete: (data: any) => void; onWillInitialize?: () => void; onDidInitialize?: () => void; onInitializeError?: (error: FlutterwaveInitError) => void; onAbort?: () => void; - options: Omit; customButton?: (params: CustomButtonProps) => React.ReactNode; alignLeft?: 'alignLeft' | boolean; } -interface FlutterwaveButtonState { - link: string | null; - isPending: boolean; - showDialog: boolean; - animation: Animated.Value; - tx_ref: string | null; - resetLink: boolean; - buttonSize: { - width: number; - height: number; - }; -} -declare class FlutterwaveButton extends React.Component { - static propTypes: { - alignLeft: PropTypes.Requireable; - onAbort: PropTypes.Requireable<(...args: any[]) => any>; - onComplete: PropTypes.Validator<(...args: any[]) => any>; - onWillInitialize: PropTypes.Requireable<(...args: any[]) => any>; - onDidInitialize: PropTypes.Requireable<(...args: any[]) => any>; - onInitializeError: PropTypes.Requireable<(...args: any[]) => any>; - options: PropTypes.Validator; - tx_ref: PropTypes.Validator; - amount: PropTypes.Validator; - currency: PropTypes.Validator; - integrity_hash: PropTypes.Requireable; - payment_options: (props: { - [k: string]: any; - }, propName: string) => Error | null; - payment_plan: PropTypes.Requireable; - customer: PropTypes.Validator; - phonenumber: PropTypes.Requireable; - email: PropTypes.Validator; - }>>; - subaccounts: PropTypes.Requireable<(number | null | undefined)[]>; - meta: PropTypes.Requireable<(object | null | undefined)[]>; - customizations: PropTypes.Requireable; - logo: PropTypes.Requireable; - description: PropTypes.Requireable; - }>>; - }>>; - customButton: PropTypes.Requireable<(...args: any[]) => any>; - }; - state: FlutterwaveButtonState; - webviewRef: WebView | null; - abortController?: AbortController; - componentDidUpdate(prevProps: FlutterwaveButtonProps): void; - componentWillUnmount(): void; - reset: () => void; - handleOptionsChanged: () => void; - handleNavigationStateChange: (ev: WebViewNavigation) => void; - handleComplete(data: RedirectParams): void; - handleReload: () => void; - handleAbortConfirm: () => void; - handleAbort: () => void; - handleButtonResize: (size: { - width: number; - height: number; - }) => void; - getRedirectParams: (url: string) => RedirectParams; - show: () => void; - dismiss: () => void; - handleInit: () => void | null; - render(): JSX.Element; - renderButton(): {} | null | undefined; - renderBackdrop(): JSX.Element; - renderLoading(): JSX.Element; - renderError: () => JSX.Element; -} +export declare const OptionsPropTypeBase: { + amount: PropTypes.Validator; + currency: PropTypes.Requireable; + payment_plan: PropTypes.Requireable; + subaccounts: PropTypes.Requireable<(PropTypes.InferProps<{ + id: PropTypes.Validator; + transaction_split_ratio: PropTypes.Requireable; + transaction_charge_type: PropTypes.Requireable; + transaction_charge: PropTypes.Requireable; + }> | null | undefined)[]>; + integrity_hash: PropTypes.Requireable; +}; export default FlutterwaveButton; //# sourceMappingURL=FlutterwaveButton.d.ts.map \ No newline at end of file diff --git a/dist/FlutterwaveButton.d.ts.map b/dist/FlutterwaveButton.d.ts.map index 52ec128..05723cd 100644 --- a/dist/FlutterwaveButton.d.ts.map +++ b/dist/FlutterwaveButton.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"FlutterwaveButton.d.ts","sourceRoot":"","sources":["../src/FlutterwaveButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAIL,QAAQ,EAGR,SAAS,EAMV,MAAM,cAAc,CAAC;AACtB,OAAO,OAAO,MAAM,sBAAsB,CAAC;AAC3C,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,EAAC,iBAAiB,EAAC,MAAM,uCAAuC,CAAC;AACxE,OAAwB,EAAC,sBAAsB,EAAC,MAAM,mBAAmB,CAAC;AAI1E,OAAO,oBAAoB,MAAM,8BAA8B,CAAC;AAYhE,UAAU,iBAAiB;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,UAAU,cAAc;IACtB,MAAM,EAAE,YAAY,GAAG,WAAW,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,UAAU,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAC3C,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC1D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,IAAI,CAAC,sBAAsB,EAAE,cAAc,CAAC,CAAC;IACtD,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,KAAK,CAAC,SAAS,CAAC;IAC9D,SAAS,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;CACnC;AAED,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC;IAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,cAAM,iBAAkB,SAAQ,KAAK,CAAC,SAAS,CAC7C,sBAAsB,EACtB,sBAAsB,CACvB;IACC,MAAM,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA6Bd;IAEF,KAAK,EAAE,sBAAsB,CAW3B;IAEF,UAAU,EAAE,OAAO,GAAG,IAAI,CAAQ;IAElC,eAAe,CAAC,EAAE,eAAe,CAAC;IAElC,kBAAkB,CAAC,SAAS,EAAE,sBAAsB;IAMpD,oBAAoB;IAMpB,KAAK,aAWH;IAEF,oBAAoB,aAYnB;IAED,2BAA2B,kCASzB;IAEF,cAAc,CAAC,IAAI,EAAE,cAAc;IAgBnC,YAAY,aAKV;IAEF,kBAAkB,aAQhB;IAEF,WAAW,aAST;IAEF,kBAAkB;;;eAKhB;IAEF,iBAAiB,kCAgBf;IAEF,IAAI,aAUF;IAEF,OAAO,aAOL;IAEF,UAAU,oBAqER;IAEF,MAAM;IAsCN,YAAY;IA+CZ,cAAc;IAcd,aAAa;IAYb,WAAW,oBAkBT;CACH;AAuED,eAAe,iBAAiB,CAAC"} \ No newline at end of file +{"version":3,"file":"FlutterwaveButton.d.ts","sourceRoot":"","sources":["../src/FlutterwaveButton.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAC,SAAS,EAAE,SAAS,EAAC,MAAM,cAAc,CAAC;AAClD,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,iBAAiB,MAAM,wBAAwB,CAAC;AACvD,OAAO,oBAAoB,MAAM,8BAA8B,CAAC;AAEhE,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,YAAY,GAAG,WAAW,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,UAAU,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAChC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC1D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,KAAK,CAAC,SAAS,CAAC;IAC9D,SAAS,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;CACnC;AAED,eAAO,MAAM,mBAAmB;;;;;;;;;;;CAW/B,CAAC;AAEF,eAAe,iBAAiB,CAAC"} \ No newline at end of file diff --git a/dist/FlutterwaveButton.js b/dist/FlutterwaveButton.js index 2394b99..7cb4685 100644 --- a/dist/FlutterwaveButton.js +++ b/dist/FlutterwaveButton.js @@ -1,480 +1,15 @@ -var __extends = (this && this.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -var __assign = (this && this.__assign) || function () { - __assign = Object.assign || function(t) { - for (var s, i = 1, n = arguments.length; i < n; i++) { - s = arguments[i]; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) - t[p] = s[p]; - } - return t; - }; - return __assign.apply(this, arguments); -}; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (_) try { - 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; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -}; -import React from 'react'; -import { StyleSheet, Modal, View, Animated, TouchableWithoutFeedback, Text, Alert, Image, Platform, Dimensions, Easing, } from 'react-native'; -import WebView from 'react-native-webview'; import PropTypes from 'prop-types'; -import FlutterwaveInit from './FlutterwaveInit'; -import { colors, REDIRECT_URL } from './configs'; -import { PaymentOptionsPropRule } from './utils/CustomPropTypesRules'; -import DefaultButton from './DefaultButton'; -import FlutterwaveInitError from './utils/FlutterwaveInitError'; -var loader = require('./loader.gif'); -var pryContent = require('./pry-button-content.png'); -var contentWidthPercentage = 0.6549707602; -var contentSizeDimension = 8.2962962963; -var contentMaxWidth = 187.3; -var contentMaxHeight = contentMaxWidth / contentSizeDimension; -var contentMinWidth = 187.3; -var contentMinHeight = contentMinWidth / contentSizeDimension; -var borderRadiusDimension = 24 / 896; -var windowHeight = Dimensions.get('window').height; -var FlutterwaveButton = /** @class */ (function (_super) { - __extends(FlutterwaveButton, _super); - function FlutterwaveButton() { - var _this = _super !== null && _super.apply(this, arguments) || this; - _this.state = { - isPending: false, - link: null, - resetLink: false, - showDialog: false, - animation: new Animated.Value(0), - tx_ref: null, - buttonSize: { - width: 0, - height: 0 - } - }; - _this.webviewRef = null; - _this.reset = function () { - if (_this.abortController) { - _this.abortController.abort(); - } - // reset the necessaries - _this.setState(function (_a) { - var resetLink = _a.resetLink, link = _a.link; - return ({ - isPending: false, - link: resetLink ? null : link, - resetLink: false, - showDialog: false - }); - }); - }; - _this.handleOptionsChanged = function () { - var _a = _this.state, showDialog = _a.showDialog, link = _a.link; - if (!link) { - return; - } - if (!showDialog) { - return _this.setState({ - link: null, - tx_ref: null - }); - } - _this.setState({ resetLink: true }); - }; - _this.handleNavigationStateChange = function (ev) { - // cregex to check if redirect has occured on completion/cancel - var rx = /\/flutterwave\.com\/rn-redirect/; - // Don't end payment if not redirected back - if (!rx.test(ev.url)) { - return; - } - // fire handle complete - _this.handleComplete(_this.getRedirectParams(ev.url)); - }; - _this.handleReload = function () { - // fire if webview is set - if (_this.webviewRef) { - _this.webviewRef.reload(); - } - }; - _this.handleAbortConfirm = function () { - var onAbort = _this.props.onAbort; - // abort action - if (onAbort) { - onAbort(); - } - // remove tx_ref and dismiss - _this.dismiss(); - }; - _this.handleAbort = function () { - Alert.alert('', 'Are you sure you want to cancel this payment?', [ - { text: 'No' }, - { - text: 'Yes, Cancel', - style: 'destructive', - onPress: _this.handleAbortConfirm - }, - ]); - }; - _this.handleButtonResize = function (size) { - var buttonSize = _this.state.buttonSize; - if (JSON.stringify(buttonSize) !== JSON.stringify(size)) { - _this.setState({ buttonSize: size }); - } - }; - _this.getRedirectParams = function (url) { - // initialize result container - var res = {}; - // if url has params - if (url.split('?').length > 1) { - // get query params in an array - var params = url.split('?')[1].split('&'); - // add url params to result - for (var i = 0; i < params.length; i++) { - var param = params[i].split('='); - var val = decodeURIComponent(param[1]).trim(); - res[param[0]] = String(val); - } - } - // return result - return res; - }; - _this.show = function () { - var animation = _this.state.animation; - _this.setState({ showDialog: true }, function () { - Animated.timing(animation, { - toValue: 1, - duration: 700, - easing: Easing["in"](Easing.elastic(0.72)), - useNativeDriver: false - }).start(); - }); - }; - _this.dismiss = function () { - var animation = _this.state.animation; - Animated.timing(animation, { - toValue: 0, - duration: 400, - useNativeDriver: false - }).start(_this.reset); - }; - _this.handleInit = function () { - var _a = _this.props, options = _a.options, onWillInitialize = _a.onWillInitialize, onInitializeError = _a.onInitializeError, onDidInitialize = _a.onDidInitialize; - var _b = _this.state, isPending = _b.isPending, tx_ref = _b.tx_ref, link = _b.link; - // just show the dialod if the link is already set - if (link) { - return _this.show(); - } - // throw error if transaction reference has not changed - if (tx_ref === options.tx_ref) { - return onInitializeError ? onInitializeError(new FlutterwaveInitError({ - message: 'Please generate a new transaction reference.', - code: 'SAME_TXREF' - })) : null; - } - // stop if currently in pending mode - if (isPending) { - return; - } - // initialize abort controller if not set - _this.abortController = new AbortController; - // fire will initialize handler if available - if (onWillInitialize) { - onWillInitialize(); - } - // @ts-ignore - // delete redirect url if set - delete options.redirect_url; - // set pending state to true - _this.setState({ - isPending: true, - link: null, - tx_ref: options.tx_ref - }, function () { return __awaiter(_this, void 0, void 0, function () { - var link_1, error_1; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - _a.trys.push([0, 2, , 3]); - return [4 /*yield*/, FlutterwaveInit(__assign(__assign({}, options), { redirect_url: REDIRECT_URL }), this.abortController)]; - case 1: - link_1 = _a.sent(); - // resent pending mode - this.setState({ link: link_1, isPending: false }, this.show); - // fire did initialize handler if available - if (onDidInitialize) { - onDidInitialize(); - } - return [3 /*break*/, 3]; - case 2: - error_1 = _a.sent(); - // stop if request was canceled - if (/aborterror/i.test(error_1.code)) { - return [2 /*return*/]; - } - if (onInitializeError) { - onInitializeError(error_1); - } - return [2 /*return*/, this.setState({ - resetLink: true, - tx_ref: null - }, this.dismiss)]; - case 3: return [2 /*return*/]; - } - }); - }); }); - }; - _this.renderError = function () { - var link = _this.state.link; - return ( - {link ? (<> - - The page failed to load, please try again. - - - - Try Again - - - ) : null} - ); - }; - return _this; - } - FlutterwaveButton.prototype.componentDidUpdate = function (prevProps) { - if (JSON.stringify(prevProps.options) !== JSON.stringify(this.props.options)) { - this.handleOptionsChanged(); - } - }; - FlutterwaveButton.prototype.componentWillUnmount = function () { - if (this.abortController) { - this.abortController.abort(); - } - }; - FlutterwaveButton.prototype.handleComplete = function (data) { - var _this = this; - var onComplete = this.props.onComplete; - // reset payment link - this.setState(function (_a) { - var resetLink = _a.resetLink, tx_ref = _a.tx_ref; - return ({ - tx_ref: data.status === 'successful' ? null : tx_ref, - resetLink: data.status === 'successful' ? true : resetLink - }); - }, function () { - // reset - _this.dismiss(); - // fire onComplete handler - onComplete(data); - }); - }; - FlutterwaveButton.prototype.render = function () { - var _this = this; - var _a = this.state, link = _a.link, animation = _a.animation, showDialog = _a.showDialog; - var marginTop = animation.interpolate({ - inputRange: [0, 1], - outputRange: [windowHeight, Platform.OS === 'ios' ? 46 : 14] - }); - var opacity = animation.interpolate({ - inputRange: [0, 0.3, 1], - outputRange: [0, 1, 1] - }); - // render UI - return (<> - {this.renderButton()} - - {this.renderBackdrop()} - - - - - ); - }; - FlutterwaveButton.prototype.renderButton = function () { - var _a = this.props, customButton = _a.customButton, style = _a.style, alignLeft = _a.alignLeft; - var _b = this.state, isPending = _b.isPending, link = _b.link, showDialog = _b.showDialog, buttonSize = _b.buttonSize; - var contentWidth = buttonSize.width * contentWidthPercentage; - var contentHeight = contentWidth / contentSizeDimension; - var contentSizeStyle = { - width: contentWidth > contentMaxWidth - ? contentMaxWidth - : contentWidth < contentMinWidth - ? contentMinWidth - : contentWidth, - height: contentHeight > contentMaxHeight - ? contentMaxHeight - : contentHeight < contentMinHeight - ? contentMinHeight - : contentHeight - }; - // render custom button - if (customButton) { - return customButton({ - isInitializing: isPending && !link ? true : false, - disabled: isPending || showDialog ? true : false, - onPress: this.handleInit - }); - } - // render primary button - return ( - - ); - }; - FlutterwaveButton.prototype.renderBackdrop = function () { - var animation = this.state.animation; - // Interpolation backdrop animation - var backgroundColor = animation.interpolate({ - inputRange: [0, 0.3, 1], - outputRange: [colors.transparent, colors.transparent, 'rgba(0,0,0,0.5)'] - }); - return ( - - ); - }; - FlutterwaveButton.prototype.renderLoading = function () { - return ( - - ); - }; - FlutterwaveButton.propTypes = { - alignLeft: PropTypes.bool, - onAbort: PropTypes.func, - onComplete: PropTypes.func.isRequired, - onWillInitialize: PropTypes.func, - onDidInitialize: PropTypes.func, - onInitializeError: PropTypes.func, - options: PropTypes.shape({ - authorization: PropTypes.string.isRequired, - tx_ref: PropTypes.string.isRequired, - amount: PropTypes.number.isRequired, - currency: PropTypes.oneOf(['NGN', 'USD', 'GBP', 'GHS', 'KES', 'ZAR', 'TZS']).isRequired, - integrity_hash: PropTypes.string, - payment_options: PaymentOptionsPropRule, - payment_plan: PropTypes.number, - customer: PropTypes.shape({ - name: PropTypes.string, - phonenumber: PropTypes.string, - email: PropTypes.string.isRequired - }).isRequired, - subaccounts: PropTypes.arrayOf(PropTypes.number), - meta: PropTypes.arrayOf(PropTypes.object), - customizations: PropTypes.shape({ - title: PropTypes.string, - logo: PropTypes.string, - description: PropTypes.string - }) - }).isRequired, - customButton: PropTypes.func - }; - return FlutterwaveButton; -}(React.Component)); -var styles = StyleSheet.create({ - promtActions: { - flexDirection: 'row', - alignItems: 'center' - }, - promptActionText: { - textAlign: 'center', - color: colors.primary, - fontSize: 16, - paddingHorizontal: 16, - paddingVertical: 16 - }, - promptQuestion: { - color: colors.secondary, - textAlign: 'center', - marginBottom: 32, - fontSize: 18 - }, - prompt: { - position: 'absolute', - left: 0, - right: 0, - bottom: 0, - top: 0, - backgroundColor: '#ffffff', - justifyContent: 'center', - alignItems: 'center', - paddingHorizontal: 56 - }, - backdrop: { - position: 'absolute', - left: 0, - right: 0, - bottom: 0, - top: 0 - }, - loadingImage: { - width: 64, - height: 64, - resizeMode: 'contain' - }, - loading: { - position: 'absolute', - top: 0, - right: 0, - bottom: 0, - left: 0, - backgroundColor: 'rgba(255, 255, 255, 0.3)', - justifyContent: 'center', - alignItems: 'center' - }, - webviewContainer: { - top: 50, - flex: 1, - backgroundColor: '#efefef', - paddingBottom: 50, - overflow: 'hidden', - borderTopLeftRadius: windowHeight * borderRadiusDimension, - borderTopRightRadius: windowHeight * borderRadiusDimension - }, - webview: { - flex: 1, - backgroundColor: 'rgba(0,0,0,0)' - }, - buttonContent: { - resizeMode: 'contain' - } -}); +import FlutterwaveButton from './v3/FlutterwaveButton'; +export var OptionsPropTypeBase = { + amount: PropTypes.number.isRequired, + currency: PropTypes.oneOf(['GBP', 'NGN', 'USD', 'GHS', 'KES', 'ZAR', 'TZS']), + payment_plan: PropTypes.number, + subaccounts: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string.isRequired, + transaction_split_ratio: PropTypes.number, + transaction_charge_type: PropTypes.string, + transaction_charge: PropTypes.number + })), + integrity_hash: PropTypes.string +}; export default FlutterwaveButton; diff --git a/dist/FlutterwaveInit.d.ts b/dist/FlutterwaveInit.d.ts index 063485c..391cd83 100644 --- a/dist/FlutterwaveInit.d.ts +++ b/dist/FlutterwaveInit.d.ts @@ -1,58 +1,18 @@ -/// -interface FlutterwavePaymentMeta { - [k: string]: any; -} -export interface FlutterwaveInitCustomer { - email: string; - phonenumber?: string; - name?: string; -} -export interface FlutterwaveInitCustomizations { - title?: string; - logo?: string; - description?: string; -} +import FlutterwaveInit from './v3/FlutterwaveInit'; export interface FlutterwaveInitSubAccount { id: string; transaction_split_ratio?: number; transaction_charge_type?: string; transaction_charge?: number; } -export interface FlutterwaveInitOptions { - authorization: string; - tx_ref: string; +export interface FlutterwaveInitOptionsBase { amount: number; - currency: string; + currency?: string; integrity_hash?: string; payment_options?: string; payment_plan?: number; redirect_url: string; - customer: FlutterwaveInitCustomer; subaccounts?: Array; - meta?: Array; - customizations?: FlutterwaveInitCustomizations; -} -export interface FieldError { - field: string; - message: string; -} -export interface ResponseData { - status?: 'success' | 'error'; - message: string; - error_id?: string; - errors?: Array; - code?: string; - data?: { - link: string; - }; } -/** - * This function is responsible for making the request to - * initialize a Flutterwave payment. - * @param options FlutterwaveInitOptions - * @param abortController AbortController - * @return Promise - */ -export default function FlutterwaveInit(options: FlutterwaveInitOptions, abortController?: AbortController): Promise; -export {}; +export default FlutterwaveInit; //# sourceMappingURL=FlutterwaveInit.d.ts.map \ No newline at end of file diff --git a/dist/FlutterwaveInit.d.ts.map b/dist/FlutterwaveInit.d.ts.map index 726de58..7973f12 100644 --- a/dist/FlutterwaveInit.d.ts.map +++ b/dist/FlutterwaveInit.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"FlutterwaveInit.d.ts","sourceRoot":"","sources":["../src/FlutterwaveInit.ts"],"names":[],"mappings":";AAIA,UAAU,sBAAsB;IAC9B,CAAC,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,6BAA6B;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,uBAAuB,CAAC;IAClC,WAAW,CAAC,EAAE,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC/C,IAAI,CAAC,EAAE,KAAK,CAAC,sBAAsB,CAAC,CAAC;IACrC,cAAc,CAAC,EAAE,6BAA6B,CAAC;CAChD;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AASD;;;;;;GAMG;AACH,wBAA8B,eAAe,CAC3C,OAAO,EAAE,sBAAsB,EAC/B,eAAe,CAAC,EAAE,eAAe,GAChC,OAAO,CAAC,MAAM,CAAC,CAgCjB"} \ No newline at end of file +{"version":3,"file":"FlutterwaveInit.d.ts","sourceRoot":"","sources":["../src/FlutterwaveInit.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,MAAM,sBAAsB,CAAC;AAEnD,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,KAAK,CAAC,yBAAyB,CAAC,CAAC;CAChD;AAED,eAAe,eAAe,CAAC"} \ No newline at end of file diff --git a/dist/FlutterwaveInit.js b/dist/FlutterwaveInit.js index 13d7f87..508ec51 100644 --- a/dist/FlutterwaveInit.js +++ b/dist/FlutterwaveInit.js @@ -1,101 +1,2 @@ -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (_) try { - 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; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -}; -var __rest = (this && this.__rest) || function (s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) - t[p[i]] = s[p[i]]; - } - return t; -}; -import { STANDARD_URL } from './configs'; -import ResponseParser from './utils/ResponseParser'; -import FlutterwaveInitError from './utils/FlutterwaveInitError'; -/** - * This function is responsible for making the request to - * initialize a Flutterwave payment. - * @param options FlutterwaveInitOptions - * @param abortController AbortController - * @return Promise - */ -export default function FlutterwaveInit(options, abortController) { - return __awaiter(this, void 0, void 0, function () { - var authorization, body, headers, fetchOptions, response, responseData, _a, _b, e_1, error; - return __generator(this, function (_c) { - switch (_c.label) { - case 0: - _c.trys.push([0, 4, , 5]); - authorization = options.authorization, body = __rest(options, ["authorization"]); - headers = new Headers; - headers.append('Content-Type', 'application/json'); - headers.append('Authorization', "Bearer " + authorization); - fetchOptions = { - method: 'POST', - body: JSON.stringify(body), - headers: headers - }; - // add abortController if defined - if (abortController) { - fetchOptions.signal = abortController.signal; - } - ; - return [4 /*yield*/, fetch(STANDARD_URL, fetchOptions)]; - case 1: - response = _c.sent(); - return [4 /*yield*/, response.json()]; - case 2: - responseData = _c.sent(); - _b = (_a = Promise).resolve; - return [4 /*yield*/, ResponseParser(responseData)]; - case 3: - // resolve with the payment link - return [2 /*return*/, _b.apply(_a, [_c.sent()])]; - case 4: - e_1 = _c.sent(); - error = e_1 instanceof FlutterwaveInitError - ? e_1 - : new FlutterwaveInitError({ message: e_1.message, code: e_1.name.toUpperCase() }); - // resolve with error - return [2 /*return*/, Promise.reject(error)]; - case 5: return [2 /*return*/]; - } - }); - }); -} +import FlutterwaveInit from './v3/FlutterwaveInit'; +export default FlutterwaveInit; diff --git a/dist/loader.gif b/dist/assets/loader.gif similarity index 100% rename from dist/loader.gif rename to dist/assets/loader.gif diff --git a/dist/pry-button-content.png b/dist/assets/pry-button-content.png similarity index 100% rename from dist/pry-button-content.png rename to dist/assets/pry-button-content.png diff --git a/dist/configs.d.ts b/dist/configs.d.ts index 624273a..6e6b793 100644 --- a/dist/configs.d.ts +++ b/dist/configs.d.ts @@ -1,12 +1,29 @@ /** - * Flutterwaves standard init url. + * V# API Standard initialization endpoint */ export declare const STANDARD_URL = "https://api.flutterwave.com/v3/payments"; +/** + * Redirect URL used in V3 FlutterwaveButton + */ export declare const REDIRECT_URL = "https://flutterwave.com/rn-redirect"; +/** + * Fluttereave volors + */ export declare const colors: { primary: string; secondary: string; transparent: string; }; +/** + * Payment options available in V3 + */ export declare const PAYMENT_OPTIONS: string[]; +/** + * V2 API standard initialization endpoint + */ +export declare const STANDARD_URL_V2: string; +/** + * Payment options available in V2 API + */ +export declare const PAYMENT_OPTIONS_V2: string[]; //# sourceMappingURL=configs.d.ts.map \ No newline at end of file diff --git a/dist/configs.d.ts.map b/dist/configs.d.ts.map index 54c6658..bf4cdcd 100644 --- a/dist/configs.d.ts.map +++ b/dist/configs.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"configs.d.ts","sourceRoot":"","sources":["../src/configs.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,YAAY,4CAA4C,CAAC;AAEtE,eAAO,MAAM,YAAY,wCAAwC,CAAC;AAElE,eAAO,MAAM,MAAM;;;;CAIlB,CAAC;AAEF,eAAO,MAAM,eAAe,UAkB3B,CAAC"} \ No newline at end of file +{"version":3,"file":"configs.d.ts","sourceRoot":"","sources":["../src/configs.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,YAAY,4CAA4C,CAAC;AAEtE;;GAEG;AACH,eAAO,MAAM,YAAY,wCAAwC,CAAC;AAElE;;GAEG;AACH,eAAO,MAAM,MAAM;;;;CAIlB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,UAkB3B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,MACiC,CAAC;AAEhE;;GAEG;AACH,eAAO,MAAM,kBAAkB,UAc9B,CAAC"} \ No newline at end of file diff --git a/dist/configs.js b/dist/configs.js index 3f84bee..cbece43 100644 --- a/dist/configs.js +++ b/dist/configs.js @@ -1,13 +1,22 @@ /** - * Flutterwaves standard init url. + * V# API Standard initialization endpoint */ export var STANDARD_URL = 'https://api.flutterwave.com/v3/payments'; +/** + * Redirect URL used in V3 FlutterwaveButton + */ export var REDIRECT_URL = 'https://flutterwave.com/rn-redirect'; +/** + * Fluttereave volors + */ export var colors = { primary: '#f5a623', secondary: '#12122C', transparent: 'rgba(0,0,0,0)' }; +/** + * Payment options available in V3 + */ export var PAYMENT_OPTIONS = [ 'account', 'card', @@ -27,3 +36,25 @@ export var PAYMENT_OPTIONS = [ '1voucher', 'mobilemoneytanzania', ]; +/** + * V2 API standard initialization endpoint + */ +export var STANDARD_URL_V2 = 'https://api.ravepay.co/flwv3-pug/getpaidx/api/v2/hosted/pay'; +/** + * Payment options available in V2 API + */ +export var PAYMENT_OPTIONS_V2 = [ + 'card', + 'account', + 'ussd', + 'qr', + 'mpesa', + 'mobilemoneyghana', + 'mobilemoneyuganda', + 'mobilemoneyrwanda', + 'mobilemoneyzambia', + 'mobilemoneytanzania', + 'barter', + 'bank transfer', + 'wechat', +]; diff --git a/dist/index.d.ts b/dist/index.d.ts index 4dfcca9..1deec54 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,6 +1,8 @@ import FlutterwaveInit from './FlutterwaveInit'; import FlutterwaveButton from './FlutterwaveButton'; +import FlutterwaveInitV2 from './v2/FlutterwaveInit'; +import FlutterwaveButtonV2 from './v2/FlutterwaveButton'; import DefaultButton from './DefaultButton'; -export { FlutterwaveInit, FlutterwaveButton, DefaultButton }; +export { FlutterwaveInit, FlutterwaveButton, FlutterwaveInitV2, FlutterwaveButtonV2, DefaultButton, }; export default FlutterwaveButton; //# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/index.d.ts.map b/dist/index.d.ts.map index 236c983..fb45d47 100644 --- a/dist/index.d.ts.map +++ b/dist/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAChD,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,aAAa,MAAM,iBAAiB,CAAC;AAG5C,OAAO,EAAC,eAAe,EAAE,iBAAiB,EAAE,aAAa,EAAC,CAAC;AAG3D,eAAe,iBAAiB,CAAC"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAChD,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,iBAAiB,MAAM,sBAAsB,CAAC;AACrD,OAAO,mBAAmB,MAAM,wBAAwB,CAAC;AACzD,OAAO,aAAa,MAAM,iBAAiB,CAAC;AAG5C,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,aAAa,GACd,CAAC;AAGF,eAAe,iBAAiB,CAAC"} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index df1c4a6..e08582c 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,7 +1,9 @@ import FlutterwaveInit from './FlutterwaveInit'; import FlutterwaveButton from './FlutterwaveButton'; +import FlutterwaveInitV2 from './v2/FlutterwaveInit'; +import FlutterwaveButtonV2 from './v2/FlutterwaveButton'; import DefaultButton from './DefaultButton'; // export modules -export { FlutterwaveInit, FlutterwaveButton, DefaultButton }; -// export init as default +export { FlutterwaveInit, FlutterwaveButton, FlutterwaveInitV2, FlutterwaveButtonV2, DefaultButton, }; +// export v3 button as default export default FlutterwaveButton; diff --git a/dist/utils/CustomPropTypesRules.d.ts b/dist/utils/CustomPropTypesRules.d.ts index ff349f6..80b4353 100644 --- a/dist/utils/CustomPropTypesRules.d.ts +++ b/dist/utils/CustomPropTypesRules.d.ts @@ -1,4 +1,4 @@ -export declare const PaymentOptionsPropRule: (props: { +export declare const PaymentOptionsPropRule: (options: string[]) => (props: { [k: string]: any; }, propName: string) => Error | null; //# sourceMappingURL=CustomPropTypesRules.d.ts.map \ No newline at end of file diff --git a/dist/utils/CustomPropTypesRules.d.ts.map b/dist/utils/CustomPropTypesRules.d.ts.map index f2687be..f2db249 100644 --- a/dist/utils/CustomPropTypesRules.d.ts.map +++ b/dist/utils/CustomPropTypesRules.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"CustomPropTypesRules.d.ts","sourceRoot":"","sources":["../../src/utils/CustomPropTypesRules.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,sBAAsB;;oCA4BlC,CAAA"} \ No newline at end of file +{"version":3,"file":"CustomPropTypesRules.d.ts","sourceRoot":"","sources":["../../src/utils/CustomPropTypesRules.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,sBAAsB;;oCA4BlC,CAAA"} \ No newline at end of file diff --git a/dist/utils/CustomPropTypesRules.js b/dist/utils/CustomPropTypesRules.js index 25edbed..9e3645a 100644 --- a/dist/utils/CustomPropTypesRules.js +++ b/dist/utils/CustomPropTypesRules.js @@ -1,5 +1,4 @@ -import { PAYMENT_OPTIONS } from "../configs"; -export var PaymentOptionsPropRule = function (props, propName) { +export var PaymentOptionsPropRule = function (options) { return function (props, propName) { // skip check if payment options is not defined if (props[propName] === undefined) { return null; @@ -10,8 +9,8 @@ export var PaymentOptionsPropRule = function (props, propName) { } var paymentOptionsList = props[propName].split(','); var _loop_1 = function (i) { - if (PAYMENT_OPTIONS.findIndex(function (j) { return j.trim() === paymentOptionsList[i].trim(); }) === -1) { - return { value: new Error("\"payment_options\"(" + props[propName] + ") must be any of the following values.\n" + PAYMENT_OPTIONS.map(function (i, n) { return n + 1 + ". " + i + "\n"; }).join('')) }; + if (options.findIndex(function (j) { return j.trim() === paymentOptionsList[i].trim(); }) === -1) { + return { value: new Error("\"payment_options\"(" + props[propName] + ") must be any of the following values.\n" + options.map(function (i, n) { return n + 1 + ". " + i + "\n"; }).join('')) }; } }; for (var i = 0; i < paymentOptionsList.length; i++) { @@ -20,4 +19,4 @@ export var PaymentOptionsPropRule = function (props, propName) { return state_1.value; } return null; -}; +}; }; diff --git a/dist/utils/CustomPropTypesRules.ts b/dist/utils/CustomPropTypesRules.ts deleted file mode 100644 index 900c488..0000000 --- a/dist/utils/CustomPropTypesRules.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { PAYMENT_OPTIONS } from "../configs"; - -export const PaymentOptionsPropRule = (props:{[k: string]: any}, propName: string) => { - // skip check if payment options is not defined - if (props[propName] === undefined) { - return null; - } - // if not an array of payment options - if (typeof props[propName] !== 'string') { - return new Error( - '"payment_methods" should be a string.', - ); - } - const paymentOptionsList = props[propName].split(','); - for (let i = 0; i < paymentOptionsList.length; i++) { - if ( - PAYMENT_OPTIONS.findIndex( - (j) => j.trim() === paymentOptionsList[i].trim(), - ) === -1 - ) { - return new Error( - `"payment_options"(${ - props[propName] - }) must be any of the following values.\n${PAYMENT_OPTIONS.map( - (i, n) => `${n + 1}. ${i}\n`, - ).join('')}`, - ); - } - } - return null; -} diff --git a/dist/utils/ResponseParser.d.ts b/dist/utils/ResponseParser.d.ts index 62c8308..0343b6f 100644 --- a/dist/utils/ResponseParser.d.ts +++ b/dist/utils/ResponseParser.d.ts @@ -1,4 +1,4 @@ -import { ResponseData } from "../FlutterwaveInit"; +import { ResponseData } from "../v3/FlutterwaveInit"; /** * The purpose of this function is to parse the response message gotten from a * payment initialization error. diff --git a/dist/utils/ResponseParser.d.ts.map b/dist/utils/ResponseParser.d.ts.map index b50336e..a1f4b96 100644 --- a/dist/utils/ResponseParser.d.ts.map +++ b/dist/utils/ResponseParser.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"ResponseParser.d.ts","sourceRoot":"","sources":["../../src/utils/ResponseParser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,cAAc,CACpC,EACE,MAAM,EACN,MAAM,EACN,OAAO,EACP,IAAI,EACJ,IAAI,EACJ,QAAQ,GACT,EAAE,YAAY,GACd,OAAO,CAAC,MAAM,CAAC,CA6CjB"} \ No newline at end of file +{"version":3,"file":"ResponseParser.d.ts","sourceRoot":"","sources":["../../src/utils/ResponseParser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,MAAM,uBAAuB,CAAC;AAGnD;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,cAAc,CACpC,EACE,MAAM,EACN,MAAM,EACN,OAAO,EACP,IAAI,EACJ,IAAI,EACJ,QAAQ,GACT,EAAE,YAAY,GACd,OAAO,CAAC,MAAM,CAAC,CA6CjB"} \ No newline at end of file diff --git a/dist/v2/FlutterwaveButton.d.ts b/dist/v2/FlutterwaveButton.d.ts new file mode 100644 index 0000000..f877798 --- /dev/null +++ b/dist/v2/FlutterwaveButton.d.ts @@ -0,0 +1,91 @@ +import React from 'react'; +import { Animated } from 'react-native'; +import WebView from 'react-native-webview'; +import PropTypes from 'prop-types'; +import { WebViewNavigation } from 'react-native-webview/lib/WebViewTypes'; +import { FlutterwaveButtonPropsBase, RedirectParamsV2 } from '../FlutterwaveButton'; +import { FlutterwaveInitOptions } from './FlutterwaveInit'; +export declare type FlutterwaveButtonProps = FlutterwaveButtonPropsBase & { + onComplete: (data: RedirectParamsV2) => void; + options: Omit; +}; +interface FlutterwaveButtonState { + link: string | null; + isPending: boolean; + showDialog: boolean; + animation: Animated.Value; + txref: string | null; + resetLink: boolean; + buttonSize: { + width: number; + height: number; + }; +} +declare class FlutterwaveButton extends React.Component { + static propTypes: { + alignLeft: PropTypes.Requireable; + onAbort: PropTypes.Requireable<(...args: any[]) => any>; + onComplete: PropTypes.Validator<(...args: any[]) => any>; + onWillInitialize: PropTypes.Requireable<(...args: any[]) => any>; + onDidInitialize: PropTypes.Requireable<(...args: any[]) => any>; + onInitializeError: PropTypes.Requireable<(...args: any[]) => any>; + options: PropTypes.Validator Error | null; + txref: PropTypes.Validator; + PBFPubKey: PropTypes.Validator; + customer_firstname: PropTypes.Requireable; + customer_lastname: PropTypes.Requireable; + customer_email: PropTypes.Validator; + customer_phone: PropTypes.Requireable; + country: PropTypes.Requireable; + pay_button_text: PropTypes.Requireable; + custom_title: PropTypes.Requireable; + custom_description: PropTypes.Requireable; + custom_logo: PropTypes.Requireable; + meta: PropTypes.Requireable<(PropTypes.InferProps<{ + metaname: PropTypes.Requireable; + metavalue: PropTypes.Requireable; + }> | null | undefined)[]>; + amount: PropTypes.Validator; + currency: PropTypes.Requireable; + payment_plan: PropTypes.Requireable; + subaccounts: PropTypes.Requireable<(PropTypes.InferProps<{ + id: PropTypes.Validator; + transaction_split_ratio: PropTypes.Requireable; + transaction_charge_type: PropTypes.Requireable; + transaction_charge: PropTypes.Requireable; + }> | null | undefined)[]>; + integrity_hash: PropTypes.Requireable; + }>>; + customButton: PropTypes.Requireable<(...args: any[]) => any>; + }; + state: FlutterwaveButtonState; + webviewRef: WebView | null; + abortController?: AbortController; + componentDidUpdate(prevProps: FlutterwaveButtonProps): void; + componentWillUnmount(): void; + reset: () => void; + handleOptionsChanged: () => void; + handleNavigationStateChange: (ev: WebViewNavigation) => void; + handleComplete(data: RedirectParamsV2): void; + handleReload: () => void; + handleAbortConfirm: () => void; + handleAbort: () => void; + handleButtonResize: (size: { + width: number; + height: number; + }) => void; + getRedirectParams: (url: string) => RedirectParamsV2; + show: () => void; + dismiss: () => void; + handleInit: () => void | null; + render(): JSX.Element; + renderButton(): {} | null | undefined; + renderBackdrop(): JSX.Element; + renderLoading(): JSX.Element; + renderError: () => JSX.Element; +} +export default FlutterwaveButton; +//# sourceMappingURL=FlutterwaveButton.d.ts.map \ No newline at end of file diff --git a/dist/v2/FlutterwaveButton.d.ts.map b/dist/v2/FlutterwaveButton.d.ts.map new file mode 100644 index 0000000..0439e85 --- /dev/null +++ b/dist/v2/FlutterwaveButton.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"FlutterwaveButton.d.ts","sourceRoot":"","sources":["../../src/v2/FlutterwaveButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAIL,QAAQ,EAQT,MAAM,cAAc,CAAC;AACtB,OAAO,OAAO,MAAM,sBAAsB,CAAC;AAC3C,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,EAAC,iBAAiB,EAAC,MAAM,uCAAuC,CAAC;AACxE,OAAO,EAAC,0BAA0B,EAAE,gBAAgB,EAAsB,MAAM,sBAAsB,CAAC;AACvG,OAAwB,EAAC,sBAAsB,EAAC,MAAM,mBAAmB,CAAC;AAgB1E,oBAAY,sBAAsB,GAAG,0BAA0B,GAAG;IAChE,UAAU,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC7C,OAAO,EAAE,IAAI,CAAC,sBAAsB,EAAE,cAAc,CAAC,CAAC;CACvD,CAAA;AAED,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,cAAM,iBAAkB,SAAQ,KAAK,CAAC,SAAS,CAC7C,sBAAsB,EACtB,sBAAsB,CACvB;IACC,MAAM,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA2Bd;IAEF,KAAK,EAAE,sBAAsB,CAW3B;IAEF,UAAU,EAAE,OAAO,GAAG,IAAI,CAAQ;IAElC,eAAe,CAAC,EAAE,eAAe,CAAC;IAElC,kBAAkB,CAAC,SAAS,EAAE,sBAAsB;IAMpD,oBAAoB;IAMpB,KAAK,aAWH;IAEF,oBAAoB,aAYnB;IAED,2BAA2B,kCASzB;IAEF,cAAc,CAAC,IAAI,EAAE,gBAAgB;IAoBrC,YAAY,aAKV;IAEF,kBAAkB,aAQhB;IAEF,WAAW,aAST;IAEF,kBAAkB;;;eAKhB;IAEF,iBAAiB,oCAgBf;IAEF,IAAI,aAUF;IAEF,OAAO,aAOL;IAEF,UAAU,oBAkER;IAEF,MAAM;IAsCN,YAAY;IAiDZ,cAAc;IAcd,aAAa;IAYb,WAAW,oBAkBT;CACH;AAuED,eAAe,iBAAiB,CAAC"} \ No newline at end of file diff --git a/dist/v2/FlutterwaveButton.js b/dist/v2/FlutterwaveButton.js new file mode 100644 index 0000000..caaf7a0 --- /dev/null +++ b/dist/v2/FlutterwaveButton.js @@ -0,0 +1,466 @@ +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + 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; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +import React from 'react'; +import { StyleSheet, Modal, View, Animated, TouchableWithoutFeedback, Text, Alert, Image, Platform, Dimensions, Easing, } from 'react-native'; +import WebView from 'react-native-webview'; +import PropTypes from 'prop-types'; +import { OptionsPropTypeBase } from '../FlutterwaveButton'; +import FlutterwaveInit from './FlutterwaveInit'; +import DefaultButton from '../DefaultButton'; +import { PAYMENT_OPTIONS_V2, colors, REDIRECT_URL } from '../configs'; +import { PaymentOptionsPropRule } from '../utils/CustomPropTypesRules'; +import FlutterwaveInitError from '../utils/FlutterwaveInitError'; +var loader = require('../assets/loader.gif'); +var pryContent = require('../assets/pry-button-content.png'); +var contentWidthPercentage = 0.6549707602; +var contentSizeDimension = 8.2962962963; +var contentMaxWidth = 187.3; +var contentMaxHeight = contentMaxWidth / contentSizeDimension; +var contentMinWidth = 187.3; +var contentMinHeight = contentMinWidth / contentSizeDimension; +var borderRadiusDimension = 24 / 896; +var windowHeight = Dimensions.get('window').height; +var FlutterwaveButton = /** @class */ (function (_super) { + __extends(FlutterwaveButton, _super); + function FlutterwaveButton() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.state = { + isPending: false, + link: null, + resetLink: false, + showDialog: false, + animation: new Animated.Value(0), + txref: null, + buttonSize: { + width: 0, + height: 0 + } + }; + _this.webviewRef = null; + _this.reset = function () { + if (_this.abortController) { + _this.abortController.abort(); + } + // reset the necessaries + _this.setState(function (_a) { + var resetLink = _a.resetLink, link = _a.link; + return ({ + isPending: false, + link: resetLink ? null : link, + resetLink: false, + showDialog: false + }); + }); + }; + _this.handleOptionsChanged = function () { + var _a = _this.state, showDialog = _a.showDialog, link = _a.link; + if (!link) { + return; + } + if (!showDialog) { + return _this.setState({ + link: null, + txref: null + }); + } + _this.setState({ resetLink: true }); + }; + _this.handleNavigationStateChange = function (ev) { + // cregex to check if redirect has occured on completion/cancel + var rx = /\/flutterwave\.com\/rn-redirect/; + // Don't end payment if not redirected back + if (!rx.test(ev.url)) { + return; + } + // fire handle complete + _this.handleComplete(_this.getRedirectParams(ev.url)); + }; + _this.handleReload = function () { + // fire if webview is set + if (_this.webviewRef) { + _this.webviewRef.reload(); + } + }; + _this.handleAbortConfirm = function () { + var onAbort = _this.props.onAbort; + // abort action + if (onAbort) { + onAbort(); + } + // remove txref and dismiss + _this.dismiss(); + }; + _this.handleAbort = function () { + Alert.alert('', 'Are you sure you want to cancel this payment?', [ + { text: 'No' }, + { + text: 'Yes, Cancel', + style: 'destructive', + onPress: _this.handleAbortConfirm + }, + ]); + }; + _this.handleButtonResize = function (size) { + var buttonSize = _this.state.buttonSize; + if (JSON.stringify(buttonSize) !== JSON.stringify(size)) { + _this.setState({ buttonSize: size }); + } + }; + _this.getRedirectParams = function (url) { + // initialize result container + var res = {}; + // if url has params + if (url.split('?').length > 1) { + // get query params in an array + var params = url.split('?')[1].split('&'); + // add url params to result + for (var i = 0; i < params.length; i++) { + var param = params[i].split('='); + var val = decodeURIComponent(param[1]).trim(); + res[param[0]] = String(val); + } + } + // return result + return res; + }; + _this.show = function () { + var animation = _this.state.animation; + _this.setState({ showDialog: true }, function () { + Animated.timing(animation, { + toValue: 1, + duration: 700, + easing: Easing["in"](Easing.elastic(0.72)), + useNativeDriver: false + }).start(); + }); + }; + _this.dismiss = function () { + var animation = _this.state.animation; + Animated.timing(animation, { + toValue: 0, + duration: 400, + useNativeDriver: false + }).start(_this.reset); + }; + _this.handleInit = function () { + var _a = _this.props, options = _a.options, onWillInitialize = _a.onWillInitialize, onInitializeError = _a.onInitializeError, onDidInitialize = _a.onDidInitialize; + var _b = _this.state, isPending = _b.isPending, txref = _b.txref, link = _b.link; + // just show the dialod if the link is already set + if (link) { + return _this.show(); + } + // throw error if transaction reference has not changed + if (txref === options.txref) { + return onInitializeError ? onInitializeError(new FlutterwaveInitError({ + message: 'Please generate a new transaction reference.', + code: 'SAME_TXREF' + })) : null; + } + // stop if currently in pending mode + if (isPending) { + return; + } + // initialize abort controller if not set + _this.abortController = new AbortController; + // fire will initialize handler if available + if (onWillInitialize) { + onWillInitialize(); + } + // set pending state to true + _this.setState({ + isPending: true, + link: null, + txref: options.txref + }, function () { return __awaiter(_this, void 0, void 0, function () { + var paymentLink, error_1; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 2, , 3]); + return [4 /*yield*/, FlutterwaveInit(__assign(__assign({}, options), { redirect_url: REDIRECT_URL }), this.abortController)]; + case 1: + paymentLink = _a.sent(); + // set payment link + this.setState({ + link: paymentLink, + isPending: false + }, this.show); + // fire did initialize handler if available + if (onDidInitialize) { + onDidInitialize(); + } + return [3 /*break*/, 3]; + case 2: + error_1 = _a.sent(); + // stop if request was canceled + if (error_1 && /aborterror/i.test(error_1.code)) { + return [2 /*return*/]; + } + // call onInitializeError handler if an error occured + if (onInitializeError) { + onInitializeError(error_1); + } + return [2 /*return*/, this.dismiss()]; + case 3: return [2 /*return*/]; + } + }); + }); }); + }; + _this.renderError = function () { + var link = _this.state.link; + return ( + {link ? (<> + + The page failed to load, please try again. + + + + Try Again + + + ) : null} + ); + }; + return _this; + } + FlutterwaveButton.prototype.componentDidUpdate = function (prevProps) { + if (JSON.stringify(prevProps.options) !== JSON.stringify(this.props.options)) { + this.handleOptionsChanged(); + } + }; + FlutterwaveButton.prototype.componentWillUnmount = function () { + if (this.abortController) { + this.abortController.abort(); + } + }; + FlutterwaveButton.prototype.handleComplete = function (data) { + var _this = this; + var onComplete = this.props.onComplete; + // reset payment link + this.setState(function (_a) { + var resetLink = _a.resetLink, txref = _a.txref; + return ({ + txref: data.flwref && !data.cancelled ? null : txref, + resetLink: data.flwref && !data.cancelled ? true : resetLink + }); + }, function () { + // reset + _this.dismiss(); + // fire onComplete handler + onComplete({ + flwref: data.flwref, + txref: data.txref, + cancelled: data.cancelled + }); + }); + }; + FlutterwaveButton.prototype.render = function () { + var _this = this; + var _a = this.state, link = _a.link, animation = _a.animation, showDialog = _a.showDialog; + var marginTop = animation.interpolate({ + inputRange: [0, 1], + outputRange: [windowHeight, Platform.OS === 'ios' ? 46 : 14] + }); + var opacity = animation.interpolate({ + inputRange: [0, 0.3, 1], + outputRange: [0, 1, 1] + }); + // render UI + return (<> + {this.renderButton()} + + {this.renderBackdrop()} + + + + + ); + }; + FlutterwaveButton.prototype.renderButton = function () { + var _a = this.props, customButton = _a.customButton, style = _a.style, alignLeft = _a.alignLeft, children = _a.children; + var _b = this.state, isPending = _b.isPending, link = _b.link, showDialog = _b.showDialog, buttonSize = _b.buttonSize; + var contentWidth = buttonSize.width * contentWidthPercentage; + var contentHeight = contentWidth / contentSizeDimension; + var contentSizeStyle = { + width: contentWidth > contentMaxWidth + ? contentMaxWidth + : contentWidth < contentMinWidth + ? contentMinWidth + : contentWidth, + height: contentHeight > contentMaxHeight + ? contentMaxHeight + : contentHeight < contentMinHeight + ? contentMinHeight + : contentHeight + }; + // render custom button + if (customButton) { + return customButton({ + isInitializing: isPending && !link ? true : false, + disabled: isPending || showDialog ? true : false, + onPress: this.handleInit + }); + } + // render primary button + return ( + {children ? children : ()} + ); + }; + FlutterwaveButton.prototype.renderBackdrop = function () { + var animation = this.state.animation; + // Interpolation backdrop animation + var backgroundColor = animation.interpolate({ + inputRange: [0, 0.3, 1], + outputRange: [colors.transparent, colors.transparent, 'rgba(0,0,0,0.5)'] + }); + return ( + + ); + }; + FlutterwaveButton.prototype.renderLoading = function () { + return ( + + ); + }; + FlutterwaveButton.propTypes = { + alignLeft: PropTypes.bool, + onAbort: PropTypes.func, + onComplete: PropTypes.func.isRequired, + onWillInitialize: PropTypes.func, + onDidInitialize: PropTypes.func, + onInitializeError: PropTypes.func, + options: PropTypes.shape(__assign(__assign({}, OptionsPropTypeBase), { payment_options: PaymentOptionsPropRule(PAYMENT_OPTIONS_V2), txref: PropTypes.string.isRequired, PBFPubKey: PropTypes.string.isRequired, customer_firstname: PropTypes.string, customer_lastname: PropTypes.string, customer_email: PropTypes.string.isRequired, customer_phone: PropTypes.string, country: PropTypes.string, pay_button_text: PropTypes.string, custom_title: PropTypes.string, custom_description: PropTypes.string, custom_logo: PropTypes.string, meta: PropTypes.arrayOf(PropTypes.shape({ + metaname: PropTypes.string, + metavalue: PropTypes.string + })) })).isRequired, + customButton: PropTypes.func + }; + return FlutterwaveButton; +}(React.Component)); +var styles = StyleSheet.create({ + promtActions: { + flexDirection: 'row', + alignItems: 'center' + }, + promptActionText: { + textAlign: 'center', + color: colors.primary, + fontSize: 16, + paddingHorizontal: 16, + paddingVertical: 16 + }, + promptQuestion: { + color: colors.secondary, + textAlign: 'center', + marginBottom: 32, + fontSize: 18 + }, + prompt: { + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + top: 0, + backgroundColor: '#ffffff', + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: 56 + }, + backdrop: { + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + top: 0 + }, + loadingImage: { + width: 64, + height: 64, + resizeMode: 'contain' + }, + loading: { + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 0, + backgroundColor: 'rgba(255, 255, 255, 0.3)', + justifyContent: 'center', + alignItems: 'center' + }, + webviewContainer: { + top: 50, + flex: 1, + backgroundColor: '#efefef', + paddingBottom: 50, + overflow: 'hidden', + borderTopLeftRadius: windowHeight * borderRadiusDimension, + borderTopRightRadius: windowHeight * borderRadiusDimension + }, + webview: { + flex: 1, + backgroundColor: 'rgba(0,0,0,0)' + }, + buttonContent: { + resizeMode: 'contain' + } +}); +export default FlutterwaveButton; diff --git a/dist/v2/FlutterwaveInit.d.ts b/dist/v2/FlutterwaveInit.d.ts new file mode 100644 index 0000000..7012be5 --- /dev/null +++ b/dist/v2/FlutterwaveInit.d.ts @@ -0,0 +1,35 @@ +/// +import { FlutterwaveInitOptionsBase } from '../FlutterwaveInit'; +interface FlutterwavePaymentMetaV2 { + metaname: string; + metavalue: string; +} +export declare type FlutterwaveInitOptions = FlutterwaveInitOptionsBase & { + txref: string; + PBFPubKey: string; + customer_firstname?: string; + customer_lastname?: string; + customer_phone?: string; + customer_email: string; + country?: string; + pay_button_text?: string; + custom_title?: string; + custom_description?: string; + custom_logo?: string; + meta?: Array; +}; +/** + * This function is responsible for making the request to + * initialize a Flutterwave payment. + * @param options FlutterwaveInitOptions + * @return Promise<{ + * error: { + * code: string; + * message: string; + * } | null; + * link?: string | null; + * }> + */ +export default function FlutterwaveInit(options: FlutterwaveInitOptions, abortController?: AbortController): Promise; +export {}; +//# sourceMappingURL=FlutterwaveInit.d.ts.map \ No newline at end of file diff --git a/dist/v2/FlutterwaveInit.d.ts.map b/dist/v2/FlutterwaveInit.d.ts.map new file mode 100644 index 0000000..f885683 --- /dev/null +++ b/dist/v2/FlutterwaveInit.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"FlutterwaveInit.d.ts","sourceRoot":"","sources":["../../src/v2/FlutterwaveInit.ts"],"names":[],"mappings":";AACA,OAAO,EAAC,0BAA0B,EAAC,MAAM,oBAAoB,CAAC;AAG9D,UAAU,wBAAwB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,oBAAY,sBAAsB,GAAG,0BAA0B,GAAG;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,KAAK,CAAC,wBAAwB,CAAC,CAAC;CACxC,CAAA;AAmBD;;;;;;;;;;;GAWG;AACH,wBAA8B,eAAe,CAC3C,OAAO,EAAE,sBAAsB,EAC/B,eAAe,CAAC,EAAE,eAAe,GAChC,OAAO,CAAC,MAAM,CAAC,CA6CjB"} \ No newline at end of file diff --git a/dist/v2/FlutterwaveInit.js b/dist/v2/FlutterwaveInit.js new file mode 100644 index 0000000..5bd2507 --- /dev/null +++ b/dist/v2/FlutterwaveInit.js @@ -0,0 +1,115 @@ +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + 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; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +import { STANDARD_URL_V2 } from '../configs'; +import FlutterwaveInitError from '../utils/FlutterwaveInitError'; +/** + * This function is responsible for making the request to + * initialize a Flutterwave payment. + * @param options FlutterwaveInitOptions + * @return Promise<{ + * error: { + * code: string; + * message: string; + * } | null; + * link?: string | null; + * }> + */ +export default function FlutterwaveInit(options, abortController) { + return __awaiter(this, void 0, void 0, function () { + var body, headers, fetchOptions, response, responseJSON, e_1, error; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 3, , 4]); + body = __assign({}, options); + headers = new Headers; + headers.append('Content-Type', 'application/json'); + fetchOptions = { + method: 'POST', + body: JSON.stringify(body), + headers: headers + }; + // add abort controller if defined + if (abortController) { + fetchOptions.signal = abortController.signal; + } + ; + return [4 /*yield*/, fetch(STANDARD_URL_V2, fetchOptions)]; + case 1: + response = _a.sent(); + return [4 /*yield*/, response.json()]; + case 2: + responseJSON = _a.sent(); + // check if data is missing from response + if (!responseJSON.data) { + throw new FlutterwaveInitError({ + code: 'STANDARD_INIT_ERROR', + message: responseJSON.message || 'An unknown error occured!' + }); + } + // check if the link is missing in data + if (!responseJSON.data.link) { + throw new FlutterwaveInitError({ + code: responseJSON.data.code || 'MALFORMED_RESPONSE', + message: responseJSON.data.message || 'An unknown error occured!' + }); + } + // resolve with the payment link + return [2 /*return*/, Promise.resolve(responseJSON.data.link)]; + case 3: + e_1 = _a.sent(); + error = e_1 instanceof FlutterwaveInitError + ? e_1 + : new FlutterwaveInitError({ message: e_1.message, code: e_1.name.toUpperCase() }); + // resolve with error + return [2 /*return*/, Promise.reject(error)]; + case 4: return [2 /*return*/]; + } + }); + }); +} diff --git a/dist/v3/FlutterwaveButton.d.ts b/dist/v3/FlutterwaveButton.d.ts new file mode 100644 index 0000000..8addad6 --- /dev/null +++ b/dist/v3/FlutterwaveButton.d.ts @@ -0,0 +1,89 @@ +import React from 'react'; +import { Animated } from 'react-native'; +import WebView from 'react-native-webview'; +import PropTypes from 'prop-types'; +import { WebViewNavigation } from 'react-native-webview/lib/WebViewTypes'; +import { FlutterwaveButtonPropsBase, RedirectParams } from '../FlutterwaveButton'; +import { FlutterwaveInitOptions } from './FlutterwaveInit'; +export declare type FlutterwaveButtonProps = FlutterwaveButtonPropsBase & { + onComplete: (data: RedirectParams) => void; + options: Omit; +}; +interface FlutterwaveButtonState { + link: string | null; + isPending: boolean; + showDialog: boolean; + animation: Animated.Value; + tx_ref: string | null; + resetLink: boolean; + buttonSize: { + width: number; + height: number; + }; +} +declare class FlutterwaveButton extends React.Component { + static propTypes: { + alignLeft: PropTypes.Requireable; + onAbort: PropTypes.Requireable<(...args: any[]) => any>; + onComplete: PropTypes.Validator<(...args: any[]) => any>; + onWillInitialize: PropTypes.Requireable<(...args: any[]) => any>; + onDidInitialize: PropTypes.Requireable<(...args: any[]) => any>; + onInitializeError: PropTypes.Requireable<(...args: any[]) => any>; + options: PropTypes.Validator; + tx_ref: PropTypes.Validator; + payment_options: (props: { + [k: string]: any; + }, propName: string) => Error | null; + customer: PropTypes.Validator; + phonenumber: PropTypes.Requireable; + email: PropTypes.Validator; + }>>; + meta: PropTypes.Requireable<(object | null | undefined)[]>; + customizations: PropTypes.Requireable; + logo: PropTypes.Requireable; + description: PropTypes.Requireable; + }>>; + amount: PropTypes.Validator; + currency: PropTypes.Requireable; + payment_plan: PropTypes.Requireable; + subaccounts: PropTypes.Requireable<(PropTypes.InferProps<{ + id: PropTypes.Validator; + transaction_split_ratio: PropTypes.Requireable; + transaction_charge_type: PropTypes.Requireable; + transaction_charge: PropTypes.Requireable; + }> | null | undefined)[]>; + integrity_hash: PropTypes.Requireable; + }>>; + customButton: PropTypes.Requireable<(...args: any[]) => any>; + }; + state: FlutterwaveButtonState; + webviewRef: WebView | null; + abortController?: AbortController; + componentDidUpdate(prevProps: FlutterwaveButtonProps): void; + componentWillUnmount(): void; + reset: () => void; + handleOptionsChanged: () => void; + handleNavigationStateChange: (ev: WebViewNavigation) => void; + handleComplete(data: RedirectParams): void; + handleReload: () => void; + handleAbortConfirm: () => void; + handleAbort: () => void; + handleButtonResize: (size: { + width: number; + height: number; + }) => void; + getRedirectParams: (url: string) => RedirectParams; + show: () => void; + dismiss: () => void; + handleInit: () => void | null; + render(): JSX.Element; + renderButton(): {} | null | undefined; + renderBackdrop(): JSX.Element; + renderLoading(): JSX.Element; + renderError: () => JSX.Element; +} +export default FlutterwaveButton; +//# sourceMappingURL=FlutterwaveButton.d.ts.map \ No newline at end of file diff --git a/dist/v3/FlutterwaveButton.d.ts.map b/dist/v3/FlutterwaveButton.d.ts.map new file mode 100644 index 0000000..e4c471b --- /dev/null +++ b/dist/v3/FlutterwaveButton.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"FlutterwaveButton.d.ts","sourceRoot":"","sources":["../../src/v3/FlutterwaveButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAIL,QAAQ,EAQT,MAAM,cAAc,CAAC;AACtB,OAAO,OAAO,MAAM,sBAAsB,CAAC;AAC3C,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,EAAC,iBAAiB,EAAC,MAAM,uCAAuC,CAAC;AACxE,OAAO,EAAC,0BAA0B,EAAE,cAAc,EAAsB,MAAM,sBAAsB,CAAC;AACrG,OAAwB,EAAC,sBAAsB,EAAC,MAAM,mBAAmB,CAAC;AAgB1E,oBAAY,sBAAsB,GAAG,0BAA0B,GAAG;IAChE,UAAU,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAC3C,OAAO,EAAE,IAAI,CAAC,sBAAsB,EAAE,cAAc,CAAC,CAAC;CACvD,CAAA;AAED,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC;IAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,cAAM,iBAAkB,SAAQ,KAAK,CAAC,SAAS,CAC7C,sBAAsB,EACtB,sBAAsB,CACvB;IACC,MAAM,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAyBd;IAEF,KAAK,EAAE,sBAAsB,CAW3B;IAEF,UAAU,EAAE,OAAO,GAAG,IAAI,CAAQ;IAElC,eAAe,CAAC,EAAE,eAAe,CAAC;IAElC,kBAAkB,CAAC,SAAS,EAAE,sBAAsB;IAMpD,oBAAoB;IAMpB,KAAK,aAWH;IAEF,oBAAoB,aAYnB;IAED,2BAA2B,kCASzB;IAEF,cAAc,CAAC,IAAI,EAAE,cAAc;IAgBnC,YAAY,aAKV;IAEF,kBAAkB,aAQhB;IAEF,WAAW,aAST;IAEF,kBAAkB;;;eAKhB;IAEF,iBAAiB,kCAgBf;IAEF,IAAI,aAUF;IAEF,OAAO,aAOL;IAEF,UAAU,oBAqER;IAEF,MAAM;IAsCN,YAAY;IA+CZ,cAAc;IAcd,aAAa;IAYb,WAAW,oBAkBT;CACH;AAuED,eAAe,iBAAiB,CAAC"} \ No newline at end of file diff --git a/dist/v3/FlutterwaveButton.js b/dist/v3/FlutterwaveButton.js new file mode 100644 index 0000000..b8dd5b1 --- /dev/null +++ b/dist/v3/FlutterwaveButton.js @@ -0,0 +1,469 @@ +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + 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; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +import React from 'react'; +import { StyleSheet, Modal, View, Animated, TouchableWithoutFeedback, Text, Alert, Image, Platform, Dimensions, Easing, } from 'react-native'; +import WebView from 'react-native-webview'; +import PropTypes from 'prop-types'; +import { OptionsPropTypeBase } from '../FlutterwaveButton'; +import FlutterwaveInit from './FlutterwaveInit'; +import DefaultButton from '../DefaultButton'; +import { colors, REDIRECT_URL, PAYMENT_OPTIONS } from '../configs'; +import { PaymentOptionsPropRule } from '../utils/CustomPropTypesRules'; +import FlutterwaveInitError from '../utils/FlutterwaveInitError'; +var loader = require('../assets/loader.gif'); +var pryContent = require('../assets/pry-button-content.png'); +var contentWidthPercentage = 0.6549707602; +var contentSizeDimension = 8.2962962963; +var contentMaxWidth = 187.3; +var contentMaxHeight = contentMaxWidth / contentSizeDimension; +var contentMinWidth = 187.3; +var contentMinHeight = contentMinWidth / contentSizeDimension; +var borderRadiusDimension = 24 / 896; +var windowHeight = Dimensions.get('window').height; +var FlutterwaveButton = /** @class */ (function (_super) { + __extends(FlutterwaveButton, _super); + function FlutterwaveButton() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.state = { + isPending: false, + link: null, + resetLink: false, + showDialog: false, + animation: new Animated.Value(0), + tx_ref: null, + buttonSize: { + width: 0, + height: 0 + } + }; + _this.webviewRef = null; + _this.reset = function () { + if (_this.abortController) { + _this.abortController.abort(); + } + // reset the necessaries + _this.setState(function (_a) { + var resetLink = _a.resetLink, link = _a.link; + return ({ + isPending: false, + link: resetLink ? null : link, + resetLink: false, + showDialog: false + }); + }); + }; + _this.handleOptionsChanged = function () { + var _a = _this.state, showDialog = _a.showDialog, link = _a.link; + if (!link) { + return; + } + if (!showDialog) { + return _this.setState({ + link: null, + tx_ref: null + }); + } + _this.setState({ resetLink: true }); + }; + _this.handleNavigationStateChange = function (ev) { + // cregex to check if redirect has occured on completion/cancel + var rx = /\/flutterwave\.com\/rn-redirect/; + // Don't end payment if not redirected back + if (!rx.test(ev.url)) { + return; + } + // fire handle complete + _this.handleComplete(_this.getRedirectParams(ev.url)); + }; + _this.handleReload = function () { + // fire if webview is set + if (_this.webviewRef) { + _this.webviewRef.reload(); + } + }; + _this.handleAbortConfirm = function () { + var onAbort = _this.props.onAbort; + // abort action + if (onAbort) { + onAbort(); + } + // remove tx_ref and dismiss + _this.dismiss(); + }; + _this.handleAbort = function () { + Alert.alert('', 'Are you sure you want to cancel this payment?', [ + { text: 'No' }, + { + text: 'Yes, Cancel', + style: 'destructive', + onPress: _this.handleAbortConfirm + }, + ]); + }; + _this.handleButtonResize = function (size) { + var buttonSize = _this.state.buttonSize; + if (JSON.stringify(buttonSize) !== JSON.stringify(size)) { + _this.setState({ buttonSize: size }); + } + }; + _this.getRedirectParams = function (url) { + // initialize result container + var res = {}; + // if url has params + if (url.split('?').length > 1) { + // get query params in an array + var params = url.split('?')[1].split('&'); + // add url params to result + for (var i = 0; i < params.length; i++) { + var param = params[i].split('='); + var val = decodeURIComponent(param[1]).trim(); + res[param[0]] = String(val); + } + } + // return result + return res; + }; + _this.show = function () { + var animation = _this.state.animation; + _this.setState({ showDialog: true }, function () { + Animated.timing(animation, { + toValue: 1, + duration: 700, + easing: Easing["in"](Easing.elastic(0.72)), + useNativeDriver: false + }).start(); + }); + }; + _this.dismiss = function () { + var animation = _this.state.animation; + Animated.timing(animation, { + toValue: 0, + duration: 400, + useNativeDriver: false + }).start(_this.reset); + }; + _this.handleInit = function () { + var _a = _this.props, options = _a.options, onWillInitialize = _a.onWillInitialize, onInitializeError = _a.onInitializeError, onDidInitialize = _a.onDidInitialize; + var _b = _this.state, isPending = _b.isPending, tx_ref = _b.tx_ref, link = _b.link; + // just show the dialod if the link is already set + if (link) { + return _this.show(); + } + // throw error if transaction reference has not changed + if (tx_ref === options.tx_ref) { + return onInitializeError ? onInitializeError(new FlutterwaveInitError({ + message: 'Please generate a new transaction reference.', + code: 'SAME_TXREF' + })) : null; + } + // stop if currently in pending mode + if (isPending) { + return; + } + // initialize abort controller if not set + _this.abortController = new AbortController; + // fire will initialize handler if available + if (onWillInitialize) { + onWillInitialize(); + } + // @ts-ignore + // delete redirect url if set + delete options.redirect_url; + // set pending state to true + _this.setState({ + isPending: true, + link: null, + tx_ref: options.tx_ref + }, function () { return __awaiter(_this, void 0, void 0, function () { + var link_1, error_1; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 2, , 3]); + return [4 /*yield*/, FlutterwaveInit(__assign(__assign({}, options), { redirect_url: REDIRECT_URL }), this.abortController)]; + case 1: + link_1 = _a.sent(); + // resent pending mode + this.setState({ link: link_1, isPending: false }, this.show); + // fire did initialize handler if available + if (onDidInitialize) { + onDidInitialize(); + } + return [3 /*break*/, 3]; + case 2: + error_1 = _a.sent(); + // stop if request was canceled + if (/aborterror/i.test(error_1.code)) { + return [2 /*return*/]; + } + if (onInitializeError) { + onInitializeError(error_1); + } + return [2 /*return*/, this.setState({ + resetLink: true, + tx_ref: null + }, this.dismiss)]; + case 3: return [2 /*return*/]; + } + }); + }); }); + }; + _this.renderError = function () { + var link = _this.state.link; + return ( + {link ? (<> + + The page failed to load, please try again. + + + + Try Again + + + ) : null} + ); + }; + return _this; + } + FlutterwaveButton.prototype.componentDidUpdate = function (prevProps) { + if (JSON.stringify(prevProps.options) !== JSON.stringify(this.props.options)) { + this.handleOptionsChanged(); + } + }; + FlutterwaveButton.prototype.componentWillUnmount = function () { + if (this.abortController) { + this.abortController.abort(); + } + }; + FlutterwaveButton.prototype.handleComplete = function (data) { + var _this = this; + var onComplete = this.props.onComplete; + // reset payment link + this.setState(function (_a) { + var resetLink = _a.resetLink, tx_ref = _a.tx_ref; + return ({ + tx_ref: data.status === 'successful' ? null : tx_ref, + resetLink: data.status === 'successful' ? true : resetLink + }); + }, function () { + // reset + _this.dismiss(); + // fire onComplete handler + onComplete(data); + }); + }; + FlutterwaveButton.prototype.render = function () { + var _this = this; + var _a = this.state, link = _a.link, animation = _a.animation, showDialog = _a.showDialog; + var marginTop = animation.interpolate({ + inputRange: [0, 1], + outputRange: [windowHeight, Platform.OS === 'ios' ? 46 : 14] + }); + var opacity = animation.interpolate({ + inputRange: [0, 0.3, 1], + outputRange: [0, 1, 1] + }); + // render UI + return (<> + {this.renderButton()} + + {this.renderBackdrop()} + + + + + ); + }; + FlutterwaveButton.prototype.renderButton = function () { + var _a = this.props, customButton = _a.customButton, style = _a.style, alignLeft = _a.alignLeft; + var _b = this.state, isPending = _b.isPending, link = _b.link, showDialog = _b.showDialog, buttonSize = _b.buttonSize; + var contentWidth = buttonSize.width * contentWidthPercentage; + var contentHeight = contentWidth / contentSizeDimension; + var contentSizeStyle = { + width: contentWidth > contentMaxWidth + ? contentMaxWidth + : contentWidth < contentMinWidth + ? contentMinWidth + : contentWidth, + height: contentHeight > contentMaxHeight + ? contentMaxHeight + : contentHeight < contentMinHeight + ? contentMinHeight + : contentHeight + }; + // render custom button + if (customButton) { + return customButton({ + isInitializing: isPending && !link ? true : false, + disabled: isPending || showDialog ? true : false, + onPress: this.handleInit + }); + } + // render primary button + return ( + + ); + }; + FlutterwaveButton.prototype.renderBackdrop = function () { + var animation = this.state.animation; + // Interpolation backdrop animation + var backgroundColor = animation.interpolate({ + inputRange: [0, 0.3, 1], + outputRange: [colors.transparent, colors.transparent, 'rgba(0,0,0,0.5)'] + }); + return ( + + ); + }; + FlutterwaveButton.prototype.renderLoading = function () { + return ( + + ); + }; + FlutterwaveButton.propTypes = { + alignLeft: PropTypes.bool, + onAbort: PropTypes.func, + onComplete: PropTypes.func.isRequired, + onWillInitialize: PropTypes.func, + onDidInitialize: PropTypes.func, + onInitializeError: PropTypes.func, + options: PropTypes.shape(__assign(__assign({}, OptionsPropTypeBase), { authorization: PropTypes.string.isRequired, tx_ref: PropTypes.string.isRequired, payment_options: PaymentOptionsPropRule(PAYMENT_OPTIONS), customer: PropTypes.shape({ + name: PropTypes.string, + phonenumber: PropTypes.string, + email: PropTypes.string.isRequired + }).isRequired, meta: PropTypes.arrayOf(PropTypes.object), customizations: PropTypes.shape({ + title: PropTypes.string, + logo: PropTypes.string, + description: PropTypes.string + }) })).isRequired, + customButton: PropTypes.func + }; + return FlutterwaveButton; +}(React.Component)); +var styles = StyleSheet.create({ + promtActions: { + flexDirection: 'row', + alignItems: 'center' + }, + promptActionText: { + textAlign: 'center', + color: colors.primary, + fontSize: 16, + paddingHorizontal: 16, + paddingVertical: 16 + }, + promptQuestion: { + color: colors.secondary, + textAlign: 'center', + marginBottom: 32, + fontSize: 18 + }, + prompt: { + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + top: 0, + backgroundColor: '#ffffff', + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: 56 + }, + backdrop: { + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + top: 0 + }, + loadingImage: { + width: 64, + height: 64, + resizeMode: 'contain' + }, + loading: { + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 0, + backgroundColor: 'rgba(255, 255, 255, 0.3)', + justifyContent: 'center', + alignItems: 'center' + }, + webviewContainer: { + top: 50, + flex: 1, + backgroundColor: '#efefef', + paddingBottom: 50, + overflow: 'hidden', + borderTopLeftRadius: windowHeight * borderRadiusDimension, + borderTopRightRadius: windowHeight * borderRadiusDimension + }, + webview: { + flex: 1, + backgroundColor: 'rgba(0,0,0,0)' + }, + buttonContent: { + resizeMode: 'contain' + } +}); +export default FlutterwaveButton; diff --git a/dist/v3/FlutterwaveInit.d.ts b/dist/v3/FlutterwaveInit.d.ts new file mode 100644 index 0000000..80677e3 --- /dev/null +++ b/dist/v3/FlutterwaveInit.d.ts @@ -0,0 +1,46 @@ +/// +import { FlutterwaveInitOptionsBase } from '../FlutterwaveInit'; +interface FlutterwavePaymentMeta { + [k: string]: any; +} +export interface FlutterwaveInitCustomer { + email: string; + phonenumber?: string; + name?: string; +} +export interface FlutterwaveInitCustomizations { + title?: string; + logo?: string; + description?: string; +} +export declare type FlutterwaveInitOptions = FlutterwaveInitOptionsBase & { + authorization: string; + tx_ref: string; + customer: FlutterwaveInitCustomer; + meta?: Array; + customizations?: FlutterwaveInitCustomizations; +}; +export interface FieldError { + field: string; + message: string; +} +export interface ResponseData { + status?: 'success' | 'error'; + message: string; + error_id?: string; + errors?: Array; + code?: string; + data?: { + link: string; + }; +} +/** + * This function is responsible for making the request to + * initialize a Flutterwave payment. + * @param options FlutterwaveInitOptions + * @param abortController AbortController + * @return Promise + */ +export default function FlutterwaveInit(options: FlutterwaveInitOptions, abortController?: AbortController): Promise; +export {}; +//# sourceMappingURL=FlutterwaveInit.d.ts.map \ No newline at end of file diff --git a/dist/v3/FlutterwaveInit.d.ts.map b/dist/v3/FlutterwaveInit.d.ts.map new file mode 100644 index 0000000..583599a --- /dev/null +++ b/dist/v3/FlutterwaveInit.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"FlutterwaveInit.d.ts","sourceRoot":"","sources":["../../src/v3/FlutterwaveInit.ts"],"names":[],"mappings":";AAGA,OAAO,EAAC,0BAA0B,EAAC,MAAM,oBAAoB,CAAC;AAE9D,UAAU,sBAAsB;IAC9B,CAAC,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,6BAA6B;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,oBAAY,sBAAsB,GAAG,0BAA0B,GAAG;IAChE,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,uBAAuB,CAAC;IAClC,IAAI,CAAC,EAAE,KAAK,CAAC,sBAAsB,CAAC,CAAC;IACrC,cAAc,CAAC,EAAE,6BAA6B,CAAC;CAChD,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AASD;;;;;;GAMG;AACH,wBAA8B,eAAe,CAC3C,OAAO,EAAE,sBAAsB,EAC/B,eAAe,CAAC,EAAE,eAAe,GAChC,OAAO,CAAC,MAAM,CAAC,CAgCjB"} \ No newline at end of file diff --git a/dist/v3/FlutterwaveInit.js b/dist/v3/FlutterwaveInit.js new file mode 100644 index 0000000..17c859b --- /dev/null +++ b/dist/v3/FlutterwaveInit.js @@ -0,0 +1,101 @@ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + 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; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +import { STANDARD_URL } from '../configs'; +import ResponseParser from '../utils/ResponseParser'; +import FlutterwaveInitError from '../utils/FlutterwaveInitError'; +/** + * This function is responsible for making the request to + * initialize a Flutterwave payment. + * @param options FlutterwaveInitOptions + * @param abortController AbortController + * @return Promise + */ +export default function FlutterwaveInit(options, abortController) { + return __awaiter(this, void 0, void 0, function () { + var authorization, body, headers, fetchOptions, response, responseData, _a, _b, e_1, error; + return __generator(this, function (_c) { + switch (_c.label) { + case 0: + _c.trys.push([0, 4, , 5]); + authorization = options.authorization, body = __rest(options, ["authorization"]); + headers = new Headers; + headers.append('Content-Type', 'application/json'); + headers.append('Authorization', "Bearer " + authorization); + fetchOptions = { + method: 'POST', + body: JSON.stringify(body), + headers: headers + }; + // add abortController if defined + if (abortController) { + fetchOptions.signal = abortController.signal; + } + ; + return [4 /*yield*/, fetch(STANDARD_URL, fetchOptions)]; + case 1: + response = _c.sent(); + return [4 /*yield*/, response.json()]; + case 2: + responseData = _c.sent(); + _b = (_a = Promise).resolve; + return [4 /*yield*/, ResponseParser(responseData)]; + case 3: + // resolve with the payment link + return [2 /*return*/, _b.apply(_a, [_c.sent()])]; + case 4: + e_1 = _c.sent(); + error = e_1 instanceof FlutterwaveInitError + ? e_1 + : new FlutterwaveInitError({ message: e_1.message, code: e_1.name.toUpperCase() }); + // resolve with error + return [2 /*return*/, Promise.reject(error)]; + case 5: return [2 /*return*/]; + } + }); + }); +} From 496aae31f6325a8211b9fe9fb2d418414b1d506b Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 16:59:41 +0100 Subject: [PATCH 082/129] fix(flutterwavebutton): define prop(txref) in v2 redirect params as always defined --- src/FlutterwaveButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FlutterwaveButton.tsx b/src/FlutterwaveButton.tsx index 1014ec0..f2b6b64 100644 --- a/src/FlutterwaveButton.tsx +++ b/src/FlutterwaveButton.tsx @@ -12,7 +12,7 @@ export interface RedirectParams { export interface RedirectParamsV2 { cancelled?: 'true' | 'false'; flwref?: string; - txref?: string; + txref: string; } export interface CustomButtonProps { From d7dd4d54f6c9bb53f09761bf3a73db71451e0422 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 17:07:18 +0100 Subject: [PATCH 083/129] docs(readme): properly reference version 2 documentaion --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9ffd670..194646b 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Easily implement Flutterwave for payments in your React Native appliction. This - Flutterwave designed button. ## :warning: If Using Version 2 API :warning: -This version of the library uses Version 3 of Flutterwave's API, if you are still using the Version 2 API please use [this documentation](https://github.com/thecodecafe/react-native-flutterwave) instead. +This version of the library's docs focuses on use cases with the Version 3 of Flutterwaves API, if you are still using the Version 2 API please use [this documentation](./README.v2.md) instead. ## Installation This library is available on npm, you can install it by running `npm install --save react-native-flutterwave` or `yarn add react-native-flutterwave` @@ -145,7 +145,7 @@ import {DefaultButton} from 'react-native-flutterwave'; ```` ### Flutterwave Standard Init -When called, this function returns a Promise which resolves to a string on success and rejects if an error occurs. [See all config options](#flutterwaveinitioptions) +When called, this function returns a Promise which resolves to a string on success and rejects if an error occurs. [See all config options](#flutterwaveinitoptions) Import `FlutterwaveInit` from `react-native-flutterwave` and use it like so. ````javascript @@ -341,4 +341,4 @@ interface DefaultButtonProps { ## Contributing For information on how you can contribute to this repo, simply [go here](./CONTRIBUTING.md), all contributions are greatly appreciated. -Built with love. :yellow_heart: +With love from Flutterwave. :yellow_heart: From a15826c4fba05125e3659d879d4a16acfba8a427 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 17:12:01 +0100 Subject: [PATCH 084/129] docs(readme.v2): reference v3 documentation --- README.v2.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.v2.md b/README.v2.md index 1c03831..1d1b92c 100644 --- a/README.v2.md +++ b/README.v2.md @@ -10,6 +10,7 @@ Easily implement Flutterwave for payments in your React Native appliction. This ## Table Of Content - Getting Started + - [V3 API](#warning-if-using-version-3-api-warning) - [Installation](#installation) - [Dependencies](#dependencies) - [Activity Indicator (Android)](#activity-indicator-only-needed-for-android) @@ -39,6 +40,9 @@ Easily implement Flutterwave for payments in your React Native appliction. This - Standard payment initialization function. - Flutterwave designed button. +## :warning: If Using Version 3 API :warning: +This version of the library's docs focuses on use cases with the Version 2 of Flutterwaves API, if you are using the Version 3 API please use [this documentation](./README.md) instead. + ## Installation This library is available on npm, you can install it by running `npm install --save react-native-flutterwave` or `yarn add react-native-flutterwave` From ccd74bfbfd27f512948ff5ed25f28e2299f50125 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 17:13:55 +0100 Subject: [PATCH 085/129] docs(readme.v2): replace onCompleteData with RedirectParamsV2 --- README.v2.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.v2.md b/README.v2.md index 1d1b92c..acd444d 100644 --- a/README.v2.md +++ b/README.v2.md @@ -31,7 +31,7 @@ Easily implement Flutterwave for payments in your React Native appliction. This - [Flutterwave Init Options](#flutterwaveinitoptions-interface) - [Flutterwave Init Error](#flutterwaveiniterror) - [FlutterwavePaymentMeta](#flutterwavepaymentmeta) - - [OnCompleteData](#oncompletedata) + - [RedirectParamsV2](#redirectparamsv2) - [CustomButtonProps](#custombuttonprops) - [Contributing](./CONTRIBUTING.md) @@ -230,10 +230,10 @@ interface CustomButtonProps { } ```` -#### OnCompleteData +#### RedirectParamsV2 ````typescript -interface OnCompleteData { - canceled: boolean; +interface RedirectParamsV2 { + canceled?: 'true' | 'false'; flwref?: string; txref: string; } @@ -303,7 +303,7 @@ export interface FlutterwaveInitOptions { ````typescript interface FlutterwaveButtonProps { style?: ViewStyle; - onComplete: (data: OnCompleteData) => void; + onComplete: (data: RedirectParamsV2) => void; onWillInitialize?: () => void; onDidInitialize?: () => void; onInitializeError?: (error: FlutterwaveInitError) => void; From 295a47dc51a1ec472f597bd838e4ac1053b242ef Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 17:16:21 +0100 Subject: [PATCH 086/129] docs(readme.v2): update examples with V2 modules --- README.v2.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/README.v2.md b/README.v2.md index acd444d..c6558dc 100644 --- a/README.v2.md +++ b/README.v2.md @@ -76,12 +76,12 @@ Below are a few examples showcasing how you can use the library to implement pay [View All Props](#flutterwavebuttonprops) -Import `FlutterwaveButton` from `react-native-flutterwave` and use it like so. +Import `FlutterwaveButtonV2` from `react-native-flutterwave` and use it like so. ````jsx -import {FlutterwaveButton} from 'react-native-flutterwave'; -// or import FlutterwaveButton from 'react-native-flutterwave'; +import {FlutterwaveButtonV2} from 'react-native-flutterwave'; +// or import FlutterwaveButtonV2 from 'react-native-flutterwave/v2'; - Date: Tue, 21 Jul 2020 17:17:54 +0100 Subject: [PATCH 087/129] docs(readme.v2): remove redundant code and update footnote --- README.v2.md | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/README.v2.md b/README.v2.md index c6558dc..99f4f0b 100644 --- a/README.v2.md +++ b/README.v2.md @@ -248,16 +248,6 @@ interface FlutterwaveInitError { } ```` - - -#### FlutterwaveInitResult -````typescript -interface FlutterwaveInitResult { - error?: FlutterwaveInitError | null; - link?: string | null; -} -```` - ### FlutterwaveInitSubAccount ```typescript interface FlutterwaveInitSubAccount { @@ -331,4 +321,4 @@ interface DefaultButtonProps { ## Contributing For information on how you can contribute to this repo, simply [go here](./CONTRIBUTING.md), all contributions are greatly appreciated. -Built with love. :yellow_heart: +With love from Flutterwave. :yellow_heart: From 29fb73a087139e8fc20fd8036c0338fa457dbc60 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 17:18:15 +0100 Subject: [PATCH 088/129] docs(contributing.md): add footnote --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bac3bad..e600f97 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,3 +43,5 @@ To start a commit simply run the following cli command from within the project ` 4. Have your branch get merged in! :white_check_mark: If you experience a problem at any point, please don't hesitate to file an issue to get some assistance! + +With love from Flutterwave. :yellow_heart: From 9ea86ff1d258a39d1f92fc1e1e4f77b5a67873a6 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 17:20:05 +0100 Subject: [PATCH 089/129] docs(v2/abortingpaymentinitialization): use new v2 flutterwave init and adjust copy --- docs/v2/AbortingPaymentInitialization.md | 42 +++++++++++++----------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/docs/v2/AbortingPaymentInitialization.md b/docs/v2/AbortingPaymentInitialization.md index 3f9ebb7..e38c2f4 100644 --- a/docs/v2/AbortingPaymentInitialization.md +++ b/docs/v2/AbortingPaymentInitialization.md @@ -1,12 +1,12 @@ # Aborting Payment Initialization -Hi :wave:, so there are cases where you have already initialized a payment with `FlutterwaveInit` but might also want to be able to cancel the payment initialization should in case your component is being unmounted or you want to allow users cancel the action before the payment is initialized, we have provided a way for you to do this, we use `fetch` underneath the hood to make the request to the standard payment endpoint and `fetch` allows you to pass an [abort controller](https://github.com/mo/abortcontroller-polyfill) which you can use to cancel ongoing requests, if your version of React Native does not have the abort functionality for fetch in the Javascript runtime, you will need to [install the polyfill](https://github.com/mo/abortcontroller-polyfill) before moving on. Below is a code snippet showcasing how you can go about cancelling an ongoing payment initialization. +:wave:Hi, so there are cases where you have already initialized a payment with `FlutterwaveInitV2` but might also want to be able to cancel the payment initialization should in case your component is being unmounted or you want to allow users cancel the action before the payment is initialized, we have provided a way for you to do this, we use `fetch` underneath the hood to make the request to the standard payment endpoint and `fetch` allows you to pass an [abort controller](https://github.com/mo/abortcontroller-polyfill) which you can use to cancel ongoing requests, if your version of React Native does not have the abort functionality for fetch in the Javascript runtime, you will need to [install the polyfill](https://github.com/mo/abortcontroller-polyfill) before moving on. Below is a code snippet showcasing how you can go about cancelling an ongoing payment initialization. -**:point_right:`If you have already installed the polyfill or have it already available in the Javascript runtime, this action happens automatically within FlutterwaveButton.`** +:point_right:**`If you have already installed the polyfill or have it already available in the Javascript runtime, this action happens automatically within FlutterwaveButton.`** ````jsx import React from 'react'; import {View, TouchableOpacity} from 'react-native'; -import {FlutterwaveInit} from 'react-native-flutterwave'; +import {FlutterwaveInitV2} from 'react-native-flutterwave'; class MyCart extends React.Component { abortController = null; @@ -23,25 +23,27 @@ class MyCart extends React.Component { }, () => { // set abort controller this.abortController = new AbortController; - // initialize a new payment - const payment = await FlutterwaveInit({ - txref: generateTransactionRef(), - PBFPubKey: '[Your Flutterwave Public Key]', - amount: 100, - currency: 'USD', - }, { - canceller: this.abortController, - }); - // do nothing if our payment initialization was aborted - if (payment.error && payment.error.code === 'ABORTERROR') { - return; - } - // link is available if payment initialized successfully - if (payment.link) { + try { + // initialize a new payment + const paymentLink = await FlutterwaveInitV2( + { + txref: generateTransactionRef(), + PBFPubKey: '[Your Flutterwave Public Key]', + amount: 100, + currency: 'USD', + }, + this.abortController + ); // use payment link - return; + return this.usePaymentLink(paymentLink); + } catch (error) { + // do nothing if our payment initialization was aborted + if (error.code === 'ABORTERROR') { + return; + } + // handle other errors + this.displayErrorMessage(error.message); } - // handle other errors }) } From 76e73a4d16702b8ce519f35fdd1e08ba9a201690 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 17:23:37 +0100 Subject: [PATCH 090/129] docs: add space between wave emoji and "Hi" --- README.md | 2 +- docs/v2/AbortingPaymentInitialization.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 194646b..accb485 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ try { } ```` ### Aborting Payment Initialization -:wave:Hi, so there are cases where you have already initialized a payment with `FlutterwaveInit` but might also want to be able to cancel the payment initialization should in case your component is being unmounted or you want to allow users cancel the action before the payment is initialized, we have provided a way for you to do this... [continue reading](./docs/AbortingPaymentInitialization.md) +:wave: Hi, so there are cases where you have already initialized a payment with `FlutterwaveInit` but might also want to be able to cancel the payment initialization should in case your component is being unmounted or you want to allow users cancel the action before the payment is initialized, we have provided a way for you to do this... [continue reading](./docs/AbortingPaymentInitialization.md) ## Props diff --git a/docs/v2/AbortingPaymentInitialization.md b/docs/v2/AbortingPaymentInitialization.md index e38c2f4..6607d66 100644 --- a/docs/v2/AbortingPaymentInitialization.md +++ b/docs/v2/AbortingPaymentInitialization.md @@ -1,5 +1,5 @@ # Aborting Payment Initialization -:wave:Hi, so there are cases where you have already initialized a payment with `FlutterwaveInitV2` but might also want to be able to cancel the payment initialization should in case your component is being unmounted or you want to allow users cancel the action before the payment is initialized, we have provided a way for you to do this, we use `fetch` underneath the hood to make the request to the standard payment endpoint and `fetch` allows you to pass an [abort controller](https://github.com/mo/abortcontroller-polyfill) which you can use to cancel ongoing requests, if your version of React Native does not have the abort functionality for fetch in the Javascript runtime, you will need to [install the polyfill](https://github.com/mo/abortcontroller-polyfill) before moving on. Below is a code snippet showcasing how you can go about cancelling an ongoing payment initialization. +:wave: Hi, so there are cases where you have already initialized a payment with `FlutterwaveInitV2` but might also want to be able to cancel the payment initialization should in case your component is being unmounted or you want to allow users cancel the action before the payment is initialized, we have provided a way for you to do this, we use `fetch` underneath the hood to make the request to the standard payment endpoint and `fetch` allows you to pass an [abort controller](https://github.com/mo/abortcontroller-polyfill) which you can use to cancel ongoing requests, if your version of React Native does not have the abort functionality for fetch in the Javascript runtime, you will need to [install the polyfill](https://github.com/mo/abortcontroller-polyfill) before moving on. Below is a code snippet showcasing how you can go about cancelling an ongoing payment initialization. :point_right:**`If you have already installed the polyfill or have it already available in the Javascript runtime, this action happens automatically within FlutterwaveButton.`** From 9fdc0c4a51d570a8dfe1144ba6573ed185225133 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 17:25:52 +0100 Subject: [PATCH 091/129] docs(readme): remove FlutterwaveSubAccount type repetetion --- README.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/README.md b/README.md index accb485..9e1978b 100644 --- a/README.md +++ b/README.md @@ -247,16 +247,6 @@ interface FlutterwaveInitError { } ```` -### FlutterwaveInitSubAccount -```typescript -interface FlutterwaveInitSubAccount { - id: string; - transaction_split_ratio?: number; - transaction_charge_type?: string; - transaction_charge?: number; -} -``` - #### FlutterwavePaymentMeta ````typescript interface FlutterwavePaymentMeta { From 1fc9f7556562b51ca8e60007fb929ccfab17aff3 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 17:33:52 +0100 Subject: [PATCH 092/129] docs(readme.v2): add API version to readme welcome message --- README.v2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.v2.md b/README.v2.md index 99f4f0b..1aa05f6 100644 --- a/README.v2.md +++ b/README.v2.md @@ -1,5 +1,5 @@ # React Native Flutterwave -Easily implement Flutterwave for payments in your React Native appliction. This library has support for both Android and iOS. +Easily implement Flutterwave for payments in your React Native appliction. This library supports both Android and iOS, and use the Flutterwave's V2 API. [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) From f618b23e1b2a3b77ca772e300db9493b6d71d703 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 17:34:28 +0100 Subject: [PATCH 093/129] docs(readme): add API version to header welcome message --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9e1978b..b081e5b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # React Native Flutterwave -Easily implement Flutterwave for payments in your React Native appliction. This library has support for both Android and iOS. +Easily implement Flutterwave for payments in your React Native appliction. This library supports both Android and iOS, and use the Flutterwave's V3 API [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) From 1de66bc340f8afab0cda08c910bb471d91559f14 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 21 Jul 2020 17:39:27 +0100 Subject: [PATCH 094/129] docs: add api version badge --- README.md | 2 +- README.v2.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b081e5b..2d49279 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # React Native Flutterwave Easily implement Flutterwave for payments in your React Native appliction. This library supports both Android and iOS, and use the Flutterwave's V3 API -[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) +[![V2 API](https://img.shields.io/badge/API-V3-brightgreen)](https://developer.flutterwave.com/docs) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)

ios-preview diff --git a/README.v2.md b/README.v2.md index 1aa05f6..69f4e19 100644 --- a/README.v2.md +++ b/README.v2.md @@ -1,7 +1,7 @@ # React Native Flutterwave Easily implement Flutterwave for payments in your React Native appliction. This library supports both Android and iOS, and use the Flutterwave's V2 API. -[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) +[![V2 API](https://img.shields.io/badge/API-V2-brightgreen)](https://developer.flutterwave.com/v2.0/docs/getting-started) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)

ios-preview From de2d3f9953cbf66dc1754e0cdeac8a61e9ebc850 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 09:14:55 +0100 Subject: [PATCH 095/129] refactor(timetravel): move animation timetravel helper into a seperate file --- setupJest.js | 20 +++----------------- timeTravel.js | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 17 deletions(-) create mode 100644 timeTravel.js diff --git a/setupJest.js b/setupJest.js index 38d0e33..2e35a88 100644 --- a/setupJest.js +++ b/setupJest.js @@ -1,6 +1,5 @@ import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'; require('jest-fetch-mock').enableMocks(); -const MockDate = require('mockdate'); const frameTime = 1; jest.mock('react-native/Libraries/Alert/Alert', () => { @@ -18,19 +17,6 @@ jest.mock('react-native/Libraries/Utilities/Dimensions', () => { }; }); -// global time travel mock for mocking "RN Animated" fram steps; -global.timeTravel = (time = frameTime) => { - // tick travel handler - const tickTravel = () => { - // move time forward by number of fram time ms - const now = Date.now(); - MockDate.set(new Date(now + frameTime)); - // run the timers forward - jest.advanceTimersByTime(frameTime); - } - // step through each of the frames - const frames = time / frameTime; - for (let i = 0; i < frames; i++) { - tickTravel(); - } -}; +global.requestAnimationFrame = cb => { + setTimeout(cb, frameTime) +} diff --git a/timeTravel.js b/timeTravel.js new file mode 100644 index 0000000..f780ae5 --- /dev/null +++ b/timeTravel.js @@ -0,0 +1,26 @@ +const MockDate = require('mockdate'); +export const FRAME_TIME = 1; + +const advanceOneFrame = () => { + MockDate.set(new Date(Date.now() + FRAME_TIME)) + jest.advanceTimersByTime(FRAME_TIME) +} + +// global time travel mock for mocking "RN Animated" fram steps; +const timeTravel = (msToAdvance = FRAME_TIME) => { + const numberOfFramesToRun = msToAdvance / FRAME_TIME + let framesElapsed = 0 + + // Step through each of the frames until we've ran them all + while (framesElapsed < numberOfFramesToRun) { + advanceOneFrame() + framesElapsed++ + } +} + +export const setupTimeTravel = () => { + MockDate.set(0) + jest.useFakeTimers() +} + +export default timeTravel From c44f12cea77322040919694e59e62fd0e5952024 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 09:18:51 +0100 Subject: [PATCH 096/129] feat(flwbutton): add component for displaying a pay with flutterwave button --- src/FlwButton.tsx | 99 +++++++++++++++++++++++++++++++++++++++++++++++ src/configs.ts | 1 + 2 files changed, 100 insertions(+) create mode 100644 src/FlwButton.tsx diff --git a/src/FlwButton.tsx b/src/FlwButton.tsx new file mode 100644 index 0000000..22f1e03 --- /dev/null +++ b/src/FlwButton.tsx @@ -0,0 +1,99 @@ +import React from 'react'; +import { + StyleSheet, + Image, + StyleProp, + ViewStyle, + TouchableHighlight, + View, +} from 'react-native'; +import {colors} from './configs'; +const pryContent = require('./assets/pry-button-content.png'); +const contentSizeDimension = 8.2962962963; + +interface FlwButtonProps { + style?: StyleProp; + disabled?: boolean; + alignLeft?: boolean; + onPress?: () => void; +} + +const FlwButton: React.FC< + FlwButtonProps +> = function FlwButton({ + style, + alignLeft, + children, + disabled, + onPress +}) { + // render primary button + return ( + + <> + {children ? children : ( + + )} + {disabled + ? () + : null} + + + ); +} + +// component UI styles +const styles = StyleSheet.create({ + buttonBusyOvelay: { + position: 'absolute', + left: 0, + top: 0, + bottom: 0, + right: 0, + backgroundColor: 'rgba(255, 255, 255, 0.6)', + }, + buttonBusy: { + borderColor: colors.primaryLight, + }, + buttonAlignLeft: { + justifyContent: 'flex-start', + }, + button: { + paddingHorizontal: 16, + minWidth: 100, + height: 52, + borderColor: colors.primary, + borderWidth: 1, + borderRadius: 6, + backgroundColor: colors.primary, + alignItems: 'center', + justifyContent: 'center', + flexDirection: 'row', + overflow: 'hidden', + }, + buttonContent: { + resizeMode: 'contain', + width: 187.3, + height: 187.3 / contentSizeDimension + }, +}); + +// export component as default +export default FlwButton; diff --git a/src/configs.ts b/src/configs.ts index 5d4e7ea..e54bb7b 100644 --- a/src/configs.ts +++ b/src/configs.ts @@ -13,6 +13,7 @@ export const REDIRECT_URL = 'https://flutterwave.com/rn-redirect'; */ export const colors = { primary: '#f5a623', + primaryLight: '#f9ce85', secondary: '#12122C', transparent: 'rgba(0,0,0,0)', }; From 97b29b918863e6e78240397c0d476443e437d886 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 09:20:41 +0100 Subject: [PATCH 097/129] test(flwbutton): tests and snapshot of flwbutton component --- __tests__/FlwButton.spec.tsx | 57 +++++ .../__snapshots__/FlwButton.spec.tsx.snap | 215 ++++++++++++++++++ 2 files changed, 272 insertions(+) create mode 100644 __tests__/FlwButton.spec.tsx create mode 100644 __tests__/__snapshots__/FlwButton.spec.tsx.snap diff --git a/__tests__/FlwButton.spec.tsx b/__tests__/FlwButton.spec.tsx new file mode 100644 index 0000000..7151224 --- /dev/null +++ b/__tests__/FlwButton.spec.tsx @@ -0,0 +1,57 @@ +import 'react-native'; +import React from 'react'; +import {Text} from 'react-native'; +import renderer from 'react-test-renderer'; +import FlwButton from '../src/FlwButton'; +const testID = 'flw-button'; + +describe('', () => { + it('renders pay with flutterwave by default', () => { + // create test renderer + const TestRenderer = renderer.create(); + // run assertions + expect(TestRenderer.toJSON()).toMatchSnapshot(); + }); + + it('renders with overlay and inactive style of button is disabled', () => { + // create test renderer + const TestRenderer = renderer.create(); + // run assertions + expect(TestRenderer.toJSON()).toMatchSnapshot(); + }); + + it('applies left aligned style if alignLeft prop is present', () => { + // create test renderer + const TestRenderer = renderer.create(); + // run assertions + expect(TestRenderer.toJSON()).toMatchSnapshot(); + }); + + it('replaces pay with flutterwave image with children', () => { + // create test renderer + const TestRenderer = renderer.create( + + Hello, World! + + ); + // run assertions + expect(TestRenderer.toJSON()).toMatchSnapshot(); + }); + + it('fires on press if set', () => { + // create onPress function + const onPress = jest.fn(); + // create test renderer + const TestRenderer = renderer.create( + + Hello, World! + + ); + // fire onPress function + TestRenderer.root.findByProps({testID}).props.onPress(); + // run assertions + expect(TestRenderer.root.findByProps({testID}).props.onPress).toBeDefined(); + expect(onPress).toBeCalledTimes(1); + }); +}); + diff --git a/__tests__/__snapshots__/FlwButton.spec.tsx.snap b/__tests__/__snapshots__/FlwButton.spec.tsx.snap new file mode 100644 index 0000000..ba17517 --- /dev/null +++ b/__tests__/__snapshots__/FlwButton.spec.tsx.snap @@ -0,0 +1,215 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` applies left aligned style if alignLeft prop is present 1`] = ` + + + +`; + +exports[` renders pay with flutterwave by default 1`] = ` + + + +`; + +exports[` renders with overlay and inactive style of button is disabled 1`] = ` + + + + +`; + +exports[` replaces pay with flutterwave image with children 1`] = ` + + + Hello, World! + + +`; From b88ee69c64995945082818f0360aad453ff45cd2 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 09:22:01 +0100 Subject: [PATCH 098/129] feat(flwcheckout): add flutterwave checkout modal component this component is responsible for rendering the checkout screen and redirecting --- src/FlwCheckout.tsx | 299 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 src/FlwCheckout.tsx diff --git a/src/FlwCheckout.tsx b/src/FlwCheckout.tsx new file mode 100644 index 0000000..16a5c0c --- /dev/null +++ b/src/FlwCheckout.tsx @@ -0,0 +1,299 @@ +import React from 'react'; +import { + StyleSheet, + Modal, + View, + Animated, + TouchableWithoutFeedback, + Text, + Alert, + Image, + Dimensions, + Easing, + TouchableOpacity, + Platform, +} from 'react-native'; +import WebView from 'react-native-webview'; +import {WebViewNavigation} from 'react-native-webview/lib/WebViewTypes'; +import {colors} from './configs'; +const loader = require('./assets/loader.gif'); +const borderRadiusDimension = 24 / 896; +const windowHeight = Dimensions.get('window').height; + +export interface FlwCheckoutProps { + onRedirect?: (data: any) => void; + onAbort?: () => void; + link?: string; + visible?: boolean; +} + +interface FlwCheckoutBackdropProps { + animation: Animated.Value, + onPress?: () => void; +} + +interface FlwCheckoutErrorProps { + hasLink: boolean; + onTryAgain: () => void; +} + +const getRedirectParams = (url: string): {[k: string]: string} => { + // initialize result container + const res: any = {}; + // if url has params + if (url.split('?').length > 1) { + // get query params in an array + const params = url.split('?')[1].split('&'); + // add url params to result + for (let i = 0; i < params.length; i++) { + const param: Array = params[i].split('='); + const val = decodeURIComponent(param[1]).trim(); + res[param[0]] = String(val); + } + } + // return result + return res; +}; + +const FlwCheckout: React.FC = function FlwCheckout(props) { + const {link, visible, onRedirect, onAbort} = props; + const [show, setShow] = React.useState(false); + const webviewRef = React.useRef(null); + const animation = React.useRef(new Animated.Value(0)); + + const animateIn = React.useCallback(() => { + setShow(true); + Animated.timing(animation.current, { + toValue: 1, + duration: 700, + easing: Easing.in(Easing.elastic(0.72)), + useNativeDriver: false, + }).start(); + }, []); + + const animateOut = React.useCallback(() => { + return new Promise(resolve => { + Animated.timing(animation.current, { + toValue: 0, + duration: 400, + useNativeDriver: false, + }).start(() => { + setShow(false); + resolve(); + }); + }) + }, []); + + const handleReload = React.useCallback(() => { + if (webviewRef.current) { + webviewRef.current.reload(); + } + }, []); + + const handleAbort = React.useCallback((confirmed: boolean = false) => { + if (!confirmed) { + Alert.alert('', 'Are you sure you want to cancel this payment?', [ + {text: 'No'}, + { + text: 'Yes, Cancel', + style: 'destructive', + onPress: () => handleAbort(true), + }, + ]); + return; + } + // remove tx_ref and dismiss + animateOut().then(onAbort); + }, [onAbort, animateOut]); + + const handleNavigationStateChange = React.useCallback((ev: WebViewNavigation) => { + // cregex to check if redirect has occured on completion/cancel + const rx = /\/flutterwave\.com\/rn-redirect/; + // Don't end payment if not redirected back + if (!rx.test(ev.url)) { + return + } + // dismiss modal + animateOut().then(() => { + if (onRedirect) { + onRedirect(getRedirectParams(ev.url)) + } + }); + }, [onRedirect]); + + const doAnimate = React.useCallback(() => { + if (visible === show) { + return; + } + if (visible) { + return animateIn(); + } + animateOut().then(() => {}); + }, [visible, show, animateOut, animateIn]); + + React.useEffect(() => { + doAnimate(); + return () => {}; + }, [doAnimate]); + + const marginTop = animation.current.interpolate({ + inputRange: [0, 1], + outputRange: [windowHeight, 0], + }); + const opacity = animation.current.interpolate({ + inputRange: [0, 0.3, 1], + outputRange: [0, 1, 1], + }); + + return ( + + handleAbort()} animation={animation.current} /> + + } + renderLoading={() => } + /> + + + ) +} + +const FlwCheckoutBackdrop: React.FC< + FlwCheckoutBackdropProps +> = function FlwCheckoutBackdrop({ + animation, + onPress +}) { + // Interpolation backdrop animation + const backgroundColor = animation.interpolate({ + inputRange: [0, 0.3, 1], + outputRange: [colors.transparent, colors.transparent, 'rgba(0,0,0,0.5)'], + }); + return ( + + + + ); +} + +export const FlwCheckoutError: React.FC = ({ + hasLink, + onTryAgain +}): React.ReactElement => { + return ( + + {hasLink ? ( + <> + + An error occurred, please tab below to try again. + + + Try Again + + + ) : ( + + An error occurred, please close the checkout dialog and try again. + + )} + + ); +} + +const FlwCheckoutLoader: React.FC<{}> = (): React.ReactElement => { + return ( + + + + ); +} + +const styles = StyleSheet.create({ + errorActionButtonText: { + textAlign: 'center', + color: colors.primary, + fontSize: 16, + }, + errorActionButton: { + paddingHorizontal: 16, + paddingVertical: 16, + }, + errorText: { + color: colors.secondary, + textAlign: 'center', + marginBottom: 32, + fontSize: 18, + }, + error: { + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + top: 0, + backgroundColor: '#ffffff', + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: 56, + }, + backdrop: { + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + top: 0, + }, + loadingImage: { + width: 64, + height: 64, + resizeMode: 'contain', + }, + loading: { + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 0, + backgroundColor: 'rgba(255, 255, 255, 0.3)', + justifyContent: 'center', + alignItems: 'center', + }, + webviewContainer: { + top: Platform.select({ios: 96, android: 64}), // status bar height aware for ios + flex: 1, + backgroundColor: '#efefef', + paddingBottom: Platform.select({ios: 96, android: 64}), // status bar height aware for ios + overflow: 'hidden', + borderTopLeftRadius: windowHeight * borderRadiusDimension, + borderTopRightRadius: windowHeight * borderRadiusDimension, + }, + webview: { + flex: 1, + backgroundColor: 'rgba(0,0,0,0)', + }, +}); + +export default FlwCheckout; From 26148b6f8bc813f48190bb22b72ae3aae2ad0396 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 09:23:47 +0100 Subject: [PATCH 099/129] test(flwcheckout): test and generate snapshot of flwcheckout component --- __tests__/FlwCheckout.spec.tsx | 241 ++++++++++++++++ .../__snapshots__/FlwCheckout.spec.tsx.snap | 263 ++++++++++++++++++ 2 files changed, 504 insertions(+) create mode 100644 __tests__/FlwCheckout.spec.tsx create mode 100644 __tests__/__snapshots__/FlwCheckout.spec.tsx.snap diff --git a/__tests__/FlwCheckout.spec.tsx b/__tests__/FlwCheckout.spec.tsx new file mode 100644 index 0000000..bab3cdd --- /dev/null +++ b/__tests__/FlwCheckout.spec.tsx @@ -0,0 +1,241 @@ +import 'react-native'; +import React from 'react'; +import {TouchableOpacity, Alert} from 'react-native'; +import renderer from 'react-test-renderer'; +import FlwCheckout, {FlwCheckoutError} from '../src/FlwCheckout'; +import timeTravel, {setupTimeTravel} from '../timeTravel'; +import {REDIRECT_URL} from '../src/configs'; +import WebView from 'react-native-webview'; +const link = 'http://example.com'; + +beforeEach(() => setupTimeTravel()); +afterEach(() => jest.useRealTimers()); +describe('', () => { + it('renders with modal closed if visible prop is not true', () => { + // create test renderer + const TestRenderer = renderer.create( + {}} /> + ); + // run assertions + expect(TestRenderer.toJSON()).toMatchSnapshot(); + }); + + it('renders with modal open if visible props is true', () => { + // create test renderer + const TestRenderer = renderer.create( + {}} visible /> + ); + // simulate animation timeframes + timeTravel(); + // run assertions + expect(TestRenderer.toJSON()).toMatchSnapshot(); + }); + + it('fires onRedirect if redirect url is correct', (done) => { + const onRedirect = jest.fn(); + const url = REDIRECT_URL + '?foo=bar'; + // create test renderer + const TestRenderer = renderer.create( + + ); + // fire on navigation state change + TestRenderer.root.findByType(WebView).props.onNavigationStateChange({url}); + // simulate animation timeframes + timeTravel(); + // revert to using real timers + jest.useRealTimers() + // wait for promise to resolve + setTimeout(() => { + // run assertions + expect(onRedirect).toBeCalledTimes(1); + expect(onRedirect).toBeCalledWith({foo: 'bar'}); + done(); + }, 10) + }); + + it('does not fire onRedirect if redirect url is not correct', (done) => { + const onRedirect = jest.fn(); + const url = 'http://example/com?foo=bar'; + // create test renderer + const TestRenderer = renderer.create( + + ); + // fire on navigation state change + TestRenderer.root.findByType(WebView).props.onNavigationStateChange({url}); + // simulate animation timeframes + timeTravel(); // revert to using real timers + jest.useRealTimers() + // wait for promise to resolve + setTimeout(() => { + // run assertions + expect(onRedirect).toBeCalledTimes(0); + done(); + }) + }); + + it('asks user to confirm abort when use taps on the backdrop', () => { + // create test renderer + const TestRenderer = renderer.create( + {}} visible link={link} /> + ); + // call backdrop onPress + TestRenderer.root.findByProps({testID: 'flw-checkout-backdrop'}).props.onPress(); + // run assertions + expect(Alert.alert).toHaveBeenCalledTimes(1); + expect(Alert.alert).toHaveBeenCalledWith( + expect.any(String), + expect.stringContaining('cancel this payment?'), + expect.any(Array), + ); + }); + + it('calls onAbort if defined', (done) => { + const onAbort = jest.fn(); + // create test renderer + const TestRenderer = renderer.create( + {}} onAbort={onAbort} visible link={link} /> + ); + // call backdrop onPress + TestRenderer.root.findByProps({testID: 'flw-checkout-backdrop'}).props.onPress(); + // call the cancel alert button + Alert.alert.mock.calls[0][2][1].onPress(); + // simulate animation timeframes + timeTravel(); + // revert to using real timers + jest.useRealTimers(); + // wait for animateOut promise to resolve + setTimeout(() => { + // run assertions + expect(Alert.alert).toHaveBeenCalledTimes(1); + expect(onAbort).toHaveBeenCalledTimes(1); + done(); + }); + }); + + it('renders error with hasLink prop as true if there is a link', () => { + const onAbort = jest.fn(); + // create test renderer + const TestRenderer = renderer.create( + {}} onAbort={onAbort} visible link={link} /> + ); + // create error test renderer + const ErrorTestRenderer = renderer.create( + TestRenderer.root.findByType(WebView).props.renderError() + ); + // run assertions + expect(ErrorTestRenderer.root.props.hasLink).toEqual(true); + }); + + it('renders error with hasLink prop as false if there is no link', () => { + const onAbort = jest.fn(); + // create test renderer + const TestRenderer = renderer.create( + {}} onAbort={onAbort} visible link={link} /> + ); + // create error test renderer + const ErrorTestRenderer = renderer.create( + TestRenderer.root.findByType(WebView).props.renderError() + ); + // run assertions + expect(ErrorTestRenderer.root.props.hasLink).toEqual(true); + }); + + // it('fires reload when called if webview reference is set', () => { + // const onAbort = jest.fn(); + // // create test renderer + // const TestRenderer = renderer.create( + // {}} + // onAbort={onAbort} + // visible + // link={link} + // /> + // ); + // // get WebView test renderer + // const WebViewTestRenderer = TestRenderer.root.findByType(WebView); + // // spy on reload method on webview + // const reload = jest.spyOn(WebViewTestRenderer.instance, 'reload'); + // // create error test renderer + // const ErrorTestRenderer = renderer.create( + // TestRenderer.root.findByType(WebView).props.renderError() + // ); + // // call try again function on webview error + // ErrorTestRenderer.root.props.onTryAgain(); + // // run assertions + // expect(reload).toHaveBeenCalledTimes(1); + // expect(1).toBeTruthy(); + // }); + + it("returns query params if available in url passed to getRedirectParams", (done) => { + const url = new String(REDIRECT_URL + '?foo=bar'); + // spy on split method on String object + const split = jest.spyOn(url, 'split'); + // create test renderer + const TestRenderer = renderer.create( + {}} visible link={link} /> + ); + // fire on navigation state change + TestRenderer.root.findByType(WebView).props.onNavigationStateChange({url}); + // simulate animation timeframes + timeTravel(); + // revert to using real times + jest.useRealTimers(); + // wait for animateOut promise to resolve + setTimeout(() => { + // run assertions + expect(split).toHaveBeenCalledTimes(2); + done(); + }); + }); + + it("does not return query params if not available in url passed to getRedirectParams", (done) => { + const url = new String(REDIRECT_URL); + // spy on split method on String object + const split = jest.spyOn(url, 'split'); + // create test renderer + const TestRenderer = renderer.create( + {}} visible link={link} /> + ); + // fire on navigation state change + TestRenderer.root.findByType(WebView).props.onNavigationStateChange({url}); + // simulate animation timeframes + timeTravel(); + // revert to using real timers + jest.useRealTimers(); + // wait for animateOut promise to resolve + setTimeout(() => { + // run assertions + expect(split).toHaveBeenCalledTimes(1); + done(); + }) + }); +}); + +describe('', () => { + it('has a retry button if hasLink prop is true', () => { + // create test renderer + const TestRenderer = renderer.create( + {}} /> + ); + // simulate animation timeframes + timeTravel(); + // font retry button + const RetryButton = TestRenderer.root.findAllByType(TouchableOpacity) + // run assertions + expect(RetryButton).toHaveLength(1); + }); + + it('does not have a retry button if hasLink prop is false', () => { + // create test renderer + const TestRenderer = renderer.create( + {}} /> + ); + // simulate animation timeframes + timeTravel(); + // font retry button + const RetryButton = TestRenderer.root.findAllByType(TouchableOpacity) + // run assertions + expect(RetryButton).toHaveLength(0); + }); +}); + diff --git a/__tests__/__snapshots__/FlwCheckout.spec.tsx.snap b/__tests__/__snapshots__/FlwCheckout.spec.tsx.snap new file mode 100644 index 0000000..7bf7ce4 --- /dev/null +++ b/__tests__/__snapshots__/FlwCheckout.spec.tsx.snap @@ -0,0 +1,263 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders with modal closed if visible prop is not true 1`] = ` + + + + + + + + + + + +`; + +exports[` renders with modal open if visible props is true 1`] = ` + + + + + + + + + + + +`; From a6185c5d4f7dd4b40ca8c27db26399b401293027 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 09:28:23 +0100 Subject: [PATCH 100/129] feat: add flwbutton and flwcheckout library entrypoint exports --- src/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/index.ts b/src/index.ts index 2b1b2f0..2fdef43 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,8 @@ import FlutterwaveButton from './FlutterwaveButton'; import FlutterwaveInitV2 from './v2/FlutterwaveInit'; import FlutterwaveButtonV2 from './v2/FlutterwaveButton'; import DefaultButton from './DefaultButton'; +import FlwButton from './FlwButton'; +import FlwCheckout from './FlwCheckout'; // export modules export { @@ -11,6 +13,8 @@ export { FlutterwaveInitV2, FlutterwaveButtonV2, DefaultButton, + FlwButton, + FlwCheckout, }; // export v3 button as default From a8a8fa5a39d777280f4077abba6fb15f2f3079d8 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 09:30:37 +0100 Subject: [PATCH 101/129] feat(flutterwaveinit): rewrite base flutterwave init as v3 --- src/FlutterwaveInit.ts | 99 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 3 deletions(-) diff --git a/src/FlutterwaveInit.ts b/src/FlutterwaveInit.ts index 2f0c4eb..dc381af 100644 --- a/src/FlutterwaveInit.ts +++ b/src/FlutterwaveInit.ts @@ -1,4 +1,8 @@ -import FlutterwaveInit from './v3/FlutterwaveInit'; +import FlutterwaveInitError from './utils/FlutterwaveInitError'; +import ResponseParser from './utils/ResponseParser'; +import {STANDARD_URL} from './configs'; + +export type Currency = 'GBP' | 'NGN' | 'USD' | 'GHS' | 'KES' | 'ZAR' | 'TZS'; export interface FlutterwaveInitSubAccount { id: string; @@ -9,7 +13,7 @@ export interface FlutterwaveInitSubAccount { export interface FlutterwaveInitOptionsBase { amount: number; - currency?: string; + currency?: Currency; integrity_hash?: string; payment_options?: string; payment_plan?: number; @@ -17,4 +21,93 @@ export interface FlutterwaveInitOptionsBase { subaccounts?: Array; } -export default FlutterwaveInit; +interface FlutterwavePaymentMeta { + [k: string]: any; +} + +export interface FlutterwaveInitCustomer { + email: string; + phonenumber?: string; + name?: string; +} + +export interface FlutterwaveInitCustomizations { + title?: string; + logo?: string; + description?: string; +} + +export type FlutterwaveInitOptions = FlutterwaveInitOptionsBase & { + authorization: string; + tx_ref: string; + customer: FlutterwaveInitCustomer; + meta?: FlutterwavePaymentMeta | null; + customizations?: FlutterwaveInitCustomizations; +}; + +export interface FieldError { + field: string; + message: string; +} + +export interface ResponseData { + status?: 'success' | 'error'; + message: string; + error_id?: string; + errors?: Array; + code?: string; + data?: { + link: string; + }; +} + +interface FetchOptions { + method: 'POST'; + body: string; + headers: Headers; + signal?: AbortSignal; +} + +/** + * This function is responsible for making the request to + * initialize a Flutterwave payment. + * @param options FlutterwaveInitOptions + * @param abortController AbortController + * @return Promise + */ +export default async function FlutterwaveInit( + options: FlutterwaveInitOptions, + abortController?: AbortController, +): Promise { + try { + // get request body and authorization + const {authorization, ...body} = options; + // make request headers + const headers = new Headers; + headers.append('Content-Type', 'application/json'); + headers.append('Authorization', `Bearer ${authorization}`); + // make fetch options + const fetchOptions: FetchOptions = { + method: 'POST', + body: JSON.stringify(body), + headers: headers, + } + // add abortController if defined + if (abortController) { + fetchOptions.signal = abortController.signal + }; + // initialize payment + const response = await fetch(STANDARD_URL, fetchOptions); + // get response data + const responseData: ResponseData = await response.json(); + // resolve with the payment link + return Promise.resolve(await ResponseParser(responseData)); + } catch (e) { + // always return a flutterwave init error + const error = e instanceof FlutterwaveInitError + ? e + : new FlutterwaveInitError({message: e.message, code: e.name.toUpperCase()}) + // resolve with error + return Promise.reject(error); + } +} From 65f1ae724f7a9f85606a036db2667f0ea3a45408 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 09:31:41 +0100 Subject: [PATCH 102/129] fix(responseparser): fix import to point to correct flutterwave init --- src/utils/ResponseParser.ts | 2 +- src/v3/FlutterwaveInit.ts | 95 ------------------------------------- 2 files changed, 1 insertion(+), 96 deletions(-) delete mode 100644 src/v3/FlutterwaveInit.ts diff --git a/src/utils/ResponseParser.ts b/src/utils/ResponseParser.ts index 708438c..a3d0168 100644 --- a/src/utils/ResponseParser.ts +++ b/src/utils/ResponseParser.ts @@ -1,4 +1,4 @@ -import {ResponseData} from "../v3/FlutterwaveInit"; +import {ResponseData} from "../FlutterwaveInit"; import FlutterwaveInitError from "./FlutterwaveInitError"; /** diff --git a/src/v3/FlutterwaveInit.ts b/src/v3/FlutterwaveInit.ts deleted file mode 100644 index 7b5d86a..0000000 --- a/src/v3/FlutterwaveInit.ts +++ /dev/null @@ -1,95 +0,0 @@ -import {STANDARD_URL} from '../configs'; -import ResponseParser from '../utils/ResponseParser'; -import FlutterwaveInitError from '../utils/FlutterwaveInitError'; -import {FlutterwaveInitOptionsBase} from '../FlutterwaveInit'; - -interface FlutterwavePaymentMeta { - [k: string]: any; -} - -export interface FlutterwaveInitCustomer { - email: string; - phonenumber?: string; - name?: string; -} - -export interface FlutterwaveInitCustomizations { - title?: string; - logo?: string; - description?: string; -} - -export type FlutterwaveInitOptions = FlutterwaveInitOptionsBase & { - authorization: string; - tx_ref: string; - customer: FlutterwaveInitCustomer; - meta?: Array; - customizations?: FlutterwaveInitCustomizations; -}; - -export interface FieldError { - field: string; - message: string; -} - -export interface ResponseData { - status?: 'success' | 'error'; - message: string; - error_id?: string; - errors?: Array; - code?: string; - data?: { - link: string; - }; -} - -interface FetchOptions { - method: 'POST'; - body: string; - headers: Headers; - signal?: AbortSignal; -} - -/** - * This function is responsible for making the request to - * initialize a Flutterwave payment. - * @param options FlutterwaveInitOptions - * @param abortController AbortController - * @return Promise - */ -export default async function FlutterwaveInit( - options: FlutterwaveInitOptions, - abortController?: AbortController, -): Promise { - try { - // get request body and authorization - const {authorization, ...body} = options; - // make request headers - const headers = new Headers; - headers.append('Content-Type', 'application/json'); - headers.append('Authorization', `Bearer ${authorization}`); - // make fetch options - const fetchOptions: FetchOptions = { - method: 'POST', - body: JSON.stringify(body), - headers: headers, - } - // add abortController if defined - if (abortController) { - fetchOptions.signal = abortController.signal - }; - // initialize payment - const response = await fetch(STANDARD_URL, fetchOptions); - // get response data - const responseData: ResponseData = await response.json(); - // resolve with the payment link - return Promise.resolve(await ResponseParser(responseData)); - } catch (e) { - // always return a flutterwave init error - const error = e instanceof FlutterwaveInitError - ? e - : new FlutterwaveInitError({message: e.message, code: e.name.toUpperCase()}) - // resolve with error - return Promise.reject(error); - } -} From b3ffae759cc1f8302d37000645966ca8ea92feb4 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 09:34:44 +0100 Subject: [PATCH 103/129] test(flutterwaveinit): move flutterwaveinit test to base of tests folder --- __tests__/{v3 => }/FlutterwaveInit.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename __tests__/{v3 => }/FlutterwaveInit.spec.ts (95%) diff --git a/__tests__/v3/FlutterwaveInit.spec.ts b/__tests__/FlutterwaveInit.spec.ts similarity index 95% rename from __tests__/v3/FlutterwaveInit.spec.ts rename to __tests__/FlutterwaveInit.spec.ts index 959fd30..6061532 100644 --- a/__tests__/v3/FlutterwaveInit.spec.ts +++ b/__tests__/FlutterwaveInit.spec.ts @@ -1,6 +1,6 @@ -import FlutterwaveInit, {FlutterwaveInitOptions} from '../../src/v3/FlutterwaveInit'; -import {STANDARD_URL} from '../../src/configs'; -import FlutterwaveInitError from '../../src/utils/FlutterwaveInitError'; +import FlutterwaveInit, {FlutterwaveInitOptions} from '../src/FlutterwaveInit'; +import {STANDARD_URL} from '../src/configs'; +import FlutterwaveInitError from '../src/utils/FlutterwaveInitError'; const AUTHORIZATION = '[AUTHORIZATION]'; @@ -10,7 +10,7 @@ FETCH_HEADER.append('Content-Type', 'application/json'); FETCH_HEADER.append('Authorization', `Bearer ${AUTHORIZATION}`); // fetch body -const FETCH_BODY = { +const FETCH_BODY: Omit = { redirect_url: 'http://flutterwave.com', amount: 50, currency: 'NGN', From c2d29e38eca118705bb6e001a2009894f3ac8abc Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 09:39:40 +0100 Subject: [PATCH 104/129] fix(flutterwaveinitv2): move to base of library folder, fix import errors, and rename types --- ...lutterwaveInit.ts => FlutterwaveInitV2.ts} | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) rename src/{v2/FlutterwaveInit.ts => FlutterwaveInitV2.ts} (78%) diff --git a/src/v2/FlutterwaveInit.ts b/src/FlutterwaveInitV2.ts similarity index 78% rename from src/v2/FlutterwaveInit.ts rename to src/FlutterwaveInitV2.ts index b51eead..047301a 100644 --- a/src/v2/FlutterwaveInit.ts +++ b/src/FlutterwaveInitV2.ts @@ -1,13 +1,35 @@ -import {STANDARD_URL_V2} from '../configs'; -import {FlutterwaveInitOptionsBase} from '../FlutterwaveInit'; -import FlutterwaveInitError from '../utils/FlutterwaveInitError'; +import FlutterwaveInitError from './utils/FlutterwaveInitError'; +import {STANDARD_URL_V2} from './configs'; +import {Currency, FlutterwaveInitSubAccount} from './FlutterwaveInit'; + +export interface FlutterwaveInitOptionsBase { + amount: number; + currency?: Currency; + integrity_hash?: string; + payment_options?: string; + payment_plan?: number; + redirect_url: string; + subaccounts?: Array; +} + +export interface FieldError { + field: string; + message: string; +} + +interface FetchOptions { + method: 'POST'; + body: string; + headers: Headers; + signal?: AbortSignal; +} interface FlutterwavePaymentMetaV2 { metaname: string; metavalue: string; } -export type FlutterwaveInitOptions = FlutterwaveInitOptionsBase & { +export type FlutterwaveInitV2Options = FlutterwaveInitOptionsBase & { txref: string; PBFPubKey: string; customer_firstname?: string; @@ -32,13 +54,6 @@ interface ResponseJSON { }; } -interface FetchOptions { - method: 'POST' | 'GET' | 'PUT' | 'DELETE'; - body: string; - headers: Headers; - signal?: AbortSignal; -} - /** * This function is responsible for making the request to * initialize a Flutterwave payment. @@ -51,8 +66,8 @@ interface FetchOptions { * link?: string | null; * }> */ -export default async function FlutterwaveInit( - options: FlutterwaveInitOptions, +export default async function FlutterwaveInitV2( + options: FlutterwaveInitV2Options, abortController?: AbortController, ): Promise { try { From 1e402d58c33326ce4480b5bb69b625d8bb13b4dd Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 09:41:03 +0100 Subject: [PATCH 105/129] fix: correctly import FlutterwaveInitv2 --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 2fdef43..26e8a7a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,8 @@ import FlutterwaveInit from './FlutterwaveInit'; import FlutterwaveButton from './FlutterwaveButton'; -import FlutterwaveInitV2 from './v2/FlutterwaveInit'; import FlutterwaveButtonV2 from './v2/FlutterwaveButton'; import DefaultButton from './DefaultButton'; +import FlutterwaveInitV2 from './FlutterwaveInitV2'; import FlwButton from './FlwButton'; import FlwCheckout from './FlwCheckout'; From c84dc58b7f4b2bbe8559422c05dbb32c6bbe8421 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 09:41:53 +0100 Subject: [PATCH 106/129] fix(flutterwaveinitv2): move to base of tests folder --- __tests__/FlutterwaveInitV2.spec.ts | 162 ++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 __tests__/FlutterwaveInitV2.spec.ts diff --git a/__tests__/FlutterwaveInitV2.spec.ts b/__tests__/FlutterwaveInitV2.spec.ts new file mode 100644 index 0000000..983fe7b --- /dev/null +++ b/__tests__/FlutterwaveInitV2.spec.ts @@ -0,0 +1,162 @@ +import FlutterwaveInitV2, {FlutterwaveInitV2Options} from '../src/FlutterwaveInitV2'; +import {STANDARD_URL_V2} from '../src/configs'; +import FlutterwaveInitError from '../src/utils/FlutterwaveInitError'; + +const AUTHORIZATION = '[PUB Key]'; + +// fetch header +const FETCH_HEADER = new Headers(); +FETCH_HEADER.append('Content-Type', 'application/json'); + +// fetch body +const FETCH_BODY: FlutterwaveInitV2Options = { + redirect_url: 'http://flutterwave.com', + PBFPubKey: AUTHORIZATION, + amount: 50, + currency: 'NGN', + customer_email: 'email@example.com', + txref: Date.now() + '-txref', +} + +// payment options +const INIT_OPTIONS: FlutterwaveInitV2Options = { + ...FETCH_BODY, +}; + +const SUCCESS_RESPONSE = { + status: 'success', + message: 'Payment link generated.', + data: { + link: 'http://payment-link.com/checkout', + }, +}; + +describe('', () => { + it('returns a payment link after initialization', async () => { + // mock next fetch request + fetchMock.mockOnce(JSON.stringify(SUCCESS_RESPONSE)); + // flutterwave init test + const link = await FlutterwaveInitV2(INIT_OPTIONS); + // expect fetch to have been called once + expect(global.fetch).toHaveBeenCalledTimes(1); + // expect fetch to have been called to the standard init url + expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL_V2, { + body: JSON.stringify(FETCH_BODY), + headers: FETCH_HEADER, + method: 'POST', + }); + expect(typeof link === 'string').toBeTruthy(); + }); + + it('includes error code and message of init error', async () => { + const message = 'An error has occurred.'; + // mock next request + fetchMock.mockOnce(JSON.stringify({ + status: 'error', + message: message, + })); + try { + // initialize payment + await FlutterwaveInitV2(INIT_OPTIONS); + } catch (error) { + // run assertions + expect(error.message).toEqual(message); + expect(error.code).toEqual('STANDARD_INIT_ERROR'); + } + }); + + it('sets error code to MALFORMED_RESPONSE if not defined in response', async () => { + const message = 'An error has occurred.'; + // reject next fetch + fetchMock.mockOnce(JSON.stringify({ + status: 'error', + data: {}, + })); + try { + // initialize payment + await FlutterwaveInitV2(INIT_OPTIONS); + } catch (error) { + // run assertions + expect(error.code).toEqual('MALFORMED_RESPONSE'); + } + }); + + it('sets error message to default message if not defined in response', async () => { + const message = 'An error has occurred.'; + // reject next fetch + fetchMock.mockOnce(JSON.stringify({ + status: 'error', + data: {}, + })); + try { + // initialize payment + await FlutterwaveInitV2(INIT_OPTIONS); + } catch (error) { + // run assertions + expect(error.message).toEqual('An unknown error occured!'); + } + }); + + it('handles missing data error', async () => { + // mock next fetch + fetchMock.mockOnce(JSON.stringify({status: 'error'})); + try { + // initialize payment + const response = await FlutterwaveInitV2(INIT_OPTIONS); + } catch (error) { + // run assertions + expect(error.code).toEqual('STANDARD_INIT_ERROR'); + } + }); + + it('rejects with an error if link is missing in data', async () => { + const errorMessage = 'Missing link test.'; + // mock next fetch + fetchMock.mockOnce( + JSON.stringify({ + status: 'error', + data: { + message: errorMessage, + code: 'MALFORMED_RESPONSE' + } + } + ) + ); + try { + // initialize payment + const response = await FlutterwaveInitV2(INIT_OPTIONS); + } catch (error) { + // run assertions + expect(error.code).toEqual('MALFORMED_RESPONSE'); + } + }); + + + it('is abortable', async () => { + // use fake jest timers + jest.useFakeTimers(); + // mock fetch response + fetchMock.mockResponse(async () => { + jest.advanceTimersByTime(60) + return JSON.stringify({ + status: 'error', + message: 'Error!', + }) + }); + // create abort controller + const abortController = new AbortController; + // abort next fetch + setTimeout(() => abortController.abort(), 50); + try { + // initialize payment + await FlutterwaveInitV2( + INIT_OPTIONS, + abortController + ) + } catch(error) { + // run assertions + expect(error).toBeInstanceOf(FlutterwaveInitError); + expect(error.code).toEqual('ABORTERROR'); + } + }); +}); From 55dcccaf55a841b13d84324616a75bd1bc35149b Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 09:43:18 +0100 Subject: [PATCH 107/129] feat(paywithflutterwavebase): add component This component carries most of the similar code both pay with flutterwave v2 and v3 components share. --- src/PaywithFlutterwaveBase.tsx | 258 +++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 src/PaywithFlutterwaveBase.tsx diff --git a/src/PaywithFlutterwaveBase.tsx b/src/PaywithFlutterwaveBase.tsx new file mode 100644 index 0000000..152411e --- /dev/null +++ b/src/PaywithFlutterwaveBase.tsx @@ -0,0 +1,258 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import FlutterwaveInitError from './utils/FlutterwaveInitError'; +import FlwCheckout from './FlwCheckout'; +import FlwButton from './FlwButton'; +import {REDIRECT_URL} from './configs'; +import { StyleProp, ViewStyle } from 'react-native'; + +export interface CustomButtonProps { + disabled: boolean; + onPress: () => void; +} + +export interface PayWithFlutterwavePropsBase { + style?: StyleProp; + onRedirect: (data: any) => void; + onWillInitialize?: () => void; + onDidInitialize?: () => void; + onInitializeError?: (error: FlutterwaveInitError) => void; + onAbort?: () => void; + customButton?: (params: CustomButtonProps) => React.ReactNode; + alignLeft?: 'alignLeft' | boolean; + meta?: Array; + currency?: string; +} + +export const PayWithFlutterwavePropTypesBase = { + alignLeft: PropTypes.bool, + onAbort: PropTypes.func, + onRedirect: PropTypes.func.isRequired, + onWillInitialize: PropTypes.func, + onDidInitialize: PropTypes.func, + onInitializeError: PropTypes.func, + customButton: PropTypes.func, +}; + +export const OptionsPropTypeBase = { + amount: PropTypes.number.isRequired, + currency: PropTypes.oneOf(['GBP', 'NGN', 'USD', 'GHS', 'KES', 'ZAR', 'TZS']), + payment_plan: PropTypes.number, + subaccounts: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string.isRequired, + transaction_split_ratio: PropTypes.number, + transaction_charge_type: PropTypes.string, + transaction_charge: PropTypes.number, + })), + integrity_hash: PropTypes.string, +}; + +interface PayWithFlutterwaveState { + link: string | null; + isPending: boolean; + showDialog: boolean; + reference: string | null; + resetLink: boolean; +} + +export type PayWithFlutterwaveBaseProps = PayWithFlutterwavePropsBase & { + options: any; + init: (options: any, abortController?: AbortController) => Promise; + reference: string; +}; + +class PayWithFlutterwaveBase

extends React.Component< + PayWithFlutterwaveBaseProps & P, + PayWithFlutterwaveState +> { + + state: PayWithFlutterwaveState = { + isPending: false, + link: null, + resetLink: false, + showDialog: false, + reference: null, + }; + + abortController?: AbortController; + + timeout: any; + + handleInitCall?: () => Promise; + + componentDidUpdate(prevProps: PayWithFlutterwaveBaseProps) { + const prevOptions = JSON.stringify(prevProps.options); + const options = JSON.stringify(this.props.options); + if (prevOptions !== options) { + this.handleOptionsChanged() + } + } + + componentWillUnmount() { + if (this.abortController) { + this.abortController.abort(); + } + } + + reset = () => { + if (this.abortController) { + this.abortController.abort(); + } + // reset the necessaries + this.setState(({resetLink, link}) => ({ + isPending: false, + link: resetLink ? null : link, + resetLink: false, + showDialog: false, + })); + }; + + handleOptionsChanged = () => { + const {showDialog, link} = this.state; + if (!link) { + return; + } + if (!showDialog) { + return this.setState({ + link: null, + reference: null, + }) + } + this.setState({resetLink: true}) + } + + handleAbort = () => { + const {onAbort} = this.props; + if (onAbort) { + onAbort(); + } + this.reset(); + } + + handleRedirect = (params: any) => { + const {onRedirect} = this.props; + // reset payment link + this.setState( + ({resetLink, reference}) => ({ + reference: params.flwref || params.status === 'successful' ? null : reference, + resetLink: params.flwref || params.status === 'successful' ? true : resetLink, + showDialog: false, + }), + () => { + onRedirect(params) + this.reset(); + } + ); + } + + handleInit = async () => { + const { + options, + onWillInitialize, + onInitializeError, + onDidInitialize, + init, + } = this.props; + const {isPending, reference, link} = this.state; + // just show the dialod if the link is already set + if (link) { + return this.setState({showDialog: true}); + } + // throw error if transaction reference has not changed + if (reference === this.props.reference) { + // fire oninitialize error handler if available + if (onInitializeError) { + onInitializeError(new FlutterwaveInitError({ + message: 'Please generate a new transaction reference.', + code: 'SAME_TXREF', + })) + } + return; + } + // stop if currently in pending mode + if (isPending) { + return; + } + // initialize abort controller if not set + this.abortController = new AbortController; + // fire will initialize handler if available + if (onWillInitialize) { + onWillInitialize(); + } + this.setState({ + isPending: true, + link: null, + reference: this.props.reference, + showDialog: false, + }, async () => { + // handle init + try { + // initialize payment + const paymentLink = await init( + {...options, redirect_url: REDIRECT_URL}, + this.abortController + ); + // set payment link + this.setState({ + link: paymentLink, + isPending: false, + showDialog: true, + }, () => { + // fire did initialize handler if available + if (onDidInitialize) { + onDidInitialize(); + } + }); + } catch (error) { + // stop if request was canceled + if (error && /aborterror/i.test(error.code)) { + return; + } + // call onInitializeError handler if an error occured + if (onInitializeError) { + onInitializeError(error); + } + // set payment link to reset + this.setState({ + resetLink: true, + reference: null, + }, this.reset); + } + }) + }; + + render() { + const {link, showDialog} = this.state; + return ( + <> + {this.renderButton()} + + + ); + } + + renderButton() { + const {alignLeft, customButton, children, style} = this.props; + const {isPending} = this.state; + if (customButton) { + return customButton({ + disabled: isPending, + onPress: this.handleInit + }); + } + return ; + } +} + +export default PayWithFlutterwaveBase; From c04f0cb41b8d995b23f3101d9b031e0c6c1fe495 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 09:44:35 +0100 Subject: [PATCH 108/129] test(paywithflutterwavebutton): test and generate snaptshots for pay with flutterwave base component --- __tests__/PayWithFlutterwaveBase.spec.tsx | 657 ++++++++++++++++++ .../PayWithFlutterwaveBase.spec.tsx.snap | 338 +++++++++ 2 files changed, 995 insertions(+) create mode 100644 __tests__/PayWithFlutterwaveBase.spec.tsx create mode 100644 __tests__/__snapshots__/PayWithFlutterwaveBase.spec.tsx.snap diff --git a/__tests__/PayWithFlutterwaveBase.spec.tsx b/__tests__/PayWithFlutterwaveBase.spec.tsx new file mode 100644 index 0000000..499cef3 --- /dev/null +++ b/__tests__/PayWithFlutterwaveBase.spec.tsx @@ -0,0 +1,657 @@ +import 'react-native'; +import React from 'react'; +import renderer from 'react-test-renderer'; +import PayWithFlutterwaveBase from '../src/PayWithFlutterwaveBase'; +import {FlutterwaveInitOptions} from '../src/FlutterwaveInit'; +import {REDIRECT_URL, STANDARD_URL} from '../src/configs'; +import {Modal, TouchableOpacity, Text} from 'react-native'; +import timeTravel, { setupTimeTravel } from '../timeTravel'; +import FlwCheckout from '../src/FlwCheckout'; +import FlutterwaveInitError from '../src/utils/FlutterwaveInitError'; +import FlutterwaveInit from '../src/FlutterwaveInit'; +const BtnTestID = 'flw-button'; +const CustomBtnTestID = 'flw-custom-button'; + +const CustomButton = ({onPress, disabled}) => ( + + {disabled ? 'Please wait...' : 'Pay'} + +) + +const SuccessResponse = { + status: 'success', + message: 'Payment link generated.', + data: { + link: 'http://payment-link.com/checkout', + }, +}; + +const PAYMENT_INFO: Omit = { + tx_ref: '34h093h09h034034', + customer: { + email: 'customer-email@example.com', + }, + authorization: '[Authorization]', + amount: 50, + currency: 'NGN', +}; + +const REQUEST_BODY = {...PAYMENT_INFO, redirect_url: REDIRECT_URL}; +delete REQUEST_BODY.authorization; + +const HEADERS = new Headers +HEADERS.append('Content-Type', 'application/json'); +HEADERS.append('Authorization', `Bearer ${PAYMENT_INFO.authorization}`); + +// set and unset time travel +beforeEach(() => { + setupTimeTravel(); + fetchMock.mockReset(); +}); +afterEach(() => jest.useRealTimers()); + +describe('PayWithFlutterwaveBase', () => { + + it('renders a default pay with flutterwave button', () => { + // create component tree + const Tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={jest.fn()} + /> + ); + // run assertions + expect(Tree.toJSON()).toMatchSnapshot(); + }); + + it('uses custom button in place of flw button if defined', () => { + const Tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={jest.fn()} + customButton={CustomButton} + /> + ); + // run assertions + expect(Tree.toJSON()).toMatchSnapshot(); + }) + + it('disables custom button and set is initializing to true when initializing payment', () => { + const customButton = jest.fn(); + // create component tree + const Tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={jest.fn()} + customButton={customButton} + /> + ); + // call the handleInit method on component instance + Tree.root.instance.handleInit(); + // run aseertions + expect(customButton).toHaveBeenNthCalledWith(1, { + disabled: false, + onPress: expect.any(Function), + }); + expect(customButton).toHaveBeenLastCalledWith({ + disabled: true, + onPress: expect.any(Function), + }); + }); + + it('disables button if in pending mode', () => { + // create component tree + const Tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={jest.fn()} + /> + ); + // enter pending mode + Tree.root.instance.setState({isPending: true}); + // get button test renderer + const ButtonTestRender = Tree.root.findByProps({testID: BtnTestID}); + // run assertions + expect(ButtonTestRender.props.disabled).toEqual(true); + }); + + it('initializes payment when button is pressed', async () => { + // use real timers + jest.useRealTimers(); + const init = jest.fn(); + // create component tree + const Tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={init} + foo="Bar" + /> + ); + // mock next request + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + // initialize payment + Tree.root.findByProps({testID: BtnTestID}).props.onPress(); + // run assertions + expect(init).toBeCalledTimes(1); + expect(init).toHaveBeenCalledWith({...PAYMENT_INFO, redirect_url: REDIRECT_URL}, new AbortController); + }); + + it('does not initialize payment if already has payment link', () => { + // create component tree + const Tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={jest.fn()} + /> + ); + // set payment link + Tree.root.instance.setState({link: 'http://checkout-url.com'}); + // mock next fetch request + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + // initialise payment + Tree.root.instance.handleInit(); + // run assertions + expect(global.fetch).toHaveBeenCalledTimes(0); + }); + + it('does not initialize payment if not using new transaction ref', () => { + const onInitializeError = jest.fn(); + const init = jest.fn(); + // create component tree + const Tree = renderer.create( + {}} + options={PAYMENT_INFO} + reference={PAYMENT_INFO.tx_ref} + init={init} + onInitializeError={onInitializeError} + /> + ); + // set payment link + Tree.root.instance.setState({reference: PAYMENT_INFO.tx_ref}); + // initialise payment + Tree.root.instance.handleInit(); + // run assertions + expect(init).toHaveBeenCalledTimes(0); + expect(onInitializeError).toHaveBeenCalledTimes(1); + expect(onInitializeError.mock.calls[0][0]).toBeInstanceOf(FlutterwaveInitError); + }); + + it('does not initialize payment if in pending state', () => { + // create component tree + const Tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={jest.fn()} + /> + ); + // set pending to true + Tree.root.instance.setState({isPending: true}); + // mock next fetch request + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + // initialise payment + Tree.root.instance.handleInit(); + // run assertions + expect(global.fetch).toHaveBeenCalledTimes(0); + }); + + it('calls onAbort if available and abort event occurred', () => { + const onAbort = jest.fn(); + // create component tree + const FlwButton = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={jest.fn()} + onAbort={onAbort} + /> + ); + // fire handle abort confirm + FlwButton.root.instance.handleAbort(); + // called on abort + expect(onAbort).toHaveBeenCalledTimes(1); + }); + + it('sets resetLink state to true on v2 success redirect', () => { + // create component tree + const Tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={jest.fn()} + /> + ); + // spy on setState method + const setState = jest.spyOn(Tree.root.instance, 'setState'); + // call handle redirect method + Tree.root.instance.handleRedirect({flwref: 'flwref'}); + // run assertions + expect(setState.mock.calls[0][0](Tree.root.instance.state)).toMatchObject({ + reference: null, + resetLink: true, + showDialog: false, + }); + }); + + it('sets resetLink state to false on v2 failure redirect', () => { + // create component tree + const Tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={jest.fn()} + /> + ); + // spy on setState method + const setState = jest.spyOn(Tree.root.instance, 'setState'); + // call handle redirect method + Tree.root.instance.handleRedirect({}); + // run assertions + expect(setState.mock.calls[0][0](Tree.root.instance.state)).toMatchObject({ + reference: null, + resetLink: false, + showDialog: false, + }); + }); + + it('sets resetLink state to true on v3 success redirect', () => { + // create component tree + const Tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={jest.fn()} + /> + ); + // spy on setState method + const setState = jest.spyOn(Tree.root.instance, 'setState'); + // call handle redirect method + Tree.root.instance.handleRedirect({status: 'successful'}); + // run assertions + expect(setState.mock.calls[0][0](Tree.root.instance.state)).toMatchObject({ + reference: null, + resetLink: true, + showDialog: false, + }); + }); + + it('does not update state if options changed and no link has been generated', () => { + // create component tree + const Tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={jest.fn()} + /> + ); + // spy on setState method + const setState = jest.spyOn(Tree.root.instance, 'setState'); + // update component + Tree.update( + {}} + reference={PAYMENT_INFO.tx_ref} + options={{...PAYMENT_INFO, txref: 'foobarbaz'}} + init={jest.fn()} + /> + ) + // run assertions + expect(setState).toHaveReturnedTimes(0); + }); + + it('unsets link and reference if options changed and dialog is not yet visible', () => { + // create component tree + const Tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={jest.fn()} + /> + ); + // set a link + Tree.root.instance.setState({link: 'http://link.com'}); + // spy on setState method + const setState = jest.spyOn(Tree.root.instance, 'setState'); + // update component + Tree.update( + {}} + reference={PAYMENT_INFO.tx_ref} + options={{...PAYMENT_INFO, txref: 'foobarbaz'}} + init={jest.fn()} + /> + ) + // run assertions + expect(setState).toHaveBeenLastCalledWith({ + link: null, + reference: null, + }); + }); + + it('schedules link reset if options changed and dialog is visible', () => { + // create component tree + const Tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={jest.fn()} + /> + ); + // set a link and disalog visibility + Tree.root.instance.setState({link: 'http://link.com', showDialog: true}); + // spy on setState method + const setState = jest.spyOn(Tree.root.instance, 'setState'); + // update component + Tree.update( + {}} + reference={PAYMENT_INFO.tx_ref} + options={{...PAYMENT_INFO, txref: 'foobarbaz'}} + init={jest.fn()} + /> + ) + // run assertions + expect(setState).toHaveBeenLastCalledWith({resetLink: true}); + }); + + it('sets resetLink state to false on v2 failure redirect', () => { + // create component tree + const Tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={jest.fn()} + /> + ); + // spy on setState method + const setState = jest.spyOn(Tree.root.instance, 'setState'); + // call handle redirect method + Tree.root.instance.handleRedirect({status: 'cancelled'}); + // run assertions + expect(setState.mock.calls[0][0](Tree.root.instance.state)).toMatchObject({ + reference: null, + resetLink: false, + showDialog: false, + }); + }); + + it('fires onDidInitialize if available', (done) => { + const onDidInitialize = jest.fn(); + // create component tree + const FlwButton = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={jest.fn()} + onDidInitialize={onDidInitialize} + /> + ); + // mock next fetch request + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + // initialize payment + FlwButton.root.instance.handleInit(); + // simulate animated timeframe + timeTravel(); + // revert to using real timers + jest.useRealTimers(); + // wait for fetch to resolve + setTimeout(() => { + // run assertions + expect(onDidInitialize).toHaveBeenCalledTimes(1); + done(); + }); + }); + + it('fires onWillInitialize if available', (done) => { + const onWillInitialize = jest.fn(); + // create component tree + const tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={jest.fn()} + onWillInitialize={onWillInitialize} + /> + ); + // mock next fetch request + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + // initialize payment + tree.root.instance.handleInit(); + // simulate animated timeframe + timeTravel(); + // revert to using real timers + jest.useRealTimers(); + // wait for fetch to resolve + setTimeout(() => { + // run assertions + expect(onWillInitialize).toHaveBeenCalledTimes(1); + done(); + }); + }); + + it('fires onInitializeError if available', (done) => { + // define variables + const err = new Error('Error occurred.'); + const onInitializeError = jest.fn(); + // create component tree + const Tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={FlutterwaveInit} + onInitializeError={onInitializeError} + /> + ); + // mock next fetch request + fetchMock.mockRejectOnce(err); + // fire on press + Tree.root.instance.handleInit(); + // revert to using real timers + jest.useRealTimers(); + // wait for fetch to resolve + setTimeout(() => { + // run assertions + expect(onInitializeError).toHaveBeenCalledTimes(1); + expect(onInitializeError).toHaveBeenCalledWith(new FlutterwaveInitError({ + code: err.name.toUpperCase(), + message: err.message + })); + // end test + done(); + }, 50); + }); + + it('does not update state if init is aborted', (done) => { + // revert to using real timers + jest.useRealTimers(); + // create component tree + const Tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={FlutterwaveInit} + /> + ); + // spy on setState method + const setState = jest.spyOn(Tree.root.instance, 'setState'); + // mock next fetch request + fetchMock.mockAbortOnce(); + // initialize payment + Tree.root.instance.handleInit(); + // wait for request to be made + setTimeout(() => { + expect(setState).toHaveBeenCalledTimes(1); + expect(Tree.root.instance.state.isPending).toBe(true); + // end test + done(); + }); + }); + + it("cancels fetch on will unmount.", () => { + const Tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={jest.fn()} + /> + ); + // mock next fetch request + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + // initialize payment + Tree.root.instance.handleInit(); + // spy on abort method + const abort = jest.spyOn(Tree.root.instance.abortController, 'abort'); + // call component will unmount + Tree.root.instance.componentWillUnmount(); + // run assertions + expect(abort).toHaveBeenCalledTimes(1); + // end test + }); + + it("does not cancel fetch on will unmount if canceller is not set.", () => { + const Tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={jest.fn()} + /> + ); + // spy on componentWillUnmount lifecycle hook method + const willUnmount = jest.spyOn(Tree.root.instance, 'componentWillUnmount'); + // call component will unmount + Tree.root.instance.componentWillUnmount(); + // run assertions + expect(willUnmount).toHaveBeenCalledTimes(1); + expect(Tree.root.instance.abortController).toBeUndefined(); + }); + + it("updates state if reset is called.", () => { + const Tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={jest.fn()} + /> + ); + // set component state + const setState = jest.spyOn(Tree.root.instance, 'setState'); + // call component will unmount + Tree.root.instance.reset(); + // run assertions + expect(setState).toHaveBeenCalledTimes(1); + expect(Tree.root.instance.abortController).toBeUndefined(); + }); + + it("cancels fetch if reset is called and abort controller is set.", () => { + const Tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={jest.fn()} + /> + ); + // mock next fetch request + fetchMock.mockOnce(JSON.stringify(SuccessResponse)); + // initialize payment + Tree.root.instance.handleInit(); + // spy on abort method on abortController + const abort = jest.spyOn(Tree.root.instance.abortController, 'abort'); + // call componentWillUnmount + Tree.root.instance.reset(); + // run assertions + expect(abort).toHaveBeenCalledTimes(1); + }); + + it("calls options change handler if options changed", () => { + const Tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={jest.fn()} + /> + ); + // spy on handleOptionsChanged method + const handleOptionsChanged = jest.spyOn(Tree.root.instance, 'handleOptionsChanged'); + // update component + Tree.update( + {}} + reference={PAYMENT_INFO.tx_ref} + options={{...PAYMENT_INFO, tx_ref: 'Updated tx_ref'}} + init={jest.fn()} + /> + ); + // run assertions + expect(handleOptionsChanged).toHaveBeenCalledTimes(1); + }); + + it('renders modal with visible prop set to false by default', () => { + // create component tree + const Tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={jest.fn()} + /> + ); + // get modal test renderer + const ModalTestRender = Tree.root.findByType(Modal); + // run assertions + expect(ModalTestRender.props.visible).toEqual(false); + }); + + it('sets checkout visible prop to true when showDialog state is true', () => { + // create component tree + const Tree = renderer.create( + {}} + reference={PAYMENT_INFO.tx_ref} + options={PAYMENT_INFO} + init={jest.fn()} + /> + ); + // enter pending mode + Tree.root.instance.setState({showDialog: true}); + // simulate animated timeframe + timeTravel(); + // get modal test renderer + const ModalTestRender = Tree.root.findByType(FlwCheckout); + // run assertions + expect(ModalTestRender.props.visible).toEqual(true); + }); +}); diff --git a/__tests__/__snapshots__/PayWithFlutterwaveBase.spec.tsx.snap b/__tests__/__snapshots__/PayWithFlutterwaveBase.spec.tsx.snap new file mode 100644 index 0000000..53c98dc --- /dev/null +++ b/__tests__/__snapshots__/PayWithFlutterwaveBase.spec.tsx.snap @@ -0,0 +1,338 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PayWithFlutterwaveBase renders a default pay with flutterwave button 1`] = ` +Array [ + + + , + + + + + + + + + + + , +] +`; + +exports[`PayWithFlutterwaveBase uses custom button in place of flw button if defined 1`] = ` +Array [ + + + Pay + + , + + + + + + + + + + + , +] +`; From c86789c7f1e4a7303b9564feff6724164cb33449 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 09:45:29 +0100 Subject: [PATCH 109/129] feat(paywithflutterwave): add v3 pay with flutterwave --- src/PayWithFlutterwave.tsx | 60 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/PayWithFlutterwave.tsx diff --git a/src/PayWithFlutterwave.tsx b/src/PayWithFlutterwave.tsx new file mode 100644 index 0000000..d2cd4a8 --- /dev/null +++ b/src/PayWithFlutterwave.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + PayWithFlutterwavePropsBase, + OptionsPropTypeBase, + PayWithFlutterwavePropTypesBase +} from './PaywithFlutterwaveBase'; +import FlutterwaveInit, {FlutterwaveInitOptions} from './FlutterwaveInit'; +import {PAYMENT_OPTIONS} from './configs'; +import {PaymentOptionsPropRule} from './utils/CustomPropTypesRules'; +import PayWithFlutterwaveBase from './PaywithFlutterwaveBase'; + +export interface RedirectParams { + status: 'successful' | 'cancelled', + transaction_id?: string; + tx_ref: string; +} + +// create V3 component props +export type PayWithFlutterwaveProps = PayWithFlutterwavePropsBase & { + onRedirect: (data: RedirectParams) => void; + options: Omit; +} + +// create V3 component +const PayWithFlutterwave:React.FC = ({options, ...props}) => { + return ( + + ); +} + +// define component prop types +PayWithFlutterwave.propTypes = { + ...PayWithFlutterwavePropTypesBase, + // @ts-ignore + options: PropTypes.shape({ + ...OptionsPropTypeBase, + authorization: PropTypes.string.isRequired, + tx_ref: PropTypes.string.isRequired, + payment_options: PaymentOptionsPropRule(PAYMENT_OPTIONS), + customer: PropTypes.shape({ + name: PropTypes.string, + phonenumber: PropTypes.string, + email: PropTypes.string.isRequired, + }).isRequired, + meta: PropTypes.object, + customizations: PropTypes.shape({ + title: PropTypes.string, + logo: PropTypes.string, + description: PropTypes.string, + }), + }).isRequired, +}; +// export component as default +export default PayWithFlutterwave; From 284daa4a618a2e8fec9d1ed41acac035549ad5c5 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 09:46:19 +0100 Subject: [PATCH 110/129] test(paywithflutterwave): pay with flutterwave v3 tests and snapshot --- __tests__/PayWithFlutterwave.spec.tsx | 53 +++ .../PayWithFlutterwave.spec.tsx.snap | 338 ++++++++++++++++++ 2 files changed, 391 insertions(+) create mode 100644 __tests__/PayWithFlutterwave.spec.tsx create mode 100644 __tests__/__snapshots__/PayWithFlutterwave.spec.tsx.snap diff --git a/__tests__/PayWithFlutterwave.spec.tsx b/__tests__/PayWithFlutterwave.spec.tsx new file mode 100644 index 0000000..08164fb --- /dev/null +++ b/__tests__/PayWithFlutterwave.spec.tsx @@ -0,0 +1,53 @@ +import 'react-native'; +import React from 'react'; +import renderer from 'react-test-renderer'; +import PayWithFlutterwave from '../src/PayWithFlutterwave'; +import {FlutterwaveInitOptions} from '../src/FlutterwaveInit'; +import {TouchableOpacity, Text} from 'react-native'; +const CustomBtnTestID = 'flw-custom-button'; + +const CustomButton = ({onPress, disabled}) => ( + + {disabled ? 'Please wait...' : 'Pay'} + +) + +const PAYMENT_INFO: Omit = { + tx_ref: '34h093h09h034034', + customer: { + email: 'customer-email@example.com', + }, + authorization: '[Authorization]', + amount: 50, + currency: 'NGN', +}; + +describe('PayWithFlutterwaveV3', () => { + + it('renders a default pay with flutterwave button', () => { + // create component tree + const Tree = renderer.create( + {}} + options={PAYMENT_INFO} + /> + ); + // run assertions + expect(Tree.toJSON()).toMatchSnapshot(); + }); + + it('uses custom button in place of flw button if defined', () => { + const Tree = renderer.create( + {}} + options={PAYMENT_INFO} + customButton={CustomButton} + /> + ); + // run assertions + expect(Tree.toJSON()).toMatchSnapshot(); + }) +}); diff --git a/__tests__/__snapshots__/PayWithFlutterwave.spec.tsx.snap b/__tests__/__snapshots__/PayWithFlutterwave.spec.tsx.snap new file mode 100644 index 0000000..6b84a70 --- /dev/null +++ b/__tests__/__snapshots__/PayWithFlutterwave.spec.tsx.snap @@ -0,0 +1,338 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PayWithFlutterwaveV3 renders a default pay with flutterwave button 1`] = ` +Array [ + + + , + + + + + + + + + + + , +] +`; + +exports[`PayWithFlutterwaveV3 uses custom button in place of flw button if defined 1`] = ` +Array [ + + + Pay + + , + + + + + + + + + + + , +] +`; From e1a0825260d50752c360c2cc26dd7ac9d54052ca Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 09:47:44 +0100 Subject: [PATCH 111/129] feat(index.ts): add PayWithFlutterwave component and make the default export --- src/index.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 26e8a7a..3af51ab 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ import FlutterwaveButton from './FlutterwaveButton'; import FlutterwaveButtonV2 from './v2/FlutterwaveButton'; import DefaultButton from './DefaultButton'; import FlutterwaveInitV2 from './FlutterwaveInitV2'; +import PayWithFlutterwave from './PayWithFlutterwave'; import FlwButton from './FlwButton'; import FlwCheckout from './FlwCheckout'; @@ -10,6 +11,7 @@ import FlwCheckout from './FlwCheckout'; export { FlutterwaveInit, FlutterwaveButton, + PayWithFlutterwave, FlutterwaveInitV2, FlutterwaveButtonV2, DefaultButton, @@ -17,5 +19,5 @@ export { FlwCheckout, }; -// export v3 button as default -export default FlutterwaveButton; +// export v3 PayWithFlutterwave as default +export default PayWithFlutterwave; From a228e9a51a1689e208c4a161effe58a1e7b4bd9d Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 09:49:21 +0100 Subject: [PATCH 112/129] feat(paywithflutterwavev2): create component --- src/PayWithFlutterwaveV2.tsx | 61 ++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/PayWithFlutterwaveV2.tsx diff --git a/src/PayWithFlutterwaveV2.tsx b/src/PayWithFlutterwaveV2.tsx new file mode 100644 index 0000000..40474d5 --- /dev/null +++ b/src/PayWithFlutterwaveV2.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + PayWithFlutterwavePropsBase, + OptionsPropTypeBase, + PayWithFlutterwavePropTypesBase +} from './PaywithFlutterwaveBase'; +import FlutterwaveInitV2, {FlutterwaveInitV2Options} from './FlutterwaveInitV2'; +import {PAYMENT_OPTIONS_V2} from './configs'; +import {PaymentOptionsPropRule} from './utils/CustomPropTypesRules'; +import PayWithFlutterwaveBase from './PaywithFlutterwaveBase'; + +export interface RedirectParamsV2 { + cancelled?: 'true' | 'false'; + flwref?: string; + txref: string; +} + +export type PayWithFlutterwaveV2Props = PayWithFlutterwavePropsBase & { + onRedirect: (data: RedirectParamsV2) => void; + options: Omit; +} + +// create V2 component +const PayWithFlutterwaveV2:React.FC = ({options, ...props}) => { + return ( + + ); +} + +// define component prop types +PayWithFlutterwaveV2.propTypes = { + ...PayWithFlutterwavePropTypesBase, + // @ts-ignore + options: PropTypes.shape({ + ...OptionsPropTypeBase, + payment_options: PaymentOptionsPropRule(PAYMENT_OPTIONS_V2), + txref: PropTypes.string.isRequired, + PBFPubKey: PropTypes.string.isRequired, + customer_firstname: PropTypes.string, + customer_lastname: PropTypes.string, + customer_email: PropTypes.string.isRequired, + customer_phone: PropTypes.string, + country: PropTypes.string, + pay_button_text: PropTypes.string, + custom_title: PropTypes.string, + custom_description: PropTypes.string, + custom_logo: PropTypes.string, + meta: PropTypes.arrayOf(PropTypes.shape({ + metaname: PropTypes.string, + metavalue: PropTypes.string, + })), + }).isRequired, +}; +// export component as default +export default PayWithFlutterwaveV2; From f988d5c8da257a4088c8ddda173a14f6ab65ca58 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 09:58:58 +0100 Subject: [PATCH 113/129] feat(index.ts): add PayWithFlutterwaveV2 to exports --- src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/index.ts b/src/index.ts index 3af51ab..654ca6f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import FlutterwaveButtonV2 from './v2/FlutterwaveButton'; import DefaultButton from './DefaultButton'; import FlutterwaveInitV2 from './FlutterwaveInitV2'; import PayWithFlutterwave from './PayWithFlutterwave'; +import PayWithFlutterwaveV2 from './PayWithFlutterwaveV2'; import FlwButton from './FlwButton'; import FlwCheckout from './FlwCheckout'; @@ -15,6 +16,7 @@ export { FlutterwaveInitV2, FlutterwaveButtonV2, DefaultButton, + PayWithFlutterwaveV2, FlwButton, FlwCheckout, }; From b73f5bd4bf044bdedd444c0189a44f0cff863359 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 09:59:52 +0100 Subject: [PATCH 114/129] test(paywithflutterwavev2): write tests and generate snapshots --- __tests__/PayWithFlutterwaveV2.spec.tsx | 51 +++ .../PayWithFlutterwaveV2.spec.tsx.snap | 338 ++++++++++++++++++ 2 files changed, 389 insertions(+) create mode 100644 __tests__/PayWithFlutterwaveV2.spec.tsx create mode 100644 __tests__/__snapshots__/PayWithFlutterwaveV2.spec.tsx.snap diff --git a/__tests__/PayWithFlutterwaveV2.spec.tsx b/__tests__/PayWithFlutterwaveV2.spec.tsx new file mode 100644 index 0000000..72f25bb --- /dev/null +++ b/__tests__/PayWithFlutterwaveV2.spec.tsx @@ -0,0 +1,51 @@ +import 'react-native'; +import React from 'react'; +import renderer from 'react-test-renderer'; +import PayWithFlutterwaveV2 from '../src/PayWithFlutterwaveV2'; +import {FlutterwaveInitV2Options} from '../src/FlutterwaveInitV2'; +import {TouchableOpacity, Text} from 'react-native'; +const CustomBtnTestID = 'flw-custom-button'; + +const CustomButton = ({onPress, disabled}) => ( + + {disabled ? 'Please wait...' : 'Pay'} + +) + +const PAYMENT_INFO: Omit = { + txref: '34h093h09h034034', + customer_email: 'customer-email@example.com', + PBFPubKey: '[Public Key]', + amount: 50, + currency: 'NGN', +}; + +describe('PayWithFlutterwaveV3', () => { + + it('renders a default pay with flutterwave button', () => { + // create component tree + const Tree = renderer.create( + {}} + options={PAYMENT_INFO} + /> + ); + // run assertions + expect(Tree.toJSON()).toMatchSnapshot(); + }); + + it('uses custom button in place of flw button if defined', () => { + const Tree = renderer.create( + {}} + options={PAYMENT_INFO} + customButton={CustomButton} + /> + ); + // run assertions + expect(Tree.toJSON()).toMatchSnapshot(); + }) +}); diff --git a/__tests__/__snapshots__/PayWithFlutterwaveV2.spec.tsx.snap b/__tests__/__snapshots__/PayWithFlutterwaveV2.spec.tsx.snap new file mode 100644 index 0000000..6b84a70 --- /dev/null +++ b/__tests__/__snapshots__/PayWithFlutterwaveV2.spec.tsx.snap @@ -0,0 +1,338 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PayWithFlutterwaveV3 renders a default pay with flutterwave button 1`] = ` +Array [ + + + , + + + + + + + + + + + , +] +`; + +exports[`PayWithFlutterwaveV3 uses custom button in place of flw button if defined 1`] = ` +Array [ + + + Pay + + , + + + + + + + + + + + , +] +`; From 4cf13f9bdd71dee6847be1d90bff4dc912e1d651 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 10:01:35 +0100 Subject: [PATCH 115/129] refactor: remove old immplementations code --- __tests__/DefaultButton.spec.tsx | 68 -- .../__snapshots__/DefaultButton.spec.tsx.snap | 137 --- __tests__/v2/FlutterwaveButton.spec.tsx | 837 ----------------- __tests__/v2/FlutterwaveInit.spec.ts | 130 --- .../FlutterwaveButton.spec.tsx.snap | 823 ----------------- __tests__/v3/FlutterwaveButton.spec.tsx | 842 ------------------ .../FlutterwaveButton.spec.tsx.snap | 823 ----------------- src/DefaultButton.tsx | 120 --- src/FlutterwaveButton.tsx | 48 - src/index.ts | 6 - src/v2/FlutterwaveButton.tsx | 518 ----------- src/v2/index.ts | 9 - src/v3/FlutterwaveButton.tsx | 513 ----------- src/v3/index.ts | 9 - 14 files changed, 4883 deletions(-) delete mode 100644 __tests__/DefaultButton.spec.tsx delete mode 100644 __tests__/__snapshots__/DefaultButton.spec.tsx.snap delete mode 100644 __tests__/v2/FlutterwaveButton.spec.tsx delete mode 100644 __tests__/v2/FlutterwaveInit.spec.ts delete mode 100644 __tests__/v2/__snapshots__/FlutterwaveButton.spec.tsx.snap delete mode 100644 __tests__/v3/FlutterwaveButton.spec.tsx delete mode 100644 __tests__/v3/__snapshots__/FlutterwaveButton.spec.tsx.snap delete mode 100644 src/DefaultButton.tsx delete mode 100644 src/FlutterwaveButton.tsx delete mode 100644 src/v2/FlutterwaveButton.tsx delete mode 100644 src/v2/index.ts delete mode 100644 src/v3/FlutterwaveButton.tsx delete mode 100644 src/v3/index.ts diff --git a/__tests__/DefaultButton.spec.tsx b/__tests__/DefaultButton.spec.tsx deleted file mode 100644 index 68c760d..0000000 --- a/__tests__/DefaultButton.spec.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import 'react-native'; -import React from 'react'; -import {Text} from 'react-native'; -import renderer from 'react-test-renderer'; -import DefaultButton from '../src/DefaultButton'; -const BtnTestID = 'flw-default-button'; - -describe('', () => { - it('renders primary button correctly', () => { - const TestRender = renderer.create( - - Hello, World - - ); - expect(TestRender.toJSON()).toMatchSnapshot(); - }); - - it('renders overlay if busy', () => { - const TestRender = renderer.create( - - Hello, World - - ); - expect(TestRender.toJSON()).toMatchSnapshot(); - }); - - it('renders with align left style', () => { - const TestRender = renderer.create( - - Hello, World - - ); - expect(TestRender.toJSON()).toMatchSnapshot(); - }); - - it('fires onSizeChange if set', () => { - const onSizeChange = jest.fn(); - const TestRender = renderer.create( - - Hello, World - - ); - TestRender.root.findByProps({testID: BtnTestID}).props.onLayout({ - nativeEvent: { - layout:{ - width: 1, - height: 1, - } - } - }); - expect(onSizeChange).toHaveBeenCalledTimes(1); - expect(onSizeChange).toHaveBeenCalledWith({width: 1, height: 1}); - }); - - it('fires onPress if set', () => { - const onPress = jest.fn(); - const TestRender = renderer.create( - - Hello, World - - ); - - TestRender.root.findByProps({testID: BtnTestID}).props.onPress(); - - expect(TestRender.root.findByProps({testID: BtnTestID}).props.onPress).toBeDefined(); - expect(onPress).toHaveBeenCalledTimes(1); - }); -}); diff --git a/__tests__/__snapshots__/DefaultButton.spec.tsx.snap b/__tests__/__snapshots__/DefaultButton.spec.tsx.snap deleted file mode 100644 index f65a202..0000000 --- a/__tests__/__snapshots__/DefaultButton.spec.tsx.snap +++ /dev/null @@ -1,137 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` renders overlay if busy 1`] = ` - - - Hello, World - - - -`; - -exports[` renders primary button correctly 1`] = ` - - - Hello, World - - -`; - -exports[` renders with align left style 1`] = ` - - - Hello, World - - -`; diff --git a/__tests__/v2/FlutterwaveButton.spec.tsx b/__tests__/v2/FlutterwaveButton.spec.tsx deleted file mode 100644 index 0e1e76c..0000000 --- a/__tests__/v2/FlutterwaveButton.spec.tsx +++ /dev/null @@ -1,837 +0,0 @@ -import 'react-native'; -import React from 'react'; -import {TouchableWithoutFeedback, Text, Alert} from 'react-native'; -import renderer from 'react-test-renderer'; -import FlutterwaveButton from '../../src/v2/FlutterwaveButton'; -import {FlutterwaveInitOptions} from '../../src/v2/FlutterwaveInit'; -import {STANDARD_URL_V2, REDIRECT_URL} from '../../src/configs'; -import WebView from 'react-native-webview'; -import DefaultButton from '../../src/DefaultButton'; -import FlutterwaveInitError from '../../src/utils/FlutterwaveInitError'; -const BtnTestID = 'flw-default-button'; - -const SuccessResponse = { - status: 'success', - message: 'Payment link generated.', - data: { - link: 'http://payment-link.com/checkout', - }, -}; - -const PAYMENT_INFO: Omit = { - txref: '34h093h09h034034', - customer_email: 'customer-email@example.com', - PBFPubKey: '[Public Key]', - amount: 50, - currency: 'NGN', -}; - -const REQUEST_BODY = {...PAYMENT_INFO, redirect_url: REDIRECT_URL}; - -const HEADERS = new Headers -HEADERS.append('Content-Type', 'application/json'); - -describe('', () => { - it('renders component correctly', () => { - const Renderer = renderer.create(); - expect(Renderer.toJSON()).toMatchSnapshot(); - }); - - it('renders busy button if isPending', () => { - // get create instance of flutterwave button - const Renderer = renderer.create(); - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - Renderer.root.findByProps({testID: BtnTestID}).props.onPress(); - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - expect(Renderer.toJSON()).toMatchSnapshot(); - }); - - it('renders modal with visibile property as true if show dialog state is true', (done) => { - // get create instance of flutterwave button - const Renderer = renderer.create(); - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - Renderer.root.findByProps({testID: BtnTestID}).props.onPress(); - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - setTimeout(() => { - expect(Renderer.toJSON()).toMatchSnapshot(); - done(); - }, 50); - }); - - it('renders custom button correctly', () => { - const TestRenderer = renderer.create( { - return ( - - {isInitializing ? (Please wait...) : (Pay)} - - ); - }} - />); - expect(TestRenderer.toJSON()).toMatchSnapshot(); - }); - - it('renders webview loading correctly', () => { - const TestRenderer = renderer.create(); - // get webview - const webView = TestRenderer.root.findByType(WebView); - // create loading renderer - const LoadingRenderer = renderer.create(webView.props.renderLoading()); - // checks - expect(LoadingRenderer).toMatchSnapshot(); - }); - - it('renders webview error correctly', (done) => { - const TestRenderer = renderer.create(); - // mock next fetch request - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - // press button - TestRenderer.root.findByProps({testID: BtnTestID}).props.onPress(); - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - // checks - setTimeout(() => { - // get webview - const webView = TestRenderer.root.findByType(WebView); - // create error renderer - const ErrorRenderer = renderer.create(webView.props.renderError()); - expect(ErrorRenderer).toMatchSnapshot(); - done(); - }, 50); - }); - - it('does not render webview error if there is no link', () => { - const TestRenderer = renderer.create(); - // get webview - const webView = TestRenderer.root.findByType(WebView); - // create error renderer - const ErrorRenderer = renderer.create(webView.props.renderError()); - // checks - expect(ErrorRenderer).toMatchSnapshot(); - }); - - it('disables custom button and set is initializing to true when initializing payment', () => { - const customButton = jest.fn(); - const TestRenderer = renderer.create(); - TestRenderer.root.instance.handleInit(); - expect(customButton).toHaveBeenLastCalledWith({ - disabled: true, - isInitializing: true, - onPress: expect.any(Function), - }); - }); - - it('disables custom button and set is initializing to false after initializing payment', (done) => { - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - const customButton = jest.fn(); - const TestRenderer = renderer.create(); - TestRenderer.root.instance.handleInit(); - setTimeout(() => { - expect(customButton).toHaveBeenLastCalledWith({ - disabled: true, - isInitializing: false, - onPress: expect.any(Function), - }); - done(); - }, 50); - }); - - it('asks user to confirm abort when pressed backdrop', () => { - const TestRenderer = renderer.create(); - // get backdrop - const Backdrop = TestRenderer.root.findByProps({testID: 'flw-backdrop'}); - // simulate backdrop onPress - Backdrop.props.onPress(); - // checks - expect(Alert.alert).toHaveBeenCalledTimes(1); - expect(Alert.alert).toHaveBeenCalledWith( - expect.any(String), - expect.stringContaining('cancel this payment'), - expect.any(Array), - ); - }); - - it('calls onAbort if available and abort event occurred', () => { - const onAbort = jest.fn(); - // get create instance of flutterwave button - const FlwButton = renderer.create(); - // fire handle abort confirm - FlwButton.root.instance.handleAbortConfirm(); - // called on abort - expect(onAbort).toHaveBeenCalledTimes(1); - }); - - it('does not call onAbort if not available and abort event occurred', () => { - const onAbort = jest.fn(); - // get create instance of flutterwave button - const FlwButton = renderer.create(); - // fire handle abort confirm - FlwButton.root.instance.handleAbortConfirm(); - // called on abort - expect(onAbort).toHaveBeenCalledTimes(0); - }); - - it('does not make standard api call if in pending state', () => { - // get create instance of flutterwave button - const FlwButton = renderer.create(); - - // mock next fetch request - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - - // fire on press - FlwButton.root.findByProps({testID: BtnTestID}).props.onPress(); - FlwButton.root.findByProps({testID: BtnTestID}).props.onPress(); - - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - - // ensure the button is disabled after beign pressed - expect(global.fetch).toHaveBeenCalledTimes(1); - }); - - it('makes call to standard endpoint when button is pressed', async () => { - const Renderer = renderer.create(); - const Button = Renderer.root.findByProps({testID: BtnTestID}); - const c = new AbortController; - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - Button.props.onPress(); - - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - - // run assertions - expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL_V2, { - body: JSON.stringify(REQUEST_BODY), - headers: HEADERS, - method: 'POST', - signal: c.signal, - }); - }); - - it("updates button size when current and new size don't match", () => { - // on layout event - const onSizeChangeEv = { - width: 100, - height: 100 - }; - - // create test renderer - const TestRenderer = renderer.create(); - - // spy on component methods - const setState = jest.spyOn(TestRenderer.root.instance, 'setState'); - const handleButtonResize = jest.spyOn(TestRenderer.root.instance, 'handleButtonResize'); - - // get default button - const Button = TestRenderer.root.findByProps({testID: BtnTestID}); - - // fire on size change on button - Button.props.onLayout({nativeEvent: {layout: onSizeChangeEv}}); - Button.props.onLayout({nativeEvent: {layout: onSizeChangeEv}}); - - // handle button resize checks - expect(handleButtonResize).toHaveBeenCalledTimes(1); - expect(handleButtonResize).toHaveBeenLastCalledWith(onSizeChangeEv); - - // set state checks - expect(setState).toHaveBeenCalledTimes(1); - expect(setState).toHaveBeenCalledWith({buttonSize: onSizeChangeEv}) - }); - - it('fires onDidInitialize if available', (done) => { - const onDidInitialize = jest.fn(); - // get create instance of flutterwave button - const FlwButton = renderer.create(); - // mock next fetch request - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - // fire on press - FlwButton.root.findByProps({testID: BtnTestID}).props.onPress(); - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - // wait for request to be made - setTimeout(() => { - expect(onDidInitialize).toHaveBeenCalledTimes(1); - // end test - done(); - }, 50); - }); - - it('fires onWillInitialize if available', (done) => { - const onWillInitialize = jest.fn(); - // get create instance of flutterwave button - const FlwButton = renderer.create(); - // mock next fetch request - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - // fire on press - FlwButton.root.findByProps({testID: BtnTestID}).props.onPress(); - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - // wait for request to be made - setTimeout(() => { - expect(onWillInitialize).toHaveBeenCalledTimes(1); - // end test - done(); - }, 50); - }); - - it('fires onInitializeError if available', (done) => { - const err = new Error('Error occurred.'); - const onInitializeError = jest.fn(); - // get create instance of flutterwave button - const FlwButton = renderer.create(); - // mock next fetch request - fetchMock.mockRejectOnce(err); - // fire on press - FlwButton.root.findByProps({testID: BtnTestID}).props.onPress(); - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - // wait for request to be made - setTimeout(() => { - expect(onInitializeError).toHaveBeenCalledTimes(1); - expect(onInitializeError).toHaveBeenCalledWith(new FlutterwaveInitError({ - code: 'STANDARD_INIT_ERROR', - message: err.message - })); - // end test - done(); - }, 50); - }); - - it('does not update state if init is aborted', (done) => { - // get create instance of flutterwave button - const FlwButton = renderer.create(); - // spy on set state - const setState = jest.spyOn(FlwButton.root.instance, 'setState'); - // mock next fetch request - fetchMock.mockAbortOnce(); - // fire on press - FlwButton.root.findByProps({testID: BtnTestID}).props.onPress(); - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - // wait for request to be made - setTimeout(() => { - expect(setState).toHaveBeenCalledTimes(1); - expect(FlwButton.root.instance.state.isPending).toBe(true); - // end test - done(); - }, 50); - }); - - it("gets redirect params and returns them on redirect", (done) => { - // define response - const response = { - flwref: 'erinf930rnf09', - txref: 'nfeinr09erss', - } - - const urlWithParams = REDIRECT_URL + '?flwref=' + response.flwref + '&txref=' + response.txref; - - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - - // spy on getRedirectParams method - const getRedirectParams = jest.spyOn(TestRenderer.root.instance, 'getRedirectParams'); - - // mock next fetch request - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - - // find button and - const Button = TestRenderer.root.findByProps({testID: BtnTestID}); - Button.props.onPress(); - - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - - // wait for fetch to complete - setTimeout(() => { - // find webview and fire webview onNavigationStateChange - const webView = TestRenderer.root.findByType(WebView); - webView.props.onNavigationStateChange({url: urlWithParams}); - - // run checks - expect(getRedirectParams).toHaveBeenCalledTimes(1); - expect(getRedirectParams).toHaveBeenCalledWith(urlWithParams); - expect(getRedirectParams).toHaveReturnedWith(response); - // end test - done(); - }, 50); - }); - - it("does not fire complete handle if redirect url does not match", (done) => { - // define url - const url = "http://redirect-url.com"; - - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - - // spy on getRedirectParams method - const handleComplete = jest.spyOn(TestRenderer.root.instance, 'handleComplete'); - const handleNavigationStateChange = jest.spyOn(TestRenderer.root.instance, 'handleNavigationStateChange'); - - // mock next fetch request - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - - // find button and - const Button = TestRenderer.root.findByProps({testID: BtnTestID}); - Button.props.onPress(); - - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - - // wait for fetch to complete - setTimeout(() => { - // find webview and fire webview onNavigationStateChange - const webView = TestRenderer.root.findByType(WebView); - webView.props.onNavigationStateChange({url: url}); - - // run checks - expect(handleNavigationStateChange).toHaveBeenCalledTimes(1); - expect(handleComplete).toHaveBeenCalledTimes(0); - - // end test - done(); - }, 50); - }); - - it("fires onComplete when redirected", (done) => { - // define response - const response = { - flwref: 'erinf930rnf09', - txref: 'nfeinr09erss', - } - - // on complete - const onComplete = jest.fn(); - - // define url - const url = REDIRECT_URL + - "?txref=" + response.txref + - "&flwref=" + response.flwref - - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - - // spy on getRedirectParams method - const handleComplete = jest.spyOn(TestRenderer.root.instance, 'handleComplete'); - const handleNavigationStateChange = jest.spyOn( - TestRenderer.root.instance, - 'handleNavigationStateChange' - ); - - // mock next fetch request - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - - // press init button - TestRenderer.root.findByProps({testID: BtnTestID}).props.onPress(); - - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - - // wait for fetch to complete - setTimeout(() => { - // find webview and fire webview onNavigationStateChange - const webView = TestRenderer.root.findByType(WebView); - webView.props.onNavigationStateChange({url: url}); - - // run checks - expect(handleNavigationStateChange).toHaveBeenCalledTimes(1); - expect(handleComplete).toHaveBeenCalledTimes(1); - expect(handleComplete).toHaveBeenCalledWith(response); - expect(onComplete).toHaveBeenCalledTimes(1); - expect(onComplete).toHaveBeenCalledWith(response); - - // end test - done(); - }, 50); - }); - - it("cancels fetch on will unmount.", () => { - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - TestRenderer - .root - .findByProps({testID: BtnTestID}) - .props - .onPress(); - // spy on abort method - const abort = jest.spyOn(TestRenderer.root.instance.abortController, 'abort'); - // call component will unmount - TestRenderer.root.instance.componentWillUnmount(); - // run checks - expect(abort).toHaveBeenCalledTimes(1); - // end test - }); - - it("does not cancel fetch on will unmount if canceller is not set.", () => { - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - const willUnmount = jest.spyOn(TestRenderer.root.instance, 'componentWillUnmount'); - // call component will unmount - TestRenderer.root.instance.componentWillUnmount(); - // run checks - expect(willUnmount).toHaveBeenCalledTimes(1); - expect(TestRenderer.root.instance.abortController).toBeUndefined(); - }); - - it('can reload webview if webview ref is set', (done) => { - // create renderer - const TestRender = renderer.create(); - // mock next fetch request - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - // fire on press - TestRender.root.findByProps({testID: BtnTestID}).props.onPress(); - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - - // wait for standard call to occurr - setTimeout(() => { - const webviewReload = jest.spyOn( - TestRender.root.instance.webviewRef, - 'reload' - ).mockImplementationOnce((() => {})); - TestRender.root.instance.handleReload(); - expect(webviewReload).toHaveBeenCalledTimes(1); - done(); - }, 50); - }); - - it('does not reload if webview ref is not set', () => { - // create renderer - const TestRender = renderer.create(); - Object.defineProperty(TestRender.root.instance, 'webviewRef', {value: null}); - const handleReload = jest.spyOn(TestRender.root.instance, 'handleReload'); - TestRender.root.instance.handleReload(); - expect(handleReload).toHaveBeenCalledTimes(1); - expect(TestRender.root.instance.webviewRef === null).toBe(true); - }); - - it("handles DefaultButton onSizeChange", () => { - const TestRenderer = renderer.create(); - const size = {width: 1200, height: 0}; - const handleButtonResize = jest.spyOn(TestRenderer.root.instance, 'handleButtonResize'); - - TestRenderer.root.findByType(DefaultButton).props.onSizeChange(size); - TestRenderer.root.findByType(DefaultButton).props.onSizeChange(size); - - expect(handleButtonResize).toHaveBeenCalledTimes(1); - expect(handleButtonResize).toHaveBeenCalledWith(size); - }); - - it("does not return query params if non is available with getRedirectParams method", () => { - const url = new String('http://example.com'); - const TestRenderer = renderer.create(); - const split = jest.spyOn(url, 'split'); - TestRenderer.root.instance.getRedirectParams(url);; - expect(split).toHaveBeenCalledTimes(1); - }); - - it("returns query params if avialeble with getRedirectParams method", () => { - const url = new String('http://example.com?foo=bar'); - const TestRenderer = renderer.create(); - const split = jest.spyOn(url, 'split'); - TestRenderer.root.instance.getRedirectParams(url);; - expect(split).toHaveBeenCalledTimes(2); - }); - - it("updates state if reset is called.", () => { - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - const setState = jest.spyOn(TestRenderer.root.instance, 'setState'); - // call component will unmount - TestRenderer.root.instance.reset(); - // run checks - expect(setState).toHaveBeenCalledTimes(1); - expect(TestRenderer.root.instance.abortController).toBeUndefined(); - }); - - it("cancels fetch if reset is called and abort controller is set.", () => { - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - TestRenderer - .root - .findByProps({testID: BtnTestID}) - .props - .onPress(); - // spy on abort method - const abort = jest.spyOn(TestRenderer.root.instance.abortController, 'abort'); - // call component will unmount - TestRenderer.root.instance.reset(); - // run checks - expect(abort).toHaveBeenCalledTimes(1); - // end test - }); - - it("calls options change handler if options changed", () => { - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - // spy on handleOptionsChanged method - const handleOptionsChanged = jest.spyOn(TestRenderer.root.instance, 'handleOptionsChanged'); - // update component - TestRenderer.update() - // run checks - expect(handleOptionsChanged).toHaveBeenCalledTimes(1); - // end test - }); - - it("does not set state if link has not been set", () => { - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - // spy on setState method - const setState = jest.spyOn(TestRenderer.root.instance, 'setState'); - // update component - TestRenderer.update() - // run checks - expect(setState).toHaveBeenCalledTimes(0); - // end test - }); - - it("resets link if dialog is not being show", (done) => { - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - // mock next fetch - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - // initialize payment - TestRenderer - .root - .findByProps({testID: BtnTestID}) - .props - .onPress(); - // wait for mocked fetch - setTimeout(() => { - // set dialog to hidden - TestRenderer.root.instance.setState({showDialog: false}); - // spy on setState method - const setState = jest.spyOn(TestRenderer.root.instance, 'setState'); - // update component - TestRenderer.update() - // run checks - expect(setState).toHaveBeenCalledTimes(1); - expect(setState).toHaveBeenCalledWith({ - link: null, - txref: null, - }); - // end test - done(); - }, 50); - }); - - it("schedules a link reset if dialog has already been shown", (done) => { - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - // mock next fetch - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - // initialize payment - TestRenderer - .root - .findByProps({testID: BtnTestID}) - .props - .onPress(); - // wait for mocked fetch - setTimeout(() => { - // spy on setState method - const setState = jest.spyOn(TestRenderer.root.instance, 'setState'); - // update component - TestRenderer.update() - // run checks - expect(setState).toHaveBeenCalledTimes(1); - expect(setState).toHaveBeenCalledWith({resetLink: true}); - // end test - done(); - }, 50); - }); - - it("renders checkout screen if link already exist when init is called", (done) => { - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - // set a payment link - TestRenderer.root.instance.setState({link: 'http://payment-link.com'}); - // spy on show method - const show = jest.spyOn(TestRenderer.root.instance, 'show'); - // initialize payment - TestRenderer - .root - .findByProps({testID: BtnTestID}) - .props - .onPress(); - setTimeout(() => { - // run checks - expect(show).toHaveBeenCalledTimes(1); - expect(global.fetch).toHaveBeenCalledTimes(0); - // end test - done(); - }, 20); - }); - - it("does not generate a new link if already generating one", () => { - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - // set a payment link - TestRenderer.root.instance.setState({isPending: true}); - // set a payment link - TestRenderer.root.instance.handleInit(); - // run checks - expect(global.fetch).toHaveBeenCalledTimes(0); - }); -}); diff --git a/__tests__/v2/FlutterwaveInit.spec.ts b/__tests__/v2/FlutterwaveInit.spec.ts deleted file mode 100644 index 01c207a..0000000 --- a/__tests__/v2/FlutterwaveInit.spec.ts +++ /dev/null @@ -1,130 +0,0 @@ -import FlutterwaveInit, {FlutterwaveInitOptions} from '../../src/v2/FlutterwaveInit'; -import {STANDARD_URL_V2} from '../../src/configs'; -import FlutterwaveInitError from '../../src/utils/FlutterwaveInitError'; - -const AUTHORIZATION = '[PUB Key]'; - -// fetch header -const FETCH_HEADER = new Headers(); -FETCH_HEADER.append('Content-Type', 'application/json'); - -// fetch body -const FETCH_BODY = { - redirect_url: 'http://flutterwave.com', - PBFPubKey: AUTHORIZATION, - amount: 50, - currency: 'NGN', - customer_email: 'email@example.com', - txref: Date.now() + '-txref', -} - -// payment options -const INIT_OPTIONS: FlutterwaveInitOptions = { - ...FETCH_BODY, -}; - -const SUCCESS_RESPONSE = { - status: 'success', - message: 'Payment link generated.', - data: { - link: 'http://payment-link.com/checkout', - }, -}; - -describe('', () => { - it('returns a payment link after initialization', async () => { - // mock next fetch request - fetchMock.mockOnce(JSON.stringify(SUCCESS_RESPONSE)); - // flutterwave init test - const link = await FlutterwaveInit(INIT_OPTIONS); - // expect fetch to have been called once - expect(global.fetch).toHaveBeenCalledTimes(1); - // expect fetch to have been called to the standard init url - expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL_V2, { - body: JSON.stringify(FETCH_BODY), - headers: FETCH_HEADER, - method: 'POST', - }); - expect(typeof link === 'string').toBeTruthy(); - }); - - it('includes error code and message of init error', async () => { - const message = 'An error has occurred.'; - // reject next fetch - fetchMock.mockOnce(JSON.stringify({ - status: 'error', - message: message, - })); - try { - // initialize payment - await FlutterwaveInit(INIT_OPTIONS); - } catch (error) { - // run assertions - expect(error.message).toEqual(message); - expect(error.code).toEqual('STANDARD_INIT_ERROR'); - } - }); - - it('handles missing data error', async () => { - // mock next fetch - fetchMock.mockOnce(JSON.stringify({status: 'error'})); - try { - // initialize payment - const response = await FlutterwaveInit(INIT_OPTIONS); - } catch (error) { - // run assertions - expect(error.code).toEqual('STANDARD_INIT_ERROR'); - } - }); - - it('rejects with an error if link is missing in data', async () => { - const errorMessage = 'Missing link test.'; - // mock next fetch - fetchMock.mockOnce( - JSON.stringify({ - status: 'error', - data: { - message: errorMessage, - code: 'MALFORMED_RESPONSE' - } - } - ) - ); - try { - // initialize payment - const response = await FlutterwaveInit(INIT_OPTIONS); - } catch (error) { - // run assertions - expect(error.code).toEqual('MALFORMED_RESPONSE'); - } - }); - - - it('is abortable', async () => { - // use fake jest timers - jest.useFakeTimers(); - // mock fetch response - fetchMock.mockResponse(async () => { - jest.advanceTimersByTime(60) - return JSON.stringify({ - status: 'error', - message: 'Error!', - }) - }); - // create abort controller - const abortController = new AbortController; - // abort next fetch - setTimeout(() => abortController.abort(), 50); - try { - // initialize payment - await FlutterwaveInit( - INIT_OPTIONS, - abortController - ) - } catch(error) { - // run assertions - expect(error).toBeInstanceOf(FlutterwaveInitError); - expect(error.code).toEqual('ABORTERROR'); - } - }); -}); diff --git a/__tests__/v2/__snapshots__/FlutterwaveButton.spec.tsx.snap b/__tests__/v2/__snapshots__/FlutterwaveButton.spec.tsx.snap deleted file mode 100644 index b8a0d54..0000000 --- a/__tests__/v2/__snapshots__/FlutterwaveButton.spec.tsx.snap +++ /dev/null @@ -1,823 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` does not render webview error if there is no link 1`] = ` - -`; - -exports[` renders busy button if isPending 1`] = ` -Array [ - - - - , - - - - - - - - - - - , -] -`; - -exports[` renders component correctly 1`] = ` -Array [ - - - , - - - - - - - - - - - , -] -`; - -exports[` renders custom button correctly 1`] = ` -Array [ - - Pay - , - - - - - - - - - - - , -] -`; - -exports[` renders modal with visibile property as true if show dialog state is true 1`] = ` -Array [ - - - , - - - - - - - - - - - , -] -`; - -exports[` renders webview error correctly 1`] = ` - - - The page failed to load, please try again. - - - - Try Again - - - -`; - -exports[` renders webview loading correctly 1`] = ` - - - -`; diff --git a/__tests__/v3/FlutterwaveButton.spec.tsx b/__tests__/v3/FlutterwaveButton.spec.tsx deleted file mode 100644 index 47c8ec6..0000000 --- a/__tests__/v3/FlutterwaveButton.spec.tsx +++ /dev/null @@ -1,842 +0,0 @@ -import 'react-native'; -import React from 'react'; -import {TouchableWithoutFeedback, Text, Alert} from 'react-native'; -import renderer from 'react-test-renderer'; -import FlutterwaveButton from '../../src/v3/FlutterwaveButton'; -import {FlutterwaveInitOptions} from '../../src/v3/FlutterwaveInit'; -import {STANDARD_URL, REDIRECT_URL} from '../../src/configs'; -import WebView from 'react-native-webview'; -import DefaultButton from '../../src/DefaultButton'; -import FlutterwaveInitError from '../../src/utils/FlutterwaveInitError'; -const BtnTestID = 'flw-default-button'; -const SuccessResponse = { - status: 'success', - message: 'Payment link generated.', - data: { - link: 'http://payment-link.com/checkout', - }, -}; - -const PAYMENT_INFO: Omit = { - tx_ref: '34h093h09h034034', - customer: { - email: 'customer-email@example.com', - }, - authorization: '[Authorization]', - amount: 50, - currency: 'NGN', -}; - -const REQUEST_BODY = {...PAYMENT_INFO, redirect_url: REDIRECT_URL}; -delete REQUEST_BODY.authorization; - -const HEADERS = new Headers -HEADERS.append('Content-Type', 'application/json'); -HEADERS.append('Authorization', `Bearer ${PAYMENT_INFO.authorization}`); - -describe('', () => { - it('renders component correctly', () => { - const Renderer = renderer.create(); - expect(Renderer.toJSON()).toMatchSnapshot(); - }); - - it('renders busy button if isPending', () => { - // get create instance of flutterwave button - const Renderer = renderer.create(); - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - Renderer.root.findByProps({testID: BtnTestID}).props.onPress(); - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - expect(Renderer.toJSON()).toMatchSnapshot(); - }); - - it('renders modal with visibile property as true if show dialog state is true', (done) => { - // get create instance of flutterwave button - const Renderer = renderer.create(); - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - Renderer.root.findByProps({testID: BtnTestID}).props.onPress(); - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - setTimeout(() => { - expect(Renderer.toJSON()).toMatchSnapshot(); - done(); - }, 50); - }); - - it('renders custom button correctly', () => { - const TestRenderer = renderer.create( { - return ( - - {isInitializing ? (Please wait...) : (Pay)} - - ); - }} - />); - expect(TestRenderer.toJSON()).toMatchSnapshot(); - }); - - it('renders webview loading correctly', () => { - const TestRenderer = renderer.create(); - // get webview - const webView = TestRenderer.root.findByType(WebView); - // create loading renderer - const LoadingRenderer = renderer.create(webView.props.renderLoading()); - // checks - expect(LoadingRenderer).toMatchSnapshot(); - }); - - it('renders webview error correctly', (done) => { - const TestRenderer = renderer.create(); - // mock next fetch request - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - // press button - TestRenderer.root.findByProps({testID: BtnTestID}).props.onPress(); - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - // checks - setTimeout(() => { - // get webview - const webView = TestRenderer.root.findByType(WebView); - // create error renderer - const ErrorRenderer = renderer.create(webView.props.renderError()); - expect(ErrorRenderer).toMatchSnapshot(); - done(); - }, 50); - }); - - it('does not render webview error if there is no link', () => { - const TestRenderer = renderer.create(); - // get webview - const webView = TestRenderer.root.findByType(WebView); - // create error renderer - const ErrorRenderer = renderer.create(webView.props.renderError()); - // checks - expect(ErrorRenderer).toMatchSnapshot(); - }); - - it('disables custom button and set is initializing to true when initializing payment', () => { - const customButton = jest.fn(); - const TestRenderer = renderer.create(); - TestRenderer.root.instance.handleInit(); - expect(customButton).toHaveBeenLastCalledWith({ - disabled: true, - isInitializing: true, - onPress: expect.any(Function), - }); - }); - - it('disables custom button and set is initializing to false after initializing payment', (done) => { - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - const customButton = jest.fn(); - const TestRenderer = renderer.create(); - TestRenderer.root.instance.handleInit(); - setTimeout(() => { - expect(customButton).toHaveBeenLastCalledWith({ - disabled: true, - isInitializing: false, - onPress: expect.any(Function), - }); - done(); - }, 50); - }); - - it('asks user to confirm abort when pressed backdrop', () => { - const TestRenderer = renderer.create(); - // get backdrop - const Backdrop = TestRenderer.root.findByProps({testID: 'flw-backdrop'}); - // simulate backdrop onPress - Backdrop.props.onPress(); - // checks - expect(Alert.alert).toHaveBeenCalledTimes(1); - expect(Alert.alert).toHaveBeenCalledWith( - expect.any(String), - expect.stringContaining('cancel this payment'), - expect.any(Array), - ); - }); - - it('calls onAbort if available and abort event occurred', () => { - const onAbort = jest.fn(); - // get create instance of flutterwave button - const FlwButton = renderer.create(); - // fire handle abort confirm - FlwButton.root.instance.handleAbortConfirm(); - // called on abort - expect(onAbort).toHaveBeenCalledTimes(1); - }); - - it('does not call onAbort if not available and abort event occurred', () => { - const onAbort = jest.fn(); - // get create instance of flutterwave button - const FlwButton = renderer.create(); - // fire handle abort confirm - FlwButton.root.instance.handleAbortConfirm(); - // called on abort - expect(onAbort).toHaveBeenCalledTimes(0); - }); - - it('does not make standard api call if in pending state', () => { - // get create instance of flutterwave button - const FlwButton = renderer.create(); - - // mock next fetch request - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - - // fire on press - FlwButton.root.findByProps({testID: BtnTestID}).props.onPress(); - FlwButton.root.findByProps({testID: BtnTestID}).props.onPress(); - - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - - // ensure the button is disabled after beign pressed - expect(global.fetch).toHaveBeenCalledTimes(1); - }); - - it('makes call to standard endpoint when button is pressed', async () => { - const Renderer = renderer.create(); - const Button = Renderer.root.findByProps({testID: BtnTestID}); - const c = new AbortController; - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - Button.props.onPress(); - - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - - // run assertions - expect(global.fetch).toHaveBeenCalledWith(STANDARD_URL, { - body: JSON.stringify(REQUEST_BODY), - headers: HEADERS, - method: 'POST', - signal: c.signal, - }); - }); - - it("updates button size when current and new size don't match", () => { - // on layout event - const onSizeChangeEv = { - width: 100, - height: 100 - }; - - // create test renderer - const TestRenderer = renderer.create(); - - // spy on component methods - const setState = jest.spyOn(TestRenderer.root.instance, 'setState'); - const handleButtonResize = jest.spyOn(TestRenderer.root.instance, 'handleButtonResize'); - - // get default button - const Button = TestRenderer.root.findByProps({testID: BtnTestID}); - - // fire on size change on button - Button.props.onLayout({nativeEvent: {layout: onSizeChangeEv}}); - Button.props.onLayout({nativeEvent: {layout: onSizeChangeEv}}); - - // handle button resize checks - expect(handleButtonResize).toHaveBeenCalledTimes(1); - expect(handleButtonResize).toHaveBeenLastCalledWith(onSizeChangeEv); - - // set state checks - expect(setState).toHaveBeenCalledTimes(1); - expect(setState).toHaveBeenCalledWith({buttonSize: onSizeChangeEv}) - }); - - it('fires onDidInitialize if available', (done) => { - const onDidInitialize = jest.fn(); - // get create instance of flutterwave button - const FlwButton = renderer.create(); - // mock next fetch request - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - // fire on press - FlwButton.root.findByProps({testID: BtnTestID}).props.onPress(); - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - // wait for request to be made - setTimeout(() => { - expect(onDidInitialize).toHaveBeenCalledTimes(1); - // end test - done(); - }, 50); - }); - - it('fires onWillInitialize if available', (done) => { - const onWillInitialize = jest.fn(); - // get create instance of flutterwave button - const FlwButton = renderer.create(); - // mock next fetch request - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - // fire on press - FlwButton.root.findByProps({testID: BtnTestID}).props.onPress(); - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - // wait for request to be made - setTimeout(() => { - expect(onWillInitialize).toHaveBeenCalledTimes(1); - // end test - done(); - }, 50); - }); - - it('fires onInitializeError if available', (done) => { - const err = new Error('Error occurred.'); - const onInitializeError = jest.fn(); - // get create instance of flutterwave button - const FlwButton = renderer.create(); - // mock next fetch request - fetchMock.mockRejectOnce(err); - // fire on press - FlwButton.root.findByProps({testID: BtnTestID}).props.onPress(); - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - // wait for request to be made - setTimeout(() => { - expect(onInitializeError).toHaveBeenCalledTimes(1); - expect(onInitializeError).toHaveBeenCalledWith(new FlutterwaveInitError({ - code: 'STANDARD_INIT_ERROR', - message: err.message - })); - // end test - done(); - }, 50); - }); - - it('does not update state if init is aborted', (done) => { - // get create instance of flutterwave button - const FlwButton = renderer.create(); - // spy on set state - const setState = jest.spyOn(FlwButton.root.instance, 'setState'); - // mock next fetch request - fetchMock.mockAbortOnce(); - // fire on press - FlwButton.root.findByProps({testID: BtnTestID}).props.onPress(); - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - // wait for request to be made - setTimeout(() => { - expect(setState).toHaveBeenCalledTimes(1); - expect(FlwButton.root.instance.state.isPending).toBe(true); - // end test - done(); - }, 50); - }); - - it("gets redirect params and returns them on redirect", (done) => { - // define response - const response = { - transaction_id: 'erinf930rnf09', - tx_ref: 'nfeinr09erss', - } - - const urlWithParams = REDIRECT_URL + '?transaction_id=' + response.transaction_id + '&tx_ref=' + response.tx_ref; - - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - - // spy on getRedirectParams method - const getRedirectParams = jest.spyOn(TestRenderer.root.instance, 'getRedirectParams'); - - // mock next fetch request - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - - // find button and - const Button = TestRenderer.root.findByProps({testID: BtnTestID}); - Button.props.onPress(); - - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - - // wait for fetch to complete - setTimeout(() => { - // find webview and fire webview onNavigationStateChange - const webView = TestRenderer.root.findByType(WebView); - webView.props.onNavigationStateChange({url: urlWithParams}); - - // run checks - expect(getRedirectParams).toHaveBeenCalledTimes(1); - expect(getRedirectParams).toHaveBeenCalledWith(urlWithParams); - expect(getRedirectParams).toHaveReturnedWith(response); - // end test - done(); - }, 50); - }); - - it("does not fire complete handle if redirect url does not match", (done) => { - // define url - const url = "http://redirect-url.com"; - - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - - // spy on getRedirectParams method - const handleComplete = jest.spyOn(TestRenderer.root.instance, 'handleComplete'); - const handleNavigationStateChange = jest.spyOn(TestRenderer.root.instance, 'handleNavigationStateChange'); - - // mock next fetch request - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - - // find button and - const Button = TestRenderer.root.findByProps({testID: BtnTestID}); - Button.props.onPress(); - - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - - // wait for fetch to complete - setTimeout(() => { - // find webview and fire webview onNavigationStateChange - const webView = TestRenderer.root.findByType(WebView); - webView.props.onNavigationStateChange({url: url}); - - // run checks - expect(handleNavigationStateChange).toHaveBeenCalledTimes(1); - expect(handleComplete).toHaveBeenCalledTimes(0); - - // end test - done(); - }, 50); - }); - - it("fires onComplete when redirected", (done) => { - // define response - const response = { - status: 'successful', - transaction_id: 'erinf930rnf09', - tx_ref: 'nfeinr09erss', - } - - // on complete - const onComplete = jest.fn(); - - // define url - const url = REDIRECT_URL + - "?status=" + response.status + - "&tx_ref=" + response.tx_ref + - "&transaction_id=" + response.transaction_id - - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - - // spy on getRedirectParams method - const handleComplete = jest.spyOn(TestRenderer.root.instance, 'handleComplete'); - const handleNavigationStateChange = jest.spyOn( - TestRenderer.root.instance, - 'handleNavigationStateChange' - ); - - // mock next fetch request - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - - // press init button - TestRenderer.root.findByProps({testID: BtnTestID}).props.onPress(); - - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - - // wait for fetch to complete - setTimeout(() => { - // find webview and fire webview onNavigationStateChange - const webView = TestRenderer.root.findByType(WebView); - webView.props.onNavigationStateChange({url: url}); - - // run checks - expect(handleNavigationStateChange).toHaveBeenCalledTimes(1); - expect(handleComplete).toHaveBeenCalledTimes(1); - expect(handleComplete).toHaveBeenCalledWith(response); - expect(onComplete).toHaveBeenCalledTimes(1); - expect(onComplete).toHaveBeenCalledWith(response); - - // end test - done(); - }, 50); - }); - - it("cancels fetch on will unmount.", () => { - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - TestRenderer - .root - .findByProps({testID: BtnTestID}) - .props - .onPress(); - // spy on abort method - const abort = jest.spyOn(TestRenderer.root.instance.abortController, 'abort'); - // call component will unmount - TestRenderer.root.instance.componentWillUnmount(); - // run checks - expect(abort).toHaveBeenCalledTimes(1); - // end test - }); - - it("does not cancel fetch on will unmount if canceller is not set.", () => { - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - const willUnmount = jest.spyOn(TestRenderer.root.instance, 'componentWillUnmount'); - // call component will unmount - TestRenderer.root.instance.componentWillUnmount(); - // run checks - expect(willUnmount).toHaveBeenCalledTimes(1); - expect(TestRenderer.root.instance.abortController).toBeUndefined(); - }); - - it('can reload webview if webview ref is set', (done) => { - // create renderer - const TestRender = renderer.create(); - // mock next fetch request - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - // fire on press - TestRender.root.findByProps({testID: BtnTestID}).props.onPress(); - // simulate animated animation - jest.useFakeTimers(); - global.timeTravel(); - jest.useRealTimers(); - - // wait for standard call to occurr - setTimeout(() => { - const webviewReload = jest.spyOn( - TestRender.root.instance.webviewRef, - 'reload' - ).mockImplementationOnce((() => {})); - TestRender.root.instance.handleReload(); - expect(webviewReload).toHaveBeenCalledTimes(1); - done(); - }, 50); - }); - - it('does not reload if webview ref is not set', () => { - // create renderer - const TestRender = renderer.create(); - Object.defineProperty(TestRender.root.instance, 'webviewRef', {value: null}); - const handleReload = jest.spyOn(TestRender.root.instance, 'handleReload'); - TestRender.root.instance.handleReload(); - expect(handleReload).toHaveBeenCalledTimes(1); - expect(TestRender.root.instance.webviewRef === null).toBe(true); - }); - - it("handles DefaultButton onSizeChange", () => { - const TestRenderer = renderer.create(); - const size = {width: 1200, height: 0}; - const handleButtonResize = jest.spyOn(TestRenderer.root.instance, 'handleButtonResize'); - - TestRenderer.root.findByType(DefaultButton).props.onSizeChange(size); - TestRenderer.root.findByType(DefaultButton).props.onSizeChange(size); - - expect(handleButtonResize).toHaveBeenCalledTimes(1); - expect(handleButtonResize).toHaveBeenCalledWith(size); - }); - - it("does not return query params if non is available with getRedirectParams method", () => { - const url = new String('http://example.com'); - const TestRenderer = renderer.create(); - const split = jest.spyOn(url, 'split'); - TestRenderer.root.instance.getRedirectParams(url);; - expect(split).toHaveBeenCalledTimes(1); - }); - - it("returns query params if avialeble with getRedirectParams method", () => { - const url = new String('http://example.com?foo=bar'); - const TestRenderer = renderer.create(); - const split = jest.spyOn(url, 'split'); - TestRenderer.root.instance.getRedirectParams(url);; - expect(split).toHaveBeenCalledTimes(2); - }); - - it("updates state if reset is called.", () => { - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - const setState = jest.spyOn(TestRenderer.root.instance, 'setState'); - // call component will unmount - TestRenderer.root.instance.reset(); - // run checks - expect(setState).toHaveBeenCalledTimes(1); - expect(TestRenderer.root.instance.abortController).toBeUndefined(); - }); - - it("cancels fetch if reset is called and abort controller is set.", () => { - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - TestRenderer - .root - .findByProps({testID: BtnTestID}) - .props - .onPress(); - // spy on abort method - const abort = jest.spyOn(TestRenderer.root.instance.abortController, 'abort'); - // call component will unmount - TestRenderer.root.instance.reset(); - // run checks - expect(abort).toHaveBeenCalledTimes(1); - // end test - }); - - it("calls options change handler if options changed", () => { - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - // spy on handleOptionsChanged method - const handleOptionsChanged = jest.spyOn(TestRenderer.root.instance, 'handleOptionsChanged'); - // update component - TestRenderer.update() - // run checks - expect(handleOptionsChanged).toHaveBeenCalledTimes(1); - // end test - }); - - it("does not set state if link has not been set", () => { - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - // spy on setState method - const setState = jest.spyOn(TestRenderer.root.instance, 'setState'); - // update component - TestRenderer.update() - // run checks - expect(setState).toHaveBeenCalledTimes(0); - // end test - }); - - it("resets link if dialog is not being show", (done) => { - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - // mock next fetch - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - // initialize payment - TestRenderer - .root - .findByProps({testID: BtnTestID}) - .props - .onPress(); - // wait for mocked fetch - setTimeout(() => { - // set dialog to hidden - TestRenderer.root.instance.setState({showDialog: false}); - // spy on setState method - const setState = jest.spyOn(TestRenderer.root.instance, 'setState'); - // update component - TestRenderer.update() - // run checks - expect(setState).toHaveBeenCalledTimes(1); - expect(setState).toHaveBeenCalledWith({ - link: null, - tx_ref: null, - }); - // end test - done(); - }, 50); - }); - - it("schedules a link reset if dialog has already been shown", (done) => { - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - // mock next fetch - fetchMock.mockOnce(JSON.stringify(SuccessResponse)); - // initialize payment - TestRenderer - .root - .findByProps({testID: BtnTestID}) - .props - .onPress(); - // wait for mocked fetch - setTimeout(() => { - // spy on setState method - const setState = jest.spyOn(TestRenderer.root.instance, 'setState'); - // update component - TestRenderer.update() - // run checks - expect(setState).toHaveBeenCalledTimes(1); - expect(setState).toHaveBeenCalledWith({resetLink: true}); - // end test - done(); - }, 50); - }); - - it("renders checkout screen if link already exist when init is called", (done) => { - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - // set a payment link - TestRenderer.root.instance.setState({link: 'http://payment-link.com'}); - // spy on show method - const show = jest.spyOn(TestRenderer.root.instance, 'show'); - // initialize payment - TestRenderer - .root - .findByProps({testID: BtnTestID}) - .props - .onPress(); - setTimeout(() => { - // run checks - expect(show).toHaveBeenCalledTimes(1); - expect(global.fetch).toHaveBeenCalledTimes(0); - // end test - done(); - }, 20); - }); - - it("does not generate a new link if already generating one", () => { - // get create instance of flutterwave button - const TestRenderer = renderer.create(); - // set a payment link - TestRenderer.root.instance.setState({isPending: true}); - // set a payment link - TestRenderer.root.instance.handleInit(); - // run checks - expect(global.fetch).toHaveBeenCalledTimes(0); - }); -}); diff --git a/__tests__/v3/__snapshots__/FlutterwaveButton.spec.tsx.snap b/__tests__/v3/__snapshots__/FlutterwaveButton.spec.tsx.snap deleted file mode 100644 index 1f5306f..0000000 --- a/__tests__/v3/__snapshots__/FlutterwaveButton.spec.tsx.snap +++ /dev/null @@ -1,823 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` does not render webview error if there is no link 1`] = ` - -`; - -exports[` renders busy button if isPending 1`] = ` -Array [ - - - - , - - - - - - - - - - - , -] -`; - -exports[` renders component correctly 1`] = ` -Array [ - - - , - - - - - - - - - - - , -] -`; - -exports[` renders custom button correctly 1`] = ` -Array [ - - Pay - , - - - - - - - - - - - , -] -`; - -exports[` renders modal with visibile property as true if show dialog state is true 1`] = ` -Array [ - - - , - - - - - - - - - - - , -] -`; - -exports[` renders webview error correctly 1`] = ` - - - The page failed to load, please try again. - - - - Try Again - - - -`; - -exports[` renders webview loading correctly 1`] = ` - - - -`; diff --git a/src/DefaultButton.tsx b/src/DefaultButton.tsx deleted file mode 100644 index 0a64bd9..0000000 --- a/src/DefaultButton.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import React from 'react'; -import { - View, - StyleSheet, - ViewStyle, - LayoutChangeEvent, - TouchableHighlight, - StyleProp, -} from "react-native"; -import {colors} from './configs'; - -interface DefaultButtonProps { - style?: StyleProp; - onPress?: () => void; - disabled?: boolean; - children: React.ReactNode; - isBusy?: boolean; - onSizeChange?: (ev: {width: number; height: number}) => void; - alignLeft?: 'alignLeft' | boolean, -} - -function getBusyStyle(isBusy?: 'isBusy' | boolean) { - if (!isBusy) { - return {}; - } - return styles.buttonBusy; -} - -function getAlginStyle(alignLeft?: 'alignLeft' | boolean) { - if (alignLeft) { - return styles.buttonAlignLeft; - } - return {}; -} - -/** - * Button base design. - * @param param0 - */ -const DefaultButton: React.FC = function Button({ - style, - onPress, - disabled, - children, - isBusy, - onSizeChange, - alignLeft, -}) { - const handleOnLayout = (ev: LayoutChangeEvent) => { - const {width, height} = ev.nativeEvent.layout; - if (onSizeChange) { - onSizeChange({width, height}); - } - }; - - return ( - - <> - {children} - {isBusy - ? () - : null} - - - ); -}; - -const styles = StyleSheet.create({ - buttonBusyOvelay: { - position: 'absolute', - left: 0, - top: 0, - bottom: 0, - right: 0, - backgroundColor: 'rgba(255, 255, 255, 0.6)', - }, - buttonAltBusyOvelay: { - backgroundColor: 'rgba(255, 255, 255, 0.8)', - }, - buttonBusy: { - borderColor: '#FBDBA7', - }, - buttonAltBusy: { - borderColor: '#D0D0D5', - }, - buttonAlignLeft: { - justifyContent: 'flex-start', - }, - buttonAlt: { - backgroundColor: '#fff', - borderColor: colors.secondary, - }, - button: { - paddingHorizontal: 16, - minWidth: 100, - height: 52, - borderColor: colors.primary, - borderWidth: 1, - borderRadius: 6, - backgroundColor: colors.primary, - alignItems: 'center', - justifyContent: 'center', - flexDirection: 'row', - overflow: 'hidden', - }, -}); - -export default DefaultButton; diff --git a/src/FlutterwaveButton.tsx b/src/FlutterwaveButton.tsx deleted file mode 100644 index f2b6b64..0000000 --- a/src/FlutterwaveButton.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import {ViewStyle, StyleProp} from 'react-native'; -import PropTypes from 'prop-types'; -import FlutterwaveButton from './v3/FlutterwaveButton'; -import FlutterwaveInitError from './utils/FlutterwaveInitError'; - -export interface RedirectParams { - status: 'successful' | 'cancelled', - transaction_id?: string; - tx_ref: string; -} - -export interface RedirectParamsV2 { - cancelled?: 'true' | 'false'; - flwref?: string; - txref: string; -} - -export interface CustomButtonProps { - disabled: boolean; - isInitializing: boolean; - onPress: () => void; -} - -export interface FlutterwaveButtonPropsBase { - style?: StyleProp; - onComplete: (data: any) => void; - onWillInitialize?: () => void; - onDidInitialize?: () => void; - onInitializeError?: (error: FlutterwaveInitError) => void; - onAbort?: () => void; - customButton?: (params: CustomButtonProps) => React.ReactNode; - alignLeft?: 'alignLeft' | boolean; -} - -export const OptionsPropTypeBase = { - amount: PropTypes.number.isRequired, - currency: PropTypes.oneOf(['GBP', 'NGN', 'USD', 'GHS', 'KES', 'ZAR', 'TZS']), - payment_plan: PropTypes.number, - subaccounts: PropTypes.arrayOf(PropTypes.shape({ - id: PropTypes.string.isRequired, - transaction_split_ratio: PropTypes.number, - transaction_charge_type: PropTypes.string, - transaction_charge: PropTypes.number, - })), - integrity_hash: PropTypes.string, -}; - -export default FlutterwaveButton; diff --git a/src/index.ts b/src/index.ts index 654ca6f..2d4ae89 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,4 @@ import FlutterwaveInit from './FlutterwaveInit'; -import FlutterwaveButton from './FlutterwaveButton'; -import FlutterwaveButtonV2 from './v2/FlutterwaveButton'; -import DefaultButton from './DefaultButton'; import FlutterwaveInitV2 from './FlutterwaveInitV2'; import PayWithFlutterwave from './PayWithFlutterwave'; import PayWithFlutterwaveV2 from './PayWithFlutterwaveV2'; @@ -11,11 +8,8 @@ import FlwCheckout from './FlwCheckout'; // export modules export { FlutterwaveInit, - FlutterwaveButton, PayWithFlutterwave, FlutterwaveInitV2, - FlutterwaveButtonV2, - DefaultButton, PayWithFlutterwaveV2, FlwButton, FlwCheckout, diff --git a/src/v2/FlutterwaveButton.tsx b/src/v2/FlutterwaveButton.tsx deleted file mode 100644 index d60bacc..0000000 --- a/src/v2/FlutterwaveButton.tsx +++ /dev/null @@ -1,518 +0,0 @@ -import React from 'react'; -import { - StyleSheet, - Modal, - View, - Animated, - TouchableWithoutFeedback, - Text, - Alert, - Image, - Platform, - Dimensions, - Easing, -} from 'react-native'; -import WebView from 'react-native-webview'; -import PropTypes from 'prop-types'; -import {WebViewNavigation} from 'react-native-webview/lib/WebViewTypes'; -import {FlutterwaveButtonPropsBase, RedirectParamsV2, OptionsPropTypeBase} from '../FlutterwaveButton'; -import FlutterwaveInit, {FlutterwaveInitOptions} from './FlutterwaveInit'; -import DefaultButton from '../DefaultButton'; -import {PAYMENT_OPTIONS_V2, colors, REDIRECT_URL} from '../configs'; -import {PaymentOptionsPropRule} from '../utils/CustomPropTypesRules'; -import FlutterwaveInitError from '../utils/FlutterwaveInitError'; -const loader = require('../assets/loader.gif'); -const pryContent = require('../assets/pry-button-content.png'); -const contentWidthPercentage = 0.6549707602; -const contentSizeDimension = 8.2962962963; -const contentMaxWidth = 187.3; -const contentMaxHeight = contentMaxWidth / contentSizeDimension; -const contentMinWidth = 187.3; -const contentMinHeight = contentMinWidth / contentSizeDimension; -const borderRadiusDimension = 24 / 896; -const windowHeight = Dimensions.get('window').height; - -export type FlutterwaveButtonProps = FlutterwaveButtonPropsBase & { - onComplete: (data: RedirectParamsV2) => void; - options: Omit; -} - -interface FlutterwaveButtonState { - link: string | null; - isPending: boolean; - showDialog: boolean; - animation: Animated.Value; - txref: string | null; - resetLink: boolean; - buttonSize: { - width: number; - height: number; - }; -} - -class FlutterwaveButton extends React.Component< - FlutterwaveButtonProps, - FlutterwaveButtonState -> { - static propTypes = { - alignLeft: PropTypes.bool, - onAbort: PropTypes.func, - onComplete: PropTypes.func.isRequired, - onWillInitialize: PropTypes.func, - onDidInitialize: PropTypes.func, - onInitializeError: PropTypes.func, - options: PropTypes.shape({ - ...OptionsPropTypeBase, - payment_options: PaymentOptionsPropRule(PAYMENT_OPTIONS_V2), - txref: PropTypes.string.isRequired, - PBFPubKey: PropTypes.string.isRequired, - customer_firstname: PropTypes.string, - customer_lastname: PropTypes.string, - customer_email: PropTypes.string.isRequired, - customer_phone: PropTypes.string, - country: PropTypes.string, - pay_button_text: PropTypes.string, - custom_title: PropTypes.string, - custom_description: PropTypes.string, - custom_logo: PropTypes.string, - meta: PropTypes.arrayOf(PropTypes.shape({ - metaname: PropTypes.string, - metavalue: PropTypes.string, - })), - }).isRequired, - customButton: PropTypes.func, - }; - - state: FlutterwaveButtonState = { - isPending: false, - link: null, - resetLink: false, - showDialog: false, - animation: new Animated.Value(0), - txref: null, - buttonSize: { - width: 0, - height: 0, - }, - }; - - webviewRef: WebView | null = null; - - abortController?: AbortController; - - componentDidUpdate(prevProps: FlutterwaveButtonProps) { - if (JSON.stringify(prevProps.options) !== JSON.stringify(this.props.options)) { - this.handleOptionsChanged() - } - } - - componentWillUnmount() { - if (this.abortController) { - this.abortController.abort(); - } - } - - reset = () => { - if (this.abortController) { - this.abortController.abort(); - } - // reset the necessaries - this.setState(({resetLink, link}) => ({ - isPending: false, - link: resetLink ? null : link, - resetLink: false, - showDialog: false, - })); - }; - - handleOptionsChanged = () => { - const {showDialog, link} = this.state; - if (!link) { - return; - } - if (!showDialog) { - return this.setState({ - link: null, - txref: null, - }) - } - this.setState({resetLink: true}) - } - - handleNavigationStateChange = (ev: WebViewNavigation) => { - // cregex to check if redirect has occured on completion/cancel - const rx = /\/flutterwave\.com\/rn-redirect/; - // Don't end payment if not redirected back - if (!rx.test(ev.url)) { - return - } - // fire handle complete - this.handleComplete(this.getRedirectParams(ev.url)); - }; - - handleComplete(data: RedirectParamsV2) { - const {onComplete} = this.props; - // reset payment link - this.setState(({resetLink, txref}) => ({ - txref: data.flwref && !data.cancelled ? null : txref, - resetLink: data.flwref && !data.cancelled ? true : resetLink - }), - () => { - // reset - this.dismiss(); - // fire onComplete handler - onComplete({ - flwref: data.flwref, - txref: data.txref, - cancelled: data.cancelled, - }); - } - ); - } - - handleReload = () => { - // fire if webview is set - if (this.webviewRef) { - this.webviewRef.reload(); - } - }; - - handleAbortConfirm = () => { - const {onAbort} = this.props; - // abort action - if (onAbort) { - onAbort(); - } - // remove txref and dismiss - this.dismiss(); - }; - - handleAbort = () => { - Alert.alert('', 'Are you sure you want to cancel this payment?', [ - {text: 'No'}, - { - text: 'Yes, Cancel', - style: 'destructive', - onPress: this.handleAbortConfirm, - }, - ]); - }; - - handleButtonResize = (size: {width: number; height: number}) => { - const {buttonSize} = this.state; - if (JSON.stringify(buttonSize) !== JSON.stringify(size)) { - this.setState({buttonSize: size}); - } - }; - - getRedirectParams = (url: string): RedirectParamsV2 => { - // initialize result container - const res: any = {}; - // if url has params - if (url.split('?').length > 1) { - // get query params in an array - const params = url.split('?')[1].split('&'); - // add url params to result - for (let i = 0; i < params.length; i++) { - const param: Array = params[i].split('='); - const val = decodeURIComponent(param[1]).trim(); - res[param[0]] = String(val); - } - } - // return result - return res; - }; - - show = () => { - const {animation} = this.state; - this.setState({showDialog: true}, () => { - Animated.timing(animation, { - toValue: 1, - duration: 700, - easing: Easing.in(Easing.elastic(0.72)), - useNativeDriver: false, - }).start(); - }); - }; - - dismiss = () => { - const {animation} = this.state; - Animated.timing(animation, { - toValue: 0, - duration: 400, - useNativeDriver: false, - }).start(this.reset); - }; - - handleInit = () => { - const {options, onWillInitialize, onInitializeError, onDidInitialize} = this.props; - const {isPending, txref, link} = this.state; - - // just show the dialod if the link is already set - if (link) { - return this.show(); - } - - // throw error if transaction reference has not changed - if (txref === options.txref) { - return onInitializeError ? onInitializeError(new FlutterwaveInitError({ - message: 'Please generate a new transaction reference.', - code: 'SAME_TXREF', - })) : null; - } - - // stop if currently in pending mode - if (isPending) { - return; - } - - // initialize abort controller if not set - this.abortController = new AbortController; - - // fire will initialize handler if available - if (onWillInitialize) { - onWillInitialize(); - } - - // set pending state to true - this.setState( - { - isPending: true, - link: null, - txref: options.txref, - }, - async () => { - try { - // make init request - const paymentLink = await FlutterwaveInit( - {...options, redirect_url: REDIRECT_URL}, - this.abortController - ); - // set payment link - this.setState({ - link: paymentLink, - isPending: false - }, this.show); - // fire did initialize handler if available - if (onDidInitialize) { - onDidInitialize(); - } - } catch (error) { - // stop if request was canceled - if (error && /aborterror/i.test(error.code)) { - return; - } - // call onInitializeError handler if an error occured - if (onInitializeError) { - onInitializeError(error); - } - return this.dismiss(); - } - }, - ); - }; - - render() { - const {link, animation, showDialog} = this.state; - const marginTop = animation.interpolate({ - inputRange: [0, 1], - outputRange: [windowHeight, Platform.OS === 'ios' ? 46 : 14], - }); - const opacity = animation.interpolate({ - inputRange: [0, 0.3, 1], - outputRange: [0, 1, 1], - }); - // render UI - return ( - <> - {this.renderButton()} - - {this.renderBackdrop()} - - (this.webviewRef = ref)} - source={{uri: link || ''}} - style={styles.webview} - startInLoadingState={true} - scalesPageToFit={true} - javaScriptEnabled={true} - onNavigationStateChange={this.handleNavigationStateChange} - renderError={this.renderError} - renderLoading={this.renderLoading} - /> - - - - ); - } - - renderButton() { - const {customButton, style, alignLeft, children} = this.props; - const {isPending, link, showDialog, buttonSize} = this.state; - const contentWidth = buttonSize.width * contentWidthPercentage; - const contentHeight = contentWidth / contentSizeDimension; - const contentSizeStyle = { - width: - contentWidth > contentMaxWidth - ? contentMaxWidth - : contentWidth < contentMinWidth - ? contentMinWidth - : contentWidth, - height: - contentHeight > contentMaxHeight - ? contentMaxHeight - : contentHeight < contentMinHeight - ? contentMinHeight - : contentHeight, - }; - // render custom button - if (customButton) { - return customButton({ - isInitializing: isPending && !link ? true : false, - disabled: isPending || showDialog? true : false, - onPress: this.handleInit, - }); - } - // render primary button - return ( - - {children ? children : ( - - )} - - ); - } - - renderBackdrop() { - const {animation} = this.state; - // Interpolation backdrop animation - const backgroundColor = animation.interpolate({ - inputRange: [0, 0.3, 1], - outputRange: [colors.transparent, colors.transparent, 'rgba(0,0,0,0.5)'], - }); - return ( - - - - ); - } - - renderLoading() { - return ( - - - - ); - } - - renderError = () => { - const {link} = this.state; - return ( - - {link ? ( - <> - - The page failed to load, please try again. - - - - Try Again - - - - ) : null} - - ); - }; -} - -const styles = StyleSheet.create({ - promtActions: { - flexDirection: 'row', - alignItems: 'center', - }, - promptActionText: { - textAlign: 'center', - color: colors.primary, - fontSize: 16, - paddingHorizontal: 16, - paddingVertical: 16, - }, - promptQuestion: { - color: colors.secondary, - textAlign: 'center', - marginBottom: 32, - fontSize: 18, - }, - prompt: { - position: 'absolute', - left: 0, - right: 0, - bottom: 0, - top: 0, - backgroundColor: '#ffffff', - justifyContent: 'center', - alignItems: 'center', - paddingHorizontal: 56, - }, - backdrop: { - position: 'absolute', - left: 0, - right: 0, - bottom: 0, - top: 0, - }, - loadingImage: { - width: 64, - height: 64, - resizeMode: 'contain', - }, - loading: { - position: 'absolute', - top: 0, - right: 0, - bottom: 0, - left: 0, - backgroundColor: 'rgba(255, 255, 255, 0.3)', - justifyContent: 'center', - alignItems: 'center', - }, - webviewContainer: { - top: 50, - flex: 1, - backgroundColor: '#efefef', - paddingBottom: 50, - overflow: 'hidden', - borderTopLeftRadius: windowHeight * borderRadiusDimension, - borderTopRightRadius: windowHeight * borderRadiusDimension, - }, - webview: { - flex: 1, - backgroundColor: 'rgba(0,0,0,0)', - }, - buttonContent: { - resizeMode: 'contain', - }, -}); - -export default FlutterwaveButton; diff --git a/src/v2/index.ts b/src/v2/index.ts deleted file mode 100644 index 1af517d..0000000 --- a/src/v2/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import FlutterwaveInit from './FlutterwaveInit'; -import FlutterwaveButton from './FlutterwaveButton'; -import DefaultButton from '../DefaultButton'; - -// export modules -export {FlutterwaveInit, FlutterwaveButton, DefaultButton}; - -// export button as default -export default FlutterwaveButton; diff --git a/src/v3/FlutterwaveButton.tsx b/src/v3/FlutterwaveButton.tsx deleted file mode 100644 index ebf2a87..0000000 --- a/src/v3/FlutterwaveButton.tsx +++ /dev/null @@ -1,513 +0,0 @@ -import React from 'react'; -import { - StyleSheet, - Modal, - View, - Animated, - TouchableWithoutFeedback, - Text, - Alert, - Image, - Platform, - Dimensions, - Easing, -} from 'react-native'; -import WebView from 'react-native-webview'; -import PropTypes from 'prop-types'; -import {WebViewNavigation} from 'react-native-webview/lib/WebViewTypes'; -import {FlutterwaveButtonPropsBase, RedirectParams, OptionsPropTypeBase} from '../FlutterwaveButton'; -import FlutterwaveInit, {FlutterwaveInitOptions} from './FlutterwaveInit'; -import DefaultButton from '../DefaultButton'; -import {colors, REDIRECT_URL, PAYMENT_OPTIONS} from '../configs'; -import {PaymentOptionsPropRule} from '../utils/CustomPropTypesRules'; -import FlutterwaveInitError from '../utils/FlutterwaveInitError'; -const loader = require('../assets/loader.gif'); -const pryContent = require('../assets/pry-button-content.png'); -const contentWidthPercentage = 0.6549707602; -const contentSizeDimension = 8.2962962963; -const contentMaxWidth = 187.3; -const contentMaxHeight = contentMaxWidth / contentSizeDimension; -const contentMinWidth = 187.3; -const contentMinHeight = contentMinWidth / contentSizeDimension; -const borderRadiusDimension = 24 / 896; -const windowHeight = Dimensions.get('window').height; - -export type FlutterwaveButtonProps = FlutterwaveButtonPropsBase & { - onComplete: (data: RedirectParams) => void; - options: Omit; -} - -interface FlutterwaveButtonState { - link: string | null; - isPending: boolean; - showDialog: boolean; - animation: Animated.Value; - tx_ref: string | null; - resetLink: boolean; - buttonSize: { - width: number; - height: number; - }; -} - -class FlutterwaveButton extends React.Component< - FlutterwaveButtonProps, - FlutterwaveButtonState -> { - static propTypes = { - alignLeft: PropTypes.bool, - onAbort: PropTypes.func, - onComplete: PropTypes.func.isRequired, - onWillInitialize: PropTypes.func, - onDidInitialize: PropTypes.func, - onInitializeError: PropTypes.func, - options: PropTypes.shape({ - ...OptionsPropTypeBase, - authorization: PropTypes.string.isRequired, - tx_ref: PropTypes.string.isRequired, - payment_options: PaymentOptionsPropRule(PAYMENT_OPTIONS), - customer: PropTypes.shape({ - name: PropTypes.string, - phonenumber: PropTypes.string, - email: PropTypes.string.isRequired, - }).isRequired, - meta: PropTypes.arrayOf(PropTypes.object), - customizations: PropTypes.shape({ - title: PropTypes.string, - logo: PropTypes.string, - description: PropTypes.string, - }), - }).isRequired, - customButton: PropTypes.func, - }; - - state: FlutterwaveButtonState = { - isPending: false, - link: null, - resetLink: false, - showDialog: false, - animation: new Animated.Value(0), - tx_ref: null, - buttonSize: { - width: 0, - height: 0, - }, - }; - - webviewRef: WebView | null = null; - - abortController?: AbortController; - - componentDidUpdate(prevProps: FlutterwaveButtonProps) { - if (JSON.stringify(prevProps.options) !== JSON.stringify(this.props.options)) { - this.handleOptionsChanged() - } - } - - componentWillUnmount() { - if (this.abortController) { - this.abortController.abort(); - } - } - - reset = () => { - if (this.abortController) { - this.abortController.abort(); - } - // reset the necessaries - this.setState(({resetLink, link}) => ({ - isPending: false, - link: resetLink ? null : link, - resetLink: false, - showDialog: false, - })); - }; - - handleOptionsChanged = () => { - const {showDialog, link} = this.state; - if (!link) { - return; - } - if (!showDialog) { - return this.setState({ - link: null, - tx_ref: null, - }) - } - this.setState({resetLink: true}) - } - - handleNavigationStateChange = (ev: WebViewNavigation) => { - // cregex to check if redirect has occured on completion/cancel - const rx = /\/flutterwave\.com\/rn-redirect/; - // Don't end payment if not redirected back - if (!rx.test(ev.url)) { - return - } - // fire handle complete - this.handleComplete(this.getRedirectParams(ev.url)); - }; - - handleComplete(data: RedirectParams) { - const {onComplete} = this.props; - // reset payment link - this.setState(({resetLink, tx_ref}) => ({ - tx_ref: data.status === 'successful' ? null : tx_ref, - resetLink: data.status === 'successful' ? true : resetLink - }), - () => { - // reset - this.dismiss(); - // fire onComplete handler - onComplete(data); - } - ); - } - - handleReload = () => { - // fire if webview is set - if (this.webviewRef) { - this.webviewRef.reload(); - } - }; - - handleAbortConfirm = () => { - const {onAbort} = this.props; - // abort action - if (onAbort) { - onAbort(); - } - // remove tx_ref and dismiss - this.dismiss(); - }; - - handleAbort = () => { - Alert.alert('', 'Are you sure you want to cancel this payment?', [ - {text: 'No'}, - { - text: 'Yes, Cancel', - style: 'destructive', - onPress: this.handleAbortConfirm, - }, - ]); - }; - - handleButtonResize = (size: {width: number; height: number}) => { - const {buttonSize} = this.state; - if (JSON.stringify(buttonSize) !== JSON.stringify(size)) { - this.setState({buttonSize: size}); - } - }; - - getRedirectParams = (url: string): RedirectParams => { - // initialize result container - const res: any = {}; - // if url has params - if (url.split('?').length > 1) { - // get query params in an array - const params = url.split('?')[1].split('&'); - // add url params to result - for (let i = 0; i < params.length; i++) { - const param: Array = params[i].split('='); - const val = decodeURIComponent(param[1]).trim(); - res[param[0]] = String(val); - } - } - // return result - return res; - }; - - show = () => { - const {animation} = this.state; - this.setState({showDialog: true}, () => { - Animated.timing(animation, { - toValue: 1, - duration: 700, - easing: Easing.in(Easing.elastic(0.72)), - useNativeDriver: false, - }).start(); - }); - }; - - dismiss = () => { - const {animation} = this.state; - Animated.timing(animation, { - toValue: 0, - duration: 400, - useNativeDriver: false, - }).start(this.reset); - }; - - handleInit = () => { - const {options, onWillInitialize, onInitializeError, onDidInitialize} = this.props; - const {isPending, tx_ref, link} = this.state; - - // just show the dialod if the link is already set - if (link) { - return this.show(); - } - - // throw error if transaction reference has not changed - if (tx_ref === options.tx_ref) { - return onInitializeError ? onInitializeError(new FlutterwaveInitError({ - message: 'Please generate a new transaction reference.', - code: 'SAME_TXREF', - })) : null; - } - - // stop if currently in pending mode - if (isPending) { - return; - } - - // initialize abort controller if not set - this.abortController = new AbortController; - - // fire will initialize handler if available - if (onWillInitialize) { - onWillInitialize(); - } - - // @ts-ignore - // delete redirect url if set - delete options.redirect_url; - - // set pending state to true - this.setState( - { - isPending: true, - link: null, - tx_ref: options.tx_ref, - }, - async () => { - try { - // initialis payment - const link = await FlutterwaveInit( - {...options, redirect_url: REDIRECT_URL}, - this.abortController - ); - // resent pending mode - this.setState({link, isPending: false}, this.show); - // fire did initialize handler if available - if (onDidInitialize) { - onDidInitialize(); - } - } catch (error) { - // stop if request was canceled - if (/aborterror/i.test(error.code)) { - return; - } - if (onInitializeError) { - onInitializeError(error); - } - return this.setState({ - resetLink: true, - tx_ref: null - }, this.dismiss); - } - }, - ); - }; - - render() { - const {link, animation, showDialog} = this.state; - const marginTop = animation.interpolate({ - inputRange: [0, 1], - outputRange: [windowHeight, Platform.OS === 'ios' ? 46 : 14], - }); - const opacity = animation.interpolate({ - inputRange: [0, 0.3, 1], - outputRange: [0, 1, 1], - }); - // render UI - return ( - <> - {this.renderButton()} - - {this.renderBackdrop()} - - (this.webviewRef = ref)} - source={{uri: link || ''}} - style={styles.webview} - startInLoadingState={true} - scalesPageToFit={true} - javaScriptEnabled={true} - onNavigationStateChange={this.handleNavigationStateChange} - renderError={this.renderError} - renderLoading={this.renderLoading} - /> - - - - ); - } - - renderButton() { - const {customButton, style, alignLeft} = this.props; - const {isPending, link, showDialog, buttonSize} = this.state; - const contentWidth = buttonSize.width * contentWidthPercentage; - const contentHeight = contentWidth / contentSizeDimension; - const contentSizeStyle = { - width: - contentWidth > contentMaxWidth - ? contentMaxWidth - : contentWidth < contentMinWidth - ? contentMinWidth - : contentWidth, - height: - contentHeight > contentMaxHeight - ? contentMaxHeight - : contentHeight < contentMinHeight - ? contentMinHeight - : contentHeight, - }; - // render custom button - if (customButton) { - return customButton({ - isInitializing: isPending && !link ? true : false, - disabled: isPending || showDialog? true : false, - onPress: this.handleInit, - }); - } - // render primary button - return ( - - - - ); - } - - renderBackdrop() { - const {animation} = this.state; - // Interpolation backdrop animation - const backgroundColor = animation.interpolate({ - inputRange: [0, 0.3, 1], - outputRange: [colors.transparent, colors.transparent, 'rgba(0,0,0,0.5)'], - }); - return ( - - - - ); - } - - renderLoading() { - return ( - - - - ); - } - - renderError = () => { - const {link} = this.state; - return ( - - {link ? ( - <> - - The page failed to load, please try again. - - - - Try Again - - - - ) : null} - - ); - }; -} - -const styles = StyleSheet.create({ - promtActions: { - flexDirection: 'row', - alignItems: 'center', - }, - promptActionText: { - textAlign: 'center', - color: colors.primary, - fontSize: 16, - paddingHorizontal: 16, - paddingVertical: 16, - }, - promptQuestion: { - color: colors.secondary, - textAlign: 'center', - marginBottom: 32, - fontSize: 18, - }, - prompt: { - position: 'absolute', - left: 0, - right: 0, - bottom: 0, - top: 0, - backgroundColor: '#ffffff', - justifyContent: 'center', - alignItems: 'center', - paddingHorizontal: 56, - }, - backdrop: { - position: 'absolute', - left: 0, - right: 0, - bottom: 0, - top: 0, - }, - loadingImage: { - width: 64, - height: 64, - resizeMode: 'contain', - }, - loading: { - position: 'absolute', - top: 0, - right: 0, - bottom: 0, - left: 0, - backgroundColor: 'rgba(255, 255, 255, 0.3)', - justifyContent: 'center', - alignItems: 'center', - }, - webviewContainer: { - top: 50, - flex: 1, - backgroundColor: '#efefef', - paddingBottom: 50, - overflow: 'hidden', - borderTopLeftRadius: windowHeight * borderRadiusDimension, - borderTopRightRadius: windowHeight * borderRadiusDimension, - }, - webview: { - flex: 1, - backgroundColor: 'rgba(0,0,0,0)', - }, - buttonContent: { - resizeMode: 'contain', - }, -}); - -export default FlutterwaveButton; diff --git a/src/v3/index.ts b/src/v3/index.ts deleted file mode 100644 index 1af517d..0000000 --- a/src/v3/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import FlutterwaveInit from './FlutterwaveInit'; -import FlutterwaveButton from './FlutterwaveButton'; -import DefaultButton from '../DefaultButton'; - -// export modules -export {FlutterwaveInit, FlutterwaveButton, DefaultButton}; - -// export button as default -export default FlutterwaveButton; From d346ad6d38ffbaf93d40df689c479aefc2e5a7ca Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 10:02:10 +0100 Subject: [PATCH 116/129] refactor(customproptypesrules): code cleaning --- src/utils/CustomPropTypesRules.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/CustomPropTypesRules.ts b/src/utils/CustomPropTypesRules.ts index 78f02b3..6752fe4 100644 --- a/src/utils/CustomPropTypesRules.ts +++ b/src/utils/CustomPropTypesRules.ts @@ -1,4 +1,3 @@ - export const PaymentOptionsPropRule = (options: Array) => (props:{[k: string]: any}, propName: string) => { // skip check if payment options is not defined if (props[propName] === undefined) { From bddbb907396599dcc13f396a84c26fbbfb34d2c1 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 10:03:05 +0100 Subject: [PATCH 117/129] refactor(dist): remove old implementation builds --- dist/DefaultButton.d.ts | 21 -- dist/DefaultButton.d.ts.map | 1 - dist/DefaultButton.js | 81 ----- dist/FlutterwaveButton.d.ts | 44 --- dist/FlutterwaveButton.d.ts.map | 1 - dist/FlutterwaveButton.js | 15 - dist/v2/FlutterwaveButton.d.ts | 91 ------ dist/v2/FlutterwaveButton.d.ts.map | 1 - dist/v2/FlutterwaveButton.js | 466 ---------------------------- dist/v2/FlutterwaveInit.d.ts | 35 --- dist/v2/FlutterwaveInit.d.ts.map | 1 - dist/v2/FlutterwaveInit.js | 115 ------- dist/v3/FlutterwaveButton.d.ts | 89 ------ dist/v3/FlutterwaveButton.d.ts.map | 1 - dist/v3/FlutterwaveButton.js | 469 ----------------------------- dist/v3/FlutterwaveInit.d.ts | 46 --- dist/v3/FlutterwaveInit.d.ts.map | 1 - dist/v3/FlutterwaveInit.js | 101 ------- 18 files changed, 1579 deletions(-) delete mode 100644 dist/DefaultButton.d.ts delete mode 100644 dist/DefaultButton.d.ts.map delete mode 100644 dist/DefaultButton.js delete mode 100644 dist/FlutterwaveButton.d.ts delete mode 100644 dist/FlutterwaveButton.d.ts.map delete mode 100644 dist/FlutterwaveButton.js delete mode 100644 dist/v2/FlutterwaveButton.d.ts delete mode 100644 dist/v2/FlutterwaveButton.d.ts.map delete mode 100644 dist/v2/FlutterwaveButton.js delete mode 100644 dist/v2/FlutterwaveInit.d.ts delete mode 100644 dist/v2/FlutterwaveInit.d.ts.map delete mode 100644 dist/v2/FlutterwaveInit.js delete mode 100644 dist/v3/FlutterwaveButton.d.ts delete mode 100644 dist/v3/FlutterwaveButton.d.ts.map delete mode 100644 dist/v3/FlutterwaveButton.js delete mode 100644 dist/v3/FlutterwaveInit.d.ts delete mode 100644 dist/v3/FlutterwaveInit.d.ts.map delete mode 100644 dist/v3/FlutterwaveInit.js diff --git a/dist/DefaultButton.d.ts b/dist/DefaultButton.d.ts deleted file mode 100644 index 8c363fa..0000000 --- a/dist/DefaultButton.d.ts +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import { ViewStyle, StyleProp } from "react-native"; -interface DefaultButtonProps { - style?: StyleProp; - onPress?: () => void; - disabled?: boolean; - children: React.ReactNode; - isBusy?: boolean; - onSizeChange?: (ev: { - width: number; - height: number; - }) => void; - alignLeft?: 'alignLeft' | boolean; -} -/** - * Button base design. - * @param param0 - */ -declare const DefaultButton: React.FC; -export default DefaultButton; -//# sourceMappingURL=DefaultButton.d.ts.map \ No newline at end of file diff --git a/dist/DefaultButton.d.ts.map b/dist/DefaultButton.d.ts.map deleted file mode 100644 index 555096f..0000000 --- a/dist/DefaultButton.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"DefaultButton.d.ts","sourceRoot":"","sources":["../src/DefaultButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAGL,SAAS,EAGT,SAAS,EACV,MAAM,cAAc,CAAC;AAGtB,UAAU,kBAAkB;IAC1B,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAC,KAAK,IAAI,CAAC;IAC7D,SAAS,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;CACnC;AAgBD;;;GAGG;AACH,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAsC/C,CAAC;AA0CF,eAAe,aAAa,CAAC"} \ No newline at end of file diff --git a/dist/DefaultButton.js b/dist/DefaultButton.js deleted file mode 100644 index f629ea8..0000000 --- a/dist/DefaultButton.js +++ /dev/null @@ -1,81 +0,0 @@ -import React from 'react'; -import { View, StyleSheet, TouchableHighlight, } from "react-native"; -import { colors } from './configs'; -function getBusyStyle(isBusy) { - if (!isBusy) { - return {}; - } - return styles.buttonBusy; -} -function getAlginStyle(alignLeft) { - if (alignLeft) { - return styles.buttonAlignLeft; - } - return {}; -} -/** - * Button base design. - * @param param0 - */ -var DefaultButton = function Button(_a) { - var style = _a.style, onPress = _a.onPress, disabled = _a.disabled, children = _a.children, isBusy = _a.isBusy, onSizeChange = _a.onSizeChange, alignLeft = _a.alignLeft; - var handleOnLayout = function (ev) { - var _a = ev.nativeEvent.layout, width = _a.width, height = _a.height; - if (onSizeChange) { - onSizeChange({ width: width, height: height }); - } - }; - return ( - <> - {children} - {isBusy - ? () - : null} - - ); -}; -var styles = StyleSheet.create({ - buttonBusyOvelay: { - position: 'absolute', - left: 0, - top: 0, - bottom: 0, - right: 0, - backgroundColor: 'rgba(255, 255, 255, 0.6)' - }, - buttonAltBusyOvelay: { - backgroundColor: 'rgba(255, 255, 255, 0.8)' - }, - buttonBusy: { - borderColor: '#FBDBA7' - }, - buttonAltBusy: { - borderColor: '#D0D0D5' - }, - buttonAlignLeft: { - justifyContent: 'flex-start' - }, - buttonAlt: { - backgroundColor: '#fff', - borderColor: colors.secondary - }, - button: { - paddingHorizontal: 16, - minWidth: 100, - height: 52, - borderColor: colors.primary, - borderWidth: 1, - borderRadius: 6, - backgroundColor: colors.primary, - alignItems: 'center', - justifyContent: 'center', - flexDirection: 'row', - overflow: 'hidden' - } -}); -export default DefaultButton; diff --git a/dist/FlutterwaveButton.d.ts b/dist/FlutterwaveButton.d.ts deleted file mode 100644 index 2b94bb2..0000000 --- a/dist/FlutterwaveButton.d.ts +++ /dev/null @@ -1,44 +0,0 @@ -/// -import { ViewStyle, StyleProp } from 'react-native'; -import PropTypes from 'prop-types'; -import FlutterwaveButton from './v3/FlutterwaveButton'; -import FlutterwaveInitError from './utils/FlutterwaveInitError'; -export interface RedirectParams { - status: 'successful' | 'cancelled'; - transaction_id?: string; - tx_ref: string; -} -export interface RedirectParamsV2 { - cancelled?: 'true' | 'false'; - flwref?: string; - txref?: string; -} -export interface CustomButtonProps { - disabled: boolean; - isInitializing: boolean; - onPress: () => void; -} -export interface FlutterwaveButtonPropsBase { - style?: StyleProp; - onComplete: (data: any) => void; - onWillInitialize?: () => void; - onDidInitialize?: () => void; - onInitializeError?: (error: FlutterwaveInitError) => void; - onAbort?: () => void; - customButton?: (params: CustomButtonProps) => React.ReactNode; - alignLeft?: 'alignLeft' | boolean; -} -export declare const OptionsPropTypeBase: { - amount: PropTypes.Validator; - currency: PropTypes.Requireable; - payment_plan: PropTypes.Requireable; - subaccounts: PropTypes.Requireable<(PropTypes.InferProps<{ - id: PropTypes.Validator; - transaction_split_ratio: PropTypes.Requireable; - transaction_charge_type: PropTypes.Requireable; - transaction_charge: PropTypes.Requireable; - }> | null | undefined)[]>; - integrity_hash: PropTypes.Requireable; -}; -export default FlutterwaveButton; -//# sourceMappingURL=FlutterwaveButton.d.ts.map \ No newline at end of file diff --git a/dist/FlutterwaveButton.d.ts.map b/dist/FlutterwaveButton.d.ts.map deleted file mode 100644 index 05723cd..0000000 --- a/dist/FlutterwaveButton.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"FlutterwaveButton.d.ts","sourceRoot":"","sources":["../src/FlutterwaveButton.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAC,SAAS,EAAE,SAAS,EAAC,MAAM,cAAc,CAAC;AAClD,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,iBAAiB,MAAM,wBAAwB,CAAC;AACvD,OAAO,oBAAoB,MAAM,8BAA8B,CAAC;AAEhE,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,YAAY,GAAG,WAAW,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,UAAU,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAChC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC1D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,KAAK,CAAC,SAAS,CAAC;IAC9D,SAAS,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;CACnC;AAED,eAAO,MAAM,mBAAmB;;;;;;;;;;;CAW/B,CAAC;AAEF,eAAe,iBAAiB,CAAC"} \ No newline at end of file diff --git a/dist/FlutterwaveButton.js b/dist/FlutterwaveButton.js deleted file mode 100644 index 7cb4685..0000000 --- a/dist/FlutterwaveButton.js +++ /dev/null @@ -1,15 +0,0 @@ -import PropTypes from 'prop-types'; -import FlutterwaveButton from './v3/FlutterwaveButton'; -export var OptionsPropTypeBase = { - amount: PropTypes.number.isRequired, - currency: PropTypes.oneOf(['GBP', 'NGN', 'USD', 'GHS', 'KES', 'ZAR', 'TZS']), - payment_plan: PropTypes.number, - subaccounts: PropTypes.arrayOf(PropTypes.shape({ - id: PropTypes.string.isRequired, - transaction_split_ratio: PropTypes.number, - transaction_charge_type: PropTypes.string, - transaction_charge: PropTypes.number - })), - integrity_hash: PropTypes.string -}; -export default FlutterwaveButton; diff --git a/dist/v2/FlutterwaveButton.d.ts b/dist/v2/FlutterwaveButton.d.ts deleted file mode 100644 index f877798..0000000 --- a/dist/v2/FlutterwaveButton.d.ts +++ /dev/null @@ -1,91 +0,0 @@ -import React from 'react'; -import { Animated } from 'react-native'; -import WebView from 'react-native-webview'; -import PropTypes from 'prop-types'; -import { WebViewNavigation } from 'react-native-webview/lib/WebViewTypes'; -import { FlutterwaveButtonPropsBase, RedirectParamsV2 } from '../FlutterwaveButton'; -import { FlutterwaveInitOptions } from './FlutterwaveInit'; -export declare type FlutterwaveButtonProps = FlutterwaveButtonPropsBase & { - onComplete: (data: RedirectParamsV2) => void; - options: Omit; -}; -interface FlutterwaveButtonState { - link: string | null; - isPending: boolean; - showDialog: boolean; - animation: Animated.Value; - txref: string | null; - resetLink: boolean; - buttonSize: { - width: number; - height: number; - }; -} -declare class FlutterwaveButton extends React.Component { - static propTypes: { - alignLeft: PropTypes.Requireable; - onAbort: PropTypes.Requireable<(...args: any[]) => any>; - onComplete: PropTypes.Validator<(...args: any[]) => any>; - onWillInitialize: PropTypes.Requireable<(...args: any[]) => any>; - onDidInitialize: PropTypes.Requireable<(...args: any[]) => any>; - onInitializeError: PropTypes.Requireable<(...args: any[]) => any>; - options: PropTypes.Validator Error | null; - txref: PropTypes.Validator; - PBFPubKey: PropTypes.Validator; - customer_firstname: PropTypes.Requireable; - customer_lastname: PropTypes.Requireable; - customer_email: PropTypes.Validator; - customer_phone: PropTypes.Requireable; - country: PropTypes.Requireable; - pay_button_text: PropTypes.Requireable; - custom_title: PropTypes.Requireable; - custom_description: PropTypes.Requireable; - custom_logo: PropTypes.Requireable; - meta: PropTypes.Requireable<(PropTypes.InferProps<{ - metaname: PropTypes.Requireable; - metavalue: PropTypes.Requireable; - }> | null | undefined)[]>; - amount: PropTypes.Validator; - currency: PropTypes.Requireable; - payment_plan: PropTypes.Requireable; - subaccounts: PropTypes.Requireable<(PropTypes.InferProps<{ - id: PropTypes.Validator; - transaction_split_ratio: PropTypes.Requireable; - transaction_charge_type: PropTypes.Requireable; - transaction_charge: PropTypes.Requireable; - }> | null | undefined)[]>; - integrity_hash: PropTypes.Requireable; - }>>; - customButton: PropTypes.Requireable<(...args: any[]) => any>; - }; - state: FlutterwaveButtonState; - webviewRef: WebView | null; - abortController?: AbortController; - componentDidUpdate(prevProps: FlutterwaveButtonProps): void; - componentWillUnmount(): void; - reset: () => void; - handleOptionsChanged: () => void; - handleNavigationStateChange: (ev: WebViewNavigation) => void; - handleComplete(data: RedirectParamsV2): void; - handleReload: () => void; - handleAbortConfirm: () => void; - handleAbort: () => void; - handleButtonResize: (size: { - width: number; - height: number; - }) => void; - getRedirectParams: (url: string) => RedirectParamsV2; - show: () => void; - dismiss: () => void; - handleInit: () => void | null; - render(): JSX.Element; - renderButton(): {} | null | undefined; - renderBackdrop(): JSX.Element; - renderLoading(): JSX.Element; - renderError: () => JSX.Element; -} -export default FlutterwaveButton; -//# sourceMappingURL=FlutterwaveButton.d.ts.map \ No newline at end of file diff --git a/dist/v2/FlutterwaveButton.d.ts.map b/dist/v2/FlutterwaveButton.d.ts.map deleted file mode 100644 index 0439e85..0000000 --- a/dist/v2/FlutterwaveButton.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"FlutterwaveButton.d.ts","sourceRoot":"","sources":["../../src/v2/FlutterwaveButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAIL,QAAQ,EAQT,MAAM,cAAc,CAAC;AACtB,OAAO,OAAO,MAAM,sBAAsB,CAAC;AAC3C,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,EAAC,iBAAiB,EAAC,MAAM,uCAAuC,CAAC;AACxE,OAAO,EAAC,0BAA0B,EAAE,gBAAgB,EAAsB,MAAM,sBAAsB,CAAC;AACvG,OAAwB,EAAC,sBAAsB,EAAC,MAAM,mBAAmB,CAAC;AAgB1E,oBAAY,sBAAsB,GAAG,0BAA0B,GAAG;IAChE,UAAU,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC7C,OAAO,EAAE,IAAI,CAAC,sBAAsB,EAAE,cAAc,CAAC,CAAC;CACvD,CAAA;AAED,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,cAAM,iBAAkB,SAAQ,KAAK,CAAC,SAAS,CAC7C,sBAAsB,EACtB,sBAAsB,CACvB;IACC,MAAM,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA2Bd;IAEF,KAAK,EAAE,sBAAsB,CAW3B;IAEF,UAAU,EAAE,OAAO,GAAG,IAAI,CAAQ;IAElC,eAAe,CAAC,EAAE,eAAe,CAAC;IAElC,kBAAkB,CAAC,SAAS,EAAE,sBAAsB;IAMpD,oBAAoB;IAMpB,KAAK,aAWH;IAEF,oBAAoB,aAYnB;IAED,2BAA2B,kCASzB;IAEF,cAAc,CAAC,IAAI,EAAE,gBAAgB;IAoBrC,YAAY,aAKV;IAEF,kBAAkB,aAQhB;IAEF,WAAW,aAST;IAEF,kBAAkB;;;eAKhB;IAEF,iBAAiB,oCAgBf;IAEF,IAAI,aAUF;IAEF,OAAO,aAOL;IAEF,UAAU,oBAkER;IAEF,MAAM;IAsCN,YAAY;IAiDZ,cAAc;IAcd,aAAa;IAYb,WAAW,oBAkBT;CACH;AAuED,eAAe,iBAAiB,CAAC"} \ No newline at end of file diff --git a/dist/v2/FlutterwaveButton.js b/dist/v2/FlutterwaveButton.js deleted file mode 100644 index caaf7a0..0000000 --- a/dist/v2/FlutterwaveButton.js +++ /dev/null @@ -1,466 +0,0 @@ -var __extends = (this && this.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -var __assign = (this && this.__assign) || function () { - __assign = Object.assign || function(t) { - for (var s, i = 1, n = arguments.length; i < n; i++) { - s = arguments[i]; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) - t[p] = s[p]; - } - return t; - }; - return __assign.apply(this, arguments); -}; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (_) try { - 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; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -}; -import React from 'react'; -import { StyleSheet, Modal, View, Animated, TouchableWithoutFeedback, Text, Alert, Image, Platform, Dimensions, Easing, } from 'react-native'; -import WebView from 'react-native-webview'; -import PropTypes from 'prop-types'; -import { OptionsPropTypeBase } from '../FlutterwaveButton'; -import FlutterwaveInit from './FlutterwaveInit'; -import DefaultButton from '../DefaultButton'; -import { PAYMENT_OPTIONS_V2, colors, REDIRECT_URL } from '../configs'; -import { PaymentOptionsPropRule } from '../utils/CustomPropTypesRules'; -import FlutterwaveInitError from '../utils/FlutterwaveInitError'; -var loader = require('../assets/loader.gif'); -var pryContent = require('../assets/pry-button-content.png'); -var contentWidthPercentage = 0.6549707602; -var contentSizeDimension = 8.2962962963; -var contentMaxWidth = 187.3; -var contentMaxHeight = contentMaxWidth / contentSizeDimension; -var contentMinWidth = 187.3; -var contentMinHeight = contentMinWidth / contentSizeDimension; -var borderRadiusDimension = 24 / 896; -var windowHeight = Dimensions.get('window').height; -var FlutterwaveButton = /** @class */ (function (_super) { - __extends(FlutterwaveButton, _super); - function FlutterwaveButton() { - var _this = _super !== null && _super.apply(this, arguments) || this; - _this.state = { - isPending: false, - link: null, - resetLink: false, - showDialog: false, - animation: new Animated.Value(0), - txref: null, - buttonSize: { - width: 0, - height: 0 - } - }; - _this.webviewRef = null; - _this.reset = function () { - if (_this.abortController) { - _this.abortController.abort(); - } - // reset the necessaries - _this.setState(function (_a) { - var resetLink = _a.resetLink, link = _a.link; - return ({ - isPending: false, - link: resetLink ? null : link, - resetLink: false, - showDialog: false - }); - }); - }; - _this.handleOptionsChanged = function () { - var _a = _this.state, showDialog = _a.showDialog, link = _a.link; - if (!link) { - return; - } - if (!showDialog) { - return _this.setState({ - link: null, - txref: null - }); - } - _this.setState({ resetLink: true }); - }; - _this.handleNavigationStateChange = function (ev) { - // cregex to check if redirect has occured on completion/cancel - var rx = /\/flutterwave\.com\/rn-redirect/; - // Don't end payment if not redirected back - if (!rx.test(ev.url)) { - return; - } - // fire handle complete - _this.handleComplete(_this.getRedirectParams(ev.url)); - }; - _this.handleReload = function () { - // fire if webview is set - if (_this.webviewRef) { - _this.webviewRef.reload(); - } - }; - _this.handleAbortConfirm = function () { - var onAbort = _this.props.onAbort; - // abort action - if (onAbort) { - onAbort(); - } - // remove txref and dismiss - _this.dismiss(); - }; - _this.handleAbort = function () { - Alert.alert('', 'Are you sure you want to cancel this payment?', [ - { text: 'No' }, - { - text: 'Yes, Cancel', - style: 'destructive', - onPress: _this.handleAbortConfirm - }, - ]); - }; - _this.handleButtonResize = function (size) { - var buttonSize = _this.state.buttonSize; - if (JSON.stringify(buttonSize) !== JSON.stringify(size)) { - _this.setState({ buttonSize: size }); - } - }; - _this.getRedirectParams = function (url) { - // initialize result container - var res = {}; - // if url has params - if (url.split('?').length > 1) { - // get query params in an array - var params = url.split('?')[1].split('&'); - // add url params to result - for (var i = 0; i < params.length; i++) { - var param = params[i].split('='); - var val = decodeURIComponent(param[1]).trim(); - res[param[0]] = String(val); - } - } - // return result - return res; - }; - _this.show = function () { - var animation = _this.state.animation; - _this.setState({ showDialog: true }, function () { - Animated.timing(animation, { - toValue: 1, - duration: 700, - easing: Easing["in"](Easing.elastic(0.72)), - useNativeDriver: false - }).start(); - }); - }; - _this.dismiss = function () { - var animation = _this.state.animation; - Animated.timing(animation, { - toValue: 0, - duration: 400, - useNativeDriver: false - }).start(_this.reset); - }; - _this.handleInit = function () { - var _a = _this.props, options = _a.options, onWillInitialize = _a.onWillInitialize, onInitializeError = _a.onInitializeError, onDidInitialize = _a.onDidInitialize; - var _b = _this.state, isPending = _b.isPending, txref = _b.txref, link = _b.link; - // just show the dialod if the link is already set - if (link) { - return _this.show(); - } - // throw error if transaction reference has not changed - if (txref === options.txref) { - return onInitializeError ? onInitializeError(new FlutterwaveInitError({ - message: 'Please generate a new transaction reference.', - code: 'SAME_TXREF' - })) : null; - } - // stop if currently in pending mode - if (isPending) { - return; - } - // initialize abort controller if not set - _this.abortController = new AbortController; - // fire will initialize handler if available - if (onWillInitialize) { - onWillInitialize(); - } - // set pending state to true - _this.setState({ - isPending: true, - link: null, - txref: options.txref - }, function () { return __awaiter(_this, void 0, void 0, function () { - var paymentLink, error_1; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - _a.trys.push([0, 2, , 3]); - return [4 /*yield*/, FlutterwaveInit(__assign(__assign({}, options), { redirect_url: REDIRECT_URL }), this.abortController)]; - case 1: - paymentLink = _a.sent(); - // set payment link - this.setState({ - link: paymentLink, - isPending: false - }, this.show); - // fire did initialize handler if available - if (onDidInitialize) { - onDidInitialize(); - } - return [3 /*break*/, 3]; - case 2: - error_1 = _a.sent(); - // stop if request was canceled - if (error_1 && /aborterror/i.test(error_1.code)) { - return [2 /*return*/]; - } - // call onInitializeError handler if an error occured - if (onInitializeError) { - onInitializeError(error_1); - } - return [2 /*return*/, this.dismiss()]; - case 3: return [2 /*return*/]; - } - }); - }); }); - }; - _this.renderError = function () { - var link = _this.state.link; - return ( - {link ? (<> - - The page failed to load, please try again. - - - - Try Again - - - ) : null} - ); - }; - return _this; - } - FlutterwaveButton.prototype.componentDidUpdate = function (prevProps) { - if (JSON.stringify(prevProps.options) !== JSON.stringify(this.props.options)) { - this.handleOptionsChanged(); - } - }; - FlutterwaveButton.prototype.componentWillUnmount = function () { - if (this.abortController) { - this.abortController.abort(); - } - }; - FlutterwaveButton.prototype.handleComplete = function (data) { - var _this = this; - var onComplete = this.props.onComplete; - // reset payment link - this.setState(function (_a) { - var resetLink = _a.resetLink, txref = _a.txref; - return ({ - txref: data.flwref && !data.cancelled ? null : txref, - resetLink: data.flwref && !data.cancelled ? true : resetLink - }); - }, function () { - // reset - _this.dismiss(); - // fire onComplete handler - onComplete({ - flwref: data.flwref, - txref: data.txref, - cancelled: data.cancelled - }); - }); - }; - FlutterwaveButton.prototype.render = function () { - var _this = this; - var _a = this.state, link = _a.link, animation = _a.animation, showDialog = _a.showDialog; - var marginTop = animation.interpolate({ - inputRange: [0, 1], - outputRange: [windowHeight, Platform.OS === 'ios' ? 46 : 14] - }); - var opacity = animation.interpolate({ - inputRange: [0, 0.3, 1], - outputRange: [0, 1, 1] - }); - // render UI - return (<> - {this.renderButton()} - - {this.renderBackdrop()} - - - - - ); - }; - FlutterwaveButton.prototype.renderButton = function () { - var _a = this.props, customButton = _a.customButton, style = _a.style, alignLeft = _a.alignLeft, children = _a.children; - var _b = this.state, isPending = _b.isPending, link = _b.link, showDialog = _b.showDialog, buttonSize = _b.buttonSize; - var contentWidth = buttonSize.width * contentWidthPercentage; - var contentHeight = contentWidth / contentSizeDimension; - var contentSizeStyle = { - width: contentWidth > contentMaxWidth - ? contentMaxWidth - : contentWidth < contentMinWidth - ? contentMinWidth - : contentWidth, - height: contentHeight > contentMaxHeight - ? contentMaxHeight - : contentHeight < contentMinHeight - ? contentMinHeight - : contentHeight - }; - // render custom button - if (customButton) { - return customButton({ - isInitializing: isPending && !link ? true : false, - disabled: isPending || showDialog ? true : false, - onPress: this.handleInit - }); - } - // render primary button - return ( - {children ? children : ()} - ); - }; - FlutterwaveButton.prototype.renderBackdrop = function () { - var animation = this.state.animation; - // Interpolation backdrop animation - var backgroundColor = animation.interpolate({ - inputRange: [0, 0.3, 1], - outputRange: [colors.transparent, colors.transparent, 'rgba(0,0,0,0.5)'] - }); - return ( - - ); - }; - FlutterwaveButton.prototype.renderLoading = function () { - return ( - - ); - }; - FlutterwaveButton.propTypes = { - alignLeft: PropTypes.bool, - onAbort: PropTypes.func, - onComplete: PropTypes.func.isRequired, - onWillInitialize: PropTypes.func, - onDidInitialize: PropTypes.func, - onInitializeError: PropTypes.func, - options: PropTypes.shape(__assign(__assign({}, OptionsPropTypeBase), { payment_options: PaymentOptionsPropRule(PAYMENT_OPTIONS_V2), txref: PropTypes.string.isRequired, PBFPubKey: PropTypes.string.isRequired, customer_firstname: PropTypes.string, customer_lastname: PropTypes.string, customer_email: PropTypes.string.isRequired, customer_phone: PropTypes.string, country: PropTypes.string, pay_button_text: PropTypes.string, custom_title: PropTypes.string, custom_description: PropTypes.string, custom_logo: PropTypes.string, meta: PropTypes.arrayOf(PropTypes.shape({ - metaname: PropTypes.string, - metavalue: PropTypes.string - })) })).isRequired, - customButton: PropTypes.func - }; - return FlutterwaveButton; -}(React.Component)); -var styles = StyleSheet.create({ - promtActions: { - flexDirection: 'row', - alignItems: 'center' - }, - promptActionText: { - textAlign: 'center', - color: colors.primary, - fontSize: 16, - paddingHorizontal: 16, - paddingVertical: 16 - }, - promptQuestion: { - color: colors.secondary, - textAlign: 'center', - marginBottom: 32, - fontSize: 18 - }, - prompt: { - position: 'absolute', - left: 0, - right: 0, - bottom: 0, - top: 0, - backgroundColor: '#ffffff', - justifyContent: 'center', - alignItems: 'center', - paddingHorizontal: 56 - }, - backdrop: { - position: 'absolute', - left: 0, - right: 0, - bottom: 0, - top: 0 - }, - loadingImage: { - width: 64, - height: 64, - resizeMode: 'contain' - }, - loading: { - position: 'absolute', - top: 0, - right: 0, - bottom: 0, - left: 0, - backgroundColor: 'rgba(255, 255, 255, 0.3)', - justifyContent: 'center', - alignItems: 'center' - }, - webviewContainer: { - top: 50, - flex: 1, - backgroundColor: '#efefef', - paddingBottom: 50, - overflow: 'hidden', - borderTopLeftRadius: windowHeight * borderRadiusDimension, - borderTopRightRadius: windowHeight * borderRadiusDimension - }, - webview: { - flex: 1, - backgroundColor: 'rgba(0,0,0,0)' - }, - buttonContent: { - resizeMode: 'contain' - } -}); -export default FlutterwaveButton; diff --git a/dist/v2/FlutterwaveInit.d.ts b/dist/v2/FlutterwaveInit.d.ts deleted file mode 100644 index 7012be5..0000000 --- a/dist/v2/FlutterwaveInit.d.ts +++ /dev/null @@ -1,35 +0,0 @@ -/// -import { FlutterwaveInitOptionsBase } from '../FlutterwaveInit'; -interface FlutterwavePaymentMetaV2 { - metaname: string; - metavalue: string; -} -export declare type FlutterwaveInitOptions = FlutterwaveInitOptionsBase & { - txref: string; - PBFPubKey: string; - customer_firstname?: string; - customer_lastname?: string; - customer_phone?: string; - customer_email: string; - country?: string; - pay_button_text?: string; - custom_title?: string; - custom_description?: string; - custom_logo?: string; - meta?: Array; -}; -/** - * This function is responsible for making the request to - * initialize a Flutterwave payment. - * @param options FlutterwaveInitOptions - * @return Promise<{ - * error: { - * code: string; - * message: string; - * } | null; - * link?: string | null; - * }> - */ -export default function FlutterwaveInit(options: FlutterwaveInitOptions, abortController?: AbortController): Promise; -export {}; -//# sourceMappingURL=FlutterwaveInit.d.ts.map \ No newline at end of file diff --git a/dist/v2/FlutterwaveInit.d.ts.map b/dist/v2/FlutterwaveInit.d.ts.map deleted file mode 100644 index f885683..0000000 --- a/dist/v2/FlutterwaveInit.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"FlutterwaveInit.d.ts","sourceRoot":"","sources":["../../src/v2/FlutterwaveInit.ts"],"names":[],"mappings":";AACA,OAAO,EAAC,0BAA0B,EAAC,MAAM,oBAAoB,CAAC;AAG9D,UAAU,wBAAwB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,oBAAY,sBAAsB,GAAG,0BAA0B,GAAG;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,KAAK,CAAC,wBAAwB,CAAC,CAAC;CACxC,CAAA;AAmBD;;;;;;;;;;;GAWG;AACH,wBAA8B,eAAe,CAC3C,OAAO,EAAE,sBAAsB,EAC/B,eAAe,CAAC,EAAE,eAAe,GAChC,OAAO,CAAC,MAAM,CAAC,CA6CjB"} \ No newline at end of file diff --git a/dist/v2/FlutterwaveInit.js b/dist/v2/FlutterwaveInit.js deleted file mode 100644 index 5bd2507..0000000 --- a/dist/v2/FlutterwaveInit.js +++ /dev/null @@ -1,115 +0,0 @@ -var __assign = (this && this.__assign) || function () { - __assign = Object.assign || function(t) { - for (var s, i = 1, n = arguments.length; i < n; i++) { - s = arguments[i]; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) - t[p] = s[p]; - } - return t; - }; - return __assign.apply(this, arguments); -}; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (_) try { - 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; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -}; -import { STANDARD_URL_V2 } from '../configs'; -import FlutterwaveInitError from '../utils/FlutterwaveInitError'; -/** - * This function is responsible for making the request to - * initialize a Flutterwave payment. - * @param options FlutterwaveInitOptions - * @return Promise<{ - * error: { - * code: string; - * message: string; - * } | null; - * link?: string | null; - * }> - */ -export default function FlutterwaveInit(options, abortController) { - return __awaiter(this, void 0, void 0, function () { - var body, headers, fetchOptions, response, responseJSON, e_1, error; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - _a.trys.push([0, 3, , 4]); - body = __assign({}, options); - headers = new Headers; - headers.append('Content-Type', 'application/json'); - fetchOptions = { - method: 'POST', - body: JSON.stringify(body), - headers: headers - }; - // add abort controller if defined - if (abortController) { - fetchOptions.signal = abortController.signal; - } - ; - return [4 /*yield*/, fetch(STANDARD_URL_V2, fetchOptions)]; - case 1: - response = _a.sent(); - return [4 /*yield*/, response.json()]; - case 2: - responseJSON = _a.sent(); - // check if data is missing from response - if (!responseJSON.data) { - throw new FlutterwaveInitError({ - code: 'STANDARD_INIT_ERROR', - message: responseJSON.message || 'An unknown error occured!' - }); - } - // check if the link is missing in data - if (!responseJSON.data.link) { - throw new FlutterwaveInitError({ - code: responseJSON.data.code || 'MALFORMED_RESPONSE', - message: responseJSON.data.message || 'An unknown error occured!' - }); - } - // resolve with the payment link - return [2 /*return*/, Promise.resolve(responseJSON.data.link)]; - case 3: - e_1 = _a.sent(); - error = e_1 instanceof FlutterwaveInitError - ? e_1 - : new FlutterwaveInitError({ message: e_1.message, code: e_1.name.toUpperCase() }); - // resolve with error - return [2 /*return*/, Promise.reject(error)]; - case 4: return [2 /*return*/]; - } - }); - }); -} diff --git a/dist/v3/FlutterwaveButton.d.ts b/dist/v3/FlutterwaveButton.d.ts deleted file mode 100644 index 8addad6..0000000 --- a/dist/v3/FlutterwaveButton.d.ts +++ /dev/null @@ -1,89 +0,0 @@ -import React from 'react'; -import { Animated } from 'react-native'; -import WebView from 'react-native-webview'; -import PropTypes from 'prop-types'; -import { WebViewNavigation } from 'react-native-webview/lib/WebViewTypes'; -import { FlutterwaveButtonPropsBase, RedirectParams } from '../FlutterwaveButton'; -import { FlutterwaveInitOptions } from './FlutterwaveInit'; -export declare type FlutterwaveButtonProps = FlutterwaveButtonPropsBase & { - onComplete: (data: RedirectParams) => void; - options: Omit; -}; -interface FlutterwaveButtonState { - link: string | null; - isPending: boolean; - showDialog: boolean; - animation: Animated.Value; - tx_ref: string | null; - resetLink: boolean; - buttonSize: { - width: number; - height: number; - }; -} -declare class FlutterwaveButton extends React.Component { - static propTypes: { - alignLeft: PropTypes.Requireable; - onAbort: PropTypes.Requireable<(...args: any[]) => any>; - onComplete: PropTypes.Validator<(...args: any[]) => any>; - onWillInitialize: PropTypes.Requireable<(...args: any[]) => any>; - onDidInitialize: PropTypes.Requireable<(...args: any[]) => any>; - onInitializeError: PropTypes.Requireable<(...args: any[]) => any>; - options: PropTypes.Validator; - tx_ref: PropTypes.Validator; - payment_options: (props: { - [k: string]: any; - }, propName: string) => Error | null; - customer: PropTypes.Validator; - phonenumber: PropTypes.Requireable; - email: PropTypes.Validator; - }>>; - meta: PropTypes.Requireable<(object | null | undefined)[]>; - customizations: PropTypes.Requireable; - logo: PropTypes.Requireable; - description: PropTypes.Requireable; - }>>; - amount: PropTypes.Validator; - currency: PropTypes.Requireable; - payment_plan: PropTypes.Requireable; - subaccounts: PropTypes.Requireable<(PropTypes.InferProps<{ - id: PropTypes.Validator; - transaction_split_ratio: PropTypes.Requireable; - transaction_charge_type: PropTypes.Requireable; - transaction_charge: PropTypes.Requireable; - }> | null | undefined)[]>; - integrity_hash: PropTypes.Requireable; - }>>; - customButton: PropTypes.Requireable<(...args: any[]) => any>; - }; - state: FlutterwaveButtonState; - webviewRef: WebView | null; - abortController?: AbortController; - componentDidUpdate(prevProps: FlutterwaveButtonProps): void; - componentWillUnmount(): void; - reset: () => void; - handleOptionsChanged: () => void; - handleNavigationStateChange: (ev: WebViewNavigation) => void; - handleComplete(data: RedirectParams): void; - handleReload: () => void; - handleAbortConfirm: () => void; - handleAbort: () => void; - handleButtonResize: (size: { - width: number; - height: number; - }) => void; - getRedirectParams: (url: string) => RedirectParams; - show: () => void; - dismiss: () => void; - handleInit: () => void | null; - render(): JSX.Element; - renderButton(): {} | null | undefined; - renderBackdrop(): JSX.Element; - renderLoading(): JSX.Element; - renderError: () => JSX.Element; -} -export default FlutterwaveButton; -//# sourceMappingURL=FlutterwaveButton.d.ts.map \ No newline at end of file diff --git a/dist/v3/FlutterwaveButton.d.ts.map b/dist/v3/FlutterwaveButton.d.ts.map deleted file mode 100644 index e4c471b..0000000 --- a/dist/v3/FlutterwaveButton.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"FlutterwaveButton.d.ts","sourceRoot":"","sources":["../../src/v3/FlutterwaveButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAIL,QAAQ,EAQT,MAAM,cAAc,CAAC;AACtB,OAAO,OAAO,MAAM,sBAAsB,CAAC;AAC3C,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,EAAC,iBAAiB,EAAC,MAAM,uCAAuC,CAAC;AACxE,OAAO,EAAC,0BAA0B,EAAE,cAAc,EAAsB,MAAM,sBAAsB,CAAC;AACrG,OAAwB,EAAC,sBAAsB,EAAC,MAAM,mBAAmB,CAAC;AAgB1E,oBAAY,sBAAsB,GAAG,0BAA0B,GAAG;IAChE,UAAU,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAC3C,OAAO,EAAE,IAAI,CAAC,sBAAsB,EAAE,cAAc,CAAC,CAAC;CACvD,CAAA;AAED,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC;IAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,cAAM,iBAAkB,SAAQ,KAAK,CAAC,SAAS,CAC7C,sBAAsB,EACtB,sBAAsB,CACvB;IACC,MAAM,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAyBd;IAEF,KAAK,EAAE,sBAAsB,CAW3B;IAEF,UAAU,EAAE,OAAO,GAAG,IAAI,CAAQ;IAElC,eAAe,CAAC,EAAE,eAAe,CAAC;IAElC,kBAAkB,CAAC,SAAS,EAAE,sBAAsB;IAMpD,oBAAoB;IAMpB,KAAK,aAWH;IAEF,oBAAoB,aAYnB;IAED,2BAA2B,kCASzB;IAEF,cAAc,CAAC,IAAI,EAAE,cAAc;IAgBnC,YAAY,aAKV;IAEF,kBAAkB,aAQhB;IAEF,WAAW,aAST;IAEF,kBAAkB;;;eAKhB;IAEF,iBAAiB,kCAgBf;IAEF,IAAI,aAUF;IAEF,OAAO,aAOL;IAEF,UAAU,oBAqER;IAEF,MAAM;IAsCN,YAAY;IA+CZ,cAAc;IAcd,aAAa;IAYb,WAAW,oBAkBT;CACH;AAuED,eAAe,iBAAiB,CAAC"} \ No newline at end of file diff --git a/dist/v3/FlutterwaveButton.js b/dist/v3/FlutterwaveButton.js deleted file mode 100644 index b8dd5b1..0000000 --- a/dist/v3/FlutterwaveButton.js +++ /dev/null @@ -1,469 +0,0 @@ -var __extends = (this && this.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -var __assign = (this && this.__assign) || function () { - __assign = Object.assign || function(t) { - for (var s, i = 1, n = arguments.length; i < n; i++) { - s = arguments[i]; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) - t[p] = s[p]; - } - return t; - }; - return __assign.apply(this, arguments); -}; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (_) try { - 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; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -}; -import React from 'react'; -import { StyleSheet, Modal, View, Animated, TouchableWithoutFeedback, Text, Alert, Image, Platform, Dimensions, Easing, } from 'react-native'; -import WebView from 'react-native-webview'; -import PropTypes from 'prop-types'; -import { OptionsPropTypeBase } from '../FlutterwaveButton'; -import FlutterwaveInit from './FlutterwaveInit'; -import DefaultButton from '../DefaultButton'; -import { colors, REDIRECT_URL, PAYMENT_OPTIONS } from '../configs'; -import { PaymentOptionsPropRule } from '../utils/CustomPropTypesRules'; -import FlutterwaveInitError from '../utils/FlutterwaveInitError'; -var loader = require('../assets/loader.gif'); -var pryContent = require('../assets/pry-button-content.png'); -var contentWidthPercentage = 0.6549707602; -var contentSizeDimension = 8.2962962963; -var contentMaxWidth = 187.3; -var contentMaxHeight = contentMaxWidth / contentSizeDimension; -var contentMinWidth = 187.3; -var contentMinHeight = contentMinWidth / contentSizeDimension; -var borderRadiusDimension = 24 / 896; -var windowHeight = Dimensions.get('window').height; -var FlutterwaveButton = /** @class */ (function (_super) { - __extends(FlutterwaveButton, _super); - function FlutterwaveButton() { - var _this = _super !== null && _super.apply(this, arguments) || this; - _this.state = { - isPending: false, - link: null, - resetLink: false, - showDialog: false, - animation: new Animated.Value(0), - tx_ref: null, - buttonSize: { - width: 0, - height: 0 - } - }; - _this.webviewRef = null; - _this.reset = function () { - if (_this.abortController) { - _this.abortController.abort(); - } - // reset the necessaries - _this.setState(function (_a) { - var resetLink = _a.resetLink, link = _a.link; - return ({ - isPending: false, - link: resetLink ? null : link, - resetLink: false, - showDialog: false - }); - }); - }; - _this.handleOptionsChanged = function () { - var _a = _this.state, showDialog = _a.showDialog, link = _a.link; - if (!link) { - return; - } - if (!showDialog) { - return _this.setState({ - link: null, - tx_ref: null - }); - } - _this.setState({ resetLink: true }); - }; - _this.handleNavigationStateChange = function (ev) { - // cregex to check if redirect has occured on completion/cancel - var rx = /\/flutterwave\.com\/rn-redirect/; - // Don't end payment if not redirected back - if (!rx.test(ev.url)) { - return; - } - // fire handle complete - _this.handleComplete(_this.getRedirectParams(ev.url)); - }; - _this.handleReload = function () { - // fire if webview is set - if (_this.webviewRef) { - _this.webviewRef.reload(); - } - }; - _this.handleAbortConfirm = function () { - var onAbort = _this.props.onAbort; - // abort action - if (onAbort) { - onAbort(); - } - // remove tx_ref and dismiss - _this.dismiss(); - }; - _this.handleAbort = function () { - Alert.alert('', 'Are you sure you want to cancel this payment?', [ - { text: 'No' }, - { - text: 'Yes, Cancel', - style: 'destructive', - onPress: _this.handleAbortConfirm - }, - ]); - }; - _this.handleButtonResize = function (size) { - var buttonSize = _this.state.buttonSize; - if (JSON.stringify(buttonSize) !== JSON.stringify(size)) { - _this.setState({ buttonSize: size }); - } - }; - _this.getRedirectParams = function (url) { - // initialize result container - var res = {}; - // if url has params - if (url.split('?').length > 1) { - // get query params in an array - var params = url.split('?')[1].split('&'); - // add url params to result - for (var i = 0; i < params.length; i++) { - var param = params[i].split('='); - var val = decodeURIComponent(param[1]).trim(); - res[param[0]] = String(val); - } - } - // return result - return res; - }; - _this.show = function () { - var animation = _this.state.animation; - _this.setState({ showDialog: true }, function () { - Animated.timing(animation, { - toValue: 1, - duration: 700, - easing: Easing["in"](Easing.elastic(0.72)), - useNativeDriver: false - }).start(); - }); - }; - _this.dismiss = function () { - var animation = _this.state.animation; - Animated.timing(animation, { - toValue: 0, - duration: 400, - useNativeDriver: false - }).start(_this.reset); - }; - _this.handleInit = function () { - var _a = _this.props, options = _a.options, onWillInitialize = _a.onWillInitialize, onInitializeError = _a.onInitializeError, onDidInitialize = _a.onDidInitialize; - var _b = _this.state, isPending = _b.isPending, tx_ref = _b.tx_ref, link = _b.link; - // just show the dialod if the link is already set - if (link) { - return _this.show(); - } - // throw error if transaction reference has not changed - if (tx_ref === options.tx_ref) { - return onInitializeError ? onInitializeError(new FlutterwaveInitError({ - message: 'Please generate a new transaction reference.', - code: 'SAME_TXREF' - })) : null; - } - // stop if currently in pending mode - if (isPending) { - return; - } - // initialize abort controller if not set - _this.abortController = new AbortController; - // fire will initialize handler if available - if (onWillInitialize) { - onWillInitialize(); - } - // @ts-ignore - // delete redirect url if set - delete options.redirect_url; - // set pending state to true - _this.setState({ - isPending: true, - link: null, - tx_ref: options.tx_ref - }, function () { return __awaiter(_this, void 0, void 0, function () { - var link_1, error_1; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - _a.trys.push([0, 2, , 3]); - return [4 /*yield*/, FlutterwaveInit(__assign(__assign({}, options), { redirect_url: REDIRECT_URL }), this.abortController)]; - case 1: - link_1 = _a.sent(); - // resent pending mode - this.setState({ link: link_1, isPending: false }, this.show); - // fire did initialize handler if available - if (onDidInitialize) { - onDidInitialize(); - } - return [3 /*break*/, 3]; - case 2: - error_1 = _a.sent(); - // stop if request was canceled - if (/aborterror/i.test(error_1.code)) { - return [2 /*return*/]; - } - if (onInitializeError) { - onInitializeError(error_1); - } - return [2 /*return*/, this.setState({ - resetLink: true, - tx_ref: null - }, this.dismiss)]; - case 3: return [2 /*return*/]; - } - }); - }); }); - }; - _this.renderError = function () { - var link = _this.state.link; - return ( - {link ? (<> - - The page failed to load, please try again. - - - - Try Again - - - ) : null} - ); - }; - return _this; - } - FlutterwaveButton.prototype.componentDidUpdate = function (prevProps) { - if (JSON.stringify(prevProps.options) !== JSON.stringify(this.props.options)) { - this.handleOptionsChanged(); - } - }; - FlutterwaveButton.prototype.componentWillUnmount = function () { - if (this.abortController) { - this.abortController.abort(); - } - }; - FlutterwaveButton.prototype.handleComplete = function (data) { - var _this = this; - var onComplete = this.props.onComplete; - // reset payment link - this.setState(function (_a) { - var resetLink = _a.resetLink, tx_ref = _a.tx_ref; - return ({ - tx_ref: data.status === 'successful' ? null : tx_ref, - resetLink: data.status === 'successful' ? true : resetLink - }); - }, function () { - // reset - _this.dismiss(); - // fire onComplete handler - onComplete(data); - }); - }; - FlutterwaveButton.prototype.render = function () { - var _this = this; - var _a = this.state, link = _a.link, animation = _a.animation, showDialog = _a.showDialog; - var marginTop = animation.interpolate({ - inputRange: [0, 1], - outputRange: [windowHeight, Platform.OS === 'ios' ? 46 : 14] - }); - var opacity = animation.interpolate({ - inputRange: [0, 0.3, 1], - outputRange: [0, 1, 1] - }); - // render UI - return (<> - {this.renderButton()} - - {this.renderBackdrop()} - - - - - ); - }; - FlutterwaveButton.prototype.renderButton = function () { - var _a = this.props, customButton = _a.customButton, style = _a.style, alignLeft = _a.alignLeft; - var _b = this.state, isPending = _b.isPending, link = _b.link, showDialog = _b.showDialog, buttonSize = _b.buttonSize; - var contentWidth = buttonSize.width * contentWidthPercentage; - var contentHeight = contentWidth / contentSizeDimension; - var contentSizeStyle = { - width: contentWidth > contentMaxWidth - ? contentMaxWidth - : contentWidth < contentMinWidth - ? contentMinWidth - : contentWidth, - height: contentHeight > contentMaxHeight - ? contentMaxHeight - : contentHeight < contentMinHeight - ? contentMinHeight - : contentHeight - }; - // render custom button - if (customButton) { - return customButton({ - isInitializing: isPending && !link ? true : false, - disabled: isPending || showDialog ? true : false, - onPress: this.handleInit - }); - } - // render primary button - return ( - - ); - }; - FlutterwaveButton.prototype.renderBackdrop = function () { - var animation = this.state.animation; - // Interpolation backdrop animation - var backgroundColor = animation.interpolate({ - inputRange: [0, 0.3, 1], - outputRange: [colors.transparent, colors.transparent, 'rgba(0,0,0,0.5)'] - }); - return ( - - ); - }; - FlutterwaveButton.prototype.renderLoading = function () { - return ( - - ); - }; - FlutterwaveButton.propTypes = { - alignLeft: PropTypes.bool, - onAbort: PropTypes.func, - onComplete: PropTypes.func.isRequired, - onWillInitialize: PropTypes.func, - onDidInitialize: PropTypes.func, - onInitializeError: PropTypes.func, - options: PropTypes.shape(__assign(__assign({}, OptionsPropTypeBase), { authorization: PropTypes.string.isRequired, tx_ref: PropTypes.string.isRequired, payment_options: PaymentOptionsPropRule(PAYMENT_OPTIONS), customer: PropTypes.shape({ - name: PropTypes.string, - phonenumber: PropTypes.string, - email: PropTypes.string.isRequired - }).isRequired, meta: PropTypes.arrayOf(PropTypes.object), customizations: PropTypes.shape({ - title: PropTypes.string, - logo: PropTypes.string, - description: PropTypes.string - }) })).isRequired, - customButton: PropTypes.func - }; - return FlutterwaveButton; -}(React.Component)); -var styles = StyleSheet.create({ - promtActions: { - flexDirection: 'row', - alignItems: 'center' - }, - promptActionText: { - textAlign: 'center', - color: colors.primary, - fontSize: 16, - paddingHorizontal: 16, - paddingVertical: 16 - }, - promptQuestion: { - color: colors.secondary, - textAlign: 'center', - marginBottom: 32, - fontSize: 18 - }, - prompt: { - position: 'absolute', - left: 0, - right: 0, - bottom: 0, - top: 0, - backgroundColor: '#ffffff', - justifyContent: 'center', - alignItems: 'center', - paddingHorizontal: 56 - }, - backdrop: { - position: 'absolute', - left: 0, - right: 0, - bottom: 0, - top: 0 - }, - loadingImage: { - width: 64, - height: 64, - resizeMode: 'contain' - }, - loading: { - position: 'absolute', - top: 0, - right: 0, - bottom: 0, - left: 0, - backgroundColor: 'rgba(255, 255, 255, 0.3)', - justifyContent: 'center', - alignItems: 'center' - }, - webviewContainer: { - top: 50, - flex: 1, - backgroundColor: '#efefef', - paddingBottom: 50, - overflow: 'hidden', - borderTopLeftRadius: windowHeight * borderRadiusDimension, - borderTopRightRadius: windowHeight * borderRadiusDimension - }, - webview: { - flex: 1, - backgroundColor: 'rgba(0,0,0,0)' - }, - buttonContent: { - resizeMode: 'contain' - } -}); -export default FlutterwaveButton; diff --git a/dist/v3/FlutterwaveInit.d.ts b/dist/v3/FlutterwaveInit.d.ts deleted file mode 100644 index 80677e3..0000000 --- a/dist/v3/FlutterwaveInit.d.ts +++ /dev/null @@ -1,46 +0,0 @@ -/// -import { FlutterwaveInitOptionsBase } from '../FlutterwaveInit'; -interface FlutterwavePaymentMeta { - [k: string]: any; -} -export interface FlutterwaveInitCustomer { - email: string; - phonenumber?: string; - name?: string; -} -export interface FlutterwaveInitCustomizations { - title?: string; - logo?: string; - description?: string; -} -export declare type FlutterwaveInitOptions = FlutterwaveInitOptionsBase & { - authorization: string; - tx_ref: string; - customer: FlutterwaveInitCustomer; - meta?: Array; - customizations?: FlutterwaveInitCustomizations; -}; -export interface FieldError { - field: string; - message: string; -} -export interface ResponseData { - status?: 'success' | 'error'; - message: string; - error_id?: string; - errors?: Array; - code?: string; - data?: { - link: string; - }; -} -/** - * This function is responsible for making the request to - * initialize a Flutterwave payment. - * @param options FlutterwaveInitOptions - * @param abortController AbortController - * @return Promise - */ -export default function FlutterwaveInit(options: FlutterwaveInitOptions, abortController?: AbortController): Promise; -export {}; -//# sourceMappingURL=FlutterwaveInit.d.ts.map \ No newline at end of file diff --git a/dist/v3/FlutterwaveInit.d.ts.map b/dist/v3/FlutterwaveInit.d.ts.map deleted file mode 100644 index 583599a..0000000 --- a/dist/v3/FlutterwaveInit.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"FlutterwaveInit.d.ts","sourceRoot":"","sources":["../../src/v3/FlutterwaveInit.ts"],"names":[],"mappings":";AAGA,OAAO,EAAC,0BAA0B,EAAC,MAAM,oBAAoB,CAAC;AAE9D,UAAU,sBAAsB;IAC9B,CAAC,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,6BAA6B;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,oBAAY,sBAAsB,GAAG,0BAA0B,GAAG;IAChE,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,uBAAuB,CAAC;IAClC,IAAI,CAAC,EAAE,KAAK,CAAC,sBAAsB,CAAC,CAAC;IACrC,cAAc,CAAC,EAAE,6BAA6B,CAAC;CAChD,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AASD;;;;;;GAMG;AACH,wBAA8B,eAAe,CAC3C,OAAO,EAAE,sBAAsB,EAC/B,eAAe,CAAC,EAAE,eAAe,GAChC,OAAO,CAAC,MAAM,CAAC,CAgCjB"} \ No newline at end of file diff --git a/dist/v3/FlutterwaveInit.js b/dist/v3/FlutterwaveInit.js deleted file mode 100644 index 17c859b..0000000 --- a/dist/v3/FlutterwaveInit.js +++ /dev/null @@ -1,101 +0,0 @@ -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (_) try { - 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; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -}; -var __rest = (this && this.__rest) || function (s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) - t[p[i]] = s[p[i]]; - } - return t; -}; -import { STANDARD_URL } from '../configs'; -import ResponseParser from '../utils/ResponseParser'; -import FlutterwaveInitError from '../utils/FlutterwaveInitError'; -/** - * This function is responsible for making the request to - * initialize a Flutterwave payment. - * @param options FlutterwaveInitOptions - * @param abortController AbortController - * @return Promise - */ -export default function FlutterwaveInit(options, abortController) { - return __awaiter(this, void 0, void 0, function () { - var authorization, body, headers, fetchOptions, response, responseData, _a, _b, e_1, error; - return __generator(this, function (_c) { - switch (_c.label) { - case 0: - _c.trys.push([0, 4, , 5]); - authorization = options.authorization, body = __rest(options, ["authorization"]); - headers = new Headers; - headers.append('Content-Type', 'application/json'); - headers.append('Authorization', "Bearer " + authorization); - fetchOptions = { - method: 'POST', - body: JSON.stringify(body), - headers: headers - }; - // add abortController if defined - if (abortController) { - fetchOptions.signal = abortController.signal; - } - ; - return [4 /*yield*/, fetch(STANDARD_URL, fetchOptions)]; - case 1: - response = _c.sent(); - return [4 /*yield*/, response.json()]; - case 2: - responseData = _c.sent(); - _b = (_a = Promise).resolve; - return [4 /*yield*/, ResponseParser(responseData)]; - case 3: - // resolve with the payment link - return [2 /*return*/, _b.apply(_a, [_c.sent()])]; - case 4: - e_1 = _c.sent(); - error = e_1 instanceof FlutterwaveInitError - ? e_1 - : new FlutterwaveInitError({ message: e_1.message, code: e_1.name.toUpperCase() }); - // resolve with error - return [2 /*return*/, Promise.reject(error)]; - case 5: return [2 /*return*/]; - } - }); - }); -} From e47e33edf5c4c27f4a4883c66544c80b8ad32059 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 10:04:49 +0100 Subject: [PATCH 118/129] feat(dist): generate builds for new implementation --- dist/FlutterwaveInit.d.ts | 49 ++++- dist/FlutterwaveInit.d.ts.map | 2 +- dist/FlutterwaveInit.js | 103 ++++++++++- dist/FlutterwaveInitV2.d.ts | 48 +++++ dist/FlutterwaveInitV2.d.ts.map | 1 + dist/FlutterwaveInitV2.js | 115 ++++++++++++ dist/FlwButton.d.ts | 11 ++ dist/FlwButton.d.ts.map | 1 + dist/FlwButton.js | 59 ++++++ dist/FlwCheckout.d.ts | 15 ++ dist/FlwCheckout.d.ts.map | 1 + dist/FlwCheckout.js | 214 ++++++++++++++++++++++ dist/PayWithFlutterwave.d.ts | 15 ++ dist/PayWithFlutterwave.d.ts.map | 1 + dist/PayWithFlutterwave.js | 48 +++++ dist/PayWithFlutterwaveV2.d.ts | 15 ++ dist/PayWithFlutterwaveV2.d.ts.map | 1 + dist/PayWithFlutterwaveV2.js | 43 +++++ dist/PaywithFlutterwaveBase.d.ts | 70 +++++++ dist/PaywithFlutterwaveBase.d.ts.map | 1 + dist/PaywithFlutterwaveBase.js | 263 +++++++++++++++++++++++++++ dist/index.d.ts | 13 +- dist/index.d.ts.map | 2 +- dist/index.js | 15 +- dist/utils/ResponseParser.d.ts | 2 +- 25 files changed, 1087 insertions(+), 21 deletions(-) create mode 100644 dist/FlutterwaveInitV2.d.ts create mode 100644 dist/FlutterwaveInitV2.d.ts.map create mode 100644 dist/FlutterwaveInitV2.js create mode 100644 dist/FlwButton.d.ts create mode 100644 dist/FlwButton.d.ts.map create mode 100644 dist/FlwButton.js create mode 100644 dist/FlwCheckout.d.ts create mode 100644 dist/FlwCheckout.d.ts.map create mode 100644 dist/FlwCheckout.js create mode 100644 dist/PayWithFlutterwave.d.ts create mode 100644 dist/PayWithFlutterwave.d.ts.map create mode 100644 dist/PayWithFlutterwave.js create mode 100644 dist/PayWithFlutterwaveV2.d.ts create mode 100644 dist/PayWithFlutterwaveV2.d.ts.map create mode 100644 dist/PayWithFlutterwaveV2.js create mode 100644 dist/PaywithFlutterwaveBase.d.ts create mode 100644 dist/PaywithFlutterwaveBase.d.ts.map create mode 100644 dist/PaywithFlutterwaveBase.js diff --git a/dist/FlutterwaveInit.d.ts b/dist/FlutterwaveInit.d.ts index 391cd83..1c4fd9f 100644 --- a/dist/FlutterwaveInit.d.ts +++ b/dist/FlutterwaveInit.d.ts @@ -1,4 +1,5 @@ -import FlutterwaveInit from './v3/FlutterwaveInit'; +/// +export declare type Currency = 'GBP' | 'NGN' | 'USD' | 'GHS' | 'KES' | 'ZAR' | 'TZS'; export interface FlutterwaveInitSubAccount { id: string; transaction_split_ratio?: number; @@ -7,12 +8,54 @@ export interface FlutterwaveInitSubAccount { } export interface FlutterwaveInitOptionsBase { amount: number; - currency?: string; + currency?: Currency; integrity_hash?: string; payment_options?: string; payment_plan?: number; redirect_url: string; subaccounts?: Array; } -export default FlutterwaveInit; +interface FlutterwavePaymentMeta { + [k: string]: any; +} +export interface FlutterwaveInitCustomer { + email: string; + phonenumber?: string; + name?: string; +} +export interface FlutterwaveInitCustomizations { + title?: string; + logo?: string; + description?: string; +} +export declare type FlutterwaveInitOptions = FlutterwaveInitOptionsBase & { + authorization: string; + tx_ref: string; + customer: FlutterwaveInitCustomer; + meta?: FlutterwavePaymentMeta | null; + customizations?: FlutterwaveInitCustomizations; +}; +export interface FieldError { + field: string; + message: string; +} +export interface ResponseData { + status?: 'success' | 'error'; + message: string; + error_id?: string; + errors?: Array; + code?: string; + data?: { + link: string; + }; +} +/** + * This function is responsible for making the request to + * initialize a Flutterwave payment. + * @param options FlutterwaveInitOptions + * @param abortController AbortController + * @return Promise + */ +export default function FlutterwaveInit(options: FlutterwaveInitOptions, abortController?: AbortController): Promise; +export {}; //# sourceMappingURL=FlutterwaveInit.d.ts.map \ No newline at end of file diff --git a/dist/FlutterwaveInit.d.ts.map b/dist/FlutterwaveInit.d.ts.map index 7973f12..9d265ab 100644 --- a/dist/FlutterwaveInit.d.ts.map +++ b/dist/FlutterwaveInit.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"FlutterwaveInit.d.ts","sourceRoot":"","sources":["../src/FlutterwaveInit.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,MAAM,sBAAsB,CAAC;AAEnD,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,KAAK,CAAC,yBAAyB,CAAC,CAAC;CAChD;AAED,eAAe,eAAe,CAAC"} \ No newline at end of file +{"version":3,"file":"FlutterwaveInit.d.ts","sourceRoot":"","sources":["../src/FlutterwaveInit.ts"],"names":[],"mappings":";AAIA,oBAAY,QAAQ,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AAE7E,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,KAAK,CAAC,yBAAyB,CAAC,CAAC;CAChD;AAED,UAAU,sBAAsB;IAC9B,CAAC,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,6BAA6B;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,oBAAY,sBAAsB,GAAG,0BAA0B,GAAG;IAChE,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,uBAAuB,CAAC;IAClC,IAAI,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACrC,cAAc,CAAC,EAAE,6BAA6B,CAAC;CAChD,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AASD;;;;;;GAMG;AACH,wBAA8B,eAAe,CAC3C,OAAO,EAAE,sBAAsB,EAC/B,eAAe,CAAC,EAAE,eAAe,GAChC,OAAO,CAAC,MAAM,CAAC,CAgCjB"} \ No newline at end of file diff --git a/dist/FlutterwaveInit.js b/dist/FlutterwaveInit.js index 508ec51..02fbaa2 100644 --- a/dist/FlutterwaveInit.js +++ b/dist/FlutterwaveInit.js @@ -1,2 +1,101 @@ -import FlutterwaveInit from './v3/FlutterwaveInit'; -export default FlutterwaveInit; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + 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; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +import FlutterwaveInitError from './utils/FlutterwaveInitError'; +import ResponseParser from './utils/ResponseParser'; +import { STANDARD_URL } from './configs'; +/** + * This function is responsible for making the request to + * initialize a Flutterwave payment. + * @param options FlutterwaveInitOptions + * @param abortController AbortController + * @return Promise + */ +export default function FlutterwaveInit(options, abortController) { + return __awaiter(this, void 0, void 0, function () { + var authorization, body, headers, fetchOptions, response, responseData, _a, _b, e_1, error; + return __generator(this, function (_c) { + switch (_c.label) { + case 0: + _c.trys.push([0, 4, , 5]); + authorization = options.authorization, body = __rest(options, ["authorization"]); + headers = new Headers; + headers.append('Content-Type', 'application/json'); + headers.append('Authorization', "Bearer " + authorization); + fetchOptions = { + method: 'POST', + body: JSON.stringify(body), + headers: headers + }; + // add abortController if defined + if (abortController) { + fetchOptions.signal = abortController.signal; + } + ; + return [4 /*yield*/, fetch(STANDARD_URL, fetchOptions)]; + case 1: + response = _c.sent(); + return [4 /*yield*/, response.json()]; + case 2: + responseData = _c.sent(); + _b = (_a = Promise).resolve; + return [4 /*yield*/, ResponseParser(responseData)]; + case 3: + // resolve with the payment link + return [2 /*return*/, _b.apply(_a, [_c.sent()])]; + case 4: + e_1 = _c.sent(); + error = e_1 instanceof FlutterwaveInitError + ? e_1 + : new FlutterwaveInitError({ message: e_1.message, code: e_1.name.toUpperCase() }); + // resolve with error + return [2 /*return*/, Promise.reject(error)]; + case 5: return [2 /*return*/]; + } + }); + }); +} diff --git a/dist/FlutterwaveInitV2.d.ts b/dist/FlutterwaveInitV2.d.ts new file mode 100644 index 0000000..a35dc10 --- /dev/null +++ b/dist/FlutterwaveInitV2.d.ts @@ -0,0 +1,48 @@ +/// +import { Currency, FlutterwaveInitSubAccount } from './FlutterwaveInit'; +export interface FlutterwaveInitOptionsBase { + amount: number; + currency?: Currency; + integrity_hash?: string; + payment_options?: string; + payment_plan?: number; + redirect_url: string; + subaccounts?: Array; +} +export interface FieldError { + field: string; + message: string; +} +interface FlutterwavePaymentMetaV2 { + metaname: string; + metavalue: string; +} +export declare type FlutterwaveInitV2Options = FlutterwaveInitOptionsBase & { + txref: string; + PBFPubKey: string; + customer_firstname?: string; + customer_lastname?: string; + customer_phone?: string; + customer_email: string; + country?: string; + pay_button_text?: string; + custom_title?: string; + custom_description?: string; + custom_logo?: string; + meta?: Array; +}; +/** + * This function is responsible for making the request to + * initialize a Flutterwave payment. + * @param options FlutterwaveInitOptions + * @return Promise<{ + * error: { + * code: string; + * message: string; + * } | null; + * link?: string | null; + * }> + */ +export default function FlutterwaveInitV2(options: FlutterwaveInitV2Options, abortController?: AbortController): Promise; +export {}; +//# sourceMappingURL=FlutterwaveInitV2.d.ts.map \ No newline at end of file diff --git a/dist/FlutterwaveInitV2.d.ts.map b/dist/FlutterwaveInitV2.d.ts.map new file mode 100644 index 0000000..9f28c0c --- /dev/null +++ b/dist/FlutterwaveInitV2.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"FlutterwaveInitV2.d.ts","sourceRoot":"","sources":["../src/FlutterwaveInitV2.ts"],"names":[],"mappings":";AAEA,OAAO,EAAC,QAAQ,EAAE,yBAAyB,EAAC,MAAM,mBAAmB,CAAC;AAEtE,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,KAAK,CAAC,yBAAyB,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AASD,UAAU,wBAAwB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,oBAAY,wBAAwB,GAAG,0BAA0B,GAAG;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,KAAK,CAAC,wBAAwB,CAAC,CAAC;CACxC,CAAA;AAYD;;;;;;;;;;;GAWG;AACH,wBAA8B,iBAAiB,CAC7C,OAAO,EAAE,wBAAwB,EACjC,eAAe,CAAC,EAAE,eAAe,GAChC,OAAO,CAAC,MAAM,CAAC,CA6CjB"} \ No newline at end of file diff --git a/dist/FlutterwaveInitV2.js b/dist/FlutterwaveInitV2.js new file mode 100644 index 0000000..b3e1989 --- /dev/null +++ b/dist/FlutterwaveInitV2.js @@ -0,0 +1,115 @@ +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + 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; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +import FlutterwaveInitError from './utils/FlutterwaveInitError'; +import { STANDARD_URL_V2 } from './configs'; +/** + * This function is responsible for making the request to + * initialize a Flutterwave payment. + * @param options FlutterwaveInitOptions + * @return Promise<{ + * error: { + * code: string; + * message: string; + * } | null; + * link?: string | null; + * }> + */ +export default function FlutterwaveInitV2(options, abortController) { + return __awaiter(this, void 0, void 0, function () { + var body, headers, fetchOptions, response, responseJSON, e_1, error; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 3, , 4]); + body = __assign({}, options); + headers = new Headers; + headers.append('Content-Type', 'application/json'); + fetchOptions = { + method: 'POST', + body: JSON.stringify(body), + headers: headers + }; + // add abort controller if defined + if (abortController) { + fetchOptions.signal = abortController.signal; + } + ; + return [4 /*yield*/, fetch(STANDARD_URL_V2, fetchOptions)]; + case 1: + response = _a.sent(); + return [4 /*yield*/, response.json()]; + case 2: + responseJSON = _a.sent(); + // check if data is missing from response + if (!responseJSON.data) { + throw new FlutterwaveInitError({ + code: 'STANDARD_INIT_ERROR', + message: responseJSON.message || 'An unknown error occured!' + }); + } + // check if the link is missing in data + if (!responseJSON.data.link) { + throw new FlutterwaveInitError({ + code: responseJSON.data.code || 'MALFORMED_RESPONSE', + message: responseJSON.data.message || 'An unknown error occured!' + }); + } + // resolve with the payment link + return [2 /*return*/, Promise.resolve(responseJSON.data.link)]; + case 3: + e_1 = _a.sent(); + error = e_1 instanceof FlutterwaveInitError + ? e_1 + : new FlutterwaveInitError({ message: e_1.message, code: e_1.name.toUpperCase() }); + // resolve with error + return [2 /*return*/, Promise.reject(error)]; + case 4: return [2 /*return*/]; + } + }); + }); +} diff --git a/dist/FlwButton.d.ts b/dist/FlwButton.d.ts new file mode 100644 index 0000000..0a0fe4a --- /dev/null +++ b/dist/FlwButton.d.ts @@ -0,0 +1,11 @@ +import React from 'react'; +import { StyleProp, ViewStyle } from 'react-native'; +interface FlwButtonProps { + style?: StyleProp; + disabled?: boolean; + alignLeft?: boolean; + onPress?: () => void; +} +declare const FlwButton: React.FC; +export default FlwButton; +//# sourceMappingURL=FlwButton.d.ts.map \ No newline at end of file diff --git a/dist/FlwButton.d.ts.map b/dist/FlwButton.d.ts.map new file mode 100644 index 0000000..d26df4d --- /dev/null +++ b/dist/FlwButton.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"FlwButton.d.ts","sourceRoot":"","sources":["../src/FlwButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAGL,SAAS,EACT,SAAS,EAGV,MAAM,cAAc,CAAC;AAKtB,UAAU,cAAc;IACtB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,QAAA,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CACvB,cAAc,CAsCf,CAAA;AAuCD,eAAe,SAAS,CAAC"} \ No newline at end of file diff --git a/dist/FlwButton.js b/dist/FlwButton.js new file mode 100644 index 0000000..7bf0408 --- /dev/null +++ b/dist/FlwButton.js @@ -0,0 +1,59 @@ +import React from 'react'; +import { StyleSheet, Image, TouchableHighlight, View, } from 'react-native'; +import { colors } from './configs'; +var pryContent = require('./assets/pry-button-content.png'); +var contentSizeDimension = 8.2962962963; +var FlwButton = function FlwButton(_a) { + var style = _a.style, alignLeft = _a.alignLeft, children = _a.children, disabled = _a.disabled, onPress = _a.onPress; + // render primary button + return ( + <> + {children ? children : ()} + {disabled + ? () + : null} + + ); +}; +// component UI styles +var styles = StyleSheet.create({ + buttonBusyOvelay: { + position: 'absolute', + left: 0, + top: 0, + bottom: 0, + right: 0, + backgroundColor: 'rgba(255, 255, 255, 0.6)' + }, + buttonBusy: { + borderColor: colors.primaryLight + }, + buttonAlignLeft: { + justifyContent: 'flex-start' + }, + button: { + paddingHorizontal: 16, + minWidth: 100, + height: 52, + borderColor: colors.primary, + borderWidth: 1, + borderRadius: 6, + backgroundColor: colors.primary, + alignItems: 'center', + justifyContent: 'center', + flexDirection: 'row', + overflow: 'hidden' + }, + buttonContent: { + resizeMode: 'contain', + width: 187.3, + height: 187.3 / contentSizeDimension + } +}); +// export component as default +export default FlwButton; diff --git a/dist/FlwCheckout.d.ts b/dist/FlwCheckout.d.ts new file mode 100644 index 0000000..dc30490 --- /dev/null +++ b/dist/FlwCheckout.d.ts @@ -0,0 +1,15 @@ +import React from 'react'; +export interface FlwCheckoutProps { + onRedirect?: (data: any) => void; + onAbort?: () => void; + link?: string; + visible?: boolean; +} +interface FlwCheckoutErrorProps { + hasLink: boolean; + onTryAgain: () => void; +} +declare const FlwCheckout: React.FC; +export declare const FlwCheckoutError: React.FC; +export default FlwCheckout; +//# sourceMappingURL=FlwCheckout.d.ts.map \ No newline at end of file diff --git a/dist/FlwCheckout.d.ts.map b/dist/FlwCheckout.d.ts.map new file mode 100644 index 0000000..2b74c67 --- /dev/null +++ b/dist/FlwCheckout.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"FlwCheckout.d.ts","sourceRoot":"","sources":["../src/FlwCheckout.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAsB1B,MAAM,WAAW,gBAAgB;IAC/B,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAOD,UAAU,qBAAqB;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB;AAoBD,QAAA,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAyH3C,CAAA;AAoBD,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAsB5D,CAAA;AA8ED,eAAe,WAAW,CAAC"} \ No newline at end of file diff --git a/dist/FlwCheckout.js b/dist/FlwCheckout.js new file mode 100644 index 0000000..29976b8 --- /dev/null +++ b/dist/FlwCheckout.js @@ -0,0 +1,214 @@ +import React from 'react'; +import { StyleSheet, Modal, View, Animated, TouchableWithoutFeedback, Text, Alert, Image, Dimensions, Easing, TouchableOpacity, Platform, } from 'react-native'; +import WebView from 'react-native-webview'; +import { colors } from './configs'; +var loader = require('./assets/loader.gif'); +var borderRadiusDimension = 24 / 896; +var windowHeight = Dimensions.get('window').height; +var getRedirectParams = function (url) { + // initialize result container + var res = {}; + // if url has params + if (url.split('?').length > 1) { + // get query params in an array + var params = url.split('?')[1].split('&'); + // add url params to result + for (var i = 0; i < params.length; i++) { + var param = params[i].split('='); + var val = decodeURIComponent(param[1]).trim(); + res[param[0]] = String(val); + } + } + // return result + return res; +}; +var FlwCheckout = function FlwCheckout(props) { + var link = props.link, visible = props.visible, onRedirect = props.onRedirect, onAbort = props.onAbort; + var _a = React.useState(false), show = _a[0], setShow = _a[1]; + var webviewRef = React.useRef(null); + var animation = React.useRef(new Animated.Value(0)); + var animateIn = React.useCallback(function () { + setShow(true); + Animated.timing(animation.current, { + toValue: 1, + duration: 700, + easing: Easing["in"](Easing.elastic(0.72)), + useNativeDriver: false + }).start(); + }, []); + var animateOut = React.useCallback(function () { + return new Promise(function (resolve) { + Animated.timing(animation.current, { + toValue: 0, + duration: 400, + useNativeDriver: false + }).start(function () { + setShow(false); + resolve(); + }); + }); + }, []); + var handleReload = React.useCallback(function () { + if (webviewRef.current) { + webviewRef.current.reload(); + } + }, []); + var handleAbort = React.useCallback(function (confirmed) { + if (confirmed === void 0) { confirmed = false; } + if (!confirmed) { + Alert.alert('', 'Are you sure you want to cancel this payment?', [ + { text: 'No' }, + { + text: 'Yes, Cancel', + style: 'destructive', + onPress: function () { return handleAbort(true); } + }, + ]); + return; + } + // remove tx_ref and dismiss + animateOut().then(onAbort); + }, [onAbort, animateOut]); + var handleNavigationStateChange = React.useCallback(function (ev) { + // cregex to check if redirect has occured on completion/cancel + var rx = /\/flutterwave\.com\/rn-redirect/; + // Don't end payment if not redirected back + if (!rx.test(ev.url)) { + return; + } + // dismiss modal + animateOut().then(function () { + if (onRedirect) { + onRedirect(getRedirectParams(ev.url)); + } + }); + }, [onRedirect]); + var doAnimate = React.useCallback(function () { + if (visible === show) { + return; + } + if (visible) { + return animateIn(); + } + animateOut().then(function () { }); + }, [visible, show, animateOut, animateIn]); + React.useEffect(function () { + doAnimate(); + return function () { }; + }, [doAnimate]); + var marginTop = animation.current.interpolate({ + inputRange: [0, 1], + outputRange: [windowHeight, 0] + }); + var opacity = animation.current.interpolate({ + inputRange: [0, 0.3, 1], + outputRange: [0, 1, 1] + }); + return ( + + + ; }} renderLoading={function () { return ; }}/> + + ); +}; +var FlwCheckoutBackdrop = function FlwCheckoutBackdrop(_a) { + var animation = _a.animation, onPress = _a.onPress; + // Interpolation backdrop animation + var backgroundColor = animation.interpolate({ + inputRange: [0, 0.3, 1], + outputRange: [colors.transparent, colors.transparent, 'rgba(0,0,0,0.5)'] + }); + return ( + + ); +}; +export var FlwCheckoutError = function (_a) { + var hasLink = _a.hasLink, onTryAgain = _a.onTryAgain; + return ( + {hasLink ? (<> + + An error occurred, please tab below to try again. + + + Try Again + + ) : ( + An error occurred, please close the checkout dialog and try again. + )} + ); +}; +var FlwCheckoutLoader = function () { + return ( + + ); +}; +var styles = StyleSheet.create({ + errorActionButtonText: { + textAlign: 'center', + color: colors.primary, + fontSize: 16 + }, + errorActionButton: { + paddingHorizontal: 16, + paddingVertical: 16 + }, + errorText: { + color: colors.secondary, + textAlign: 'center', + marginBottom: 32, + fontSize: 18 + }, + error: { + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + top: 0, + backgroundColor: '#ffffff', + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: 56 + }, + backdrop: { + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + top: 0 + }, + loadingImage: { + width: 64, + height: 64, + resizeMode: 'contain' + }, + loading: { + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 0, + backgroundColor: 'rgba(255, 255, 255, 0.3)', + justifyContent: 'center', + alignItems: 'center' + }, + webviewContainer: { + top: Platform.select({ ios: 96, android: 64 }), + flex: 1, + backgroundColor: '#efefef', + paddingBottom: Platform.select({ ios: 96, android: 64 }), + overflow: 'hidden', + borderTopLeftRadius: windowHeight * borderRadiusDimension, + borderTopRightRadius: windowHeight * borderRadiusDimension + }, + webview: { + flex: 1, + backgroundColor: 'rgba(0,0,0,0)' + } +}); +export default FlwCheckout; diff --git a/dist/PayWithFlutterwave.d.ts b/dist/PayWithFlutterwave.d.ts new file mode 100644 index 0000000..a787083 --- /dev/null +++ b/dist/PayWithFlutterwave.d.ts @@ -0,0 +1,15 @@ +import React from 'react'; +import { PayWithFlutterwavePropsBase } from './PaywithFlutterwaveBase'; +import { FlutterwaveInitOptions } from './FlutterwaveInit'; +export interface RedirectParams { + status: 'successful' | 'cancelled'; + transaction_id?: string; + tx_ref: string; +} +export declare type PayWithFlutterwaveProps = PayWithFlutterwavePropsBase & { + onRedirect: (data: RedirectParams) => void; + options: Omit; +}; +declare const PayWithFlutterwave: React.FC; +export default PayWithFlutterwave; +//# sourceMappingURL=PayWithFlutterwave.d.ts.map \ No newline at end of file diff --git a/dist/PayWithFlutterwave.d.ts.map b/dist/PayWithFlutterwave.d.ts.map new file mode 100644 index 0000000..b6fedee --- /dev/null +++ b/dist/PayWithFlutterwave.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"PayWithFlutterwave.d.ts","sourceRoot":"","sources":["../src/PayWithFlutterwave.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EACL,2BAA2B,EAG5B,MAAM,0BAA0B,CAAC;AAClC,OAAwB,EAAC,sBAAsB,EAAC,MAAM,mBAAmB,CAAC;AAK1E,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,YAAY,GAAG,WAAW,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAGD,oBAAY,uBAAuB,GAAG,2BAA2B,GAAG;IAClE,UAAU,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAC3C,OAAO,EAAE,IAAI,CAAC,sBAAsB,EAAE,cAAc,CAAC,CAAC;CACvD,CAAA;AAGD,QAAA,MAAM,kBAAkB,EAAC,KAAK,CAAC,EAAE,CAAC,uBAAuB,CASxD,CAAA;AAyBD,eAAe,kBAAkB,CAAC"} \ No newline at end of file diff --git a/dist/PayWithFlutterwave.js b/dist/PayWithFlutterwave.js new file mode 100644 index 0000000..edd823f --- /dev/null +++ b/dist/PayWithFlutterwave.js @@ -0,0 +1,48 @@ +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +import React from 'react'; +import PropTypes from 'prop-types'; +import { OptionsPropTypeBase, PayWithFlutterwavePropTypesBase } from './PaywithFlutterwaveBase'; +import FlutterwaveInit from './FlutterwaveInit'; +import { PAYMENT_OPTIONS } from './configs'; +import { PaymentOptionsPropRule } from './utils/CustomPropTypesRules'; +import PayWithFlutterwaveBase from './PaywithFlutterwaveBase'; +// create V3 component +var PayWithFlutterwave = function (_a) { + var options = _a.options, props = __rest(_a, ["options"]); + return (); +}; +// define component prop types +PayWithFlutterwave.propTypes = __assign(__assign({}, PayWithFlutterwavePropTypesBase), { + // @ts-ignore + options: PropTypes.shape(__assign(__assign({}, OptionsPropTypeBase), { authorization: PropTypes.string.isRequired, tx_ref: PropTypes.string.isRequired, payment_options: PaymentOptionsPropRule(PAYMENT_OPTIONS), customer: PropTypes.shape({ + name: PropTypes.string, + phonenumber: PropTypes.string, + email: PropTypes.string.isRequired + }).isRequired, meta: PropTypes.object, customizations: PropTypes.shape({ + title: PropTypes.string, + logo: PropTypes.string, + description: PropTypes.string + }) })).isRequired }); +// export component as default +export default PayWithFlutterwave; diff --git a/dist/PayWithFlutterwaveV2.d.ts b/dist/PayWithFlutterwaveV2.d.ts new file mode 100644 index 0000000..22d16c0 --- /dev/null +++ b/dist/PayWithFlutterwaveV2.d.ts @@ -0,0 +1,15 @@ +import React from 'react'; +import { PayWithFlutterwavePropsBase } from './PaywithFlutterwaveBase'; +import { FlutterwaveInitV2Options } from './FlutterwaveInitV2'; +export interface RedirectParamsV2 { + cancelled?: 'true' | 'false'; + flwref?: string; + txref: string; +} +export declare type PayWithFlutterwaveV2Props = PayWithFlutterwavePropsBase & { + onRedirect: (data: RedirectParamsV2) => void; + options: Omit; +}; +declare const PayWithFlutterwaveV2: React.FC; +export default PayWithFlutterwaveV2; +//# sourceMappingURL=PayWithFlutterwaveV2.d.ts.map \ No newline at end of file diff --git a/dist/PayWithFlutterwaveV2.d.ts.map b/dist/PayWithFlutterwaveV2.d.ts.map new file mode 100644 index 0000000..f40ba68 --- /dev/null +++ b/dist/PayWithFlutterwaveV2.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"PayWithFlutterwaveV2.d.ts","sourceRoot":"","sources":["../src/PayWithFlutterwaveV2.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EACL,2BAA2B,EAG5B,MAAM,0BAA0B,CAAC;AAClC,OAA0B,EAAC,wBAAwB,EAAC,MAAM,qBAAqB,CAAC;AAKhF,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,oBAAY,yBAAyB,GAAG,2BAA2B,GAAG;IACpE,UAAU,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC7C,OAAO,EAAE,IAAI,CAAC,wBAAwB,EAAE,cAAc,CAAC,CAAC;CACzD,CAAA;AAGD,QAAA,MAAM,oBAAoB,EAAC,KAAK,CAAC,EAAE,CAAC,yBAAyB,CAS5D,CAAA;AA2BD,eAAe,oBAAoB,CAAC"} \ No newline at end of file diff --git a/dist/PayWithFlutterwaveV2.js b/dist/PayWithFlutterwaveV2.js new file mode 100644 index 0000000..78d9452 --- /dev/null +++ b/dist/PayWithFlutterwaveV2.js @@ -0,0 +1,43 @@ +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +import React from 'react'; +import PropTypes from 'prop-types'; +import { OptionsPropTypeBase, PayWithFlutterwavePropTypesBase } from './PaywithFlutterwaveBase'; +import FlutterwaveInitV2 from './FlutterwaveInitV2'; +import { PAYMENT_OPTIONS_V2 } from './configs'; +import { PaymentOptionsPropRule } from './utils/CustomPropTypesRules'; +import PayWithFlutterwaveBase from './PaywithFlutterwaveBase'; +// create V2 component +var PayWithFlutterwaveV2 = function (_a) { + var options = _a.options, props = __rest(_a, ["options"]); + return (); +}; +// define component prop types +PayWithFlutterwaveV2.propTypes = __assign(__assign({}, PayWithFlutterwavePropTypesBase), { + // @ts-ignore + options: PropTypes.shape(__assign(__assign({}, OptionsPropTypeBase), { payment_options: PaymentOptionsPropRule(PAYMENT_OPTIONS_V2), txref: PropTypes.string.isRequired, PBFPubKey: PropTypes.string.isRequired, customer_firstname: PropTypes.string, customer_lastname: PropTypes.string, customer_email: PropTypes.string.isRequired, customer_phone: PropTypes.string, country: PropTypes.string, pay_button_text: PropTypes.string, custom_title: PropTypes.string, custom_description: PropTypes.string, custom_logo: PropTypes.string, meta: PropTypes.arrayOf(PropTypes.shape({ + metaname: PropTypes.string, + metavalue: PropTypes.string + })) })).isRequired }); +// export component as default +export default PayWithFlutterwaveV2; diff --git a/dist/PaywithFlutterwaveBase.d.ts b/dist/PaywithFlutterwaveBase.d.ts new file mode 100644 index 0000000..90290ed --- /dev/null +++ b/dist/PaywithFlutterwaveBase.d.ts @@ -0,0 +1,70 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import FlutterwaveInitError from './utils/FlutterwaveInitError'; +import { StyleProp, ViewStyle } from 'react-native'; +export interface CustomButtonProps { + disabled: boolean; + onPress: () => void; +} +export interface PayWithFlutterwavePropsBase { + style?: StyleProp; + onRedirect: (data: any) => void; + onWillInitialize?: () => void; + onDidInitialize?: () => void; + onInitializeError?: (error: FlutterwaveInitError) => void; + onAbort?: () => void; + customButton?: (params: CustomButtonProps) => React.ReactNode; + alignLeft?: 'alignLeft' | boolean; + meta?: Array; + currency?: string; +} +export declare const PayWithFlutterwavePropTypesBase: { + alignLeft: PropTypes.Requireable; + onAbort: PropTypes.Requireable<(...args: any[]) => any>; + onRedirect: PropTypes.Validator<(...args: any[]) => any>; + onWillInitialize: PropTypes.Requireable<(...args: any[]) => any>; + onDidInitialize: PropTypes.Requireable<(...args: any[]) => any>; + onInitializeError: PropTypes.Requireable<(...args: any[]) => any>; + customButton: PropTypes.Requireable<(...args: any[]) => any>; +}; +export declare const OptionsPropTypeBase: { + amount: PropTypes.Validator; + currency: PropTypes.Requireable; + payment_plan: PropTypes.Requireable; + subaccounts: PropTypes.Requireable<(PropTypes.InferProps<{ + id: PropTypes.Validator; + transaction_split_ratio: PropTypes.Requireable; + transaction_charge_type: PropTypes.Requireable; + transaction_charge: PropTypes.Requireable; + }> | null | undefined)[]>; + integrity_hash: PropTypes.Requireable; +}; +interface PayWithFlutterwaveState { + link: string | null; + isPending: boolean; + showDialog: boolean; + reference: string | null; + resetLink: boolean; +} +export declare type PayWithFlutterwaveBaseProps = PayWithFlutterwavePropsBase & { + options: any; + init: (options: any, abortController?: AbortController) => Promise; + reference: string; +}; +declare class PayWithFlutterwaveBase

extends React.Component { + state: PayWithFlutterwaveState; + abortController?: AbortController; + timeout: any; + handleInitCall?: () => Promise; + componentDidUpdate(prevProps: PayWithFlutterwaveBaseProps): void; + componentWillUnmount(): void; + reset: () => void; + handleOptionsChanged: () => void; + handleAbort: () => void; + handleRedirect: (params: any) => void; + handleInit: () => Promise; + render(): JSX.Element; + renderButton(): {} | null | undefined; +} +export default PayWithFlutterwaveBase; +//# sourceMappingURL=PaywithFlutterwaveBase.d.ts.map \ No newline at end of file diff --git a/dist/PaywithFlutterwaveBase.d.ts.map b/dist/PaywithFlutterwaveBase.d.ts.map new file mode 100644 index 0000000..dffa994 --- /dev/null +++ b/dist/PaywithFlutterwaveBase.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"PaywithFlutterwaveBase.d.ts","sourceRoot":"","sources":["../src/PaywithFlutterwaveBase.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,oBAAoB,MAAM,8BAA8B,CAAC;AAIhE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,2BAA2B;IAC1C,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,UAAU,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAChC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC1D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,KAAK,CAAC,SAAS,CAAC;IAC9D,SAAS,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;IAClC,IAAI,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,+BAA+B;;;;;;;;CAQ3C,CAAC;AAEF,eAAO,MAAM,mBAAmB;;;;;;;;;;;CAW/B,CAAC;AAEF,UAAU,uBAAuB;IAC/B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,oBAAY,2BAA2B,GAAG,2BAA2B,GAAG;IACtE,OAAO,EAAE,GAAG,CAAC;IACb,IAAI,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,eAAe,CAAC,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3E,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,cAAM,sBAAsB,CAAC,CAAC,GAAG,EAAE,CAAE,SAAQ,KAAK,CAAC,SAAS,CAC1D,2BAA2B,GAAG,CAAC,EAC/B,uBAAuB,CACxB;IAEC,KAAK,EAAE,uBAAuB,CAM5B;IAEF,eAAe,CAAC,EAAE,eAAe,CAAC;IAElC,OAAO,EAAE,GAAG,CAAC;IAEb,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAEvC,kBAAkB,CAAC,SAAS,EAAE,2BAA2B;IAQzD,oBAAoB;IAMpB,KAAK,aAWH;IAEF,oBAAoB,aAYnB;IAED,WAAW,aAMV;IAED,cAAc,wBAcb;IAED,UAAU,sBA0ER;IAEF,MAAM;IAeN,YAAY;CAiBb;AAED,eAAe,sBAAsB,CAAC"} \ No newline at end of file diff --git a/dist/PaywithFlutterwaveBase.js b/dist/PaywithFlutterwaveBase.js new file mode 100644 index 0000000..8c61521 --- /dev/null +++ b/dist/PaywithFlutterwaveBase.js @@ -0,0 +1,263 @@ +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + 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; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +import React from 'react'; +import PropTypes from 'prop-types'; +import FlutterwaveInitError from './utils/FlutterwaveInitError'; +import FlwCheckout from './FlwCheckout'; +import FlwButton from './FlwButton'; +import { REDIRECT_URL } from './configs'; +export var PayWithFlutterwavePropTypesBase = { + alignLeft: PropTypes.bool, + onAbort: PropTypes.func, + onRedirect: PropTypes.func.isRequired, + onWillInitialize: PropTypes.func, + onDidInitialize: PropTypes.func, + onInitializeError: PropTypes.func, + customButton: PropTypes.func +}; +export var OptionsPropTypeBase = { + amount: PropTypes.number.isRequired, + currency: PropTypes.oneOf(['GBP', 'NGN', 'USD', 'GHS', 'KES', 'ZAR', 'TZS']), + payment_plan: PropTypes.number, + subaccounts: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string.isRequired, + transaction_split_ratio: PropTypes.number, + transaction_charge_type: PropTypes.string, + transaction_charge: PropTypes.number + })), + integrity_hash: PropTypes.string +}; +var PayWithFlutterwaveBase = /** @class */ (function (_super) { + __extends(PayWithFlutterwaveBase, _super); + function PayWithFlutterwaveBase() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.state = { + isPending: false, + link: null, + resetLink: false, + showDialog: false, + reference: null + }; + _this.reset = function () { + if (_this.abortController) { + _this.abortController.abort(); + } + // reset the necessaries + _this.setState(function (_a) { + var resetLink = _a.resetLink, link = _a.link; + return ({ + isPending: false, + link: resetLink ? null : link, + resetLink: false, + showDialog: false + }); + }); + }; + _this.handleOptionsChanged = function () { + var _a = _this.state, showDialog = _a.showDialog, link = _a.link; + if (!link) { + return; + } + if (!showDialog) { + return _this.setState({ + link: null, + reference: null + }); + } + _this.setState({ resetLink: true }); + }; + _this.handleAbort = function () { + var onAbort = _this.props.onAbort; + if (onAbort) { + onAbort(); + } + _this.reset(); + }; + _this.handleRedirect = function (params) { + var onRedirect = _this.props.onRedirect; + // reset payment link + _this.setState(function (_a) { + var resetLink = _a.resetLink, reference = _a.reference; + return ({ + reference: params.flwref || params.status === 'successful' ? null : reference, + resetLink: params.flwref || params.status === 'successful' ? true : resetLink, + showDialog: false + }); + }, function () { + onRedirect(params); + _this.reset(); + }); + }; + _this.handleInit = function () { return __awaiter(_this, void 0, void 0, function () { + var _a, options, onWillInitialize, onInitializeError, onDidInitialize, init, _b, isPending, reference, link; + var _this = this; + return __generator(this, function (_c) { + _a = this.props, options = _a.options, onWillInitialize = _a.onWillInitialize, onInitializeError = _a.onInitializeError, onDidInitialize = _a.onDidInitialize, init = _a.init; + _b = this.state, isPending = _b.isPending, reference = _b.reference, link = _b.link; + // just show the dialod if the link is already set + if (link) { + return [2 /*return*/, this.setState({ showDialog: true })]; + } + // throw error if transaction reference has not changed + if (reference === this.props.reference) { + // fire oninitialize error handler if available + if (onInitializeError) { + onInitializeError(new FlutterwaveInitError({ + message: 'Please generate a new transaction reference.', + code: 'SAME_TXREF' + })); + } + return [2 /*return*/]; + } + // stop if currently in pending mode + if (isPending) { + return [2 /*return*/]; + } + // initialize abort controller if not set + this.abortController = new AbortController; + // fire will initialize handler if available + if (onWillInitialize) { + onWillInitialize(); + } + this.setState({ + isPending: true, + link: null, + reference: this.props.reference, + showDialog: false + }, function () { return __awaiter(_this, void 0, void 0, function () { + var paymentLink, error_1; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 2, , 3]); + return [4 /*yield*/, init(__assign(__assign({}, options), { redirect_url: REDIRECT_URL }), this.abortController)]; + case 1: + paymentLink = _a.sent(); + // set payment link + this.setState({ + link: paymentLink, + isPending: false, + showDialog: true + }, function () { + // fire did initialize handler if available + if (onDidInitialize) { + onDidInitialize(); + } + }); + return [3 /*break*/, 3]; + case 2: + error_1 = _a.sent(); + // stop if request was canceled + if (error_1 && /aborterror/i.test(error_1.code)) { + return [2 /*return*/]; + } + // call onInitializeError handler if an error occured + if (onInitializeError) { + onInitializeError(error_1); + } + // set payment link to reset + this.setState({ + resetLink: true, + reference: null + }, this.reset); + return [3 /*break*/, 3]; + case 3: return [2 /*return*/]; + } + }); + }); }); + return [2 /*return*/]; + }); + }); }; + return _this; + } + PayWithFlutterwaveBase.prototype.componentDidUpdate = function (prevProps) { + var prevOptions = JSON.stringify(prevProps.options); + var options = JSON.stringify(this.props.options); + if (prevOptions !== options) { + this.handleOptionsChanged(); + } + }; + PayWithFlutterwaveBase.prototype.componentWillUnmount = function () { + if (this.abortController) { + this.abortController.abort(); + } + }; + PayWithFlutterwaveBase.prototype.render = function () { + var _a = this.state, link = _a.link, showDialog = _a.showDialog; + return (<> + {this.renderButton()} + + ); + }; + PayWithFlutterwaveBase.prototype.renderButton = function () { + var _a = this.props, alignLeft = _a.alignLeft, customButton = _a.customButton, children = _a.children, style = _a.style; + var isPending = this.state.isPending; + if (customButton) { + return customButton({ + disabled: isPending, + onPress: this.handleInit + }); + } + return ; + }; + return PayWithFlutterwaveBase; +}(React.Component)); +export default PayWithFlutterwaveBase; diff --git a/dist/index.d.ts b/dist/index.d.ts index 1deec54..e7c3bd8 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,8 +1,9 @@ import FlutterwaveInit from './FlutterwaveInit'; -import FlutterwaveButton from './FlutterwaveButton'; -import FlutterwaveInitV2 from './v2/FlutterwaveInit'; -import FlutterwaveButtonV2 from './v2/FlutterwaveButton'; -import DefaultButton from './DefaultButton'; -export { FlutterwaveInit, FlutterwaveButton, FlutterwaveInitV2, FlutterwaveButtonV2, DefaultButton, }; -export default FlutterwaveButton; +import FlutterwaveInitV2 from './FlutterwaveInitV2'; +import PayWithFlutterwave from './PayWithFlutterwave'; +import PayWithFlutterwaveV2 from './PayWithFlutterwaveV2'; +import FlwButton from './FlwButton'; +import FlwCheckout from './FlwCheckout'; +export { FlutterwaveInit, PayWithFlutterwave, FlutterwaveInitV2, PayWithFlutterwaveV2, FlwButton, FlwCheckout, }; +export default PayWithFlutterwave; //# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/index.d.ts.map b/dist/index.d.ts.map index fb45d47..65ef3ac 100644 --- a/dist/index.d.ts.map +++ b/dist/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAChD,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,iBAAiB,MAAM,sBAAsB,CAAC;AACrD,OAAO,mBAAmB,MAAM,wBAAwB,CAAC;AACzD,OAAO,aAAa,MAAM,iBAAiB,CAAC;AAG5C,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,aAAa,GACd,CAAC;AAGF,eAAe,iBAAiB,CAAC"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAChD,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,kBAAkB,MAAM,sBAAsB,CAAC;AACtD,OAAO,oBAAoB,MAAM,wBAAwB,CAAC;AAC1D,OAAO,SAAS,MAAM,aAAa,CAAC;AACpC,OAAO,WAAW,MAAM,eAAe,CAAC;AAGxC,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,SAAS,EACT,WAAW,GACZ,CAAC;AAGF,eAAe,kBAAkB,CAAC"} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index e08582c..efafc0e 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,9 +1,10 @@ import FlutterwaveInit from './FlutterwaveInit'; -import FlutterwaveButton from './FlutterwaveButton'; -import FlutterwaveInitV2 from './v2/FlutterwaveInit'; -import FlutterwaveButtonV2 from './v2/FlutterwaveButton'; -import DefaultButton from './DefaultButton'; +import FlutterwaveInitV2 from './FlutterwaveInitV2'; +import PayWithFlutterwave from './PayWithFlutterwave'; +import PayWithFlutterwaveV2 from './PayWithFlutterwaveV2'; +import FlwButton from './FlwButton'; +import FlwCheckout from './FlwCheckout'; // export modules -export { FlutterwaveInit, FlutterwaveButton, FlutterwaveInitV2, FlutterwaveButtonV2, DefaultButton, }; -// export v3 button as default -export default FlutterwaveButton; +export { FlutterwaveInit, PayWithFlutterwave, FlutterwaveInitV2, PayWithFlutterwaveV2, FlwButton, FlwCheckout, }; +// export v3 PayWithFlutterwave as default +export default PayWithFlutterwave; diff --git a/dist/utils/ResponseParser.d.ts b/dist/utils/ResponseParser.d.ts index 0343b6f..62c8308 100644 --- a/dist/utils/ResponseParser.d.ts +++ b/dist/utils/ResponseParser.d.ts @@ -1,4 +1,4 @@ -import { ResponseData } from "../v3/FlutterwaveInit"; +import { ResponseData } from "../FlutterwaveInit"; /** * The purpose of this function is to parse the response message gotten from a * payment initialization error. From 63ee87d768315c032b8c5e6bb338a524a04bb325 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 10:05:35 +0100 Subject: [PATCH 119/129] feat(dist): generate build for updates --- dist/configs.d.ts | 1 + dist/configs.d.ts.map | 2 +- dist/configs.js | 1 + dist/utils/CustomPropTypesRules.d.ts.map | 2 +- dist/utils/ResponseParser.d.ts.map | 2 +- 5 files changed, 5 insertions(+), 3 deletions(-) diff --git a/dist/configs.d.ts b/dist/configs.d.ts index 6e6b793..d39f2b9 100644 --- a/dist/configs.d.ts +++ b/dist/configs.d.ts @@ -11,6 +11,7 @@ export declare const REDIRECT_URL = "https://flutterwave.com/rn-redirect"; */ export declare const colors: { primary: string; + primaryLight: string; secondary: string; transparent: string; }; diff --git a/dist/configs.d.ts.map b/dist/configs.d.ts.map index bf4cdcd..c833d5e 100644 --- a/dist/configs.d.ts.map +++ b/dist/configs.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"configs.d.ts","sourceRoot":"","sources":["../src/configs.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,YAAY,4CAA4C,CAAC;AAEtE;;GAEG;AACH,eAAO,MAAM,YAAY,wCAAwC,CAAC;AAElE;;GAEG;AACH,eAAO,MAAM,MAAM;;;;CAIlB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,UAkB3B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,MACiC,CAAC;AAEhE;;GAEG;AACH,eAAO,MAAM,kBAAkB,UAc9B,CAAC"} \ No newline at end of file +{"version":3,"file":"configs.d.ts","sourceRoot":"","sources":["../src/configs.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,YAAY,4CAA4C,CAAC;AAEtE;;GAEG;AACH,eAAO,MAAM,YAAY,wCAAwC,CAAC;AAElE;;GAEG;AACH,eAAO,MAAM,MAAM;;;;;CAKlB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,UAkB3B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,MACiC,CAAC;AAEhE;;GAEG;AACH,eAAO,MAAM,kBAAkB,UAc9B,CAAC"} \ No newline at end of file diff --git a/dist/configs.js b/dist/configs.js index cbece43..d3e3978 100644 --- a/dist/configs.js +++ b/dist/configs.js @@ -11,6 +11,7 @@ export var REDIRECT_URL = 'https://flutterwave.com/rn-redirect'; */ export var colors = { primary: '#f5a623', + primaryLight: '#f9ce85', secondary: '#12122C', transparent: 'rgba(0,0,0,0)' }; diff --git a/dist/utils/CustomPropTypesRules.d.ts.map b/dist/utils/CustomPropTypesRules.d.ts.map index f2db249..eaa1542 100644 --- a/dist/utils/CustomPropTypesRules.d.ts.map +++ b/dist/utils/CustomPropTypesRules.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"CustomPropTypesRules.d.ts","sourceRoot":"","sources":["../../src/utils/CustomPropTypesRules.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,sBAAsB;;oCA4BlC,CAAA"} \ No newline at end of file +{"version":3,"file":"CustomPropTypesRules.d.ts","sourceRoot":"","sources":["../../src/utils/CustomPropTypesRules.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB;;oCA4BlC,CAAA"} \ No newline at end of file diff --git a/dist/utils/ResponseParser.d.ts.map b/dist/utils/ResponseParser.d.ts.map index a1f4b96..f0db2b0 100644 --- a/dist/utils/ResponseParser.d.ts.map +++ b/dist/utils/ResponseParser.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"ResponseParser.d.ts","sourceRoot":"","sources":["../../src/utils/ResponseParser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,MAAM,uBAAuB,CAAC;AAGnD;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,cAAc,CACpC,EACE,MAAM,EACN,MAAM,EACN,OAAO,EACP,IAAI,EACJ,IAAI,EACJ,QAAQ,GACT,EAAE,YAAY,GACd,OAAO,CAAC,MAAM,CAAC,CA6CjB"} \ No newline at end of file +{"version":3,"file":"ResponseParser.d.ts","sourceRoot":"","sources":["../../src/utils/ResponseParser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAGhD;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,cAAc,CACpC,EACE,MAAM,EACN,MAAM,EACN,OAAO,EACP,IAAI,EACJ,IAAI,EACJ,QAAQ,GACT,EAAE,YAAY,GACd,OAAO,CAAC,MAAM,CAAC,CA6CjB"} \ No newline at end of file From cc582840ecc4a8fd9a80a6d3774f7255e24a05d4 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 11:05:07 +0100 Subject: [PATCH 120/129] docs(readme): add exmples and types of new implementation --- README.md | 103 ++++++++-------- README.old.md | 334 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 382 insertions(+), 55 deletions(-) create mode 100644 README.old.md diff --git a/README.md b/README.md index 2d49279..681637b 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ Easily implement Flutterwave for payments in your React Native appliction. This [![V2 API](https://img.shields.io/badge/API-V3-brightgreen)](https://developer.flutterwave.com/docs) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)

- ios-preview - android-preview + ios-preview + android-preview

## Table Of Content @@ -16,23 +16,23 @@ Easily implement Flutterwave for payments in your React Native appliction. This - [Activity Indicator (Android)](#activity-indicator-only-needed-for-android) - [Important Information](#fire-important-information-fire) - Usage - - [Flutterwave Button ](#flutterwave-button) - - [Flutterwave Button (with custom render)](#flutterwave-button-with-custom-render) - - [Default Button (Flutterwave styled button)](#defaultbutton-flutterwave-styled-button) - - [Flutterwave Standard Init](#flutterwave-standard-init) + - [PayWithFlutterwave ](#flutterwave-button) + - [PayWithFlutterwave (with custom render)](#flutterwave-button-with-custom-render) + - [FlwButton (Flutterwave styled button)](#flwbutton-flutterwave-styled-button) + - [FlutterwaveInit](#flutterwaveinit) - [Aborting Payment Initialization](#aborting-payment-initialization) - Props - - [Flutterwave Button Props](#flutterwavebuttonprops) - - [Default Button Props](#defaultbuttonprops) - - [Flutterwave Init Options](#flutterwaveinitoptions) + - [FlutterwaveInitOptions](#flutterwaveinitoptions) + - [PayWithFlutterwaveProps](#flutterwavebuttonprops) + - [FlwButtonProps](#flwbutton-props) - Types - - [Flutterwave Button Props](#flutterwavebuttonprops-interface) - - [Default Button Props](#defaultbuttonprops-interface) - - [Flutterwave Init Customer](#flutterwaveinitcustomer) - - [Flutterwave Init Customization](#flutterwaveinitcustomization) - - [Flutterwave Init Sub Account](#flutterwaveinitsubaccount) - - [Flutterwave Init Options](#flutterwaveinitoptions-interface) - - [Flutterwave Init Error](#flutterwaveiniterror) + - [PayWithFlutterwaveProps](#paywithflutterwaveprops-interface) + - [FlwButtonProps](#flwbuttonprops-interface) + - [FlutterwaveInitCustomer](#flutterwaveinitcustomer) + - [FlutterwaveInitCustomization](#flutterwaveinitcustomization) + - [FlutterwaveInitSubAccount](#flutterwaveinitsubaccount) + - [FlutterwaveInitOptions](#flutterwaveinitoptions-interface) + - [FlutterwaveInitError](#flutterwaveiniterror) - [FlutterwavePaymentMeta](#flutterwavepaymentmeta) - [RedirectParams](#redirectparams) - [CustomButtonProps](#custombuttonprops) @@ -66,25 +66,25 @@ dependencies { ```` ### :fire: IMPORTANT INFORMATION :fire: -If the `options` property on the [FlutterwaveButton](#flutterwavebuttonprops-interface) changes, when next the user taps on the button a new payment will be initialized whether the last one was successful or not. +If the `options` property on [PayWithFlutterwave](#paywithflutterwaveprops-interface) changes, when next the user taps on the button a new payment will be initialized whether the last one was successful or not. -Remember you cannot use the same transaction reference for two different payments, remember to recreate the transaction reference before allowing the user initiate a new payment. +Remember you cannot use the same transaction reference for two different payments, also remember to recreate the transaction reference before allowing the user initiate a new payment. ## Usage Below are a few examples showcasing how you can use the library to implement payment in your React Native app. -### Flutterwave Button +### PayWithFlutterwave preview [View All Props](#flutterwavebuttonprops) -Import `FlutterwaveButton` from `react-native-flutterwave` and use it like so. +Import `PayWithFlutterwave` from `react-native-flutterwave` and use it like so. ````jsx -import {FlutterwaveButton} from 'react-native-flutterwave'; -// or import FlutterwaveButton from 'react-native-flutterwave'; +import {PayWithFlutterwave} from 'react-native-flutterwave'; +// or import PayWithFlutterwave from 'react-native-flutterwave'; - ```` -### Flutterwave Button (with custom render) +### PayWithFlutterwave (with custom render) preview [View All Props](#flutterwavebuttonprops) -Import `FlutterwaveButton` from `react-native-flutterwave` and use it like so. +Import `PayWithFlutterwave` from `react-native-flutterwave` and use it like so. ````jsx -import {FlutterwaveButton} from 'react-native-flutterwave'; -// or import FlutterwaveButton from 'react-native-flutterwave'; +import {PayWithFlutterwave} from 'react-native-flutterwave'; +// or import PayWithFlutterwave from 'react-native-flutterwave'; - ```` -### DefaultButton (Flutterwave styled button) +### FlwButton (Flutterwave styled button) preview -[View All Props](#defaultbuttonprops) +[View All Props](#flwbuttonprops) -Import `DefaultButton` from `react-native-flutterwave` and use it like so. +Import `FlwButton` from `react-native-flutterwave` and use it like so. ````jsx -import {DefaultButton} from 'react-native-flutterwave'; +import {FlwButton} from 'react-native-flutterwave'; - Pay $500 - + ```` -### Flutterwave Standard Init +### FlutterwaveInit When called, this function returns a Promise which resolves to a string on success and rejects if an error occurs. [See all config options](#flutterwaveinitoptions) Import `FlutterwaveInit` from `react-native-flutterwave` and use it like so. @@ -192,8 +191,8 @@ try { | meta | No | array of [FlutterwavePaymentMeta](#flutterwavepaymentmeta) | undefined | This is an object that helps you include additional payment information to your request. `E.g. { 'consumer_id': 23, 'consumer_mac': '92a3-912ba-1192a' }` | | customizations | No | [FlutterwaveInitCustomizations](#flutterwaveinitcustomizations) | undefined | 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'}` | -### FlutterwaveButtonProps -[See Interface](#flutterwavebuttonprops-interface) +### PayWithFlutterwaveProps +[See Interface](#paywithflutterwaveprops-interface) | Name | Required | Type | Default | Description | | --------- | --------- | ---- | ------- | ----------- | | style | No | object | undefined | Used to apply styling to the button.| @@ -206,16 +205,13 @@ try { | customButton | No | function | undefined | This is used to render a custom button. The function a prop argument structured like [CustomButtonProps](#custombuttonprops), this function should return a valid React node. | | alignLeft | No | boolean | undefined | This aligns the content of the button to the left. | -### DefaultButtonProps -[See Interface](#defaultbuttonprops-interface) +### FlwButton Props +[See Interface](#flwbuttonprops-interface) | Name | Required | Type | Default | Description | | --------- | --------- | ---- | ------- | ----------- | -| style | No | object | undefined | Used to apply styling to the button.| -| onPress | Yes | function | undefined | This | +| style | No | ViewStyle | undefined | This component uses the same style properties that are applicable to react-native's View component style.| +| onPress | Yes | function | undefined | This property receive a function that is called on button press. | | disabled | No | boolean | undefined | This disables button, and causes onPress not to be fired.| -| isBusy | No | boolean | undefined | This puts the button in a busy state, making the content look faded.| -| onSizeChange | No | (ev: {width: number; height: number}) => void | undefined | If provided this function is fired whenever the size(height or width) of the button changes | -| children | Yes | ReactElement | undefined | This will be the content rendered within the button, if string is to be direct decendant, remember to put string in the Text component. | | alignLeft | No | boolean | undefined | This aligns the content of the button to the left. | ## Types @@ -300,9 +296,9 @@ export interface FlutterwaveInitOptions { } ```` -#### FlutterwaveButtonProps Interface +#### PayWithFlutterwaveProps Interface ````typescript -interface FlutterwaveButtonProps { +interface PayWithFlutterwaveProps { style?: ViewStyle; onComplete: (data: RedirectParams) => void; onWillInitialize?: () => void; @@ -315,16 +311,13 @@ interface FlutterwaveButtonProps { } ```` -#### DefaultButtonProps Interface +#### FlwButtonProps Interface ````typescript -interface DefaultButtonProps { - style?: ViewStyle; - onPress?: () => void; +interface FlwButtonProps { + style?: StyleProp; disabled?: boolean; - children: React.ReactElement; - isBusy?: boolean; - onSizeChange?: (ev: {width: number; height: number}) => void; - alignLeft?: 'alignLeft' | boolean, + alignLeft?: boolean; + onPress?: () => void; } ```` diff --git a/README.old.md b/README.old.md new file mode 100644 index 0000000..2d49279 --- /dev/null +++ b/README.old.md @@ -0,0 +1,334 @@ +# React Native Flutterwave +Easily implement Flutterwave for payments in your React Native appliction. This library supports both Android and iOS, and use the Flutterwave's V3 API + +[![V2 API](https://img.shields.io/badge/API-V3-brightgreen)](https://developer.flutterwave.com/docs) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) + +

+ ios-preview + android-preview +

+ +## Table Of Content +- Getting Started + - [V2 API](#warning-if-using-version-2-api-warning) + - [Installation](#installation) + - [Dependencies](#dependencies) + - [Activity Indicator (Android)](#activity-indicator-only-needed-for-android) + - [Important Information](#fire-important-information-fire) +- Usage + - [Flutterwave Button ](#flutterwave-button) + - [Flutterwave Button (with custom render)](#flutterwave-button-with-custom-render) + - [Default Button (Flutterwave styled button)](#defaultbutton-flutterwave-styled-button) + - [Flutterwave Standard Init](#flutterwave-standard-init) + - [Aborting Payment Initialization](#aborting-payment-initialization) +- Props + - [Flutterwave Button Props](#flutterwavebuttonprops) + - [Default Button Props](#defaultbuttonprops) + - [Flutterwave Init Options](#flutterwaveinitoptions) +- Types + - [Flutterwave Button Props](#flutterwavebuttonprops-interface) + - [Default Button Props](#defaultbuttonprops-interface) + - [Flutterwave Init Customer](#flutterwaveinitcustomer) + - [Flutterwave Init Customization](#flutterwaveinitcustomization) + - [Flutterwave Init Sub Account](#flutterwaveinitsubaccount) + - [Flutterwave Init Options](#flutterwaveinitoptions-interface) + - [Flutterwave Init Error](#flutterwaveiniterror) + - [FlutterwavePaymentMeta](#flutterwavepaymentmeta) + - [RedirectParams](#redirectparams) + - [CustomButtonProps](#custombuttonprops) +- [Contributing](./CONTRIBUTING.md) + +## What's Inside? +- Pay with Flutterwave button and checkout dialog. +- Standard payment initialization function. +- Flutterwave designed button. + +## :warning: If Using Version 2 API :warning: +This version of the library's docs focuses on use cases with the Version 3 of Flutterwaves API, if you are still using the Version 2 API please use [this documentation](./README.v2.md) instead. + +## Installation +This library is available on npm, you can install it by running `npm install --save react-native-flutterwave` or `yarn add react-native-flutterwave` + +### Dependencies +In order to render the Flutterwave checkout screen this library depends on [react-native-webview](https://github.com/react-native-community/react-native-webview) ensure you properly install this library before continuing. + +### Activity Indicator (only needed for android) +To display the Flutterwave styled activity indicator when the checkout screen is being loaded on Android you will need to add some modules in `android/app/build.gradle`. +***Skip this if you already have setup your app to support gif images.*** +````javascript +dependencies { + // If your app supports Android versions before Ice Cream Sandwich (API level 14) + implementation 'com.facebook.fresco:animated-base-support:1.3.0' + + // For animated GIF support + implementation 'com.facebook.fresco:animated-gif:2.0.0' +} +```` + +### :fire: IMPORTANT INFORMATION :fire: +If the `options` property on the [FlutterwaveButton](#flutterwavebuttonprops-interface) changes, when next the user taps on the button a new payment will be initialized whether the last one was successful or not. + +Remember you cannot use the same transaction reference for two different payments, remember to recreate the transaction reference before allowing the user initiate a new payment. + + +## Usage +Below are a few examples showcasing how you can use the library to implement payment in your React Native app. + +### Flutterwave Button +preview + +[View All Props](#flutterwavebuttonprops) + +Import `FlutterwaveButton` from `react-native-flutterwave` and use it like so. +````jsx +import {FlutterwaveButton} from 'react-native-flutterwave'; +// or import FlutterwaveButton from 'react-native-flutterwave'; + + +```` + +### Flutterwave Button (with custom render) +preview + +[View All Props](#flutterwavebuttonprops) + +Import `FlutterwaveButton` from `react-native-flutterwave` and use it like so. +````jsx +import {FlutterwaveButton} from 'react-native-flutterwave'; +// or import FlutterwaveButton from 'react-native-flutterwave'; + + ( + + Pay $500 + + )} +/> +```` + +### DefaultButton (Flutterwave styled button) +preview + +[View All Props](#defaultbuttonprops) + +Import `DefaultButton` from `react-native-flutterwave` and use it like so. +````jsx +import {DefaultButton} from 'react-native-flutterwave'; + + + Pay $500 + +```` + +### Flutterwave Standard Init +When called, this function returns a Promise which resolves to a string on success and rejects if an error occurs. [See all config options](#flutterwaveinitoptions) + +Import `FlutterwaveInit` from `react-native-flutterwave` and use it like so. +````javascript +import {FlutterwaveInit} from 'react-native-flutterwave'; + +try { + // initialize payment + const paymentLink = await FlutterwaveInit({ + tx_ref: generateTransactionRef(), + authorization: '[your merchant secret Key]', + amount: 100, + currency: 'USD', + customer: { + email: 'customer-email@example.com' + }, + payment_options: 'card' + }); + // use payment link + usePaymentLink(paymentLink); +} catch (error) { + // handle payment error + displayError(error.message); +} +```` +### Aborting Payment Initialization +:wave: Hi, so there are cases where you have already initialized a payment with `FlutterwaveInit` but might also want to be able to cancel the payment initialization should in case your component is being unmounted or you want to allow users cancel the action before the payment is initialized, we have provided a way for you to do this... [continue reading](./docs/AbortingPaymentInitialization.md) + +## Props + +### FlutterwaveInitOptions +[See Interface](#flutterwaveinitoptions-interface) +| Name | Required | Type | Default | Description | +| --------- | --------- | ---- | ------- | ----------- | +| authorization | Yes | string | **REQUIRED** | Your merchant secret key, see how to get your [API Keys](https://developer.flutterwave.com/v3.0/docs/api-keys)| +| tx_ref | Yes | string | **REQUIRED** | Your transaction reference. This MUST be unique for every transaction.| +| amount | Yes | string | **REQUIRED** | Amount to charge the customer. | +| currency | No | string | NGN | Currency to charge in. Defaults to NGN. | +| integrity_hash | No | string | undefined | This is a sha256 hash of your FlutterwaveCheckout values, it is used for passing secured values to the payment gateway. | +| payment_options | Yes | string | **REQUIRED** | This specifies the payment options to be displayed e.g - card, mobilemoney, ussd and so on. | +| payment_plan | No | number | undefined | This is the payment plan ID used for [Recurring billing](https://developer.flutterwave.com/v3.0/docs/recurring-billing). | +| redirect_url | Yes | string | **REQUIRED** | 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. **IMPORTANT** This only required when you are directly using [FlutterwaveInit](#flutterwave-standard-init) | +| customer | Yes | [FlutterwaveInitCustomer](#flutterwaveinitcustomer) | **REQUIRED** | This is an object that can contains your customer details. `E.g.'customer': { 'email': 'example@example.com', 'phonenumber': '08012345678', 'name': 'Takeshi Kovacs' }.` | +| subaccounts | No | array of [FlutterwaveInitSubAccount](#flutterwaveinitsubaccount) | undefined | This is an array of objects containing the subaccount IDs to split the payment into. Check out the [Split Payment page](https://developer.flutterwave.com/docs/split-payment) for more info | +| meta | No | array of [FlutterwavePaymentMeta](#flutterwavepaymentmeta) | undefined | This is an object that helps you include additional payment information to your request. `E.g. { 'consumer_id': 23, 'consumer_mac': '92a3-912ba-1192a' }` | +| customizations | No | [FlutterwaveInitCustomizations](#flutterwaveinitcustomizations) | undefined | 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'}` | + +### FlutterwaveButtonProps +[See Interface](#flutterwavebuttonprops-interface) +| Name | Required | Type | Default | Description | +| --------- | --------- | ---- | ------- | ----------- | +| style | No | object | undefined | Used to apply styling to the button.| +| onComplete | Yes | function | **REQUIRED** | Called when a payment is completed successfully or is canceled. The function will receive [redirect params](#redirectparams) as an argument.| +| onWillInitialize | No | function | undefined | This will be called before a payment link is generated.| +| onDidInitialize | No | function | undefined | This is called when a new payment link has been successfully initialized.| +| onInitializeError | No | function | undefined | This is called if an error occurred while initializing a new pyment link. The function will receive [FlutterwaveInitError](#flutterwaveiniterror) | +| onAbort | No | function | undefined | This is called if a user aborts a transaction, a user can abort a transaction when they click on the dialog's backdrop and choose cancel when prompted to cancel transaction. | +| options | Yes | [FlutterwaveInitOptions](#flutterwaveinitoptions) | **REQUIRED** | The option passed here is used to initialize a payment. | +| customButton | No | function | undefined | This is used to render a custom button. The function a prop argument structured like [CustomButtonProps](#custombuttonprops), this function should return a valid React node. | +| alignLeft | No | boolean | undefined | This aligns the content of the button to the left. | + +### DefaultButtonProps +[See Interface](#defaultbuttonprops-interface) +| Name | Required | Type | Default | Description | +| --------- | --------- | ---- | ------- | ----------- | +| style | No | object | undefined | Used to apply styling to the button.| +| onPress | Yes | function | undefined | This | +| disabled | No | boolean | undefined | This disables button, and causes onPress not to be fired.| +| isBusy | No | boolean | undefined | This puts the button in a busy state, making the content look faded.| +| onSizeChange | No | (ev: {width: number; height: number}) => void | undefined | If provided this function is fired whenever the size(height or width) of the button changes | +| children | Yes | ReactElement | undefined | This will be the content rendered within the button, if string is to be direct decendant, remember to put string in the Text component. | +| alignLeft | No | boolean | undefined | This aligns the content of the button to the left. | + +## Types +#### CustomButtonProps +````typescript +interface CustomButtonProps { + disabled: boolean; + isInitializing: boolean; + onPress: () => void; +} +```` + +#### RedirectParams +````typescript +interface RedirectParams { + status: 'successful' | 'cancelled', + transaction_id?: string; + tx_ref: string; +} +```` + +#### FlutterwaveInitError +````typescript +interface FlutterwaveInitError { + code: string; + message: string; + errorId?: string; + errors?: Array; +} +```` + +#### FlutterwavePaymentMeta +````typescript +interface FlutterwavePaymentMeta { + [k: string]: any; +} +```` + +### FlutterwaveInitCustomer +```typescript +interface FlutterwaveInitCustomer { + email: string; + phonenumber?: string; + name?: string; +} +``` + +### FlutterwaveInitCustomizations +```typescript +interface FlutterwaveInitCustomizations { + title?: string; + logo?: string; + description?: string; +} +``` + +### FlutterwaveInitSubAccount +```typescript +interface FlutterwaveInitSubAccount { + id: string; + transaction_split_ratio?: number; + transaction_charge_type?: string; + transaction_charge?: number; +} +``` + +#### FlutterwaveInitOptions Interface +````typescript +export interface FlutterwaveInitOptions { + authorization: string; + tx_ref: string; + amount: number; + currency: string; + integrity_hash?: string; + payment_options?: string; + payment_plan?: number; + redirect_url: string; + customer: FlutterwaveInitCustomer; + subaccounts?: Array; + meta?: Array; + customizations?: FlutterwaveInitCustomizations; +} +```` + +#### FlutterwaveButtonProps Interface +````typescript +interface FlutterwaveButtonProps { + style?: ViewStyle; + onComplete: (data: RedirectParams) => void; + onWillInitialize?: () => void; + onDidInitialize?: () => void; + onInitializeError?: (error: FlutterwaveInitError) => void; + onAbort?: () => void; + options: Omit; + customButton?: (props: CustomButtonProps) => React.ReactNode; + alignLeft?: 'alignLeft' | boolean; +} +```` + +#### DefaultButtonProps Interface +````typescript +interface DefaultButtonProps { + style?: ViewStyle; + onPress?: () => void; + disabled?: boolean; + children: React.ReactElement; + isBusy?: boolean; + onSizeChange?: (ev: {width: number; height: number}) => void; + alignLeft?: 'alignLeft' | boolean, +} +```` + +## Contributing +For information on how you can contribute to this repo, simply [go here](./CONTRIBUTING.md), all contributions are greatly appreciated. + +With love from Flutterwave. :yellow_heart: From 685648a45e686d59e18fac50e961ee1d8c0af9f2 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 11:05:53 +0100 Subject: [PATCH 121/129] docs(readme.v2): add exmaples and types of new v2 implementation --- README.v2.md | 115 ++++++++--------- README.v2.old.md | 324 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 378 insertions(+), 61 deletions(-) create mode 100644 README.v2.old.md diff --git a/README.v2.md b/README.v2.md index 69f4e19..971dc2a 100644 --- a/README.v2.md +++ b/README.v2.md @@ -4,8 +4,8 @@ Easily implement Flutterwave for payments in your React Native appliction. This [![V2 API](https://img.shields.io/badge/API-V2-brightgreen)](https://developer.flutterwave.com/v2.0/docs/getting-started) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)

- ios-preview - android-preview + ios-preview + android-preview

## Table Of Content @@ -16,21 +16,21 @@ Easily implement Flutterwave for payments in your React Native appliction. This - [Activity Indicator (Android)](#activity-indicator-only-needed-for-android) - [Important Information](#fire-important-information-fire) - Usage - - [Flutterwave Button ](#flutterwave-button) - - [Flutterwave Button (with custom render)](#flutterwave-button-with-custom-render) - - [Default Button (Flutterwave styled button)](#defaultbutton-flutterwave-styled-button) - - [Flutterwave Standard Init](#flutterwave-standard-init) + - [PayWithFlutterwaveV2 ](#paywithflutterwavev2) + - [PayWithFlutterwaveV2 (with custom render)](#paywithflutterwavev2-with-custom-render) + - [FlwButton (Flutterwave styled button)](#flwbutton-flutterwave-styled-button) + - [FlutterwaveInitV2](#flutterwaveinitv2) - [Aborting Payment Initialization](#aborting-payment-initialization) - Props - - [Flutterwave Button Props](#flutterwavebuttonprops) - - [Default Button Props](#defaultbuttonprops) - - [Flutterwave Init Options](#flutterwaveinitoptions) + - [FlutterwaveInitV2Options](#flutterwaveinitv2options) + - [PayWithFlutterwaveV2Props](#paywithflutterwavev2props) + - [FlwButton Props](#flwbutton-props) - Types - - [Flutterwave Button Props](#flutterwavebuttonprops-interface) - - [Default Button Props](#defaultbuttonprops-interface) - - [Flutterwave Init Options](#flutterwaveinitoptions-interface) - - [Flutterwave Init Error](#flutterwaveiniterror) - - [FlutterwavePaymentMeta](#flutterwavepaymentmeta) + - [PayWithFlutterwaveV2Props](#paywithflutterwavev2props-interface) + - [FlwButtonProps](#flwbuttonprops-interface) + - [FlutterwaveInitV2Options](#flutterwaveinitv2options-interface) + - [FlutterwaveInitError](#flutterwaveiniterror) + - [FlutterwavePaymentMetaV2](#flutterwavepaymentmetav2) - [RedirectParamsV2](#redirectparamsv2) - [CustomButtonProps](#custombuttonprops) - [Contributing](./CONTRIBUTING.md) @@ -63,7 +63,7 @@ dependencies { ```` ### :fire: IMPORTANT INFORMATION :fire: -If the `options` property on the [FlutterwaveButton](flutterwavebuttonprops-interface) changes, when next the user taps on the button a new payment will be initialized whether the last one was successful or not. +If the `options` property on the [PayWithFlutterwaveV2](flutterwavebuttonprops-interface) changes, when next the user taps on the button a new payment will be initialized whether the last one was successful or not. Remember you cannot use the same transaction reference for two different payments, remember to recreate the transaction reference before allowing the user initiate a new payment. @@ -71,17 +71,17 @@ Remember you cannot use the same transaction reference for two different payment ## Usage Below are a few examples showcasing how you can use the library to implement payment in your React Native app. -### Flutterwave Button +### PayWithFlutterwaveV2 preview [View All Props](#flutterwavebuttonprops) -Import `FlutterwaveButtonV2` from `react-native-flutterwave` and use it like so. +Import `PayWithFlutterwaveV2` from `react-native-flutterwave` and use it like so. ````jsx -import {FlutterwaveButtonV2} from 'react-native-flutterwave'; -// or import FlutterwaveButtonV2 from 'react-native-flutterwave/v2'; +import {PayWithFlutterwaveV2} from 'react-native-flutterwave'; +// or import PayWithFlutterwaveV2 from 'react-native-flutterwave/PayWithFlutterwaveV2'; - ```` -### Flutterwave Button (with custom render) +### PayWithFlutterwaveV2 (with custom render) preview [View All Props](#flutterwavebuttonprops) -Import `FlutterwaveButtonV2` from `react-native-flutterwave` and use it like so. +Import `PayWithFlutterwaveV2` from `react-native-flutterwave` and use it like so. ````jsx -import {FlutterwaveButtonV2} from 'react-native-flutterwave'; -// or import FlutterwaveButtonV2 from 'react-native-flutterwave/v2'; +import {PayWithFlutterwaveV2} from 'react-native-flutterwave'; +// or import PayWithFlutterwaveV2 from 'react-native-flutterwave/PayWithFlutterwaveV2'; - ```` -### DefaultButton (Flutterwave styled button) +### FlwButton (Flutterwave styled button) preview -[View All Props](#defaultbuttonprops) +[View All Props](#flwbuttonprops) -Import `DefaultButton` from `react-native-flutterwave` and use it like so. +Import `FlwButton` from `react-native-flutterwave` and use it like so. ````jsx -import {DefaultButton} from 'react-native-flutterwave'; +import {FlwButton} from 'react-native-flutterwave'; - Pay $500 - + ```` -### Flutterwave Standard Init -When called, this function returns a Promise which resolves to a string on success and rejects if an error occurs. [See all config options](#flutterwaveinitoptions) +### FlutterwaveInitV2 +When called, this function returns a Promise which resolves to a string on success and rejects if an error occurs. [See all config options](#flutterwaveinitv2options) Import `FlutterwaveInitV2` from `react-native-flutterwave` and use it like so. ````javascript import {FlutterwaveInitV2} from 'react-native-flutterwave';; -// or import FlutterwaveInitV2 from 'react-native-flutterwave/v2'; +// or import FlutterwaveInitV2 from 'react-native-flutterwave/FlutterwaveInitV2'; // initialize a new payment const payment = await FlutterwaveInitV2({ @@ -168,12 +167,12 @@ handlePaymentError( ); ```` ### Aborting Payment Initialization -Hi :wave:, so there are cases where you have already initialized a payment with `FlutterwaveInit` but might also want to be able to cancel the payment initialization should in case your component is being unmounted or you want to allow users cancel the action before the payment is initialized, we have provided a way for you to do this... [continue reading](./docs/v2/AbortingPaymentInitialization.md) +Hi :wave:, so there are cases where you have already initialized a payment with `FlutterwaveInitV2` but might also want to be able to cancel the payment initialization should in case your component is being unmounted or you want to allow users cancel the action before the payment is initialized, we have provided a way for you to do this... [continue reading](./docs/v2/AbortingPaymentInitialization.md) ## Props -### FlutterwaveInitOptions -[See Interface](#flutterwaveinitoptions-interface) +### FlutterwaveInitV2Options +[See Interface](#flutterwaveinitv2options-interface) | Name | Required | Type | Default | Description | | --------- | --------- | ---- | ------- | ----------- | | PBFPubKey | Yes | string | **REQUIRED** | Your merchant public key, see how to get your [API Keys](https://developer.flutterwave.com/v2.0/docs/api-keys)| @@ -193,10 +192,10 @@ Hi :wave:, so there are cases where you have already initialized a payment with | custom_title | No | string | undefined | Text to be displayed as the title of the payment modal. | | custom_description | No | string | undefined | Text to be displayed as a short modal description. | | custom_logo | No | string | undefined | Link to the Logo image. | -| meta | No | array of [FlutterwavePaymentMeta](#flutterwavepaymentmeta) | undefined | Any other custom data you wish to pass. | +| meta | No | array of [FlutterwavePaymentMetaV2](#flutterwavepaymentmetav2) | undefined | Any other custom data you wish to pass. | -### FlutterwaveButtonProps -[See Interface](#flutterwavebuttonprops-interface) +### PayWithFlutterwaveV2Props +[See Interface](#paywithflutterwavev2props-interface) | Name | Required | Type | Default | Description | | --------- | --------- | ---- | ------- | ----------- | | style | No | object | undefined | Used to apply styling to the button.| @@ -205,20 +204,17 @@ Hi :wave:, so there are cases where you have already initialized a payment with | onDidInitialize | No | function | undefined | This is called when a new payment link has been successfully initialized.| | onInitializeError | No | function | undefined | This is called if an error occurred while initializing a new pyment link. The function will receive [FlutterwaveInitError](#flutterwaveiniterror) | | onAbort | No | function | undefined | This is called if a user aborts a transaction, a user can abort a transaction when they click on the dialog's backdrop and choose cancel when prompted to cancel transaction. | -| options | Yes | **[FlutterwaveInitOptions](#flutterwaveinitoptions)** | **REQUIRED** | The option passed here is used to initialize a payment. | +| options | Yes | **[FlutterwaveInitOptions](#flutterwaveinitv2options)** | **REQUIRED** | The option passed here is used to initialize a payment. | | customButton | No | function | undefined | This is used to render a custom button. The function a prop argument structured like [CustomButtonProps](#custombuttonprops), this function should return a valid React node. | | alignLeft | No | boolean | undefined | This aligns the content of the button to the left. | -### DefaultButtonProps -[See Interface](#defaultbuttonprops-interface) +### FlwButton Props +[See Interface](#flwbuttonprops-interface) | Name | Required | Type | Default | Description | | --------- | --------- | ---- | ------- | ----------- | -| style | No | object | undefined | Used to apply styling to the button.| -| onPress | Yes | function | undefined | This | +| style | No | ViewStyle | undefined | This component uses the same style properties that are applicable to react-native's View component style.| +| onPress | Yes | function | undefined | This property receive a function that is called on button press. | | disabled | No | boolean | undefined | This disables button, and causes onPress not to be fired.| -| isBusy | No | boolean | undefined | This puts the button in a busy state, making the content look faded.| -| onSizeChange | No | (ev: {width: number; height: number}) => void | undefined | If provided this function is fired whenever the size(height or width) of the button changes | -| children | Yes | ReactElement | undefined | This will be the content rendered within the button, if string is to be direct decendant, remember to put string in the Text component. | | alignLeft | No | boolean | undefined | This aligns the content of the button to the left. | ## Types @@ -258,17 +254,17 @@ interface FlutterwaveInitSubAccount { } ``` -#### FlutterwavePaymentMeta +#### FlutterwavePaymentMetaV2 ````typescript -interface FlutterwavePaymentMeta { +interface FlutterwavePaymentMetaV2 { metaname: string; metavalue: string; } ```` -#### FlutterwaveInitOptions Interface +#### FlutterwaveInitV2Options Interface ````typescript -export interface FlutterwaveInitOptions { +export interface FlutterwaveInitV2Options { txref: string; PBFPubKey: string; customer_firstname?: string; @@ -286,13 +282,13 @@ export interface FlutterwaveInitOptions { custom_title?: string; custom_description?: string; custom_logo?: string; - meta?: Array; + meta?: Array; } ```` -#### FlutterwaveButtonProps Interface +#### PayWithFlutterwaveV2Props Interface ````typescript -interface FlutterwaveButtonProps { +interface PayWithFlutterwaveV2Props { style?: ViewStyle; onComplete: (data: RedirectParamsV2) => void; onWillInitialize?: () => void; @@ -305,15 +301,12 @@ interface FlutterwaveButtonProps { } ```` -#### DefaultButtonProps Interface +#### FlwButtonProps Interface ````typescript -interface DefaultButtonProps { +interface FlwButton { style?: ViewStyle; onPress?: () => void; disabled?: boolean; - children: React.ReactElement; - isBusy?: boolean; - onSizeChange?: (ev: {width: number; height: number}) => void; alignLeft?: 'alignLeft' | boolean, } ```` diff --git a/README.v2.old.md b/README.v2.old.md new file mode 100644 index 0000000..69f4e19 --- /dev/null +++ b/README.v2.old.md @@ -0,0 +1,324 @@ +# React Native Flutterwave +Easily implement Flutterwave for payments in your React Native appliction. This library supports both Android and iOS, and use the Flutterwave's V2 API. + +[![V2 API](https://img.shields.io/badge/API-V2-brightgreen)](https://developer.flutterwave.com/v2.0/docs/getting-started) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) + +

+ ios-preview + android-preview +

+ +## Table Of Content +- Getting Started + - [V3 API](#warning-if-using-version-3-api-warning) + - [Installation](#installation) + - [Dependencies](#dependencies) + - [Activity Indicator (Android)](#activity-indicator-only-needed-for-android) + - [Important Information](#fire-important-information-fire) +- Usage + - [Flutterwave Button ](#flutterwave-button) + - [Flutterwave Button (with custom render)](#flutterwave-button-with-custom-render) + - [Default Button (Flutterwave styled button)](#defaultbutton-flutterwave-styled-button) + - [Flutterwave Standard Init](#flutterwave-standard-init) + - [Aborting Payment Initialization](#aborting-payment-initialization) +- Props + - [Flutterwave Button Props](#flutterwavebuttonprops) + - [Default Button Props](#defaultbuttonprops) + - [Flutterwave Init Options](#flutterwaveinitoptions) +- Types + - [Flutterwave Button Props](#flutterwavebuttonprops-interface) + - [Default Button Props](#defaultbuttonprops-interface) + - [Flutterwave Init Options](#flutterwaveinitoptions-interface) + - [Flutterwave Init Error](#flutterwaveiniterror) + - [FlutterwavePaymentMeta](#flutterwavepaymentmeta) + - [RedirectParamsV2](#redirectparamsv2) + - [CustomButtonProps](#custombuttonprops) +- [Contributing](./CONTRIBUTING.md) + +## What's Inside? +- Pay with Flutterwave button and checkout dialog. +- Standard payment initialization function. +- Flutterwave designed button. + +## :warning: If Using Version 3 API :warning: +This version of the library's docs focuses on use cases with the Version 2 of Flutterwaves API, if you are using the Version 3 API please use [this documentation](./README.md) instead. + +## Installation +This library is available on npm, you can install it by running `npm install --save react-native-flutterwave` or `yarn add react-native-flutterwave` + +### Dependencies +In order to render the Flutterwave checkout screen this library depends on [react-native-webview](https://github.com/react-native-community/react-native-webview) ensure you properly install this library before continuing. + +### Activity Indicator (only needed for android) +To display the Flutterwave styled activity indicator when the checkout screen is being loaded on Android you will need to add some modules in `android/app/build.gradle`. +***Skip this if you already have setup your app to support gif images.*** +````javascript +dependencies { + // If your app supports Android versions before Ice Cream Sandwich (API level 14) + implementation 'com.facebook.fresco:animated-base-support:1.3.0' + + // For animated GIF support + implementation 'com.facebook.fresco:animated-gif:2.0.0' +} +```` + +### :fire: IMPORTANT INFORMATION :fire: +If the `options` property on the [FlutterwaveButton](flutterwavebuttonprops-interface) changes, when next the user taps on the button a new payment will be initialized whether the last one was successful or not. + +Remember you cannot use the same transaction reference for two different payments, remember to recreate the transaction reference before allowing the user initiate a new payment. + + +## Usage +Below are a few examples showcasing how you can use the library to implement payment in your React Native app. + +### Flutterwave Button +preview + +[View All Props](#flutterwavebuttonprops) + +Import `FlutterwaveButtonV2` from `react-native-flutterwave` and use it like so. +````jsx +import {FlutterwaveButtonV2} from 'react-native-flutterwave'; +// or import FlutterwaveButtonV2 from 'react-native-flutterwave/v2'; + + +```` + +### Flutterwave Button (with custom render) +preview + +[View All Props](#flutterwavebuttonprops) + +Import `FlutterwaveButtonV2` from `react-native-flutterwave` and use it like so. +````jsx +import {FlutterwaveButtonV2} from 'react-native-flutterwave'; +// or import FlutterwaveButtonV2 from 'react-native-flutterwave/v2'; + + ( + + Pay $500 + + )} +/> +```` + +### DefaultButton (Flutterwave styled button) +preview + +[View All Props](#defaultbuttonprops) + +Import `DefaultButton` from `react-native-flutterwave` and use it like so. +````jsx +import {DefaultButton} from 'react-native-flutterwave'; + + + Pay $500 + +```` + +### Flutterwave Standard Init +When called, this function returns a Promise which resolves to a string on success and rejects if an error occurs. [See all config options](#flutterwaveinitoptions) + +Import `FlutterwaveInitV2` from `react-native-flutterwave` and use it like so. +````javascript +import {FlutterwaveInitV2} from 'react-native-flutterwave';; +// or import FlutterwaveInitV2 from 'react-native-flutterwave/v2'; + +// initialize a new payment +const payment = await FlutterwaveInitV2({ + txref: generateTransactionRef(), + PBFPubKey: '[Your Flutterwave Public Key]', + amount: 100, + currency: 'USD', +}); + +// link is available if payment initialized successfully +if (payment.link) { + // use payment link + return usePaymentLink(payment.link); +} + +// handle payment error +handlePaymentError( + payment.error + ? paymet.error.message + : 'Kai, an unknown error occurred!' +); +```` +### Aborting Payment Initialization +Hi :wave:, so there are cases where you have already initialized a payment with `FlutterwaveInit` but might also want to be able to cancel the payment initialization should in case your component is being unmounted or you want to allow users cancel the action before the payment is initialized, we have provided a way for you to do this... [continue reading](./docs/v2/AbortingPaymentInitialization.md) + +## Props + +### FlutterwaveInitOptions +[See Interface](#flutterwaveinitoptions-interface) +| Name | Required | Type | Default | Description | +| --------- | --------- | ---- | ------- | ----------- | +| PBFPubKey | Yes | string | **REQUIRED** | Your merchant public key, see how to get your [API Keys](https://developer.flutterwave.com/v2.0/docs/api-keys)| +| txref | Yes | string | **REQUIRED** | Your Unique transaction reference.| +| customer_email | Yes | string | **REQUIRED** | The customer's email address. | +| customer_phone | No | string | undefined | The customer's phone number. | +| customer_firstname | No | string | undefined | The customer's first name. | +| customer_lastname | No | string | undefined | The customer's last name. | +| amount | Yes | number | undefined | Amount to charge the customer.| +| currency | No | string | NGN | Currency to charge in. Defaults to NGN. Check our [International Payments](https://developer.flutterwave.com/v2.0/docs/multicurrency-payments) section for more on international currencies.| +| redirect_url | No | string | undefined | 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. | +| payment_options | No | string | undefined | This allows you to select the payment option you want for your users, see [Choose Payment Methods](https://developer.flutterwave.com/v2.0/docs/splitting-payment-methods) for more info. | +| payment_plan | No | number | undefined | This is the payment plan ID used for [Recurring billing](https://developer.flutterwave.com/v2.0/docs/recurring-billing). | +| subaccounts | No | array of [FlutterwaveInitSubAccount](#flutterwaveinitsubaccount) | undefined | This is an array of objects containing the subaccount IDs to [split the payment](https://developer.flutterwave.com/v2.0/docs/split-payment) into. | +| country | No | string | NG | Route country. Defaults to NG | +| pay_button_text | No | string | undefined | Text to be displayed on the Rave Checkout Button. | +| custom_title | No | string | undefined | Text to be displayed as the title of the payment modal. | +| custom_description | No | string | undefined | Text to be displayed as a short modal description. | +| custom_logo | No | string | undefined | Link to the Logo image. | +| meta | No | array of [FlutterwavePaymentMeta](#flutterwavepaymentmeta) | undefined | Any other custom data you wish to pass. | + +### FlutterwaveButtonProps +[See Interface](#flutterwavebuttonprops-interface) +| Name | Required | Type | Default | Description | +| --------- | --------- | ---- | ------- | ----------- | +| style | No | object | undefined | Used to apply styling to the button.| +| onComplete | Yes | function | **REQUIRED** | Called when a payment is completed successfully or is canceled. The function will receive [on complete data](#oncompletedata)| +| onWillInitialize | No | function | undefined | This will be called before a payment link is generated.| +| onDidInitialize | No | function | undefined | This is called when a new payment link has been successfully initialized.| +| onInitializeError | No | function | undefined | This is called if an error occurred while initializing a new pyment link. The function will receive [FlutterwaveInitError](#flutterwaveiniterror) | +| onAbort | No | function | undefined | This is called if a user aborts a transaction, a user can abort a transaction when they click on the dialog's backdrop and choose cancel when prompted to cancel transaction. | +| options | Yes | **[FlutterwaveInitOptions](#flutterwaveinitoptions)** | **REQUIRED** | The option passed here is used to initialize a payment. | +| customButton | No | function | undefined | This is used to render a custom button. The function a prop argument structured like [CustomButtonProps](#custombuttonprops), this function should return a valid React node. | +| alignLeft | No | boolean | undefined | This aligns the content of the button to the left. | + +### DefaultButtonProps +[See Interface](#defaultbuttonprops-interface) +| Name | Required | Type | Default | Description | +| --------- | --------- | ---- | ------- | ----------- | +| style | No | object | undefined | Used to apply styling to the button.| +| onPress | Yes | function | undefined | This | +| disabled | No | boolean | undefined | This disables button, and causes onPress not to be fired.| +| isBusy | No | boolean | undefined | This puts the button in a busy state, making the content look faded.| +| onSizeChange | No | (ev: {width: number; height: number}) => void | undefined | If provided this function is fired whenever the size(height or width) of the button changes | +| children | Yes | ReactElement | undefined | This will be the content rendered within the button, if string is to be direct decendant, remember to put string in the Text component. | +| alignLeft | No | boolean | undefined | This aligns the content of the button to the left. | + +## Types +#### CustomButtonProps +````typescript +interface CustomButtonProps { + disabled: boolean; + isInitializing: boolean; + onPress: () => void; +} +```` + +#### RedirectParamsV2 +````typescript +interface RedirectParamsV2 { + canceled?: 'true' | 'false'; + flwref?: string; + txref: string; +} +```` + +#### FlutterwaveInitError +````typescript +interface FlutterwaveInitError { + code: string; + message: string; +} +```` + +### FlutterwaveInitSubAccount +```typescript +interface FlutterwaveInitSubAccount { + id: string; + transaction_split_ratio?: number; + transaction_charge_type?: string; + transaction_charge?: number; +} +``` + +#### FlutterwavePaymentMeta +````typescript +interface FlutterwavePaymentMeta { + metaname: string; + metavalue: string; +} +```` + +#### FlutterwaveInitOptions Interface +````typescript +export interface FlutterwaveInitOptions { + txref: string; + PBFPubKey: string; + customer_firstname?: string; + customer_lastname?: string; + customer_phone?: string; + customer_email: string; + amount: number; + currency?: string; + redirect_url?: string; + payment_options?: string; + payment_plan?: number; + subaccounts?: Array; + country?: string; + pay_button_text?: string; + custom_title?: string; + custom_description?: string; + custom_logo?: string; + meta?: Array; +} +```` + +#### FlutterwaveButtonProps Interface +````typescript +interface FlutterwaveButtonProps { + style?: ViewStyle; + onComplete: (data: RedirectParamsV2) => void; + onWillInitialize?: () => void; + onDidInitialize?: () => void; + onInitializeError?: (error: FlutterwaveInitError) => void; + onAbort?: () => void; + options: Omit; + customButton?: (props: CustomButtonProps) => React.ReactNode; + alignLeft?: 'alignLeft' | boolean; +} +```` + +#### DefaultButtonProps Interface +````typescript +interface DefaultButtonProps { + style?: ViewStyle; + onPress?: () => void; + disabled?: boolean; + children: React.ReactElement; + isBusy?: boolean; + onSizeChange?: (ev: {width: number; height: number}) => void; + alignLeft?: 'alignLeft' | boolean, +} +```` + +## Contributing +For information on how you can contribute to this repo, simply [go here](./CONTRIBUTING.md), all contributions are greatly appreciated. + +With love from Flutterwave. :yellow_heart: From a1946f9bb9d9460e5e06675d06ed4294f23d4152 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Mon, 3 Aug 2020 11:11:42 +0100 Subject: [PATCH 122/129] docs(readme): correct meta description on init options table --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 681637b..80df7ee 100644 --- a/README.md +++ b/README.md @@ -188,7 +188,7 @@ try { | redirect_url | Yes | string | **REQUIRED** | 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. **IMPORTANT** This only required when you are directly using [FlutterwaveInit](#flutterwave-standard-init) | | customer | Yes | [FlutterwaveInitCustomer](#flutterwaveinitcustomer) | **REQUIRED** | This is an object that can contains your customer details. `E.g.'customer': { 'email': 'example@example.com', 'phonenumber': '08012345678', 'name': 'Takeshi Kovacs' }.` | | subaccounts | No | array of [FlutterwaveInitSubAccount](#flutterwaveinitsubaccount) | undefined | This is an array of objects containing the subaccount IDs to split the payment into. Check out the [Split Payment page](https://developer.flutterwave.com/docs/split-payment) for more info | -| meta | No | array of [FlutterwavePaymentMeta](#flutterwavepaymentmeta) | undefined | This is an object that helps you include additional payment information to your request. `E.g. { 'consumer_id': 23, 'consumer_mac': '92a3-912ba-1192a' }` | +| meta | No | [FlutterwavePaymentMeta](#flutterwavepaymentmeta) | undefined | This is an object that helps you include additional payment information to your request. `E.g. { 'consumer_id': 23, 'consumer_mac': '92a3-912ba-1192a' }` | | customizations | No | [FlutterwaveInitCustomizations](#flutterwaveinitcustomizations) | undefined | 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'}` | ### PayWithFlutterwaveProps From 7db830a4a8f38746ddd37d87f47b8495b9185530 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Wed, 5 Aug 2020 09:34:32 +0100 Subject: [PATCH 123/129] feat(flwbutton): rename to FlutterwaveButton --- README.md | 26 +++++++++---------- README.v2.md | 26 +++++++++---------- ...on.spec.tsx => FlutterwaveButton.spec.tsx} | 18 ++++++------- ...x.snap => FlutterwaveButton.spec.tsx.snap} | 8 +++--- ...{FlwButton.d.ts => FlutterwaveButton.d.ts} | 8 +++--- dist/FlutterwaveButton.d.ts.map | 1 + dist/{FlwButton.js => FlutterwaveButton.js} | 4 +-- dist/FlwButton.d.ts.map | 1 - dist/PaywithFlutterwaveBase.js | 4 +-- dist/index.d.ts | 4 +-- dist/index.d.ts.map | 2 +- dist/index.js | 4 +-- src/{FlwButton.tsx => FlutterwaveButton.tsx} | 10 +++---- src/PaywithFlutterwaveBase.tsx | 4 +-- src/index.ts | 4 +-- 15 files changed, 62 insertions(+), 62 deletions(-) rename __tests__/{FlwButton.spec.tsx => FlutterwaveButton.spec.tsx} (76%) rename __tests__/__snapshots__/{FlwButton.spec.tsx.snap => FlutterwaveButton.spec.tsx.snap} (92%) rename dist/{FlwButton.d.ts => FlutterwaveButton.d.ts} (51%) create mode 100644 dist/FlutterwaveButton.d.ts.map rename dist/{FlwButton.js => FlutterwaveButton.js} (95%) delete mode 100644 dist/FlwButton.d.ts.map rename src/{FlwButton.tsx => FlutterwaveButton.tsx} (92%) diff --git a/README.md b/README.md index 80df7ee..ea232f1 100644 --- a/README.md +++ b/README.md @@ -18,16 +18,16 @@ Easily implement Flutterwave for payments in your React Native appliction. This - Usage - [PayWithFlutterwave ](#flutterwave-button) - [PayWithFlutterwave (with custom render)](#flutterwave-button-with-custom-render) - - [FlwButton (Flutterwave styled button)](#flwbutton-flutterwave-styled-button) + - [FlutterwaveButton (Flutterwave styled button)](#flutterwavebutton-flutterwave-styled-button) - [FlutterwaveInit](#flutterwaveinit) - [Aborting Payment Initialization](#aborting-payment-initialization) - Props - [FlutterwaveInitOptions](#flutterwaveinitoptions) - [PayWithFlutterwaveProps](#flutterwavebuttonprops) - - [FlwButtonProps](#flwbutton-props) + - [FlutterwaveButtonProps](#flutterwavebutton-props) - Types - [PayWithFlutterwaveProps](#paywithflutterwaveprops-interface) - - [FlwButtonProps](#flwbuttonprops-interface) + - [FlutterwaveButtonProps](#flutterwavebuttonprops-interface) - [FlutterwaveInitCustomer](#flutterwaveinitcustomer) - [FlutterwaveInitCustomization](#flutterwaveinitcustomization) - [FlutterwaveInitSubAccount](#flutterwaveinitsubaccount) @@ -126,21 +126,21 @@ import {PayWithFlutterwave} from 'react-native-flutterwave'; /> ```` -### FlwButton (Flutterwave styled button) +### FlutterwaveButton (Flutterwave styled button) preview -[View All Props](#flwbuttonprops) +[View All Props](#flutterwavebuttonprops) -Import `FlwButton` from `react-native-flutterwave` and use it like so. +Import `FlutterwaveButton` from `react-native-flutterwave` and use it like so. ````jsx -import {FlwButton} from 'react-native-flutterwave'; +import {FlutterwaveButton} from 'react-native-flutterwave'; - Pay $500 - +
```` ### FlutterwaveInit @@ -205,8 +205,8 @@ try { | customButton | No | function | undefined | This is used to render a custom button. The function a prop argument structured like [CustomButtonProps](#custombuttonprops), this function should return a valid React node. | | alignLeft | No | boolean | undefined | This aligns the content of the button to the left. | -### FlwButton Props -[See Interface](#flwbuttonprops-interface) +### FlutterwaveButton Props +[See Interface](#flutterwavebuttonprops-interface) | Name | Required | Type | Default | Description | | --------- | --------- | ---- | ------- | ----------- | | style | No | ViewStyle | undefined | This component uses the same style properties that are applicable to react-native's View component style.| @@ -311,9 +311,9 @@ interface PayWithFlutterwaveProps { } ```` -#### FlwButtonProps Interface +#### FlutterwaveButtonProps Interface ````typescript -interface FlwButtonProps { +interface FlutterwaveButtonProps { style?: StyleProp; disabled?: boolean; alignLeft?: boolean; diff --git a/README.v2.md b/README.v2.md index 971dc2a..f321908 100644 --- a/README.v2.md +++ b/README.v2.md @@ -18,16 +18,16 @@ Easily implement Flutterwave for payments in your React Native appliction. This - Usage - [PayWithFlutterwaveV2 ](#paywithflutterwavev2) - [PayWithFlutterwaveV2 (with custom render)](#paywithflutterwavev2-with-custom-render) - - [FlwButton (Flutterwave styled button)](#flwbutton-flutterwave-styled-button) + - [FlutterwaveButton (Flutterwave styled button)](#flutterwavebutton-flutterwave-styled-button) - [FlutterwaveInitV2](#flutterwaveinitv2) - [Aborting Payment Initialization](#aborting-payment-initialization) - Props - [FlutterwaveInitV2Options](#flutterwaveinitv2options) - [PayWithFlutterwaveV2Props](#paywithflutterwavev2props) - - [FlwButton Props](#flwbutton-props) + - [FlutterwaveButton Props](#flutterwavebutton-props) - Types - [PayWithFlutterwaveV2Props](#paywithflutterwavev2props-interface) - - [FlwButtonProps](#flwbuttonprops-interface) + - [FlutterwaveButtonProps](#flutterwavebuttonprops-interface) - [FlutterwaveInitV2Options](#flutterwaveinitv2options-interface) - [FlutterwaveInitError](#flutterwaveiniterror) - [FlutterwavePaymentMetaV2](#flutterwavepaymentmetav2) @@ -120,21 +120,21 @@ import {PayWithFlutterwaveV2} from 'react-native-flutterwave'; /> ```` -### FlwButton (Flutterwave styled button) +### FlutterwaveButton (Flutterwave styled button) preview -[View All Props](#flwbuttonprops) +[View All Props](#flutterwavebuttonprops) -Import `FlwButton` from `react-native-flutterwave` and use it like so. +Import `FlutterwaveButton` from `react-native-flutterwave` and use it like so. ````jsx -import {FlwButton} from 'react-native-flutterwave'; +import {FlutterwaveButton} from 'react-native-flutterwave'; - Pay $500 - +
```` ### FlutterwaveInitV2 @@ -208,8 +208,8 @@ Hi :wave:, so there are cases where you have already initialized a payment with | customButton | No | function | undefined | This is used to render a custom button. The function a prop argument structured like [CustomButtonProps](#custombuttonprops), this function should return a valid React node. | | alignLeft | No | boolean | undefined | This aligns the content of the button to the left. | -### FlwButton Props -[See Interface](#flwbuttonprops-interface) +### FlutterwaveButton Props +[See Interface](#flutterwavebuttonprops-interface) | Name | Required | Type | Default | Description | | --------- | --------- | ---- | ------- | ----------- | | style | No | ViewStyle | undefined | This component uses the same style properties that are applicable to react-native's View component style.| @@ -301,9 +301,9 @@ interface PayWithFlutterwaveV2Props { } ```` -#### FlwButtonProps Interface +#### FlutterwaveButtonProps Interface ````typescript -interface FlwButton { +interface FlutterwaveButton { style?: ViewStyle; onPress?: () => void; disabled?: boolean; diff --git a/__tests__/FlwButton.spec.tsx b/__tests__/FlutterwaveButton.spec.tsx similarity index 76% rename from __tests__/FlwButton.spec.tsx rename to __tests__/FlutterwaveButton.spec.tsx index 7151224..94253c1 100644 --- a/__tests__/FlwButton.spec.tsx +++ b/__tests__/FlutterwaveButton.spec.tsx @@ -2,27 +2,27 @@ import 'react-native'; import React from 'react'; import {Text} from 'react-native'; import renderer from 'react-test-renderer'; -import FlwButton from '../src/FlwButton'; +import FlutterwaveButton from '../src/FlutterwaveButton'; const testID = 'flw-button'; -describe('', () => { +describe('', () => { it('renders pay with flutterwave by default', () => { // create test renderer - const TestRenderer = renderer.create(); + const TestRenderer = renderer.create(); // run assertions expect(TestRenderer.toJSON()).toMatchSnapshot(); }); it('renders with overlay and inactive style of button is disabled', () => { // create test renderer - const TestRenderer = renderer.create(); + const TestRenderer = renderer.create(); // run assertions expect(TestRenderer.toJSON()).toMatchSnapshot(); }); it('applies left aligned style if alignLeft prop is present', () => { // create test renderer - const TestRenderer = renderer.create(); + const TestRenderer = renderer.create(); // run assertions expect(TestRenderer.toJSON()).toMatchSnapshot(); }); @@ -30,9 +30,9 @@ describe('', () => { it('replaces pay with flutterwave image with children', () => { // create test renderer const TestRenderer = renderer.create( - + Hello, World! - + ); // run assertions expect(TestRenderer.toJSON()).toMatchSnapshot(); @@ -43,9 +43,9 @@ describe('', () => { const onPress = jest.fn(); // create test renderer const TestRenderer = renderer.create( - + Hello, World! - + ); // fire onPress function TestRenderer.root.findByProps({testID}).props.onPress(); diff --git a/__tests__/__snapshots__/FlwButton.spec.tsx.snap b/__tests__/__snapshots__/FlutterwaveButton.spec.tsx.snap similarity index 92% rename from __tests__/__snapshots__/FlwButton.spec.tsx.snap rename to __tests__/__snapshots__/FlutterwaveButton.spec.tsx.snap index ba17517..fc9a708 100644 --- a/__tests__/__snapshots__/FlwButton.spec.tsx.snap +++ b/__tests__/__snapshots__/FlutterwaveButton.spec.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` applies left aligned style if alignLeft prop is present 1`] = ` +exports[` applies left aligned style if alignLeft prop is present 1`] = ` applies left aligned style if alignLeft prop is present 1` `; -exports[` renders pay with flutterwave by default 1`] = ` +exports[` renders pay with flutterwave by default 1`] = ` renders pay with flutterwave by default 1`] = ` `; -exports[` renders with overlay and inactive style of button is disabled 1`] = ` +exports[` renders with overlay and inactive style of button is disabled 1`] = ` renders with overlay and inactive style of button is disab `; -exports[` replaces pay with flutterwave image with children 1`] = ` +exports[` replaces pay with flutterwave image with children 1`] = ` ; disabled?: boolean; alignLeft?: boolean; onPress?: () => void; } -declare const FlwButton: React.FC; -export default FlwButton; -//# sourceMappingURL=FlwButton.d.ts.map \ No newline at end of file +declare const FlutterwaveButton: React.FC; +export default FlutterwaveButton; +//# sourceMappingURL=FlutterwaveButton.d.ts.map \ No newline at end of file diff --git a/dist/FlutterwaveButton.d.ts.map b/dist/FlutterwaveButton.d.ts.map new file mode 100644 index 0000000..037a901 --- /dev/null +++ b/dist/FlutterwaveButton.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"FlutterwaveButton.d.ts","sourceRoot":"","sources":["../src/FlutterwaveButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAGL,SAAS,EACT,SAAS,EAGV,MAAM,cAAc,CAAC;AAKtB,UAAU,sBAAsB;IAC9B,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,QAAA,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAC/B,sBAAsB,CAsCvB,CAAA;AAuCD,eAAe,iBAAiB,CAAC"} \ No newline at end of file diff --git a/dist/FlwButton.js b/dist/FlutterwaveButton.js similarity index 95% rename from dist/FlwButton.js rename to dist/FlutterwaveButton.js index 7bf0408..cd64bc1 100644 --- a/dist/FlwButton.js +++ b/dist/FlutterwaveButton.js @@ -3,7 +3,7 @@ import { StyleSheet, Image, TouchableHighlight, View, } from 'react-native'; import { colors } from './configs'; var pryContent = require('./assets/pry-button-content.png'); var contentSizeDimension = 8.2962962963; -var FlwButton = function FlwButton(_a) { +var FlutterwaveButton = function FlutterwaveButton(_a) { var style = _a.style, alignLeft = _a.alignLeft, children = _a.children, disabled = _a.disabled, onPress = _a.onPress; // render primary button return (; + return ; }; return PayWithFlutterwaveBase; }(React.Component)); diff --git a/dist/index.d.ts b/dist/index.d.ts index e7c3bd8..4b74a85 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -2,8 +2,8 @@ import FlutterwaveInit from './FlutterwaveInit'; import FlutterwaveInitV2 from './FlutterwaveInitV2'; import PayWithFlutterwave from './PayWithFlutterwave'; import PayWithFlutterwaveV2 from './PayWithFlutterwaveV2'; -import FlwButton from './FlwButton'; +import FlutterwaveButton from './FlutterwaveButton'; import FlwCheckout from './FlwCheckout'; -export { FlutterwaveInit, PayWithFlutterwave, FlutterwaveInitV2, PayWithFlutterwaveV2, FlwButton, FlwCheckout, }; +export { FlutterwaveInit, PayWithFlutterwave, FlutterwaveInitV2, PayWithFlutterwaveV2, FlutterwaveButton, FlwCheckout, }; export default PayWithFlutterwave; //# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/index.d.ts.map b/dist/index.d.ts.map index 65ef3ac..dd03b8f 100644 --- a/dist/index.d.ts.map +++ b/dist/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAChD,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,kBAAkB,MAAM,sBAAsB,CAAC;AACtD,OAAO,oBAAoB,MAAM,wBAAwB,CAAC;AAC1D,OAAO,SAAS,MAAM,aAAa,CAAC;AACpC,OAAO,WAAW,MAAM,eAAe,CAAC;AAGxC,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,SAAS,EACT,WAAW,GACZ,CAAC;AAGF,eAAe,kBAAkB,CAAC"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAChD,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,kBAAkB,MAAM,sBAAsB,CAAC;AACtD,OAAO,oBAAoB,MAAM,wBAAwB,CAAC;AAC1D,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,WAAW,MAAM,eAAe,CAAC;AAGxC,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,WAAW,GACZ,CAAC;AAGF,eAAe,kBAAkB,CAAC"} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index efafc0e..169b619 100644 --- a/dist/index.js +++ b/dist/index.js @@ -2,9 +2,9 @@ import FlutterwaveInit from './FlutterwaveInit'; import FlutterwaveInitV2 from './FlutterwaveInitV2'; import PayWithFlutterwave from './PayWithFlutterwave'; import PayWithFlutterwaveV2 from './PayWithFlutterwaveV2'; -import FlwButton from './FlwButton'; +import FlutterwaveButton from './FlutterwaveButton'; import FlwCheckout from './FlwCheckout'; // export modules -export { FlutterwaveInit, PayWithFlutterwave, FlutterwaveInitV2, PayWithFlutterwaveV2, FlwButton, FlwCheckout, }; +export { FlutterwaveInit, PayWithFlutterwave, FlutterwaveInitV2, PayWithFlutterwaveV2, FlutterwaveButton, FlwCheckout, }; // export v3 PayWithFlutterwave as default export default PayWithFlutterwave; diff --git a/src/FlwButton.tsx b/src/FlutterwaveButton.tsx similarity index 92% rename from src/FlwButton.tsx rename to src/FlutterwaveButton.tsx index 22f1e03..a31d6dc 100644 --- a/src/FlwButton.tsx +++ b/src/FlutterwaveButton.tsx @@ -11,16 +11,16 @@ import {colors} from './configs'; const pryContent = require('./assets/pry-button-content.png'); const contentSizeDimension = 8.2962962963; -interface FlwButtonProps { +interface FlutterwaveButtonProps { style?: StyleProp; disabled?: boolean; alignLeft?: boolean; onPress?: () => void; } -const FlwButton: React.FC< - FlwButtonProps -> = function FlwButton({ +const FlutterwaveButton: React.FC< + FlutterwaveButtonProps +> = function FlutterwaveButton({ style, alignLeft, children, @@ -96,4 +96,4 @@ const styles = StyleSheet.create({ }); // export component as default -export default FlwButton; +export default FlutterwaveButton; diff --git a/src/PaywithFlutterwaveBase.tsx b/src/PaywithFlutterwaveBase.tsx index 152411e..79ff480 100644 --- a/src/PaywithFlutterwaveBase.tsx +++ b/src/PaywithFlutterwaveBase.tsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import FlutterwaveInitError from './utils/FlutterwaveInitError'; import FlwCheckout from './FlwCheckout'; -import FlwButton from './FlwButton'; +import FlutterwaveButton from './FlutterwaveButton'; import {REDIRECT_URL} from './configs'; import { StyleProp, ViewStyle } from 'react-native'; @@ -245,7 +245,7 @@ class PayWithFlutterwaveBase

extends React.Component< onPress: this.handleInit }); } - return Date: Wed, 5 Aug 2020 09:39:18 +0100 Subject: [PATCH 124/129] feat(flwcheckout): change name to FlutterwaveCheckout --- ....spec.tsx => FlutterwaveCheckout.spec.tsx} | 32 +++++++++---------- __tests__/PayWithFlutterwaveBase.spec.tsx | 4 +-- ...snap => FlutterwaveCheckout.spec.tsx.snap} | 4 +-- dist/FlutterwaveCheckout.d.ts | 15 +++++++++ dist/FlutterwaveCheckout.d.ts.map | 1 + ...{FlwCheckout.js => FlutterwaveCheckout.js} | 14 ++++---- dist/FlwCheckout.d.ts | 15 --------- dist/FlwCheckout.d.ts.map | 1 - dist/PaywithFlutterwaveBase.js | 4 +-- dist/index.d.ts | 4 +-- dist/index.d.ts.map | 2 +- dist/index.js | 4 +-- ...lwCheckout.tsx => FlutterwaveCheckout.tsx} | 26 +++++++-------- src/PaywithFlutterwaveBase.tsx | 4 +-- src/index.ts | 4 +-- 15 files changed, 67 insertions(+), 67 deletions(-) rename __tests__/{FlwCheckout.spec.tsx => FlutterwaveCheckout.spec.tsx} (86%) rename __tests__/__snapshots__/{FlwCheckout.spec.tsx.snap => FlutterwaveCheckout.spec.tsx.snap} (97%) create mode 100644 dist/FlutterwaveCheckout.d.ts create mode 100644 dist/FlutterwaveCheckout.d.ts.map rename dist/{FlwCheckout.js => FlutterwaveCheckout.js} (92%) delete mode 100644 dist/FlwCheckout.d.ts delete mode 100644 dist/FlwCheckout.d.ts.map rename src/{FlwCheckout.tsx => FlutterwaveCheckout.tsx} (89%) diff --git a/__tests__/FlwCheckout.spec.tsx b/__tests__/FlutterwaveCheckout.spec.tsx similarity index 86% rename from __tests__/FlwCheckout.spec.tsx rename to __tests__/FlutterwaveCheckout.spec.tsx index bab3cdd..e39db2a 100644 --- a/__tests__/FlwCheckout.spec.tsx +++ b/__tests__/FlutterwaveCheckout.spec.tsx @@ -2,7 +2,7 @@ import 'react-native'; import React from 'react'; import {TouchableOpacity, Alert} from 'react-native'; import renderer from 'react-test-renderer'; -import FlwCheckout, {FlwCheckoutError} from '../src/FlwCheckout'; +import FlutterwaveCheckout, {FlutterwaveCheckoutError} from '../src/FlutterwaveCheckout'; import timeTravel, {setupTimeTravel} from '../timeTravel'; import {REDIRECT_URL} from '../src/configs'; import WebView from 'react-native-webview'; @@ -10,11 +10,11 @@ const link = 'http://example.com'; beforeEach(() => setupTimeTravel()); afterEach(() => jest.useRealTimers()); -describe('', () => { +describe('', () => { it('renders with modal closed if visible prop is not true', () => { // create test renderer const TestRenderer = renderer.create( - {}} /> + {}} /> ); // run assertions expect(TestRenderer.toJSON()).toMatchSnapshot(); @@ -23,7 +23,7 @@ describe('', () => { it('renders with modal open if visible props is true', () => { // create test renderer const TestRenderer = renderer.create( - {}} visible /> + {}} visible /> ); // simulate animation timeframes timeTravel(); @@ -36,7 +36,7 @@ describe('', () => { const url = REDIRECT_URL + '?foo=bar'; // create test renderer const TestRenderer = renderer.create( - + ); // fire on navigation state change TestRenderer.root.findByType(WebView).props.onNavigationStateChange({url}); @@ -58,7 +58,7 @@ describe('', () => { const url = 'http://example/com?foo=bar'; // create test renderer const TestRenderer = renderer.create( - + ); // fire on navigation state change TestRenderer.root.findByType(WebView).props.onNavigationStateChange({url}); @@ -76,7 +76,7 @@ describe('', () => { it('asks user to confirm abort when use taps on the backdrop', () => { // create test renderer const TestRenderer = renderer.create( - {}} visible link={link} /> + {}} visible link={link} /> ); // call backdrop onPress TestRenderer.root.findByProps({testID: 'flw-checkout-backdrop'}).props.onPress(); @@ -93,7 +93,7 @@ describe('', () => { const onAbort = jest.fn(); // create test renderer const TestRenderer = renderer.create( - {}} onAbort={onAbort} visible link={link} /> + {}} onAbort={onAbort} visible link={link} /> ); // call backdrop onPress TestRenderer.root.findByProps({testID: 'flw-checkout-backdrop'}).props.onPress(); @@ -116,7 +116,7 @@ describe('', () => { const onAbort = jest.fn(); // create test renderer const TestRenderer = renderer.create( - {}} onAbort={onAbort} visible link={link} /> + {}} onAbort={onAbort} visible link={link} /> ); // create error test renderer const ErrorTestRenderer = renderer.create( @@ -130,7 +130,7 @@ describe('', () => { const onAbort = jest.fn(); // create test renderer const TestRenderer = renderer.create( - {}} onAbort={onAbort} visible link={link} /> + {}} onAbort={onAbort} visible link={link} /> ); // create error test renderer const ErrorTestRenderer = renderer.create( @@ -144,7 +144,7 @@ describe('', () => { // const onAbort = jest.fn(); // // create test renderer // const TestRenderer = renderer.create( - // {}} // onAbort={onAbort} // visible @@ -172,7 +172,7 @@ describe('', () => { const split = jest.spyOn(url, 'split'); // create test renderer const TestRenderer = renderer.create( - {}} visible link={link} /> + {}} visible link={link} /> ); // fire on navigation state change TestRenderer.root.findByType(WebView).props.onNavigationStateChange({url}); @@ -194,7 +194,7 @@ describe('', () => { const split = jest.spyOn(url, 'split'); // create test renderer const TestRenderer = renderer.create( - {}} visible link={link} /> + {}} visible link={link} /> ); // fire on navigation state change TestRenderer.root.findByType(WebView).props.onNavigationStateChange({url}); @@ -211,11 +211,11 @@ describe('', () => { }); }); -describe('', () => { +describe('', () => { it('has a retry button if hasLink prop is true', () => { // create test renderer const TestRenderer = renderer.create( - {}} /> + {}} /> ); // simulate animation timeframes timeTravel(); @@ -228,7 +228,7 @@ describe('', () => { it('does not have a retry button if hasLink prop is false', () => { // create test renderer const TestRenderer = renderer.create( - {}} /> + {}} /> ); // simulate animation timeframes timeTravel(); diff --git a/__tests__/PayWithFlutterwaveBase.spec.tsx b/__tests__/PayWithFlutterwaveBase.spec.tsx index 499cef3..4ef94cf 100644 --- a/__tests__/PayWithFlutterwaveBase.spec.tsx +++ b/__tests__/PayWithFlutterwaveBase.spec.tsx @@ -6,7 +6,7 @@ import {FlutterwaveInitOptions} from '../src/FlutterwaveInit'; import {REDIRECT_URL, STANDARD_URL} from '../src/configs'; import {Modal, TouchableOpacity, Text} from 'react-native'; import timeTravel, { setupTimeTravel } from '../timeTravel'; -import FlwCheckout from '../src/FlwCheckout'; +import FlutterwaveCheckout from '../src/FlutterwaveCheckout'; import FlutterwaveInitError from '../src/utils/FlutterwaveInitError'; import FlutterwaveInit from '../src/FlutterwaveInit'; const BtnTestID = 'flw-button'; @@ -650,7 +650,7 @@ describe('PayWithFlutterwaveBase', () => { // simulate animated timeframe timeTravel(); // get modal test renderer - const ModalTestRender = Tree.root.findByType(FlwCheckout); + const ModalTestRender = Tree.root.findByType(FlutterwaveCheckout); // run assertions expect(ModalTestRender.props.visible).toEqual(true); }); diff --git a/__tests__/__snapshots__/FlwCheckout.spec.tsx.snap b/__tests__/__snapshots__/FlutterwaveCheckout.spec.tsx.snap similarity index 97% rename from __tests__/__snapshots__/FlwCheckout.spec.tsx.snap rename to __tests__/__snapshots__/FlutterwaveCheckout.spec.tsx.snap index 7bf7ce4..edcf850 100644 --- a/__tests__/__snapshots__/FlwCheckout.spec.tsx.snap +++ b/__tests__/__snapshots__/FlutterwaveCheckout.spec.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` renders with modal closed if visible prop is not true 1`] = ` +exports[` renders with modal closed if visible prop is not true 1`] = ` renders with modal closed if visible prop is not true 1` `; -exports[` renders with modal open if visible props is true 1`] = ` +exports[` renders with modal open if visible props is true 1`] = ` void; + onAbort?: () => void; + link?: string; + visible?: boolean; +} +interface FlutterwaveCheckoutErrorProps { + hasLink: boolean; + onTryAgain: () => void; +} +declare const FlutterwaveCheckout: React.FC; +export declare const FlutterwaveCheckoutError: React.FC; +export default FlutterwaveCheckout; +//# sourceMappingURL=FlutterwaveCheckout.d.ts.map \ No newline at end of file diff --git a/dist/FlutterwaveCheckout.d.ts.map b/dist/FlutterwaveCheckout.d.ts.map new file mode 100644 index 0000000..b782f75 --- /dev/null +++ b/dist/FlutterwaveCheckout.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"FlutterwaveCheckout.d.ts","sourceRoot":"","sources":["../src/FlutterwaveCheckout.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAsB1B,MAAM,WAAW,wBAAwB;IACvC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAOD,UAAU,6BAA6B;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB;AAoBD,QAAA,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CAyH3D,CAAA;AAoBD,eAAO,MAAM,wBAAwB,EAAE,KAAK,CAAC,EAAE,CAAC,6BAA6B,CAsB5E,CAAA;AA8ED,eAAe,mBAAmB,CAAC"} \ No newline at end of file diff --git a/dist/FlwCheckout.js b/dist/FlutterwaveCheckout.js similarity index 92% rename from dist/FlwCheckout.js rename to dist/FlutterwaveCheckout.js index 29976b8..0a70680 100644 --- a/dist/FlwCheckout.js +++ b/dist/FlutterwaveCheckout.js @@ -22,7 +22,7 @@ var getRedirectParams = function (url) { // return result return res; }; -var FlwCheckout = function FlwCheckout(props) { +var FlutterwaveCheckout = function FlutterwaveCheckout(props) { var link = props.link, visible = props.visible, onRedirect = props.onRedirect, onAbort = props.onAbort; var _a = React.useState(false), show = _a[0], setShow = _a[1]; var webviewRef = React.useRef(null); @@ -105,7 +105,7 @@ var FlwCheckout = function FlwCheckout(props) { outputRange: [0, 1, 1] }); return ( - + - ; }} renderLoading={function () { return ; }}/> + ; }} renderLoading={function () { return ; }}/> ); }; -var FlwCheckoutBackdrop = function FlwCheckoutBackdrop(_a) { +var FlutterwaveCheckoutBackdrop = function FlutterwaveCheckoutBackdrop(_a) { var animation = _a.animation, onPress = _a.onPress; // Interpolation backdrop animation var backgroundColor = animation.interpolate({ @@ -128,7 +128,7 @@ var FlwCheckoutBackdrop = function FlwCheckoutBackdrop(_a) { ); }; -export var FlwCheckoutError = function (_a) { +export var FlutterwaveCheckoutError = function (_a) { var hasLink = _a.hasLink, onTryAgain = _a.onTryAgain; return ( {hasLink ? (<> @@ -143,7 +143,7 @@ export var FlwCheckoutError = function (_a) { )} ); }; -var FlwCheckoutLoader = function () { +var FlutterwaveCheckoutLoader = function () { return ( ); @@ -211,4 +211,4 @@ var styles = StyleSheet.create({ backgroundColor: 'rgba(0,0,0,0)' } }); -export default FlwCheckout; +export default FlutterwaveCheckout; diff --git a/dist/FlwCheckout.d.ts b/dist/FlwCheckout.d.ts deleted file mode 100644 index dc30490..0000000 --- a/dist/FlwCheckout.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -export interface FlwCheckoutProps { - onRedirect?: (data: any) => void; - onAbort?: () => void; - link?: string; - visible?: boolean; -} -interface FlwCheckoutErrorProps { - hasLink: boolean; - onTryAgain: () => void; -} -declare const FlwCheckout: React.FC; -export declare const FlwCheckoutError: React.FC; -export default FlwCheckout; -//# sourceMappingURL=FlwCheckout.d.ts.map \ No newline at end of file diff --git a/dist/FlwCheckout.d.ts.map b/dist/FlwCheckout.d.ts.map deleted file mode 100644 index 2b74c67..0000000 --- a/dist/FlwCheckout.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"FlwCheckout.d.ts","sourceRoot":"","sources":["../src/FlwCheckout.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAsB1B,MAAM,WAAW,gBAAgB;IAC/B,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAOD,UAAU,qBAAqB;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB;AAoBD,QAAA,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAyH3C,CAAA;AAoBD,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAsB5D,CAAA;AA8ED,eAAe,WAAW,CAAC"} \ No newline at end of file diff --git a/dist/PaywithFlutterwaveBase.js b/dist/PaywithFlutterwaveBase.js index 5b4d103..6719f06 100644 --- a/dist/PaywithFlutterwaveBase.js +++ b/dist/PaywithFlutterwaveBase.js @@ -61,7 +61,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) { import React from 'react'; import PropTypes from 'prop-types'; import FlutterwaveInitError from './utils/FlutterwaveInitError'; -import FlwCheckout from './FlwCheckout'; +import FlutterwaveCheckout from './FlutterwaveCheckout'; import FlutterwaveButton from './FlutterwaveButton'; import { REDIRECT_URL } from './configs'; export var PayWithFlutterwavePropTypesBase = { @@ -244,7 +244,7 @@ var PayWithFlutterwaveBase = /** @class */ (function (_super) { var _a = this.state, link = _a.link, showDialog = _a.showDialog; return (<> {this.renderButton()} - + ); }; PayWithFlutterwaveBase.prototype.renderButton = function () { diff --git a/dist/index.d.ts b/dist/index.d.ts index 4b74a85..4e41be9 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -3,7 +3,7 @@ import FlutterwaveInitV2 from './FlutterwaveInitV2'; import PayWithFlutterwave from './PayWithFlutterwave'; import PayWithFlutterwaveV2 from './PayWithFlutterwaveV2'; import FlutterwaveButton from './FlutterwaveButton'; -import FlwCheckout from './FlwCheckout'; -export { FlutterwaveInit, PayWithFlutterwave, FlutterwaveInitV2, PayWithFlutterwaveV2, FlutterwaveButton, FlwCheckout, }; +import FlutterwaveCheckout from './FlutterwaveCheckout'; +export { FlutterwaveInit, PayWithFlutterwave, FlutterwaveInitV2, PayWithFlutterwaveV2, FlutterwaveButton, FlutterwaveCheckout, }; export default PayWithFlutterwave; //# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/index.d.ts.map b/dist/index.d.ts.map index dd03b8f..4bb37e1 100644 --- a/dist/index.d.ts.map +++ b/dist/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAChD,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,kBAAkB,MAAM,sBAAsB,CAAC;AACtD,OAAO,oBAAoB,MAAM,wBAAwB,CAAC;AAC1D,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,WAAW,MAAM,eAAe,CAAC;AAGxC,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,WAAW,GACZ,CAAC;AAGF,eAAe,kBAAkB,CAAC"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAChD,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,kBAAkB,MAAM,sBAAsB,CAAC;AACtD,OAAO,oBAAoB,MAAM,wBAAwB,CAAC;AAC1D,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AAGxD,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,GACpB,CAAC;AAGF,eAAe,kBAAkB,CAAC"} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index 169b619..9aa8bf8 100644 --- a/dist/index.js +++ b/dist/index.js @@ -3,8 +3,8 @@ import FlutterwaveInitV2 from './FlutterwaveInitV2'; import PayWithFlutterwave from './PayWithFlutterwave'; import PayWithFlutterwaveV2 from './PayWithFlutterwaveV2'; import FlutterwaveButton from './FlutterwaveButton'; -import FlwCheckout from './FlwCheckout'; +import FlutterwaveCheckout from './FlutterwaveCheckout'; // export modules -export { FlutterwaveInit, PayWithFlutterwave, FlutterwaveInitV2, PayWithFlutterwaveV2, FlutterwaveButton, FlwCheckout, }; +export { FlutterwaveInit, PayWithFlutterwave, FlutterwaveInitV2, PayWithFlutterwaveV2, FlutterwaveButton, FlutterwaveCheckout, }; // export v3 PayWithFlutterwave as default export default PayWithFlutterwave; diff --git a/src/FlwCheckout.tsx b/src/FlutterwaveCheckout.tsx similarity index 89% rename from src/FlwCheckout.tsx rename to src/FlutterwaveCheckout.tsx index 16a5c0c..853f495 100644 --- a/src/FlwCheckout.tsx +++ b/src/FlutterwaveCheckout.tsx @@ -20,19 +20,19 @@ const loader = require('./assets/loader.gif'); const borderRadiusDimension = 24 / 896; const windowHeight = Dimensions.get('window').height; -export interface FlwCheckoutProps { +export interface FlutterwaveCheckoutProps { onRedirect?: (data: any) => void; onAbort?: () => void; link?: string; visible?: boolean; } -interface FlwCheckoutBackdropProps { +interface FlutterwaveCheckoutBackdropProps { animation: Animated.Value, onPress?: () => void; } -interface FlwCheckoutErrorProps { +interface FlutterwaveCheckoutErrorProps { hasLink: boolean; onTryAgain: () => void; } @@ -55,7 +55,7 @@ const getRedirectParams = (url: string): {[k: string]: string} => { return res; }; -const FlwCheckout: React.FC = function FlwCheckout(props) { +const FlutterwaveCheckout: React.FC = function FlutterwaveCheckout(props) { const {link, visible, onRedirect, onAbort} = props; const [show, setShow] = React.useState(false); const webviewRef = React.useRef(null); @@ -151,7 +151,7 @@ const FlwCheckout: React.FC = function FlwCheckout(props) { animated={false} hardwareAccelerated={false} visible={show}> - handleAbort()} animation={animation.current} /> + handleAbort()} animation={animation.current} /> = function FlwCheckout(props) { scalesPageToFit={true} javaScriptEnabled={true} onNavigationStateChange={handleNavigationStateChange} - renderError={() => } - renderLoading={() => } + renderError={() => } + renderLoading={() => } /> ) } -const FlwCheckoutBackdrop: React.FC< - FlwCheckoutBackdropProps -> = function FlwCheckoutBackdrop({ +const FlutterwaveCheckoutBackdrop: React.FC< + FlutterwaveCheckoutBackdropProps +> = function FlutterwaveCheckoutBackdrop({ animation, onPress }) { @@ -196,7 +196,7 @@ const FlwCheckoutBackdrop: React.FC< ); } -export const FlwCheckoutError: React.FC = ({ +export const FlutterwaveCheckoutError: React.FC = ({ hasLink, onTryAgain }): React.ReactElement => { @@ -220,7 +220,7 @@ export const FlwCheckoutError: React.FC = ({ ); } -const FlwCheckoutLoader: React.FC<{}> = (): React.ReactElement => { +const FlutterwaveCheckoutLoader: React.FC<{}> = (): React.ReactElement => { return ( extends React.Component< return ( <> {this.renderButton()} - Date: Wed, 5 Aug 2020 09:40:06 +0100 Subject: [PATCH 125/129] refactor(readme.v2): remove old v3 readme --- README.old.md | 334 -------------------------------------------------- 1 file changed, 334 deletions(-) delete mode 100644 README.old.md diff --git a/README.old.md b/README.old.md deleted file mode 100644 index 2d49279..0000000 --- a/README.old.md +++ /dev/null @@ -1,334 +0,0 @@ -# React Native Flutterwave -Easily implement Flutterwave for payments in your React Native appliction. This library supports both Android and iOS, and use the Flutterwave's V3 API - -[![V2 API](https://img.shields.io/badge/API-V3-brightgreen)](https://developer.flutterwave.com/docs) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) - -

- ios-preview - android-preview -

- -## Table Of Content -- Getting Started - - [V2 API](#warning-if-using-version-2-api-warning) - - [Installation](#installation) - - [Dependencies](#dependencies) - - [Activity Indicator (Android)](#activity-indicator-only-needed-for-android) - - [Important Information](#fire-important-information-fire) -- Usage - - [Flutterwave Button ](#flutterwave-button) - - [Flutterwave Button (with custom render)](#flutterwave-button-with-custom-render) - - [Default Button (Flutterwave styled button)](#defaultbutton-flutterwave-styled-button) - - [Flutterwave Standard Init](#flutterwave-standard-init) - - [Aborting Payment Initialization](#aborting-payment-initialization) -- Props - - [Flutterwave Button Props](#flutterwavebuttonprops) - - [Default Button Props](#defaultbuttonprops) - - [Flutterwave Init Options](#flutterwaveinitoptions) -- Types - - [Flutterwave Button Props](#flutterwavebuttonprops-interface) - - [Default Button Props](#defaultbuttonprops-interface) - - [Flutterwave Init Customer](#flutterwaveinitcustomer) - - [Flutterwave Init Customization](#flutterwaveinitcustomization) - - [Flutterwave Init Sub Account](#flutterwaveinitsubaccount) - - [Flutterwave Init Options](#flutterwaveinitoptions-interface) - - [Flutterwave Init Error](#flutterwaveiniterror) - - [FlutterwavePaymentMeta](#flutterwavepaymentmeta) - - [RedirectParams](#redirectparams) - - [CustomButtonProps](#custombuttonprops) -- [Contributing](./CONTRIBUTING.md) - -## What's Inside? -- Pay with Flutterwave button and checkout dialog. -- Standard payment initialization function. -- Flutterwave designed button. - -## :warning: If Using Version 2 API :warning: -This version of the library's docs focuses on use cases with the Version 3 of Flutterwaves API, if you are still using the Version 2 API please use [this documentation](./README.v2.md) instead. - -## Installation -This library is available on npm, you can install it by running `npm install --save react-native-flutterwave` or `yarn add react-native-flutterwave` - -### Dependencies -In order to render the Flutterwave checkout screen this library depends on [react-native-webview](https://github.com/react-native-community/react-native-webview) ensure you properly install this library before continuing. - -### Activity Indicator (only needed for android) -To display the Flutterwave styled activity indicator when the checkout screen is being loaded on Android you will need to add some modules in `android/app/build.gradle`. -***Skip this if you already have setup your app to support gif images.*** -````javascript -dependencies { - // If your app supports Android versions before Ice Cream Sandwich (API level 14) - implementation 'com.facebook.fresco:animated-base-support:1.3.0' - - // For animated GIF support - implementation 'com.facebook.fresco:animated-gif:2.0.0' -} -```` - -### :fire: IMPORTANT INFORMATION :fire: -If the `options` property on the [FlutterwaveButton](#flutterwavebuttonprops-interface) changes, when next the user taps on the button a new payment will be initialized whether the last one was successful or not. - -Remember you cannot use the same transaction reference for two different payments, remember to recreate the transaction reference before allowing the user initiate a new payment. - - -## Usage -Below are a few examples showcasing how you can use the library to implement payment in your React Native app. - -### Flutterwave Button -preview - -[View All Props](#flutterwavebuttonprops) - -Import `FlutterwaveButton` from `react-native-flutterwave` and use it like so. -````jsx -import {FlutterwaveButton} from 'react-native-flutterwave'; -// or import FlutterwaveButton from 'react-native-flutterwave'; - - -```` - -### Flutterwave Button (with custom render) -preview - -[View All Props](#flutterwavebuttonprops) - -Import `FlutterwaveButton` from `react-native-flutterwave` and use it like so. -````jsx -import {FlutterwaveButton} from 'react-native-flutterwave'; -// or import FlutterwaveButton from 'react-native-flutterwave'; - - ( - - Pay $500 - - )} -/> -```` - -### DefaultButton (Flutterwave styled button) -preview - -[View All Props](#defaultbuttonprops) - -Import `DefaultButton` from `react-native-flutterwave` and use it like so. -````jsx -import {DefaultButton} from 'react-native-flutterwave'; - - - Pay $500 - -```` - -### Flutterwave Standard Init -When called, this function returns a Promise which resolves to a string on success and rejects if an error occurs. [See all config options](#flutterwaveinitoptions) - -Import `FlutterwaveInit` from `react-native-flutterwave` and use it like so. -````javascript -import {FlutterwaveInit} from 'react-native-flutterwave'; - -try { - // initialize payment - const paymentLink = await FlutterwaveInit({ - tx_ref: generateTransactionRef(), - authorization: '[your merchant secret Key]', - amount: 100, - currency: 'USD', - customer: { - email: 'customer-email@example.com' - }, - payment_options: 'card' - }); - // use payment link - usePaymentLink(paymentLink); -} catch (error) { - // handle payment error - displayError(error.message); -} -```` -### Aborting Payment Initialization -:wave: Hi, so there are cases where you have already initialized a payment with `FlutterwaveInit` but might also want to be able to cancel the payment initialization should in case your component is being unmounted or you want to allow users cancel the action before the payment is initialized, we have provided a way for you to do this... [continue reading](./docs/AbortingPaymentInitialization.md) - -## Props - -### FlutterwaveInitOptions -[See Interface](#flutterwaveinitoptions-interface) -| Name | Required | Type | Default | Description | -| --------- | --------- | ---- | ------- | ----------- | -| authorization | Yes | string | **REQUIRED** | Your merchant secret key, see how to get your [API Keys](https://developer.flutterwave.com/v3.0/docs/api-keys)| -| tx_ref | Yes | string | **REQUIRED** | Your transaction reference. This MUST be unique for every transaction.| -| amount | Yes | string | **REQUIRED** | Amount to charge the customer. | -| currency | No | string | NGN | Currency to charge in. Defaults to NGN. | -| integrity_hash | No | string | undefined | This is a sha256 hash of your FlutterwaveCheckout values, it is used for passing secured values to the payment gateway. | -| payment_options | Yes | string | **REQUIRED** | This specifies the payment options to be displayed e.g - card, mobilemoney, ussd and so on. | -| payment_plan | No | number | undefined | This is the payment plan ID used for [Recurring billing](https://developer.flutterwave.com/v3.0/docs/recurring-billing). | -| redirect_url | Yes | string | **REQUIRED** | 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. **IMPORTANT** This only required when you are directly using [FlutterwaveInit](#flutterwave-standard-init) | -| customer | Yes | [FlutterwaveInitCustomer](#flutterwaveinitcustomer) | **REQUIRED** | This is an object that can contains your customer details. `E.g.'customer': { 'email': 'example@example.com', 'phonenumber': '08012345678', 'name': 'Takeshi Kovacs' }.` | -| subaccounts | No | array of [FlutterwaveInitSubAccount](#flutterwaveinitsubaccount) | undefined | This is an array of objects containing the subaccount IDs to split the payment into. Check out the [Split Payment page](https://developer.flutterwave.com/docs/split-payment) for more info | -| meta | No | array of [FlutterwavePaymentMeta](#flutterwavepaymentmeta) | undefined | This is an object that helps you include additional payment information to your request. `E.g. { 'consumer_id': 23, 'consumer_mac': '92a3-912ba-1192a' }` | -| customizations | No | [FlutterwaveInitCustomizations](#flutterwaveinitcustomizations) | undefined | 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'}` | - -### FlutterwaveButtonProps -[See Interface](#flutterwavebuttonprops-interface) -| Name | Required | Type | Default | Description | -| --------- | --------- | ---- | ------- | ----------- | -| style | No | object | undefined | Used to apply styling to the button.| -| onComplete | Yes | function | **REQUIRED** | Called when a payment is completed successfully or is canceled. The function will receive [redirect params](#redirectparams) as an argument.| -| onWillInitialize | No | function | undefined | This will be called before a payment link is generated.| -| onDidInitialize | No | function | undefined | This is called when a new payment link has been successfully initialized.| -| onInitializeError | No | function | undefined | This is called if an error occurred while initializing a new pyment link. The function will receive [FlutterwaveInitError](#flutterwaveiniterror) | -| onAbort | No | function | undefined | This is called if a user aborts a transaction, a user can abort a transaction when they click on the dialog's backdrop and choose cancel when prompted to cancel transaction. | -| options | Yes | [FlutterwaveInitOptions](#flutterwaveinitoptions) | **REQUIRED** | The option passed here is used to initialize a payment. | -| customButton | No | function | undefined | This is used to render a custom button. The function a prop argument structured like [CustomButtonProps](#custombuttonprops), this function should return a valid React node. | -| alignLeft | No | boolean | undefined | This aligns the content of the button to the left. | - -### DefaultButtonProps -[See Interface](#defaultbuttonprops-interface) -| Name | Required | Type | Default | Description | -| --------- | --------- | ---- | ------- | ----------- | -| style | No | object | undefined | Used to apply styling to the button.| -| onPress | Yes | function | undefined | This | -| disabled | No | boolean | undefined | This disables button, and causes onPress not to be fired.| -| isBusy | No | boolean | undefined | This puts the button in a busy state, making the content look faded.| -| onSizeChange | No | (ev: {width: number; height: number}) => void | undefined | If provided this function is fired whenever the size(height or width) of the button changes | -| children | Yes | ReactElement | undefined | This will be the content rendered within the button, if string is to be direct decendant, remember to put string in the Text component. | -| alignLeft | No | boolean | undefined | This aligns the content of the button to the left. | - -## Types -#### CustomButtonProps -````typescript -interface CustomButtonProps { - disabled: boolean; - isInitializing: boolean; - onPress: () => void; -} -```` - -#### RedirectParams -````typescript -interface RedirectParams { - status: 'successful' | 'cancelled', - transaction_id?: string; - tx_ref: string; -} -```` - -#### FlutterwaveInitError -````typescript -interface FlutterwaveInitError { - code: string; - message: string; - errorId?: string; - errors?: Array; -} -```` - -#### FlutterwavePaymentMeta -````typescript -interface FlutterwavePaymentMeta { - [k: string]: any; -} -```` - -### FlutterwaveInitCustomer -```typescript -interface FlutterwaveInitCustomer { - email: string; - phonenumber?: string; - name?: string; -} -``` - -### FlutterwaveInitCustomizations -```typescript -interface FlutterwaveInitCustomizations { - title?: string; - logo?: string; - description?: string; -} -``` - -### FlutterwaveInitSubAccount -```typescript -interface FlutterwaveInitSubAccount { - id: string; - transaction_split_ratio?: number; - transaction_charge_type?: string; - transaction_charge?: number; -} -``` - -#### FlutterwaveInitOptions Interface -````typescript -export interface FlutterwaveInitOptions { - authorization: string; - tx_ref: string; - amount: number; - currency: string; - integrity_hash?: string; - payment_options?: string; - payment_plan?: number; - redirect_url: string; - customer: FlutterwaveInitCustomer; - subaccounts?: Array; - meta?: Array; - customizations?: FlutterwaveInitCustomizations; -} -```` - -#### FlutterwaveButtonProps Interface -````typescript -interface FlutterwaveButtonProps { - style?: ViewStyle; - onComplete: (data: RedirectParams) => void; - onWillInitialize?: () => void; - onDidInitialize?: () => void; - onInitializeError?: (error: FlutterwaveInitError) => void; - onAbort?: () => void; - options: Omit; - customButton?: (props: CustomButtonProps) => React.ReactNode; - alignLeft?: 'alignLeft' | boolean; -} -```` - -#### DefaultButtonProps Interface -````typescript -interface DefaultButtonProps { - style?: ViewStyle; - onPress?: () => void; - disabled?: boolean; - children: React.ReactElement; - isBusy?: boolean; - onSizeChange?: (ev: {width: number; height: number}) => void; - alignLeft?: 'alignLeft' | boolean, -} -```` - -## Contributing -For information on how you can contribute to this repo, simply [go here](./CONTRIBUTING.md), all contributions are greatly appreciated. - -With love from Flutterwave. :yellow_heart: From 067b7ea6b48129603c3c08799dfb58e08232652d Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Wed, 5 Aug 2020 09:40:48 +0100 Subject: [PATCH 126/129] refactor(readme.v2.old): remove old v2 readme --- README.v2.old.md | 324 ----------------------------------------------- 1 file changed, 324 deletions(-) delete mode 100644 README.v2.old.md diff --git a/README.v2.old.md b/README.v2.old.md deleted file mode 100644 index 69f4e19..0000000 --- a/README.v2.old.md +++ /dev/null @@ -1,324 +0,0 @@ -# React Native Flutterwave -Easily implement Flutterwave for payments in your React Native appliction. This library supports both Android and iOS, and use the Flutterwave's V2 API. - -[![V2 API](https://img.shields.io/badge/API-V2-brightgreen)](https://developer.flutterwave.com/v2.0/docs/getting-started) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) - -

- ios-preview - android-preview -

- -## Table Of Content -- Getting Started - - [V3 API](#warning-if-using-version-3-api-warning) - - [Installation](#installation) - - [Dependencies](#dependencies) - - [Activity Indicator (Android)](#activity-indicator-only-needed-for-android) - - [Important Information](#fire-important-information-fire) -- Usage - - [Flutterwave Button ](#flutterwave-button) - - [Flutterwave Button (with custom render)](#flutterwave-button-with-custom-render) - - [Default Button (Flutterwave styled button)](#defaultbutton-flutterwave-styled-button) - - [Flutterwave Standard Init](#flutterwave-standard-init) - - [Aborting Payment Initialization](#aborting-payment-initialization) -- Props - - [Flutterwave Button Props](#flutterwavebuttonprops) - - [Default Button Props](#defaultbuttonprops) - - [Flutterwave Init Options](#flutterwaveinitoptions) -- Types - - [Flutterwave Button Props](#flutterwavebuttonprops-interface) - - [Default Button Props](#defaultbuttonprops-interface) - - [Flutterwave Init Options](#flutterwaveinitoptions-interface) - - [Flutterwave Init Error](#flutterwaveiniterror) - - [FlutterwavePaymentMeta](#flutterwavepaymentmeta) - - [RedirectParamsV2](#redirectparamsv2) - - [CustomButtonProps](#custombuttonprops) -- [Contributing](./CONTRIBUTING.md) - -## What's Inside? -- Pay with Flutterwave button and checkout dialog. -- Standard payment initialization function. -- Flutterwave designed button. - -## :warning: If Using Version 3 API :warning: -This version of the library's docs focuses on use cases with the Version 2 of Flutterwaves API, if you are using the Version 3 API please use [this documentation](./README.md) instead. - -## Installation -This library is available on npm, you can install it by running `npm install --save react-native-flutterwave` or `yarn add react-native-flutterwave` - -### Dependencies -In order to render the Flutterwave checkout screen this library depends on [react-native-webview](https://github.com/react-native-community/react-native-webview) ensure you properly install this library before continuing. - -### Activity Indicator (only needed for android) -To display the Flutterwave styled activity indicator when the checkout screen is being loaded on Android you will need to add some modules in `android/app/build.gradle`. -***Skip this if you already have setup your app to support gif images.*** -````javascript -dependencies { - // If your app supports Android versions before Ice Cream Sandwich (API level 14) - implementation 'com.facebook.fresco:animated-base-support:1.3.0' - - // For animated GIF support - implementation 'com.facebook.fresco:animated-gif:2.0.0' -} -```` - -### :fire: IMPORTANT INFORMATION :fire: -If the `options` property on the [FlutterwaveButton](flutterwavebuttonprops-interface) changes, when next the user taps on the button a new payment will be initialized whether the last one was successful or not. - -Remember you cannot use the same transaction reference for two different payments, remember to recreate the transaction reference before allowing the user initiate a new payment. - - -## Usage -Below are a few examples showcasing how you can use the library to implement payment in your React Native app. - -### Flutterwave Button -preview - -[View All Props](#flutterwavebuttonprops) - -Import `FlutterwaveButtonV2` from `react-native-flutterwave` and use it like so. -````jsx -import {FlutterwaveButtonV2} from 'react-native-flutterwave'; -// or import FlutterwaveButtonV2 from 'react-native-flutterwave/v2'; - - -```` - -### Flutterwave Button (with custom render) -preview - -[View All Props](#flutterwavebuttonprops) - -Import `FlutterwaveButtonV2` from `react-native-flutterwave` and use it like so. -````jsx -import {FlutterwaveButtonV2} from 'react-native-flutterwave'; -// or import FlutterwaveButtonV2 from 'react-native-flutterwave/v2'; - - ( - - Pay $500 - - )} -/> -```` - -### DefaultButton (Flutterwave styled button) -preview - -[View All Props](#defaultbuttonprops) - -Import `DefaultButton` from `react-native-flutterwave` and use it like so. -````jsx -import {DefaultButton} from 'react-native-flutterwave'; - - - Pay $500 - -```` - -### Flutterwave Standard Init -When called, this function returns a Promise which resolves to a string on success and rejects if an error occurs. [See all config options](#flutterwaveinitoptions) - -Import `FlutterwaveInitV2` from `react-native-flutterwave` and use it like so. -````javascript -import {FlutterwaveInitV2} from 'react-native-flutterwave';; -// or import FlutterwaveInitV2 from 'react-native-flutterwave/v2'; - -// initialize a new payment -const payment = await FlutterwaveInitV2({ - txref: generateTransactionRef(), - PBFPubKey: '[Your Flutterwave Public Key]', - amount: 100, - currency: 'USD', -}); - -// link is available if payment initialized successfully -if (payment.link) { - // use payment link - return usePaymentLink(payment.link); -} - -// handle payment error -handlePaymentError( - payment.error - ? paymet.error.message - : 'Kai, an unknown error occurred!' -); -```` -### Aborting Payment Initialization -Hi :wave:, so there are cases where you have already initialized a payment with `FlutterwaveInit` but might also want to be able to cancel the payment initialization should in case your component is being unmounted or you want to allow users cancel the action before the payment is initialized, we have provided a way for you to do this... [continue reading](./docs/v2/AbortingPaymentInitialization.md) - -## Props - -### FlutterwaveInitOptions -[See Interface](#flutterwaveinitoptions-interface) -| Name | Required | Type | Default | Description | -| --------- | --------- | ---- | ------- | ----------- | -| PBFPubKey | Yes | string | **REQUIRED** | Your merchant public key, see how to get your [API Keys](https://developer.flutterwave.com/v2.0/docs/api-keys)| -| txref | Yes | string | **REQUIRED** | Your Unique transaction reference.| -| customer_email | Yes | string | **REQUIRED** | The customer's email address. | -| customer_phone | No | string | undefined | The customer's phone number. | -| customer_firstname | No | string | undefined | The customer's first name. | -| customer_lastname | No | string | undefined | The customer's last name. | -| amount | Yes | number | undefined | Amount to charge the customer.| -| currency | No | string | NGN | Currency to charge in. Defaults to NGN. Check our [International Payments](https://developer.flutterwave.com/v2.0/docs/multicurrency-payments) section for more on international currencies.| -| redirect_url | No | string | undefined | 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. | -| payment_options | No | string | undefined | This allows you to select the payment option you want for your users, see [Choose Payment Methods](https://developer.flutterwave.com/v2.0/docs/splitting-payment-methods) for more info. | -| payment_plan | No | number | undefined | This is the payment plan ID used for [Recurring billing](https://developer.flutterwave.com/v2.0/docs/recurring-billing). | -| subaccounts | No | array of [FlutterwaveInitSubAccount](#flutterwaveinitsubaccount) | undefined | This is an array of objects containing the subaccount IDs to [split the payment](https://developer.flutterwave.com/v2.0/docs/split-payment) into. | -| country | No | string | NG | Route country. Defaults to NG | -| pay_button_text | No | string | undefined | Text to be displayed on the Rave Checkout Button. | -| custom_title | No | string | undefined | Text to be displayed as the title of the payment modal. | -| custom_description | No | string | undefined | Text to be displayed as a short modal description. | -| custom_logo | No | string | undefined | Link to the Logo image. | -| meta | No | array of [FlutterwavePaymentMeta](#flutterwavepaymentmeta) | undefined | Any other custom data you wish to pass. | - -### FlutterwaveButtonProps -[See Interface](#flutterwavebuttonprops-interface) -| Name | Required | Type | Default | Description | -| --------- | --------- | ---- | ------- | ----------- | -| style | No | object | undefined | Used to apply styling to the button.| -| onComplete | Yes | function | **REQUIRED** | Called when a payment is completed successfully or is canceled. The function will receive [on complete data](#oncompletedata)| -| onWillInitialize | No | function | undefined | This will be called before a payment link is generated.| -| onDidInitialize | No | function | undefined | This is called when a new payment link has been successfully initialized.| -| onInitializeError | No | function | undefined | This is called if an error occurred while initializing a new pyment link. The function will receive [FlutterwaveInitError](#flutterwaveiniterror) | -| onAbort | No | function | undefined | This is called if a user aborts a transaction, a user can abort a transaction when they click on the dialog's backdrop and choose cancel when prompted to cancel transaction. | -| options | Yes | **[FlutterwaveInitOptions](#flutterwaveinitoptions)** | **REQUIRED** | The option passed here is used to initialize a payment. | -| customButton | No | function | undefined | This is used to render a custom button. The function a prop argument structured like [CustomButtonProps](#custombuttonprops), this function should return a valid React node. | -| alignLeft | No | boolean | undefined | This aligns the content of the button to the left. | - -### DefaultButtonProps -[See Interface](#defaultbuttonprops-interface) -| Name | Required | Type | Default | Description | -| --------- | --------- | ---- | ------- | ----------- | -| style | No | object | undefined | Used to apply styling to the button.| -| onPress | Yes | function | undefined | This | -| disabled | No | boolean | undefined | This disables button, and causes onPress not to be fired.| -| isBusy | No | boolean | undefined | This puts the button in a busy state, making the content look faded.| -| onSizeChange | No | (ev: {width: number; height: number}) => void | undefined | If provided this function is fired whenever the size(height or width) of the button changes | -| children | Yes | ReactElement | undefined | This will be the content rendered within the button, if string is to be direct decendant, remember to put string in the Text component. | -| alignLeft | No | boolean | undefined | This aligns the content of the button to the left. | - -## Types -#### CustomButtonProps -````typescript -interface CustomButtonProps { - disabled: boolean; - isInitializing: boolean; - onPress: () => void; -} -```` - -#### RedirectParamsV2 -````typescript -interface RedirectParamsV2 { - canceled?: 'true' | 'false'; - flwref?: string; - txref: string; -} -```` - -#### FlutterwaveInitError -````typescript -interface FlutterwaveInitError { - code: string; - message: string; -} -```` - -### FlutterwaveInitSubAccount -```typescript -interface FlutterwaveInitSubAccount { - id: string; - transaction_split_ratio?: number; - transaction_charge_type?: string; - transaction_charge?: number; -} -``` - -#### FlutterwavePaymentMeta -````typescript -interface FlutterwavePaymentMeta { - metaname: string; - metavalue: string; -} -```` - -#### FlutterwaveInitOptions Interface -````typescript -export interface FlutterwaveInitOptions { - txref: string; - PBFPubKey: string; - customer_firstname?: string; - customer_lastname?: string; - customer_phone?: string; - customer_email: string; - amount: number; - currency?: string; - redirect_url?: string; - payment_options?: string; - payment_plan?: number; - subaccounts?: Array; - country?: string; - pay_button_text?: string; - custom_title?: string; - custom_description?: string; - custom_logo?: string; - meta?: Array; -} -```` - -#### FlutterwaveButtonProps Interface -````typescript -interface FlutterwaveButtonProps { - style?: ViewStyle; - onComplete: (data: RedirectParamsV2) => void; - onWillInitialize?: () => void; - onDidInitialize?: () => void; - onInitializeError?: (error: FlutterwaveInitError) => void; - onAbort?: () => void; - options: Omit; - customButton?: (props: CustomButtonProps) => React.ReactNode; - alignLeft?: 'alignLeft' | boolean; -} -```` - -#### DefaultButtonProps Interface -````typescript -interface DefaultButtonProps { - style?: ViewStyle; - onPress?: () => void; - disabled?: boolean; - children: React.ReactElement; - isBusy?: boolean; - onSizeChange?: (ev: {width: number; height: number}) => void; - alignLeft?: 'alignLeft' | boolean, -} -```` - -## Contributing -For information on how you can contribute to this repo, simply [go here](./CONTRIBUTING.md), all contributions are greatly appreciated. - -With love from Flutterwave. :yellow_heart: From 50044350de877eae1779a264fdfdab80adbc7a21 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 18 Aug 2020 09:56:40 +0100 Subject: [PATCH 127/129] fix(package.json): change library name --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b890b66..2297a75 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "react-native-flutterwave", + "name": "flutterwave-react-native", "version": "0.0.0", "description": "Easily plug Flutterwave into your react-native app for payments.", "main": "dist/index.js", From dc7add06fe4b7ed0f18e69bb47f3c7452ee791f5 Mon Sep 17 00:00:00 2001 From: Daniel Barde Date: Tue, 18 Aug 2020 09:57:20 +0100 Subject: [PATCH 128/129] fix: user new library name in documentation --- CONTRIBUTING.md | 6 +++--- README.md | 22 +++++++++++----------- README.v2.md | 24 ++++++++++++------------ docs/AbortingPaymentInitialization.md | 2 +- docs/v2/AbortingPaymentInitialization.md | 2 +- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e600f97..a609529 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ If you want to open a PR that fixes a bug or adds a feature, then we can't thank ## Submitting pull requests -### Modifying react-native-flutterwave +### Modifying flutterwave-react-native 1. Fork this repository 2. Clone your fork 3. Make a branch for your feature or bug fix (i.e. `git checkout -b what-im-adding`) @@ -20,12 +20,12 @@ If you want to open a PR that fixes a bug or adds a feature, then we can't thank Depending on the changes you make you might want to test to see if the feature/fix works correctly. Use the following steps to test your newly added feature/fix. 1. Set up your react-native example project or use one you already have. -2. Create a `.env` file in the root of the library (react-native-flutterwave). +2. Create a `.env` file in the root of the library (flutterwave-react-native). 3. Add **RN_FLW_EXAMPLE_PROJECT** to the `.env` file it's value should be an absolute path to the install destination in your example project. **E.g.** `RN_FLW_EXAMPLE_PROJECT="/Users/your-name/projects/example-project/src"`. 4. Run the following command `npm run set-example`. -Following these steps will result in you building and copy the built version of the library in the following directory `/Users/your-name/projects/example-project/src/react-native-flutterwave`, you can then go ahead an import the library from within your example project from the location the library has been copied to. +Following these steps will result in you building and copy the built version of the library in the following directory `/Users/your-name/projects/example-project/src/flutterwave-react-native`, you can then go ahead an import the library from within your example project from the location the library has been copied to. ### Writting Tests We currently don't have strict rules for writting tests but when writting one be sure to make your tests and their captions clear and coincise, test only what you added, and then follow up with the dependencies if need be. diff --git a/README.md b/README.md index ea232f1..1c60b43 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Easily implement Flutterwave for payments in your React Native appliction. This This version of the library's docs focuses on use cases with the Version 3 of Flutterwaves API, if you are still using the Version 2 API please use [this documentation](./README.v2.md) instead. ## Installation -This library is available on npm, you can install it by running `npm install --save react-native-flutterwave` or `yarn add react-native-flutterwave` +This library is available on npm, you can install it by running `npm install --save flutterwave-react-native` or `yarn add flutterwave-react-native` ### Dependencies In order to render the Flutterwave checkout screen this library depends on [react-native-webview](https://github.com/react-native-community/react-native-webview) ensure you properly install this library before continuing. @@ -79,10 +79,10 @@ Below are a few examples showcasing how you can use the library to implement pay [View All Props](#flutterwavebuttonprops) -Import `PayWithFlutterwave` from `react-native-flutterwave` and use it like so. +Import `PayWithFlutterwave` from `flutterwave-react-native` and use it like so. ````jsx -import {PayWithFlutterwave} from 'react-native-flutterwave'; -// or import PayWithFlutterwave from 'react-native-flutterwave'; +import {PayWithFlutterwave} from 'flutterwave-react-native'; +// or import PayWithFlutterwave from 'flutterwave-react-native'; Date: Tue, 18 Aug 2020 09:59:05 +0100 Subject: [PATCH 129/129] refactor(setexample): remove lib name from comment --- setExample.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setExample.js b/setExample.js index a78ce38..88fba2c 100644 --- a/setExample.js +++ b/setExample.js @@ -59,7 +59,7 @@ const logSuccess = (message) => { console.log(chalk.green.bold('Found Example Project')); logSuccess('Installing build...'); - // create the react-native-flutterwave directory if it does not exist + // create the library directory if it does not exist if (!existsSync(DESTINATION)) { logSuccess('Creating destination directory...'); mkdirSync(DESTINATION);