Skip to content

Commit 55dccca

Browse files
committed
feat(paywithflutterwavebase): add component
This component carries most of the similar code both pay with flutterwave v2 and v3 components share.
1 parent c84dc58 commit 55dccca

File tree

1 file changed

+258
-0
lines changed

1 file changed

+258
-0
lines changed

src/PaywithFlutterwaveBase.tsx

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import FlutterwaveInitError from './utils/FlutterwaveInitError';
4+
import FlwCheckout from './FlwCheckout';
5+
import FlwButton from './FlwButton';
6+
import {REDIRECT_URL} from './configs';
7+
import { StyleProp, ViewStyle } from 'react-native';
8+
9+
export interface CustomButtonProps {
10+
disabled: boolean;
11+
onPress: () => void;
12+
}
13+
14+
export interface PayWithFlutterwavePropsBase {
15+
style?: StyleProp<ViewStyle>;
16+
onRedirect: (data: any) => void;
17+
onWillInitialize?: () => void;
18+
onDidInitialize?: () => void;
19+
onInitializeError?: (error: FlutterwaveInitError) => void;
20+
onAbort?: () => void;
21+
customButton?: (params: CustomButtonProps) => React.ReactNode;
22+
alignLeft?: 'alignLeft' | boolean;
23+
meta?: Array<any>;
24+
currency?: string;
25+
}
26+
27+
export const PayWithFlutterwavePropTypesBase = {
28+
alignLeft: PropTypes.bool,
29+
onAbort: PropTypes.func,
30+
onRedirect: PropTypes.func.isRequired,
31+
onWillInitialize: PropTypes.func,
32+
onDidInitialize: PropTypes.func,
33+
onInitializeError: PropTypes.func,
34+
customButton: PropTypes.func,
35+
};
36+
37+
export const OptionsPropTypeBase = {
38+
amount: PropTypes.number.isRequired,
39+
currency: PropTypes.oneOf(['GBP', 'NGN', 'USD', 'GHS', 'KES', 'ZAR', 'TZS']),
40+
payment_plan: PropTypes.number,
41+
subaccounts: PropTypes.arrayOf(PropTypes.shape({
42+
id: PropTypes.string.isRequired,
43+
transaction_split_ratio: PropTypes.number,
44+
transaction_charge_type: PropTypes.string,
45+
transaction_charge: PropTypes.number,
46+
})),
47+
integrity_hash: PropTypes.string,
48+
};
49+
50+
interface PayWithFlutterwaveState {
51+
link: string | null;
52+
isPending: boolean;
53+
showDialog: boolean;
54+
reference: string | null;
55+
resetLink: boolean;
56+
}
57+
58+
export type PayWithFlutterwaveBaseProps = PayWithFlutterwavePropsBase & {
59+
options: any;
60+
init: (options: any, abortController?: AbortController) => Promise<string>;
61+
reference: string;
62+
};
63+
64+
class PayWithFlutterwaveBase<P = {}> extends React.Component<
65+
PayWithFlutterwaveBaseProps & P,
66+
PayWithFlutterwaveState
67+
> {
68+
69+
state: PayWithFlutterwaveState = {
70+
isPending: false,
71+
link: null,
72+
resetLink: false,
73+
showDialog: false,
74+
reference: null,
75+
};
76+
77+
abortController?: AbortController;
78+
79+
timeout: any;
80+
81+
handleInitCall?: () => Promise<string>;
82+
83+
componentDidUpdate(prevProps: PayWithFlutterwaveBaseProps) {
84+
const prevOptions = JSON.stringify(prevProps.options);
85+
const options = JSON.stringify(this.props.options);
86+
if (prevOptions !== options) {
87+
this.handleOptionsChanged()
88+
}
89+
}
90+
91+
componentWillUnmount() {
92+
if (this.abortController) {
93+
this.abortController.abort();
94+
}
95+
}
96+
97+
reset = () => {
98+
if (this.abortController) {
99+
this.abortController.abort();
100+
}
101+
// reset the necessaries
102+
this.setState(({resetLink, link}) => ({
103+
isPending: false,
104+
link: resetLink ? null : link,
105+
resetLink: false,
106+
showDialog: false,
107+
}));
108+
};
109+
110+
handleOptionsChanged = () => {
111+
const {showDialog, link} = this.state;
112+
if (!link) {
113+
return;
114+
}
115+
if (!showDialog) {
116+
return this.setState({
117+
link: null,
118+
reference: null,
119+
})
120+
}
121+
this.setState({resetLink: true})
122+
}
123+
124+
handleAbort = () => {
125+
const {onAbort} = this.props;
126+
if (onAbort) {
127+
onAbort();
128+
}
129+
this.reset();
130+
}
131+
132+
handleRedirect = (params: any) => {
133+
const {onRedirect} = this.props;
134+
// reset payment link
135+
this.setState(
136+
({resetLink, reference}) => ({
137+
reference: params.flwref || params.status === 'successful' ? null : reference,
138+
resetLink: params.flwref || params.status === 'successful' ? true : resetLink,
139+
showDialog: false,
140+
}),
141+
() => {
142+
onRedirect(params)
143+
this.reset();
144+
}
145+
);
146+
}
147+
148+
handleInit = async () => {
149+
const {
150+
options,
151+
onWillInitialize,
152+
onInitializeError,
153+
onDidInitialize,
154+
init,
155+
} = this.props;
156+
const {isPending, reference, link} = this.state;
157+
// just show the dialod if the link is already set
158+
if (link) {
159+
return this.setState({showDialog: true});
160+
}
161+
// throw error if transaction reference has not changed
162+
if (reference === this.props.reference) {
163+
// fire oninitialize error handler if available
164+
if (onInitializeError) {
165+
onInitializeError(new FlutterwaveInitError({
166+
message: 'Please generate a new transaction reference.',
167+
code: 'SAME_TXREF',
168+
}))
169+
}
170+
return;
171+
}
172+
// stop if currently in pending mode
173+
if (isPending) {
174+
return;
175+
}
176+
// initialize abort controller if not set
177+
this.abortController = new AbortController;
178+
// fire will initialize handler if available
179+
if (onWillInitialize) {
180+
onWillInitialize();
181+
}
182+
this.setState({
183+
isPending: true,
184+
link: null,
185+
reference: this.props.reference,
186+
showDialog: false,
187+
}, async () => {
188+
// handle init
189+
try {
190+
// initialize payment
191+
const paymentLink = await init(
192+
{...options, redirect_url: REDIRECT_URL},
193+
this.abortController
194+
);
195+
// set payment link
196+
this.setState({
197+
link: paymentLink,
198+
isPending: false,
199+
showDialog: true,
200+
}, () => {
201+
// fire did initialize handler if available
202+
if (onDidInitialize) {
203+
onDidInitialize();
204+
}
205+
});
206+
} catch (error) {
207+
// stop if request was canceled
208+
if (error && /aborterror/i.test(error.code)) {
209+
return;
210+
}
211+
// call onInitializeError handler if an error occured
212+
if (onInitializeError) {
213+
onInitializeError(error);
214+
}
215+
// set payment link to reset
216+
this.setState({
217+
resetLink: true,
218+
reference: null,
219+
}, this.reset);
220+
}
221+
})
222+
};
223+
224+
render() {
225+
const {link, showDialog} = this.state;
226+
return (
227+
<>
228+
{this.renderButton()}
229+
<FlwCheckout
230+
onAbort={this.handleAbort}
231+
onRedirect={this.handleRedirect}
232+
link={link || undefined}
233+
visible={showDialog}
234+
/>
235+
</>
236+
);
237+
}
238+
239+
renderButton() {
240+
const {alignLeft, customButton, children, style} = this.props;
241+
const {isPending} = this.state;
242+
if (customButton) {
243+
return customButton({
244+
disabled: isPending,
245+
onPress: this.handleInit
246+
});
247+
}
248+
return <FlwButton
249+
style={style}
250+
alignLeft={!!alignLeft}
251+
children={children}
252+
onPress={this.handleInit}
253+
disabled={isPending}
254+
/>;
255+
}
256+
}
257+
258+
export default PayWithFlutterwaveBase;

0 commit comments

Comments
 (0)