react-nomba-checkout-sdk 1.0.0 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-nomba-checkout-sdk",
3
- "version": "1.0.0",
3
+ "version": "1.0.3",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.esm.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,26 @@
1
+ import axios from 'axios';
2
+
3
+ export const handleNombaApiCall = async (
4
+ url: string,
5
+ method: string,
6
+ body?: {} | null,
7
+ customHeaders?: { [key: string]: string },
8
+ environment?: string
9
+ ) => {
10
+ const baseUrl =
11
+ environment === 'sandbox'
12
+ ? 'https://sandbox.nomba.com/v1'
13
+ : 'https://api.nomba.com/v1';
14
+
15
+ const options = {
16
+ method: method,
17
+ headers: {
18
+ 'content-type': 'application/json',
19
+ ...customHeaders,
20
+ },
21
+ data: body && body,
22
+ url: `${baseUrl}${url}`,
23
+ };
24
+ const response = await axios(options);
25
+ return response;
26
+ };
@@ -0,0 +1,26 @@
1
+ import axios from 'axios';
2
+
3
+ export const handleVendorApiCall = async (
4
+ url: string,
5
+ method: string,
6
+ body?: {} | null,
7
+ customHeaders?: { [key: string]: string },
8
+ environment?: string
9
+ ) => {
10
+ const baseUrl =
11
+ environment === 'sandbox'
12
+ ? 'https://sandbox.nomba.com/v1'
13
+ : 'https://api.nomba.com/v1';
14
+
15
+ const options = {
16
+ method: method,
17
+ headers: {
18
+ 'content-type': 'application/json',
19
+ ...customHeaders,
20
+ },
21
+ data: body && body,
22
+ url: `${baseUrl}${url}`,
23
+ };
24
+ const response = await axios(options);
25
+ return response;
26
+ };
@@ -1,4 +1,4 @@
1
- import { handleApiCall } from "./handleApiCall";
1
+ import { handleVendorApiCall } from "./handleVendorApiCall";
2
2
 
