@fadyshawky/react-native-magic 2.0.4 → 2.0.5

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.
Files changed (110) hide show
  1. package/package.json +1 -1
  2. package/template/src/common/ImageResources.g.ts +33 -1
  3. package/template/src/common/components/Background.tsx +3 -1
  4. package/template/src/common/components/Container.tsx +1 -1
  5. package/template/src/common/components/OTPInput.tsx +3 -2
  6. package/template/src/common/components/PrimaryButton.tsx +23 -23
  7. package/template/src/common/components/PrimaryTextInput.tsx +189 -199
  8. package/template/src/common/components/RadioIcon.tsx +4 -4
  9. package/template/src/common/components/SafeText.tsx +41 -0
  10. package/template/src/common/components/SearchBar.tsx +19 -17
  11. package/template/src/common/components/TryAgain.tsx +3 -3
  12. package/template/src/common/localization/localization.ts +10 -0
  13. package/template/src/common/localization/translations/commonLocalization.ts +97 -0
  14. package/template/src/common/localization/translations/homeLocalization.ts +24 -0
  15. package/template/src/common/localization/translations/loginLocalization.ts +28 -2
  16. package/template/src/common/localization/translations/mainNavigationLocalization.ts +30 -0
  17. package/template/src/common/localization/translations/navigationLocalization.ts +48 -0
  18. package/template/src/common/localization/translations/otpLocalization.ts +28 -0
  19. package/template/src/common/localization/translations/passwordLocalization.ts +54 -0
  20. package/template/src/common/localization/translations/posLocalization.ts +196 -0
  21. package/template/src/common/utils/FeesCaalculation.tsx +37 -0
  22. package/template/src/common/utils/index.tsx +11 -0
  23. package/template/src/common/utils/printData.tsx +161 -0
  24. package/template/src/common/validations/errorValidations.ts +3 -2
  25. package/template/src/components/PrinterExample.js +226 -0
  26. package/template/src/core/api/serverHeaders.ts +62 -1
  27. package/template/src/core/store/Categories/categoryActions.ts +33 -0
  28. package/template/src/core/store/Categories/categorySlice.ts +75 -0
  29. package/template/src/core/store/Categories/categoryState.ts +41 -0
  30. package/template/src/core/store/Providers/providersActions.ts +102 -0
  31. package/template/src/core/store/Providers/providersSlice.ts +136 -0
  32. package/template/src/core/store/Providers/providersState.ts +37 -0
  33. package/template/src/core/store/Services/servicesActions.ts +191 -0
  34. package/template/src/core/store/Services/servicesSlice.ts +205 -0
  35. package/template/src/core/store/Services/servicesState.ts +466 -0
  36. package/template/src/core/store/app/appSlice.ts +13 -5
  37. package/template/src/core/store/app/appState.ts +10 -2
  38. package/template/src/core/store/rootReducer.ts +6 -1
  39. package/template/src/core/store/store.tsx +55 -2
  40. package/template/src/core/store/user/userActions.ts +164 -26
  41. package/template/src/core/store/user/userSlice.ts +193 -21
  42. package/template/src/core/store/user/userState.ts +148 -25
  43. package/template/src/core/theme/colors.ts +70 -94
  44. package/template/src/core/theme/commonConsts.ts +1 -1
  45. package/template/src/core/theme/commonSizes.ts +94 -119
  46. package/template/src/core/theme/commonStyles.ts +22 -22
  47. package/template/src/core/theme/fonts.ts +14 -13
  48. package/template/src/core/theme/themes.ts +75 -386
  49. package/template/src/core/theme/types.ts +15 -201
  50. package/template/src/core/utils/stringUtils.ts +114 -0
  51. package/template/src/modules/SunmiCard.js +212 -0
  52. package/template/src/modules/SunmiPrepaid.ts +122 -0
  53. package/template/src/navigation/AuthStack.tsx +8 -0
  54. package/template/src/navigation/HeaderComponents.tsx +76 -1
  55. package/template/src/navigation/MainNavigation.tsx +3 -1
  56. package/template/src/navigation/MainStack.tsx +130 -56
  57. package/template/src/navigation/TabBar.tsx +111 -59
  58. package/template/src/navigation/types.ts +24 -0
  59. package/template/src/screens/Categories/Categories.tsx +141 -0
  60. package/template/src/screens/Categories/hooks/useCategoriesData.ts +33 -0
  61. package/template/src/screens/Categories/types.ts +7 -0
  62. package/template/src/screens/Favorites/Favorites.tsx +130 -0
  63. package/template/src/screens/ForceChangePassword/ForceChangePasswordScreen.tsx +155 -0
  64. package/template/src/screens/History/History.tsx +430 -0
  65. package/template/src/screens/History/hooks/useHistoryData.ts +49 -0
  66. package/template/src/screens/History/types.ts +7 -0
  67. package/template/src/screens/InquiredBill/InquiredBill.tsx +443 -0
  68. package/template/src/screens/InquiredBill/hooks/useInquiredData.ts +91 -0
  69. package/template/src/screens/Login/Login.tsx +85 -85
  70. package/template/src/screens/OTP/OTPScreen.tsx +170 -0
  71. package/template/src/screens/PaymentConfirmation/PaymentConfirmation.tsx +326 -0
  72. package/template/src/screens/Providers/Providers.tsx +166 -0
  73. package/template/src/screens/Providers/hooks/useProvidersData.ts +33 -0
  74. package/template/src/screens/Providers/types.ts +7 -0
  75. package/template/src/screens/ReceiptScreen/ReceiptScreen.tsx +181 -0
  76. package/template/src/screens/ReceiptScreen/hooks/useReceiptData.ts +46 -0
  77. package/template/src/screens/ReceiptScreen/utils/utils.tsx +156 -0
  78. package/template/src/screens/Services/Services.tsx +144 -0
  79. package/template/src/screens/Services/hooks/useServicesData.ts +41 -0
  80. package/template/src/screens/SingleService/Components/FawryInputs.tsx +446 -0
  81. package/template/src/screens/SingleService/SingleService.tsx +229 -0
  82. package/template/src/screens/SingleService/hooks/useServiceData.ts +164 -0
  83. package/template/src/screens/home/Components/PayByCode.tsx +129 -0
  84. package/template/src/screens/home/HomeScreen.tsx +268 -77
  85. package/template/src/screens/home/hooks/useHomeData.ts +32 -38
  86. package/template/src/screens/index.tsx +24 -0
  87. package/template/src/screens/profile/Profile.tsx +290 -2
  88. package/template/src/services/SunmiPrinterInternal.js +268 -0
  89. package/template/src/types/sunmiPrepaid.d.ts +20 -0
  90. package/template/src/utils/SunmiPrinter.ts +442 -0
  91. package/template/src/utils/feesCalculator.ts +92 -0
  92. package/template/src/common/components/Stepper.tsx +0 -153
  93. package/template/src/common/components/Svg.tsx +0 -25
  94. package/template/src/common/hooks/useDebounce.ts +0 -17
  95. package/template/src/common/hooks/usePrevious.ts +0 -11
  96. package/template/src/common/localization/intlFormatter.ts +0 -37
  97. package/template/src/common/urls/emailUrl.ts +0 -20
  98. package/template/src/common/urls/mapUrl.ts +0 -22
  99. package/template/src/common/utils/listHandlers.ts +0 -30
  100. package/template/src/common/utils/serializeQueryParams.ts +0 -10
  101. package/template/src/common/validations/hooks/useDatesError.ts +0 -40
  102. package/template/src/common/validations/profileValidations.ts +0 -30
  103. package/template/src/core/theme/shadows.ts +0 -135
  104. package/template/src/navigation/TopTabBar.tsx +0 -77
  105. package/template/src/screens/Settings/Settings.tsx +0 -5
  106. package/template/src/screens/home/components/CarouselSection.tsx +0 -79
  107. package/template/src/screens/home/components/FeaturedCarousel.tsx +0 -128
  108. package/template/src/screens/main/Main.tsx +0 -5
  109. package/template/src/screens/registration/RegistrationScreen.tsx +0 -198
  110. package/template/src/screens/resetPassword/ForgotPasswordScreen.tsx +0 -129