3
3
  interface CardCheckoutPayload {
4
4
  orderReference: string;
@@ -9,7 +9,7 @@ interface CardCheckoutPayload {
9
9
 
10
10
  export const useCardCheckout = async (payload: CardCheckoutPayload) => {
11
11
  try {
12
- const response = await handleApiCall(
12
+ const response = await handleVendorApiCall(
13
13
  "/checkout/checkout-card-detail",
14
14
  "POST",
15
15
  {
@@ -1,4 +1,4 @@
1
- import { handleApiCall } from "./handleApiCall";
1
+ import { handleVendorApiCall } from "./handleVendorApiCall";
2
2
 
3
3
  interface OtpRequest {
4
4
  otp: string;
@@ -9,7 +9,7 @@ interface OtpRequest {
9
9
 
10
10
  export const useCardCheckoutOtp = async (payload: OtpRequest) => {
11
11
  try {
12
- const response = await handleApiCall(
12
+ const response = await handleVendorApiCall(
13
13
  "/checkout/checkout-card-otp",
14
14
  "POST",
15
15
  payload
@@ -1,8 +1,8 @@
1
- import { handleApiCall } from "./handleApiCall";
1
+ import { handleVendorApiCall } from "./handleVendorApiCall";
2
2
 
3
3
  export const useFetchUssdBanks = async () => {
4
4
  try {
5
- const response = await handleApiCall(`/checkout/ussd/banks`, "GET");
5
+ const response = await handleVendorApiCall(`/checkout/ussd/banks`, "GET");
6
6
  return response.data;
7
7
  } catch (error: any) {
8
8
  return error;
@@ -1,8 +1,8 @@
1
- import { handleApiCall } from "./handleApiCall";
1
+ import { handleVendorApiCall } from "./handleVendorApiCall";
2
2
 
3
3
  export const useGenerateQrCode = async (orderReference: string) => {
4
4
  try {
5
- const response = await handleApiCall(
5
+ const response = await handleVendorApiCall(
6
6
  `/checkout/qr/${orderReference}`,
7
7
  "GET"
8
8
  );
@@ -0,0 +1,30 @@
1
+ import { handleNombaApiCall } from './handleNombaApiCall';
2
+
3
+ interface GenerateTokenPayload {
4
+ clientId: string;
5
+ clientSecret: string;
6
+ accountId: string;
7
+ environment?: string;
8
+ }
9
+
10
+ export const useGenerateToken = async (payload: GenerateTokenPayload) => {
11
+ try {
12
+ const response = await handleNombaApiCall(
13
+ '/auth/token/issue',
14
+ 'POST',
15
+ {
16
+ grant_type: 'client_credentials',
17
+ client_id: payload.clientId,
18
+ client_secret: payload.clientSecret,
19
+ },
20
+ {
21
+ accountId: payload.accountId,
22
+ },
23
+ payload.environment
24
+ );
25
+
26
+ return response;
27
+ } catch (error: any) {
28
+ return error;
29
+ }
30
+ };
@@ -1,8 +1,8 @@
1
- import { handleApiCall } from "./handleApiCall";
1
+ import { handleVendorApiCall } from "./handleVendorApiCall";
2
2
 
3
3
  export const useGetOrder = async (orderReference: string) => {
4
4
  try {
5
- const response = await handleApiCall(
5
+ const response = await handleVendorApiCall(
6
6
  `/checkout/order/${orderReference}`,
7
7
  "GET"
8
8
  );
@@ -1,4 +1,4 @@
1
- import { handleApiCall } from "./handleApiCall";
1
+ import { handleVendorApiCall } from "./handleVendorApiCall";
2
2
 
3
3
  interface UssdCodeRequest {
4
4
  ussdBankId: string;
@@ -16,7 +16,7 @@ export const useGetUssdCode = async (payload: {
16
16
  orderId: string;
17
17
  }) => {
18
18
  try {
19
- const response = await handleApiCall(
19
+ const response = await handleVendorApiCall(
20
20
  `/checkout/ussd/code`,
21
21
  "POST",
22
22
  payload
@@ -0,0 +1,70 @@
1
+ import eventBus from '../eventBus';
2
+ import { handleNombaApiCall } from './handleNombaApiCall';
3
+ import { useGenerateToken } from './useGenerateToken';
4
+
5
+ interface CheckoutPayload {
6
+ order: {
7
+ orderReference: string;
8
+ customerId: string;
9
+ callbackUrl: string;
10
+ customerEmail: string;
11
+ amount: string;
12
+ currency: string;
13
+ accountId: string;
14
+ splitRequest?: {
15
+ splitType: string;
16
+ splitList: [
17
+ {
18
+ accountId: string;
19
+ value: number;
20
+ },
21
+ ];
22
+ };
23
+ };
24
+ tokenizeCard: boolean;
25
+ clientId: string;
26
+ clientSecret: string;
27
+ accountId: string;
28
+ environment?: string;
29
+ onSuccess: (orderReference: string) => void;
30
+ onFailure: (e: any) => void;
31
+ onClose: () => {};
32
+ }
33
+
34
+ export const useNombaCheckout = async (payload: CheckoutPayload) => {
35
+ try {
36
+ const tokenResponse = await useGenerateToken({
37
+ clientId: payload.clientId,
38
+ clientSecret: payload.clientSecret,
39
+ accountId: payload.accountId,
40
+ environment: payload.environment,
41
+ });
42
+
43
+ const response = await handleNombaApiCall(
44
+ '/checkout/order',
45
+ 'POST',
46
+ {
47
+ order: {
48
+ ...payload.order,
49
+ accountId: payload.accountId,
50
+ },
51
+ tokenizeCard: payload.tokenizeCard,
52
+ },
53
+ {
54
+ Authorization: `Bearer ${tokenResponse?.data?.data?.access_token}`,
55
+ accountId: payload.accountId,
56
+ },
57
+ payload.environment
58
+ );
59
+
60
+ eventBus.emit('openModal', {
61
+ orderId: response?.data?.data?.orderReference,
62
+ onClose: payload.onClose,
63
+ environment: payload.environment,
64
+ });
65
+ return response.data;
66
+ } catch (error: any) {
67
+ payload.onFailure?.(error);
68
+ return error;
69
+ }
70
+ };
@@ -1,8 +1,8 @@
1
- import { handleApiCall } from "./handleApiCall";
1
+ import { handleVendorApiCall } from "./handleVendorApiCall";
2
2
 
3
3
  export const useResendOtp = async (orderReference: string) => {
4
4
  try {
5
- const response = await handleApiCall("/checkout/resend-otp", "POST", {
5
+ const response = await handleVendorApiCall("/checkout/resend-otp", "POST", {
6
6
  orderReference,
7
7
  });
8
8
  return response.data;
@@ -1,8 +1,8 @@
1
- import { handleApiCall } from "./handleApiCall";
1
+ import { handleVendorApiCall } from "./handleVendorApiCall";
2
2
 
3
3
  export const useVerifyOrderStatus = async (orderReference: string) => {
4
4
  try {
5
- const response = await handleApiCall(
5
+ const response = await handleVendorApiCall(
6
6
  "/checkout/confirm-transaction-receipt",
7
7
  "POST",
8
8
  { orderReference }
@@ -1,39 +1,72 @@
1
- import React, { Component } from "react";
2
- import "../styles.css";
3
- import Loader from "../assets/Loader";
4
- import { CloseIcon } from "../assets/CloseIcon";
5
-
6
- interface NombaCheckoutModalProps {
7
- orderId: string;
8
- onClose: () => void;
9
- }
1
+ import React, { Component } from 'react';
2
+ import '../styles.css';
3
+ import Loader from '../assets/Loader';
4
+ import { CloseIcon } from '../assets/CloseIcon';
5
+ import eventBus from '../eventBus';
10
6
 
11
7
  interface NombaCheckoutModalState {
8
+ orderId: string;
12
9
  isLoading: boolean;
10
+ isOpen: boolean;
11
+ onClose: () => void;
12
+ environment: string;
13
13
  }
14
14
 
15
- class NombaCheckoutModal extends Component<
16
- NombaCheckoutModalProps,
17
- NombaCheckoutModalState
18
- > {
19
- constructor(props: NombaCheckoutModalProps) {
15
+ class NombaCheckoutModal extends Component<{}, NombaCheckoutModalState> {
16
+ constructor(props: {}) {
20
17
  super(props);
21
18
  this.state = {
19
+ orderId: '',
22
20
  isLoading: true,
21
+ isOpen: false,
22
+ onClose: () => {},
23
+ environment: 'live',
23
24
  };
24
25
  }
25
26
 
27
+ handleOpen = (orderId: string) => {
28
+ this.setState({ isOpen: true, orderId });
29
+ };
30
+
26
31
  handleClose = () => {
27
- this.props.onClose?.();
32
+ this.state.onClose?.();
33
+ this.setState({ isOpen: false });
28
34
  };
29
35
 
30
36
  handleIframeLoad = () => {
31
37
  this.setState({ isLoading: false });
32
38
  };
33
39
 
40
+ componentDidMount() {
41
+ eventBus.on(
42
+ 'openModal',
43
+ ({
44
+ orderId,
45
+ onClose,
46
+ environment,
47
+ }: {
48
+ orderId: string;
49
+ onClose: () => void;
50
+ environment: string;
51
+ }) => {
52
+ this.setState({ isOpen: true, orderId, onClose, environment });
53
+ }
54
+ );
55
+
56
+ eventBus.on('closeModal', () => {
57
+ this.setState({ isOpen: false });
58
+ });
59
+ }
60
+
61
+ componentWillUnmount() {
62
+ eventBus.off('openModal', this.handleOpen);
63
+ eventBus.off('closeModal', this.handleClose);
64
+ }
65
+
34
66
  render() {
35
- const { orderId } = this.props;
36
- const { isLoading } = this.state;
67
+ const { isLoading, isOpen, orderId } = this.state;
68
+
69
+ if (!isOpen) return null;
37
70
 
38
71
  return (
39
72
  <div style={modalStyles.overlay} onClick={this.handleClose}>
@@ -41,7 +74,7 @@ class NombaCheckoutModal extends Component<
41
74
  <div style={modalStyles.content}>
42
75
  {isLoading && (
43
76
  <div style={modalStyles.loader}>
44
- <div className="spinner"></div>
77
+ <div className='spinner'></div>
45
78
  <Loader />
46
79
  </div>
47
80
  )}
@@ -51,12 +84,12 @@ class NombaCheckoutModal extends Component<
51
84
  </button>
52
85
 
53
86
  <iframe
54
- src={`https://checkout.nomba.com/pay/${orderId}`}
55
- allow="clipboard-write clipboard-read"
56
- width="100%"
57
- height="80vh"
87
+ src={`https://checkout.nomba.com/${this.state.environment === 'sandbox' ? 'sandbox' : 'pay'}/${orderId}`}
88
+ allow='clipboard-write clipboard-read'
89
+ width='100%'
90
+ height='80vh'
58
91
  style={modalStyles.iframe}
59
- title="Nomba checkout"
92
+ title='Nomba checkout'
60
93
  onLoad={this.handleIframeLoad}
61
94
  />
62
95
  </div>
@@ -68,64 +101,64 @@ class NombaCheckoutModal extends Component<
68
101
 
69
102
  const modalStyles: { [key: string]: React.CSSProperties } = {
70
103
  overlay: {
71
- position: "fixed",
104
+ position: 'fixed',
72
105
  top: 0,
73
106
  left: 0,
74
107
  right: 0,
75
108
  bottom: 0,
76
- backgroundColor: "rgba(0, 0, 0, 0.5)",
77
- display: "flex",
78
- justifyContent: "center",
79
- alignItems: "center",
109
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
110
+ display: 'flex',
111
+ justifyContent: 'center',
112
+ alignItems: 'center',
80
113
  zIndex: 9999,
81
114
  },
82
115
  modal: {
83
- backgroundColor: "white",
84
- width: "100%",
85
- maxWidth: "60vw",
86
- borderRadius: "8px",
87
- boxShadow: "0 4px 8px rgba(0, 0, 0, 0.1)",
88
- position: "relative",
89
- height: "600px",
90
- maxHeight: "95vh",
116
+ backgroundColor: 'white',
117
+ width: '100%',
118
+ maxWidth: '60vw',
119
+ borderRadius: '8px',
120
+ boxShadow: '0 4px 8px rgba(0, 0, 0, 0.1)',
121
+ position: 'relative',
122
+ height: '600px',
123
+ maxHeight: '95vh',
91
124
  },
92
125
  header: {
93
- position: "relative",
94
- display: "flex",
95
- justifyContent: "space-between",
96
- alignItems: "center",
126
+ position: 'relative',
127
+ display: 'flex',
128
+ justifyContent: 'space-between',
129
+ alignItems: 'center',
97
130
  },
98
131
  closeButton: {
99
- position: "absolute",
100
- top: "16px",
101
- right: "16px",
102
- border: "none",
103
- background: "transparent",
132
+ position: 'absolute',
133
+ top: '16px',
134
+ right: '16px',
135
+ border: 'none',
136
+ background: 'transparent',
104
137
  },
105
138
  content: {
106
- position: "relative",
139
+ position: 'relative',
107
140
  },
108
141
  iframe: {
109
- border: "none",
110
- borderRadius: "8px",
111
- width: "100%",
112
- maxHeight: "95vh",
113
- height: "600px",
114
- display: "block",
142
+ border: 'none',
143
+ borderRadius: '8px',
144
+ width: '100%',
145
+ maxHeight: '95vh',
146
+ height: '600px',
147
+ display: 'block',
115
148
  },
116
149
  loader: {
117
- position: "absolute",
150
+ position: 'absolute',
118
151
  top: 0,
119
152
  left: 0,
120
- width: "100%",
121
- height: "100%",
122
- backgroundColor: "rgba(255, 255, 255, 0.8)",
123
- display: "flex",
124
- flexDirection: "column",
125
- alignItems: "center",
126
- justifyContent: "center",
127
- fontSize: "18px",
128
- fontWeight: "bold",
153
+ width: '100%',
154
+ height: '100%',
155
+ backgroundColor: 'rgba(255, 255, 255, 0.8)',
156
+ display: 'flex',
157
+ flexDirection: 'column',
158
+ alignItems: 'center',
159
+ justifyContent: 'center',
160
+ fontSize: '18px',
161
+ fontWeight: 'bold',
129
162
  },
130
163
  };
131
164
 
@@ -0,0 +1,25 @@
1
+ class EventBus {
2
+ private events: { [key: string]: Array<(data: any) => void> } = {};
3
+
4
+ on(event: string, listener: (data: any) => void): void {
5
+ if (!this.events[event]) {
6
+ this.events[event] = [];
7
+ }
8
+ this.events[event].push(listener);
9
+ }
10
+
11
+ emit(event: string, data: any): void {
12
+ if (this.events[event]) {
13
+ this.events[event].forEach(listener => listener(data));
14
+ }
15
+ }
16
+
17
+ off(event: string, listener: (data: any) => void): void {
18
+ if (this.events[event]) {
19
+ this.events[event] = this.events[event].filter(l => l !== listener);
20
+ }
21
+ }
22
+ }
23
+
24
+ const eventBus = new EventBus();
25
+ export default eventBus;
@@ -0,0 +1,26 @@
1
+ import React from "react";
2
+ import ReactDOM from "react-dom/client";
3
+ import { NombaCheckoutModal } from "../components/NombaCheckoutModal";
4
+
5
+ let root: ReactDOM.Root | null = null;
6
+
7
+ export const InitializeNombaCheckout = () => {
8
+ const modalDivId = "nomba-checkout-modal";
9
+
10
+ let modalContainer = document.getElementById(modalDivId);
11
+ if (!modalContainer) {
12
+ modalContainer = document.createElement("div");
13
+ modalContainer.id = modalDivId;
14
+ document.body.appendChild(modalContainer);
15
+ }
16
+
17
+ if (!root) {
18
+ root = ReactDOM.createRoot(modalContainer);
19
+ }
20
+
21
+ root!.render(
22
+ <div>
23
+ <NombaCheckoutModal />
24
+ </div>
25
+ );
26
+ };
package/src/index.tsx CHANGED
@@ -1,6 +1,3 @@
1
- import { NombaCheckoutButton } from "./components/NombaCheckoutButton";
2
- import { NombaCheckoutModal } from "./components/NombaCheckoutModal";
3
-
4
1
  import { useGetOrder } from "./apis/useGetOrder";
5
2
  import { useCardCheckout } from "./apis/useCardCheckout";
6
3
  import { useCardCheckoutOtp } from "./apis/useCardCheckoutOtp";
@@ -9,10 +6,11 @@ import { useVerifyOrderStatus } from "./apis/useVerifyOrderStatus";
9
6
  import { useResendOtp } from "./apis/useResendOtp";
10
7
  import { useFetchUssdBanks } from "./apis/useFetchUssdBanks";
11
8
  import { useGetUssdCode } from "./apis/useGetUssdCode";
9
+ import { useGenerateToken } from "./apis/useGenerateToken";
10
+ import { useNombaCheckout } from "./apis/useNombaCheckout";
11
+ import {InitializeNombaCheckout} from "./helpers/InitializeNombaCheckout"
12
12
 
13
13
  export {
14
- NombaCheckoutButton,
15
- NombaCheckoutModal,
16
14
  useGetOrder,
17
15
  useCardCheckout,
18
16
  useCardCheckoutOtp,
@@ -21,4 +19,7 @@ export {
21
19
  useVerifyOrderStatus,
22
20
  useFetchUssdBanks,
23
21
  useGetUssdCode,
22
+ useGenerateToken,
23
+ useNombaCheckout,
24
+ InitializeNombaCheckout
24
25
  };
@@ -1,3 +0,0 @@
1
- export declare const handleApiCall: (url: string, method: string, body?: {} | null, customHeaders?: {
2
- [key: string]: string;
3
- }) => Promise<import("axios").AxiosResponse<any, any>>;
@@ -1,17 +0,0 @@
1
- import React, { Component } from "react";
2
- interface NombaCheckoutButtonProps {
3
- orderId: string;
4
- buttonText?: string;
5
- buttonStyle?: React.CSSProperties;
6
- children?: React.ReactNode;
7
- onClose: () => void;
8
- }
9
- interface NombaCheckoutButtonState {
10
- showModal: boolean;
11
- }
12
- declare class NombaCheckoutButton extends Component<NombaCheckoutButtonProps, NombaCheckoutButtonState> {
13
- constructor(props: NombaCheckoutButtonProps);
14
- handleClose: () => void;
15
- render(): React.JSX.Element;
16
- }
17
- export { NombaCheckoutButton };
@@ -1,20 +0,0 @@
1
- import axios from "axios";
2
-
3
- export const handleApiCall = async (
4
- url: string,
5
- method: string,
6
- body?: {} | null,
7
- customHeaders?: { [key: string]: string }
8
- ) => {
9
- const options = {
10
- method: method,
11
- headers: {
12
- "content-type": "application/json",
13
- ...customHeaders,
14
- },
15
- data: body && body,
16
- url: `https://vendor-api.kudi.ai/v1${url}`,
17
- };
18
- const response = await axios(options);
19
- return response;
20
- };