@@ -1,170 +1,27 @@
1
1
  import {TextStyle} from 'react-native';
2
- import {ElevationShadow} from './shadows';
3
- import {PrimaryColors, NaturalColors, AlertColors} from './colors';
4
2
 
5
3
  export type ThemeMode = 'light' | 'dark';
6
4
 
7
- export interface ISize {
8
- font: {
9
- // Almarai Design System Typography
10
- bodySmall: number;
11
- bodyMedium: number;
12
- bodyLarge: number;
13
- bodyXLarge: number;
14
- heading6: number;
15
- heading4: number;
16
- heading3: number;
17
- heading2: number;
18
- heading1: number;
19
-
20
- // Legacy support
21
- extraSmall: number;
22
- extraSmallPlus: number;
23
- small: number;
24
- smallPlus: number;
25
- medium: number;
26
- mediumPlus: number;
27
- extraMedium: number;
28
- large: number;
29
- largePlus: number;
30
- extraLarge: number;
31
- extraLargePlus: number;
32
- };
33
- letterSpacing: {
34
- // Almarai Design System
35
- bodySmall: number;
36
- bodyMedium: number;
37
- bodyLarge: number;
38
- bodyXLarge: number;
39
- heading6: number;
40
- heading4: number;
41
- heading3: number;
42
- heading2: number;
43
- heading1: number;
44
-
45
- // Legacy support
46
- extraSmall: number;
47
- extraSmallPlus: number;
48
- small: number;
49
- smallPlus: number;
50
- medium: number;
51
- mediumPlus: number;
52
- large: number;
53
- largePlus: number;
54
- extraLarge: number;
55
- extraLargePlus: number;
56
- };
57
- lineHeight: {
58
- // Almarai Design System
59
- bodySmall: number;
60
- bodyMedium: number;
61
- bodyLarge: number;
62
- bodyXLarge: number;
63
- heading6: number;
64
- heading4: number;
65
- heading3: number;
66
- heading2: number;
67
- heading1: number;
68
-
69
- // Legacy support
70
- extraSmall: number;
71
- extraSmallPlus: number;
72
- small: number;
73
- smallPlus: number;
74
- medium: number;
75
- mediumPlus: number;
76
- large: number;
77
- largePlus: number;
78
- extraLarge: number;
79
- extraLargePlus: number;
80
- };
81
- spacing: {
82
- // Design System Spacing Scale
83
- none: number; // space / 0
84
- xSmall: number; // space / 2
85
- small: number; // space / 4
86
- medium: number; // space / 8
87
- large: number; // space / 12
88
- xLarge: number; // space / 16
89
- xxLarge: number; // space / 24 (2X-Large)
90
- xxxLarge: number; // space / 32 (3X-Large)
91
- xxxxLarge: number; // space / 48 (4X-Large)
92
- xxxxxLarge: number; // space / 64 (5X-Large)
93
- xxxxxxLarge: number; // space / 80 (6X-Large)
94
- full: number; // space / 100 (Full)
95
-
96
- // Legacy support
97
- xs: number;
98
- sm: number;
99
- md: number;
100
- lg: number;
101
- xl: number;
102
- };
103
- borderRadius: {
104
- // Design System Border Radius Scale
105
- none: number; // Border / 0
106
- xSmall: number; // Border / 2
107
- small: number; // Border / 4
108
- medium: number; // Border / 8
109
- large: number; // Border / 12
110
- xLarge: number; // Border / 16
111
- xxLarge: number; // Border / 24 (2X-Large)
112
- full: number; // Border / 1000 (Full)
113
-
114
- // Legacy support
115
- xs: number;
116
- sm: number;
117
- md: number;
118
- lg: number;
119
- xl: number;
120
- };
121
- borderWidth: {
122
- // Design System Stroke Scale
123
- none: number; // Stroke / 0
124
- xSmall: number; // Stroke / 0.5
125
- small: number; // Stroke / 1
126
- medium: number; // Stroke / 1.5
127
- large: number; // Stroke / 2
128
-
129
- // Legacy support
130
- extraSmall: number;
131
- };
132
- }
133
-
134
5
  export interface Theme {
135
6
  mode: ThemeMode;
136
- colors: typeof PrimaryColors & typeof NaturalColors & typeof AlertColors;
7
+ colors: {
8
+ indigoBlue: string;
9
+ mutedLavender: string;
10
+ tintColor: string;
11
+ mutedLavender30: string;
12
+ balanceBackground: string;
13
+ white: string;
14
+ backgroundOpacity: string;
15
+ black: string;
16
+ background: string;
17
+ surface: string;
18
+ card: string;
19
+ shadow: string;
20
+ red: string;
21
+ };
137
22
  text: {
138
- // Almarai Design System Typography
139
- // Headings
140
23
  header1: TextStyle;
141
24
  header2: TextStyle;
142
- header3: TextStyle;
143
- header4: TextStyle;
144
- header6: TextStyle;
145
-
146
- // Body Text with different weights
147
- bodyXLargeLight: TextStyle;
148
- bodyXLargeRegular: TextStyle;
149
- bodyXLargeBold: TextStyle;
150
- bodyXLargeExtraBold: TextStyle;
151
-
152
- bodyLargeLight: TextStyle;
153
- bodyLargeRegular: TextStyle;
154
- bodyLargeBold: TextStyle;
155
- bodyLargeExtraBold: TextStyle;
156
-
157
- bodyMediumLight: TextStyle;
158
- bodyMediumRegular: TextStyle;
159
- bodyMediumBold: TextStyle;
160
- bodyMediumExtraBold: TextStyle;
161
-
162
- bodySmallLight: TextStyle;
163
- bodySmallRegular: TextStyle;
164
- bodySmallBold: TextStyle;
165
- bodySmallExtraBold: TextStyle;
166
-
167
- // Legacy styles for backward compatibility
168
25
  balanceTitle: TextStyle;
169
26
  balanceAmount: TextStyle;
170
27
  balanceLabel: TextStyle;
@@ -178,21 +35,6 @@ export interface Theme {
178
35
  navBar: TextStyle;
179
36
  };
180
37
  spacing: {
181
- // Design System Spacing Scale
182
- none: number;
183
- xSmall: number;
184
- small: number;
185
- medium: number;
186
- large: number;
187
- xLarge: number;
188
- xxLarge: number;
189
- xxxLarge: number;
190
- xxxxLarge: number;
191
- xxxxxLarge: number;
192
- xxxxxxLarge: number;
193
- full: number;
194
-
195
- // Legacy support
196
38
  xs: number;
197
39
  sm: number;
198
40
  md: number;
@@ -200,38 +42,10 @@ export interface Theme {
200
42
  xl: number;
201
43
  };
202
44
  borderRadius: {
203
- // Design System Border Radius Scale
204
- none: number;
205
- xSmall: number;
206
- small: number;
207
- medium: number;
208
- large: number;
209
- xLarge: number;
210
- xxLarge: number;
211
- full: number;
212
-
213
- // Legacy support
214
45
  xs: number;
215
46
  sm: number;
216
47
  md: number;
217
48
  lg: number;
218
49
  xl: number;
219
50
  };
220
- borderWidth: {
221
- // Design System Stroke Scale
222
- none: number;
223
- xSmall: number;
224
- small: number;
225
- medium: number;
226
- large: number;
227
-
228
- // Legacy support
229
- extraSmall: number;
230
- };
231
- shadows: {
232
- elevation1: ElevationShadow;
233
- elevation2: ElevationShadow;
234
- elevation3: ElevationShadow;
235
- elevation4: ElevationShadow;
236
- };
237
51
  }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Safely converts any value to a string
3
+ * This is especially useful for error messages that might be objects
4
+ */
5
+ import CryptoJS from 'crypto-js';
6
+
7
+ export const ensureString = (value: any): string => {
8
+ if (value === null || value === undefined) {
9
+ return '';
10
+ }
11
+
12
+ if (typeof value === 'string') {
13
+ return value;
14
+ }
15
+
16
+ if (typeof value === 'object') {
17
+ try {
18
+ // Try to extract a message property if it exists
19
+ if (value.message && typeof value.message === 'string') {
20
+ return value.message;
21
+ }
22
+
23
+ // Otherwise stringify the object
24
+ return JSON.stringify(value);
25
+ } catch (e) {
26
+ return '[Object]';
27
+ }
28
+ }
29
+
30
+ // For numbers, booleans, etc.
31
+ return String(value);
32
+ };
33
+
34
+ export function decryptTripleDES(
35
+ encryptedBase64: string,
36
+ secretKeyBase64: string,
37
+ ) {
38
+ try {
39
+ // Decode the key from base64
40
+ const decodedKey = CryptoJS.enc.Base64.parse(secretKeyBase64);
41
+
42
+ // Decode the encrypted value from base64
43
+ const encryptedWordArray = CryptoJS.enc.Base64.parse(encryptedBase64);
44
+
45
+ // Decrypt using Triple DES
46
+ const decrypted = CryptoJS.TripleDES.decrypt(
47
+ {ciphertext: encryptedWordArray},
48
+ decodedKey,
49
+ {
50
+ mode: CryptoJS.mode.ECB,
51
+ padding: CryptoJS.pad.Pkcs7,
52
+ },
53
+ );
54
+
55
+ let decryptedText = decrypted.toString(CryptoJS.enc.Utf8);
56
+
57
+ if (!decryptedText) {
58
+ throw new Error('Decryption resulted in empty string');
59
+ }
60
+
61
+ decryptedText = decryptedText.replace(/(.{4})/g, '$1-');
62
+ if (decryptedText.endsWith('-')) {
63
+ decryptedText = decryptedText.slice(0, -1);
64
+ }
65
+
66
+ return decryptedText;
67
+ } catch (error) {
68
+ throw new Error(`Decryption failed`);
69
+ }
70
+ }
71
+
72
+ // Example usage:
73
+ // const decrypted = decryptBase64('encrypted_base64_string', 'your_secret_key');
74
+
75
+ /**
76
+ * Converts a camelCase string to PascalCase with specific formatting
77
+ * Example: billerId -> BillerId
78
+ * @param str The camelCase string to convert
79
+ * @returns The converted PascalCase string
80
+ */
81
+ export const toPascalCase = (str: string): string => {
82
+ if (!str) return '';
83
+
84
+ // Split by capital letters and convert to array
85
+ const words = str.split(/(?=[A-Z])/);
86
+
87
+ // Capitalize first letter of each word and join
88
+ return words
89
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
90
+ .join('');
91
+ };
92
+
93
+ // Example usage:
94
+ // toPascalCase('billerId') => 'BillerId'
95
+ // toPascalCase('customerName') => 'CustomerName'
96
+ // toPascalCase('accountNumber') => 'AccountNumber'
97
+
98
+ /**
99
+ * Converts a PascalCase string to camelCase
100
+ * Example: BillerId -> billerId
101
+ * @param str The PascalCase string to convert
102
+ * @returns The converted camelCase string
103
+ */
104
+ export const toCamelCase = (str: string): string => {
105
+ if (!str) return '';
106
+
107
+ // Convert first character to lowercase and keep the rest as is
108
+ return str.charAt(0).toLowerCase() + str.slice(1);
109
+ };
110
+
111
+ // Example usage:
112
+ // toCamelCase('BillerId') => 'billerId'
113
+ // toCamelCase('CustomerName') => 'customerName'
114
+ // toCamelCase('AccountNumber') => 'accountNumber'
@@ -0,0 +1,212 @@
1
+ import {NativeModules, Platform} from 'react-native';
2
+
3
+ const {SunmiCardModule} = NativeModules;
4
+
5
+ if (!SunmiCardModule) {
6
+ throw new Error('SunmiCardModule is not available on this device');
7
+ }
8
+
9
+ /**
10
+ * Interface for Sunmi Card Reader functionality
11
+ */
12
+ class SunmiCard {
13
+ /**
14
+ * Card types constants
15
+ */
16
+ static CARD_TYPE_MAGNETIC = SunmiCardModule.CARD_TYPE_MAGNETIC;
17
+ static CARD_TYPE_IC = SunmiCardModule.CARD_TYPE_IC;
18
+ static CARD_TYPE_NFC = SunmiCardModule.CARD_TYPE_NFC;
19
+ static CARD_TYPE_MAGNETIC_IC = SunmiCardModule.CARD_TYPE_MAGNETIC_IC;
20
+ static CARD_TYPE_MAGNETIC_NFC = SunmiCardModule.CARD_TYPE_MAGNETIC_NFC;
21
+ static CARD_TYPE_IC_NFC = SunmiCardModule.CARD_TYPE_IC_NFC;
22
+ static CARD_TYPE_MAGNETIC_IC_NFC = SunmiCardModule.CARD_TYPE_MAGNETIC_IC_NFC;
23
+
24
+ /**
25
+ * Card reader mode constants
26
+ */
27
+ static READ_MODE_MAGNETIC = SunmiCardModule.READ_MODE_MAGNETIC;
28
+ static READ_MODE_IC = SunmiCardModule.READ_MODE_IC;
29
+ static READ_MODE_NFC = SunmiCardModule.READ_MODE_NFC;
30
+
31
+ /**
32
+ * Track error code constants
33
+ */
34
+ static TRACK_ERROR_NONE = SunmiCardModule.TRACK_ERROR_NONE; // No error
35
+ static TRACK_ERROR_NO_DATA = SunmiCardModule.TRACK_ERROR_NO_DATA; // Track has no data
36
+ static TRACK_ERROR_PARITY = SunmiCardModule.TRACK_ERROR_PARITY; // Track parity check error
37
+ static TRACK_ERROR_LRC = SunmiCardModule.TRACK_ERROR_LRC; // Track LRC check error
38
+
39
+ /**
40
+ * Check if the Sunmi Pay SDK is connected
41
+ * @returns {Promise<boolean>} - True if connected
42
+ */
43
+ static isConnected() {
44
+ if (Platform.OS !== 'android') {
45
+ return Promise.reject(
46
+ new Error('SunmiCard is only available on Android'),
47
+ );
48
+ }
49
+
50
+ return SunmiCardModule.isConnected();
51
+ }
52
+
53
+ /**
54
+ * Check for a card to be presented
55
+ * @param {number} cardType - Type of card to check for (use CARD_TYPE_* constants)
56
+ * @param {number} timeout - Timeout in seconds
57
+ * @returns {Promise<Object>} - Card information or timeout/error
58
+ *
59
+ * For magnetic cards, the result will include:
60
+ * - type: "magnetic"
61
+ * - track1: Track 1 data (if available)
62
+ * - track2: Track 2 data (if available)
63
+ * - track3: Track 3 data (if available)
64
+ * - track1ErrorCode: Error code for track 1 (0 = no error, -1 = no data, -2 = parity error, -3 = LRC error)
65
+ * - track2ErrorCode: Error code for track 2
66
+ * - track3ErrorCode: Error code for track 3
67
+ *
68
+ * For IC cards, the result will include:
69
+ * - type: "ic"
70
+ * - atr: ATR data
71
+ *
72
+ * For NFC cards, the result will include:
73
+ * - type: "nfc"
74
+ * - uuid: Card UUID
75
+ *
76
+ * For timeout, the result will include:
77
+ * - type: "timeout"
78
+ */
79
+ static checkCard(cardType, timeout = 60) {
80
+ if (Platform.OS !== 'android') {
81
+ return Promise.reject(
82
+ new Error('SunmiCard is only available on Android'),
83
+ );
84
+ }
85
+
86
+ if (typeof cardType !== 'number') {
87
+ return Promise.reject(new Error('cardType must be a number'));
88
+ }
89
+
90
+ if (typeof timeout !== 'number') {
91
+ return Promise.reject(new Error('timeout must be a number'));
92
+ }
93
+
94
+ return SunmiCardModule.checkCard(cardType, timeout);
95
+ }
96
+
97
+ /**
98
+ * Cancel an ongoing card check operation
99
+ * @returns {Promise<boolean>} - True if successful
100
+ */
101
+ static cancelCardCheck() {
102
+ if (Platform.OS !== 'android') {
103
+ return Promise.reject(
104
+ new Error('SunmiCard is only available on Android'),
105
+ );
106
+ }
107
+
108
+ return SunmiCardModule.cancelCardCheck();
109
+ }
110
+
111
+ /**
112
+ * Read data from an IC card
113
+ * Note: This method is provided for backward compatibility.
114
+ * It's recommended to use checkCard with CARD_TYPE_IC instead.
115
+ * @returns {Promise<Object>} - IC card information
116
+ */
117
+ static readICCard() {
118
+ if (Platform.OS !== 'android') {
119
+ return Promise.reject(
120
+ new Error('SunmiCard is only available on Android'),
121
+ );
122
+ }
123
+
124
+ return SunmiCardModule.readICCard();
125
+ }
126
+
127
+ /**
128
+ * Read data from an NFC card
129
+ * Note: This method is provided for backward compatibility.
130
+ * It's recommended to use checkCard with CARD_TYPE_NFC instead.
131
+ * @returns {Promise<Object>} - NFC card information
132
+ */
133
+ static readNFCCard() {
134
+ if (Platform.OS !== 'android') {
135
+ return Promise.reject(
136
+ new Error('SunmiCard is only available on Android'),
137
+ );
138
+ }
139
+
140
+ return SunmiCardModule.readNFCCard();
141
+ }
142
+
143
+ /**
144
+ * Transaction types
145
+ */
146
+ static TRANS_TYPE_CONSUME = SunmiCardModule.TRANS_TYPE_CONSUME;
147
+ static TRANS_TYPE_REFUND = SunmiCardModule.TRANS_TYPE_REFUND;
148
+
149
+ /**
150
+ * Transaction result codes
151
+ */
152
+ static TRANS_RESULT_SUCCESS = SunmiCardModule.TRANS_RESULT_SUCCESS;
153
+ static TRANS_RESULT_OFFLINE_APPROVED =
154
+ SunmiCardModule.TRANS_RESULT_OFFLINE_APPROVED;
155
+ static TRANS_RESULT_DECLINED = SunmiCardModule.TRANS_RESULT_DECLINED;
156
+ static TRANS_RESULT_TERMINATED = SunmiCardModule.TRANS_RESULT_TERMINATED;
157
+ static TRANS_RESULT_CANCEL = SunmiCardModule.TRANS_RESULT_CANCEL;
158
+ static TRANS_RESULT_TIMEOUT = SunmiCardModule.TRANS_RESULT_TIMEOUT;
159
+ static TRANS_RESULT_OTHER_ERROR = SunmiCardModule.TRANS_RESULT_OTHER_ERROR;
160
+ static TRANS_RESULT_MAC_ERROR = SunmiCardModule.TRANS_RESULT_MAC_ERROR;
161
+ static TRANS_RESULT_FALLBACK = SunmiCardModule.TRANS_RESULT_FALLBACK;
162
+
163
+ /**
164
+ * Start a card payment transaction
165
+ * @param {number} amount - The transaction amount in cents (e.g., 1000 for $10.00)
166
+ * @param {string} currency - The currency code (e.g., "USD")
167
+ * @param {string} transactionType - The transaction type ("consume" or "refund")
168
+ * @param {number} timeout - Timeout in seconds for card detection
169
+ * @returns {Promise<Object>} - Transaction result object
170
+ */
171
+ static startCardPayment(
172
+ amount,
173
+ currency = 'USD',
174
+ transactionType = 'consume',
175
+ timeout = 60,
176
+ ) {
177
+ if (typeof amount !== 'number' || amount <= 0) {
178
+ return Promise.reject(new Error('Amount must be a positive number'));
179
+ }
180
+
181
+ // Convert transaction type string to integer
182
+ let transType = SunmiCard.TRANS_TYPE_CONSUME;
183
+ if (transactionType.toLowerCase() === 'refund') {
184
+ transType = SunmiCard.TRANS_TYPE_REFUND;
185
+ }
186
+
187
+ return SunmiCardModule.startCardPayment(
188
+ amount,
189
+ currency,
190
+ transactionType,
191
+ timeout,
192
+ );
193
+ }
194
+
195
+ /**
196
+ * Cancel an ongoing card payment transaction
197
+ * @returns {Promise<boolean>} - True if canceled successfully
198
+ */
199
+ static cancelCardPayment() {
200
+ return SunmiCardModule.cancelCardPayment();
201
+ }
202
+
203
+ /**
204
+ * Get the result of the last transaction
205
+ * @returns {Promise<Object|null>} - Transaction result object or null if no transaction
206
+ */
207
+ static getLastTransactionResult() {
208
+ return SunmiCardModule.getLastTransactionResult();
209
+ }
210
+ }
211
+
212
+ export default SunmiCard;
@@ -0,0 +1,122 @@
1
+ import {NativeModules} from 'react-native';
2
+
3
+ const {SunmiPrepaid} = NativeModules;
4
+
5
+ export interface PrepaidResponse {
6
+ success: boolean;
7
+ message: string;
8
+ billingAcct: string;
9
+ extraBillingAcctKey: any[];
10
+ customProperties: any[];
11
+ }
12
+
13
+ export interface ServiceStatus {
14
+ isInitialized: boolean;
15
+ }
16
+
17
+ function checkMeeterFeedBack(response: any): {Key: string; Value: string}[] {
18
+ let chunksNum;
19
+ if (response.meterFeedback.length % 1024 == 0)
20
+ chunksNum = response.meterFeedback.length / 1024;
21
+ else {
22
+ chunksNum = response.meterFeedback.length / 1024 + 1;
23
+ }
24
+
25
+ let tmp = [];
26
+
27
+ for (let i = 1; i <= chunksNum; i++) {
28
+ let meterFeedbackKey = 'ESCFBT0';
29
+ meterFeedbackKey += i;
30
+ let chunkLength = i * 1024;
31
+ let previousChunkLength = (i - 1) * 1024;
32
+ if (chunkLength > response.meterFeedback.length)
33
+ chunkLength = response.meterFeedback.length;
34
+ let chunkData = response.meterFeedback.substring(
35
+ previousChunkLength,
36
+ chunkLength,
37
+ );
38
+ tmp.push({Key: meterFeedbackKey, Value: chunkData});
39
+ }
40
+ return tmp;
41
+ }
42
+
43
+ class SunmiPrepaidModule {
44
+ async initializePrepaid(): Promise<PrepaidResponse> {
45
+ try {
46
+ return await SunmiPrepaid.initializePrepaid();
47
+ } catch (error) {
48
+ throw new Error(`Failed to initialize prepaid service: ${error}`);
49
+ }
50
+ }
51
+
52
+ async forceCardFacadeDetection(): Promise<PrepaidResponse> {
53
+ try {
54
+ return await SunmiPrepaid.forceCardFacadeDetection();
55
+ } catch (error) {
56
+ throw new Error(`Failed to force card facade detection: ${error}`);
57
+ }
58
+ }
59
+
60
+ async readCardMetadata(
61
+ extraKeys: any,
62
+ type: string,
63
+ ): Promise<PrepaidResponse> {
64
+ try {
65
+ const response = await SunmiPrepaid.readCardMetadata(type);
66
+
67
+ let keysArray = [
68
+ {Key: 'Key1', Value: response.vendor},
69
+ {Key: 'Key2', Value: response.sectorIdentifier},
70
+ {Key: 'Key3', Value: response.meterIdentifier},
71
+ ];
72
+
73
+ let propertiesArray = [
74
+ {
75
+ Key:
76
+ type === 'SC' || type === 'GASSC'
77
+ ? 'ElectrictyCompany'
78
+ : 'WaterCompany',
79
+ Value: response.companyIdentifier,
80
+ },
81
+ {Key: 'ClientIdentifier', Value: response.clientIdentifier},
82
+ {Key: 'CardMetadata', Value: response.metaData},
83
+ ];
84
+
85
+ if (response.meterFeedback.length < 1024) {
86
+ propertiesArray.push({
87
+ Key: 'ESCFBT01',
88
+ Value: response.meterFeedback,
89
+ });
90
+ } else {
91
+ propertiesArray.push(...checkMeeterFeedBack(response));
92
+ }
93
+
94
+ if (response.metaData) {
95
+ propertiesArray.push({Key: 'CardMetadata', Value: response.metaData});
96
+ }
97
+
98
+ return {
99
+ success: true,
100
+ message: 'success',
101
+ billingAcct: response.identifier,
102
+ extraBillingAcctKey: keysArray,
103
+ customProperties: propertiesArray,
104
+ };
105
+ } catch (error) {
106
+ throw new Error(`Failed to read card metadata: ${error}`);
107
+ }
108
+ }
109
+
110
+ async writeCardCharge(
111
+ billEncryptInfo: string,
112
+ cardMetadata: string,
113
+ ): Promise<PrepaidResponse> {
114
+ try {
115
+ return await SunmiPrepaid.writeCardCharge(billEncryptInfo, cardMetadata);
116
+ } catch (error) {
117
+ throw new Error(`Failed to write card charge: ${error}`);
118
+ }
119
+ }
120
+ }
121
+
122
+ export default new SunmiPrepaidModule();
@@ -1,6 +1,7 @@
1
1
  import {createNativeStackNavigator} from '@react-navigation/native-stack';
2
2
  import {useTranslation} from '../common/localization/LocalizationProvider';
3
3
  import {Login} from '../screens/Login/Login';
4
+ import {OTPScreen} from '../screens/OTP/OTPScreen';
4
5
  import {Splash} from '../screens/splash/Splash';
5
6
 
6
7
  const Stack = createNativeStackNavigator();
@@ -23,6 +24,13 @@ export function AuthStack() {
23
24
  headerShown: false,
24
25
  },
25
26
  },
27
+ {
28
+ id: 'OTP',
29
+ component: OTPScreen,
30
+ options: {
31
+ headerShown: false,
32
+ },
33
+ },
26
34
  ];
27
35
 
28
36
  return (