@myazahq/kyc-sdk-react-native 2.0.2 → 2.1.0

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 (281) hide show
  1. package/README.md +111 -8
  2. package/android/build.gradle +6 -0
  3. package/android/src/main/AndroidManifest.xml +14 -4
  4. package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaEmrtd.kt +358 -0
  5. package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaFaceDetector.kt +88 -1
  6. package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaRectDetector.kt +34 -0
  7. package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaTextRecognizer.kt +118 -0
  8. package/android/src/main/java/com/margelo/nitro/myazakyc/Jp2Decoder.kt +117 -0
  9. package/app.plugin.js +102 -13
  10. package/ios/HybridMyazaEmrtd.swift +447 -0
  11. package/ios/HybridMyazaFaceDetector.swift +104 -2
  12. package/ios/HybridMyazaRectDetector.swift +103 -0
  13. package/ios/HybridMyazaTextRecognizer.swift +111 -0
  14. package/nitrogen/generated/.gitattributes +1 -0
  15. package/nitrogen/generated/android/KycSdkReactNative+autolinking.cmake +87 -0
  16. package/nitrogen/generated/android/KycSdkReactNative+autolinking.gradle +27 -0
  17. package/nitrogen/generated/android/KycSdkReactNativeOnLoad.cpp +102 -0
  18. package/nitrogen/generated/android/KycSdkReactNativeOnLoad.hpp +34 -0
  19. package/nitrogen/generated/android/c++/JDetectedRect.hpp +81 -0
  20. package/nitrogen/generated/android/c++/JEmrtdApduResponse.hpp +61 -0
  21. package/nitrogen/generated/android/c++/JEmrtdTagInfo.hpp +61 -0
  22. package/nitrogen/generated/android/c++/JFaceResult.hpp +113 -0
  23. package/nitrogen/generated/android/c++/JHybridMyazaEmrtdSpec.cpp +176 -0
  24. package/nitrogen/generated/android/c++/JHybridMyazaEmrtdSpec.hpp +75 -0
  25. package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.cpp +60 -0
  26. package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.hpp +63 -0
  27. package/nitrogen/generated/android/c++/JHybridMyazaRectDetectorSpec.cpp +60 -0
  28. package/nitrogen/generated/android/c++/JHybridMyazaRectDetectorSpec.hpp +63 -0
  29. package/nitrogen/generated/android/c++/JHybridMyazaTextRecognizerSpec.cpp +80 -0
  30. package/nitrogen/generated/android/c++/JHybridMyazaTextRecognizerSpec.hpp +64 -0
  31. package/nitrogen/generated/android/c++/JTextResult.hpp +76 -0
  32. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/DetectedRect.kt +81 -0
  33. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/EmrtdApduResponse.kt +56 -0
  34. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/EmrtdTagInfo.kt +56 -0
  35. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/FaceResult.kt +121 -0
  36. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaEmrtdSpec.kt +103 -0
  37. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaFaceDetectorSpec.kt +55 -0
  38. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaRectDetectorSpec.kt +55 -0
  39. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaTextRecognizerSpec.kt +60 -0
  40. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/KycSdkReactNativeOnLoad.kt +35 -0
  41. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/TextResult.kt +51 -0
  42. package/nitrogen/generated/ios/KycSdkReactNative+autolinking.rb +62 -0
  43. package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.cpp +143 -0
  44. package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.hpp +423 -0
  45. package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Umbrella.hpp +81 -0
  46. package/nitrogen/generated/ios/KycSdkReactNativeAutolinking.mm +57 -0
  47. package/nitrogen/generated/ios/KycSdkReactNativeAutolinking.swift +62 -0
  48. package/nitrogen/generated/ios/c++/HybridMyazaEmrtdSpecSwift.cpp +11 -0
  49. package/nitrogen/generated/ios/c++/HybridMyazaEmrtdSpecSwift.hpp +184 -0
  50. package/nitrogen/generated/ios/c++/HybridMyazaFaceDetectorSpecSwift.cpp +11 -0
  51. package/nitrogen/generated/ios/c++/HybridMyazaFaceDetectorSpecSwift.hpp +87 -0
  52. package/nitrogen/generated/ios/c++/HybridMyazaRectDetectorSpecSwift.cpp +11 -0
  53. package/nitrogen/generated/ios/c++/HybridMyazaRectDetectorSpecSwift.hpp +87 -0
  54. package/nitrogen/generated/ios/c++/HybridMyazaTextRecognizerSpecSwift.cpp +11 -0
  55. package/nitrogen/generated/ios/c++/HybridMyazaTextRecognizerSpecSwift.hpp +98 -0
  56. package/nitrogen/generated/ios/swift/DetectedRect.swift +59 -0
  57. package/nitrogen/generated/ios/swift/EmrtdApduResponse.swift +34 -0
  58. package/nitrogen/generated/ios/swift/EmrtdTagInfo.swift +34 -0
  59. package/nitrogen/generated/ios/swift/FaceResult.swift +99 -0
  60. package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  61. package/nitrogen/generated/ios/swift/Func_void_EmrtdApduResponse.swift +46 -0
  62. package/nitrogen/generated/ios/swift/Func_void_EmrtdTagInfo.swift +46 -0
  63. package/nitrogen/generated/ios/swift/Func_void_TextResult.swift +46 -0
  64. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  65. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  66. package/nitrogen/generated/ios/swift/HybridMyazaEmrtdSpec.swift +67 -0
  67. package/nitrogen/generated/ios/swift/HybridMyazaEmrtdSpec_cxx.swift +317 -0
  68. package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec.swift +56 -0
  69. package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec_cxx.swift +143 -0
  70. package/nitrogen/generated/ios/swift/HybridMyazaRectDetectorSpec.swift +56 -0
  71. package/nitrogen/generated/ios/swift/HybridMyazaRectDetectorSpec_cxx.swift +143 -0
  72. package/nitrogen/generated/ios/swift/HybridMyazaTextRecognizerSpec.swift +57 -0
  73. package/nitrogen/generated/ios/swift/HybridMyazaTextRecognizerSpec_cxx.swift +162 -0
  74. package/nitrogen/generated/ios/swift/TextResult.swift +35 -0
  75. package/nitrogen/generated/shared/c++/DetectedRect.hpp +107 -0
  76. package/nitrogen/generated/shared/c++/EmrtdApduResponse.hpp +87 -0
  77. package/nitrogen/generated/shared/c++/EmrtdTagInfo.hpp +87 -0
  78. package/nitrogen/generated/shared/c++/FaceResult.hpp +139 -0
  79. package/nitrogen/generated/shared/c++/HybridMyazaEmrtdSpec.cpp +33 -0
  80. package/nitrogen/generated/shared/c++/HybridMyazaEmrtdSpec.hpp +80 -0
  81. package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.cpp +21 -0
  82. package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.hpp +67 -0
  83. package/nitrogen/generated/shared/c++/HybridMyazaRectDetectorSpec.cpp +21 -0
  84. package/nitrogen/generated/shared/c++/HybridMyazaRectDetectorSpec.hpp +67 -0
  85. package/nitrogen/generated/shared/c++/HybridMyazaTextRecognizerSpec.cpp +22 -0
  86. package/nitrogen/generated/shared/c++/HybridMyazaTextRecognizerSpec.hpp +70 -0
  87. package/nitrogen/generated/shared/c++/TextResult.hpp +84 -0
  88. package/package.json +16 -4
  89. package/src/MyazaKYC.tsx +115 -22
  90. package/src/capture/documentIdentity.ts +153 -0
  91. package/src/capture/documentSignals.ts +177 -0
  92. package/src/capture/documentTextGate.ts +171 -0
  93. package/src/capture/hints.ts +57 -0
  94. package/src/capture/index.ts +24 -0
  95. package/src/capture/rectDetector.ts +84 -0
  96. package/src/capture/rectGate.ts +91 -0
  97. package/src/capture/useAutoCapture.ts +198 -0
  98. package/src/components/CameraViewfinder.tsx +226 -70
  99. package/src/components/ContactCodeEntry.tsx +139 -0
  100. package/src/components/CountryField.tsx +87 -0
  101. package/src/components/CountryFlag.tsx +15 -15
  102. package/src/components/DialCodePicker.tsx +186 -0
  103. package/src/components/DocumentCropper.tsx +1 -1
  104. package/src/components/DocumentReview.tsx +142 -0
  105. package/src/components/DocumentReviewSide.tsx +135 -0
  106. package/src/components/DocumentReviewZoom.tsx +119 -0
  107. package/src/components/ExpiryCountdown.tsx +52 -0
  108. package/src/components/FlashOverlay.tsx +156 -0
  109. package/src/components/Icon.tsx +67 -1
  110. package/src/components/KycFlow.tsx +66 -94
  111. package/src/components/KycSheet.tsx +148 -18
  112. package/src/components/MediaSourceSheet.tsx +163 -0
  113. package/src/components/MyazaButton.tsx +14 -2
  114. package/src/components/MyazaDateField.tsx +240 -0
  115. package/src/components/MyazaInput.tsx +73 -26
  116. package/src/components/MyazaSelect.tsx +283 -0
  117. package/src/components/MyazaWordmark.tsx +33 -0
  118. package/src/components/OptionRow.tsx +132 -0
  119. package/src/components/PhoneNumberInput.tsx +149 -0
  120. package/src/components/PoweredBy.tsx +89 -0
  121. package/src/components/ReadyPrimer.tsx +149 -0
  122. package/src/components/StepView.tsx +91 -0
  123. package/src/components/Typography.tsx +18 -3
  124. package/src/components/VerifiedNotice.tsx +38 -0
  125. package/src/components/WhatsAppIcon.tsx +29 -0
  126. package/src/components/WorkflowGate.tsx +128 -0
  127. package/src/components/brand-font.ts +113 -0
  128. package/src/components/flashHoleGeometry.ts +35 -0
  129. package/src/components/flow/FatalConfigError.tsx +56 -0
  130. package/src/components/fonts.ts +2 -19
  131. package/src/components/readyPrimerContent.ts +52 -0
  132. package/src/components/runtime.tsx +38 -43
  133. package/src/components/stepHeaderMeta.tsx +87 -0
  134. package/src/components/theme-provider.tsx +89 -0
  135. package/src/components/useWorkflowMount.ts +116 -0
  136. package/src/components/viewfinder/CardGuide.tsx +60 -0
  137. package/src/components/viewfinder/DocumentGhost.tsx +149 -0
  138. package/src/components/viewfinder/ImmersiveBottomBar.tsx +172 -0
  139. package/src/components/viewfinder/ImmersiveControls.tsx +127 -0
  140. package/src/components/viewfinder/ImmersiveGuide.tsx +174 -0
  141. package/src/components/viewfinder/ImmersiveOverlay.tsx +79 -0
  142. package/src/components/viewfinder/ViewfinderControls.tsx +154 -0
  143. package/src/config/brand.ts +97 -0
  144. package/src/config/business.ts +179 -0
  145. package/src/config/businessSteps.ts +105 -0
  146. package/src/config/contact.ts +159 -0
  147. package/src/config/countryNames.g.ts +254 -0
  148. package/src/config/currencyFlags.ts +144 -0
  149. package/src/config/font-resolve.ts +78 -0
  150. package/src/config/idTypes.ts +96 -1
  151. package/src/config/keyPeople.ts +152 -0
  152. package/src/config/phone.ts +39 -0
  153. package/src/config/proofOfAddress.ts +75 -0
  154. package/src/config/questionnaire.ts +153 -0
  155. package/src/config/regions.ts +118 -0
  156. package/src/config/registrationHint.ts +87 -0
  157. package/src/config/stepOrder.ts +118 -0
  158. package/src/config/theme.ts +38 -2
  159. package/src/config/workflowMerge.ts +124 -0
  160. package/src/emrtd/bac.ts +128 -0
  161. package/src/emrtd/bytes.ts +127 -0
  162. package/src/emrtd/crypto.ts +163 -0
  163. package/src/emrtd/der.ts +192 -0
  164. package/src/emrtd/dg2.ts +111 -0
  165. package/src/emrtd/files.ts +215 -0
  166. package/src/emrtd/index.ts +30 -0
  167. package/src/emrtd/mrzKey.ts +54 -0
  168. package/src/emrtd/native.ts +79 -0
  169. package/src/emrtd/optionalRead.ts +42 -0
  170. package/src/emrtd/read.ts +224 -0
  171. package/src/emrtd/retry.ts +98 -0
  172. package/src/emrtd/secureMessaging.ts +178 -0
  173. package/src/emrtd/session.ts +200 -0
  174. package/src/emrtd/stages.ts +122 -0
  175. package/src/index.ts +110 -0
  176. package/src/lib/calendar.ts +71 -0
  177. package/src/lib/use-keyboard-inset.ts +34 -0
  178. package/src/liveness/challengeManager.ts +27 -1
  179. package/src/liveness/faceContinuity.ts +153 -0
  180. package/src/liveness/flashDetector.ts +145 -0
  181. package/src/liveness/flashReadyGate.ts +106 -0
  182. package/src/liveness/flashRunner.ts +118 -0
  183. package/src/liveness/integritySignals.ts +60 -0
  184. package/src/liveness/livenessState.ts +139 -0
  185. package/src/liveness/speech.ts +19 -1
  186. package/src/liveness/types.ts +48 -1
  187. package/src/liveness/useFlashPhase.ts +78 -0
  188. package/src/liveness/useLiveness.ts +260 -75
  189. package/src/liveness/visionCameraFaceDetector.ts +14 -0
  190. package/src/mrz/extract.ts +101 -0
  191. package/src/mrz/parse.ts +178 -0
  192. package/src/mrz/textRecognizer.ts +149 -0
  193. package/src/screens/ApplicantRoleStep.tsx +331 -0
  194. package/src/screens/BusinessDetailsStep.tsx +212 -0
  195. package/src/screens/BusinessDocumentSlot.tsx +167 -0
  196. package/src/screens/BusinessDocumentsStep.tsx +213 -0
  197. package/src/screens/BusinessKeyPeopleStep.tsx +151 -0
  198. package/src/screens/BusinessKeyPersonRow.tsx +170 -0
  199. package/src/screens/CompanyInfoFields.tsx +83 -0
  200. package/src/screens/ConsentStep.tsx +68 -68
  201. package/src/screens/ContactChannelChoice.tsx +133 -0
  202. package/src/screens/ContactCodeStep.tsx +130 -0
  203. package/src/screens/ContactDestinationField.tsx +70 -0
  204. package/src/screens/ContactFooterNote.tsx +34 -0
  205. package/src/screens/ContactVerificationStep.tsx +199 -0
  206. package/src/screens/ContactVerifiedPanel.tsx +36 -0
  207. package/src/screens/CountrySelectStep.tsx +210 -0
  208. package/src/screens/DocumentCaptureStep.tsx +156 -132
  209. package/src/screens/IdInputStep.tsx +13 -5
  210. package/src/screens/IdTypeStep.tsx +74 -33
  211. package/src/screens/KeyPeopleInviteLinks.tsx +239 -0
  212. package/src/screens/LivenessStep.tsx +155 -247
  213. package/src/screens/MrzScanView.tsx +173 -0
  214. package/src/screens/NfcStep.tsx +290 -0
  215. package/src/screens/ProofOfAddressStep.tsx +238 -0
  216. package/src/screens/QuestionnaireField.tsx +219 -0
  217. package/src/screens/QuestionnaireMoneyField.tsx +134 -0
  218. package/src/screens/QuestionnaireStep.tsx +76 -0
  219. package/src/screens/SubmittedStep.tsx +5 -0
  220. package/src/screens/consent/model.ts +109 -0
  221. package/src/screens/document/CameraPhase.tsx +150 -0
  222. package/src/screens/document/RequiredPill.tsx +60 -0
  223. package/src/screens/liveness/LightingBanner.tsx +50 -0
  224. package/src/screens/liveness/LivenessOutcome.tsx +109 -0
  225. package/src/screens/liveness/ProgressDots.tsx +67 -0
  226. package/src/screens/liveness/SelfiePreview.tsx +39 -0
  227. package/src/screens/liveness/constants.ts +31 -0
  228. package/src/screens/liveness/index.ts +8 -0
  229. package/src/screens/liveness/resolveGuidance.ts +27 -0
  230. package/src/screens/liveness/useSelfieUpload.ts +107 -0
  231. package/src/screens/nfc/NfcMrzPrompt.tsx +40 -0
  232. package/src/screens/nfc/NfcReadBody.tsx +63 -0
  233. package/src/screens/nfc/NfcReadProgress.tsx +96 -0
  234. package/src/screens/nfc/NfcScanIllustration.tsx +244 -0
  235. package/src/screens/nfc/NfcScannedSummary.tsx +74 -0
  236. package/src/screens/nfc/NfcSuccessPanel.tsx +162 -0
  237. package/src/screens/nfc/availability.ts +73 -0
  238. package/src/screens/nfc/readErrorMessage.ts +93 -0
  239. package/src/screens/nfc/useChipPortrait.ts +58 -0
  240. package/src/screens/nfc/useNfcAvailability.ts +82 -0
  241. package/src/screens/useFlashHole.ts +105 -0
  242. package/src/screens/useFlashSequence.ts +96 -0
  243. package/src/screens/usePoaAttach.ts +121 -0
  244. package/src/services/api-types.ts +164 -0
  245. package/src/services/api-verify-types.ts +99 -0
  246. package/src/services/api.ts +72 -102
  247. package/src/services/cardCrop.ts +25 -10
  248. package/src/services/contactErrors.ts +43 -0
  249. package/src/services/deviceMetadata.ts +1 -1
  250. package/src/services/documentPicker.ts +20 -0
  251. package/src/services/fingerprint-sources.ts +129 -0
  252. package/src/services/fingerprint.ts +101 -0
  253. package/src/services/haptics.ts +38 -0
  254. package/src/services/mediaCompress.ts +7 -2
  255. package/src/services/resolveUrl.ts +2 -2
  256. package/src/services/workflowGate.ts +138 -0
  257. package/src/specs/MyazaEmrtd.nitro.ts +114 -0
  258. package/src/specs/MyazaFaceDetector.nitro.ts +10 -0
  259. package/src/specs/MyazaRectDetector.nitro.ts +50 -0
  260. package/src/specs/MyazaTextRecognizer.nitro.ts +48 -0
  261. package/src/store/derive.ts +225 -0
  262. package/src/store/kycStore.ts +250 -149
  263. package/src/store/state.ts +253 -0
  264. package/src/store/submit.ts +101 -0
  265. package/src/store/submitApplicant.ts +81 -0
  266. package/src/types/appearance.ts +113 -0
  267. package/src/types/business.ts +131 -0
  268. package/src/types/config.ts +150 -124
  269. package/src/types/id-types.ts +57 -0
  270. package/src/types/verification.ts +5 -0
  271. package/src/types/workflow.ts +141 -0
  272. package/src/utils/amountFormat.ts +73 -0
  273. package/src/utils/tokens.ts +4 -2
  274. package/src/__tests__/cardCrop.test.ts +0 -39
  275. package/src/__tests__/deviceMetadata.test.ts +0 -34
  276. package/src/__tests__/errors.test.ts +0 -34
  277. package/src/__tests__/flow.test.ts +0 -61
  278. package/src/__tests__/gestureDetector.test.ts +0 -37
  279. package/src/__tests__/liveness.test.ts +0 -112
  280. package/src/__tests__/resolveUrl.test.ts +0 -64
  281. package/src/__tests__/validators.test.ts +0 -38
@@ -0,0 +1,179 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Business (KYB) product catalogue + flow helpers.
3
+ //
4
+ // Mirrors the server's `business-products.ts` for DISPLAY only — labels and
5
+ // input hints. The server stays the source of truth: an unknown or unoffered
6
+ // product is rejected at POST /verify with `product_unsupported`, so nothing
7
+ // here needs to be authoritative, only honest about what to type.
8
+ // ---------------------------------------------------------------------------
9
+
10
+ import type {
11
+ CompanyInfoField,
12
+ CompanyInfoMode,
13
+ KeyPersonRole,
14
+ SubjectType,
15
+ WorkflowBusinessConfig,
16
+ } from '../types/business';
17
+ import { regionCountryName } from './regions';
18
+
19
+ export interface BusinessProductDef {
20
+ key: string;
21
+ label: string;
22
+ /** What the user types — drives the input label/placeholder. */
23
+ inputLabel: string;
24
+ placeholder: string;
25
+ /** Countries offering this product; absent = every supported country. */
26
+ countries?: string[];
27
+ }
28
+
29
+ export const DEFAULT_BUSINESS_PRODUCT = 'business';
30
+
31
+ export const BUSINESS_PRODUCTS: readonly BusinessProductDef[] = [
32
+ {
33
+ key: 'business',
34
+ label: 'Business verification',
35
+ inputLabel: 'Registration number',
36
+ placeholder: 'e.g. RC0000000',
37
+ },
38
+ {
39
+ key: 'business-tax',
40
+ label: 'Business + Tax ID',
41
+ inputLabel: 'Registration number',
42
+ placeholder: 'e.g. RC0000000',
43
+ countries: ['NG'],
44
+ },
45
+ {
46
+ key: 'business-taxid',
47
+ label: 'Tax ID',
48
+ inputLabel: 'Registration number',
49
+ placeholder: 'e.g. RC0000000',
50
+ countries: ['NG'],
51
+ },
52
+ {
53
+ // A TIN is NOT the Tax ID product — different number, different endpoint.
54
+ key: 'business-tin',
55
+ label: 'TIN',
56
+ inputLabel: 'TIN',
57
+ placeholder: 'e.g. 01234567-0001',
58
+ countries: ['NG'],
59
+ },
60
+ ];
61
+
62
+ /** Display definition for a product key (unknown keys get a generic fallback). */
63
+ export function getBusinessProductDef(key: string): BusinessProductDef {
64
+ return (
65
+ BUSINESS_PRODUCTS.find((p) => p.key === key) ?? {
66
+ key,
67
+ label: key,
68
+ inputLabel: 'Registration number',
69
+ placeholder: 'Enter your registration number',
70
+ }
71
+ );
72
+ }
73
+
74
+ /** The product keys a business workflow offers (the default when unset). */
75
+ export function businessProductsFor(business: WorkflowBusinessConfig | undefined): string[] {
76
+ const products = business?.products;
77
+ return products && products.length > 0 ? products : [DEFAULT_BUSINESS_PRODUCT];
78
+ }
79
+
80
+ /**
81
+ * The products offered for ONE picked country — the configured list narrowed by
82
+ * each product's availability, with the global default as the backstop.
83
+ *
84
+ * The narrowing matters: TIN is Nigeria-only, so offering it after the visitor
85
+ * picks Ghana produces a submission the server refuses.
86
+ */
87
+ export function businessProductsForCountry(
88
+ business: WorkflowBusinessConfig | undefined,
89
+ country: string,
90
+ ): string[] {
91
+ const offered = businessProductsFor(business).filter((key) => {
92
+ const def = BUSINESS_PRODUCTS.find((p) => p.key === key);
93
+ return !def?.countries || def.countries.includes(country);
94
+ });
95
+ return offered.length > 0 ? offered : [DEFAULT_BUSINESS_PRODUCT];
96
+ }
97
+
98
+ /** Registry countries the visitor may pick (primary always included, first). */
99
+ export function businessCountriesFor(business: WorkflowBusinessConfig | undefined): string[] {
100
+ if (!business) return [];
101
+ if (business.countries && business.countries.length > 0) {
102
+ return business.countries.includes(business.country)
103
+ ? business.countries
104
+ : [business.country, ...business.countries];
105
+ }
106
+ return [business.country];
107
+ }
108
+
109
+ /** English country name for an ISO-2 code (falls back to the code itself). */
110
+ export const businessCountryName = regionCountryName;
111
+
112
+ /**
113
+ * Whether the resolved config runs the business (KYB) flow.
114
+ *
115
+ * Both halves are required: `subjectType` alone would send a submission with no
116
+ * registry country, which the server rejects. Single source of truth for the
117
+ * gate — the step order, consent routing and submission all read it.
118
+ */
119
+ export function isBusinessFlow(config: {
120
+ subjectType?: SubjectType;
121
+ business?: WorkflowBusinessConfig;
122
+ }): boolean {
123
+ return config.subjectType === 'business' && !!config.business;
124
+ }
125
+
126
+ /** All key-person roles — the in-scope set when `keyPeople.roles` is unset. */
127
+ const KEY_PERSON_ROLES: KeyPersonRole[] = [
128
+ 'director',
129
+ 'beneficial_owner',
130
+ 'signatory',
131
+ 'shareholder',
132
+ ];
133
+
134
+ /**
135
+ * Whether the business-details step should collect a contact email.
136
+ *
137
+ * Only when invites actually go out by email AND at least one in-scope role
138
+ * needs full KYC — asking for an address the flow will never write to is a
139
+ * field the user fills in for nothing.
140
+ */
141
+ export function keyPeopleNeedsContactEmail(business: WorkflowBusinessConfig | undefined): boolean {
142
+ const keyPeople = business?.keyPeople;
143
+ if (!keyPeople?.enabled || keyPeople.invite?.channel !== 'email') return false;
144
+ const roles = keyPeople.roles && keyPeople.roles.length > 0 ? keyPeople.roles : KEY_PERSON_ROLES;
145
+ return roles.some(
146
+ (role) => (keyPeople.perRole?.[role] ?? keyPeople.level ?? 'screening_only') === 'full_kyc',
147
+ );
148
+ }
149
+
150
+ /**
151
+ * Effective per-field company-profile mode.
152
+ *
153
+ * `collectCompanyInfo: false` turns the whole section off; an unlisted field is
154
+ * 'optional'. Mirrors the server's resolution exactly — a mismatch here means
155
+ * either a field the user cannot fill or a 422 they cannot act on.
156
+ */
157
+ export function companyInfoFieldModes(
158
+ business: WorkflowBusinessConfig | undefined,
159
+ ): Record<CompanyInfoField, CompanyInfoMode> {
160
+ const off = business?.collectCompanyInfo === false;
161
+ const modes = business?.companyInfo ?? {};
162
+ return {
163
+ address: off ? 'off' : (modes.address ?? 'optional'),
164
+ email: off ? 'off' : (modes.email ?? 'optional'),
165
+ phone: off ? 'off' : (modes.phone ?? 'optional'),
166
+ website: off ? 'off' : (modes.website ?? 'optional'),
167
+ };
168
+ }
169
+
170
+ /** Company-profile fields still missing that the workflow marks required. */
171
+ export function missingRequiredCompanyInfo(
172
+ business: WorkflowBusinessConfig | undefined,
173
+ values: Partial<Record<CompanyInfoField, string>>,
174
+ ): CompanyInfoField[] {
175
+ const modes = companyInfoFieldModes(business);
176
+ return (Object.keys(modes) as CompanyInfoField[]).filter(
177
+ (field) => modes[field] === 'required' && (values[field] ?? '').trim() === '',
178
+ );
179
+ }
@@ -0,0 +1,105 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Business (KYB) APPLICATION step gates.
3
+ //
4
+ // Which steps a KYB workflow adds beyond the registration details: key-people
5
+ // collection, supporting documents, and applicant identity verification. Single
6
+ // source of truth for what is in the flow and in what order — the step order,
7
+ // the progress bar, and each step's navigation all read these.
8
+ //
9
+ // Mirrors the web SDK's `lib/business-application.ts` (the sequencing half; the
10
+ // row-validation/payload half lands with the KYB step itself).
11
+ // ---------------------------------------------------------------------------
12
+
13
+ import type { BusinessDocumentKey, WorkflowBusinessConfig } from '../types/business';
14
+ import type { KYCStep } from '../types/config';
15
+
16
+ /** Default display labels per document key (server contract). */
17
+ export const BUSINESS_DOCUMENT_LABELS: Record<BusinessDocumentKey, string> = {
18
+ incorporation_certificate: 'Certificate of incorporation',
19
+ memart: 'MEMART / articles of association',
20
+ proof_of_address: 'Proof of business address',
21
+ tax_document: 'Tax document',
22
+ regulatory_license: 'Regulatory license',
23
+ board_resolution: 'Board resolution',
24
+ other: 'Other document',
25
+ };
26
+
27
+ /** Whether the flow collects key people from the applicant. */
28
+ export function hasKeyPeopleCollection(business: WorkflowBusinessConfig | undefined): boolean {
29
+ return business?.keyPeople?.enabled === true && business.keyPeople.collect === true;
30
+ }
31
+
32
+ /** Whether the flow collects supporting business documents. */
33
+ export function hasBusinessDocumentsStep(business: WorkflowBusinessConfig | undefined): boolean {
34
+ return business?.documents?.enabled === true;
35
+ }
36
+
37
+ /** Whether the applicant verifies their own identity in-flow. */
38
+ export function hasApplicantVerification(business: WorkflowBusinessConfig | undefined): boolean {
39
+ return business?.applicant?.verification === true;
40
+ }
41
+
42
+ export interface ResolvedBusinessDocumentType {
43
+ key: BusinessDocumentKey;
44
+ label: string;
45
+ required: boolean;
46
+ }
47
+
48
+ /**
49
+ * The document slots the flow renders. Enabled with absent/empty `types`
50
+ * defaults to just a required incorporation certificate (server contract).
51
+ */
52
+ export function resolveBusinessDocumentTypes(
53
+ business: WorkflowBusinessConfig | undefined,
54
+ ): ResolvedBusinessDocumentType[] {
55
+ if (!hasBusinessDocumentsStep(business)) return [];
56
+ const types = business?.documents?.types;
57
+ if (!types || types.length === 0) {
58
+ return [
59
+ {
60
+ key: 'incorporation_certificate',
61
+ label: BUSINESS_DOCUMENT_LABELS.incorporation_certificate,
62
+ required: true,
63
+ },
64
+ ];
65
+ }
66
+ return types.map((t) => ({
67
+ key: t.key,
68
+ label: t.label ?? BUSINESS_DOCUMENT_LABELS[t.key] ?? t.key,
69
+ required: t.required === true,
70
+ }));
71
+ }
72
+
73
+ /** Minimum applicant-listed people the workflow demands (0 = skippable). */
74
+ export function keyPeopleMinEntries(business: WorkflowBusinessConfig | undefined): number {
75
+ const kp = business?.keyPeople;
76
+ if (!kp?.enabled || !kp.collect) return 0;
77
+ return kp.minEntries ?? 0;
78
+ }
79
+
80
+ export type BusinessSectionStep =
81
+ | 'business-details'
82
+ | 'business-key-people'
83
+ | 'business-documents'
84
+ | 'applicant-role';
85
+
86
+ /** The ordered business-application steps this workflow configures. */
87
+ export function businessSectionSteps(
88
+ business: WorkflowBusinessConfig | undefined,
89
+ ): BusinessSectionStep[] {
90
+ const steps: BusinessSectionStep[] = ['business-details'];
91
+ if (hasKeyPeopleCollection(business)) steps.push('business-key-people');
92
+ if (hasBusinessDocumentsStep(business)) steps.push('business-documents');
93
+ if (hasApplicantVerification(business)) steps.push('applicant-role');
94
+ return steps;
95
+ }
96
+
97
+ /** Type guard so the step order can be walked without casting. */
98
+ export function isBusinessSectionStep(step: KYCStep): step is BusinessSectionStep {
99
+ return (
100
+ step === 'business-details' ||
101
+ step === 'business-key-people' ||
102
+ step === 'business-documents' ||
103
+ step === 'applicant-role'
104
+ );
105
+ }
@@ -0,0 +1,159 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Contact verification (email / phone OTP) — the pure half.
3
+ //
4
+ // Possession checks that sit right after consent, before any capture or
5
+ // registry spend. A passing check yields a single-use proof token that rides
6
+ // the /verify submission; the server is what actually enforces it, so
7
+ // everything here is about not wasting the user's time on a round trip that
8
+ // will obviously fail.
9
+ // ---------------------------------------------------------------------------
10
+
11
+ import type { EmailVerificationConfig, PhoneVerificationConfig } from '../types/workflow';
12
+
13
+ /**
14
+ * What a contact step is currently doing, published to the store so the SHEET
15
+ * HEADER can describe it — the header is rendered by the shell, which cannot
16
+ * see the step's local state.
17
+ *
18
+ * `channel` is what makes it safe to leave behind: a header only trusts state
19
+ * raised by the step it is titling, so a leftover email entry can never caption
20
+ * the phone step.
21
+ */
22
+ export interface ContactChallenge {
23
+ channel: 'email' | 'phone';
24
+ /** Delivery channel the code will go out on, or went out on (phone only). */
25
+ via?: 'sms' | 'whatsapp';
26
+ /**
27
+ * Set once a code is actually out. Its presence is what turns the header from
28
+ * a promise ("we'll send a code") into an instruction ("enter the code we
29
+ * sent to …").
30
+ */
31
+ destination?: string;
32
+ }
33
+
34
+ /** Server-clamped bounds; mirrored so the UI renders the right number of slots. */
35
+ export const MIN_CODE_LENGTH = 4;
36
+ export const MAX_CODE_LENGTH = 8;
37
+ export const DEFAULT_CODE_LENGTH = 6;
38
+
39
+ export function hasEmailVerificationStep(cfg: EmailVerificationConfig | undefined | null): boolean {
40
+ return cfg?.enabled === true;
41
+ }
42
+
43
+ export function hasPhoneVerificationStep(cfg: PhoneVerificationConfig | undefined | null): boolean {
44
+ return cfg?.enabled === true;
45
+ }
46
+
47
+ /**
48
+ * Whether the user may skip this check.
49
+ *
50
+ * Required defaults TRUE when the step is enabled: a workflow that adds an OTP
51
+ * step and says nothing about `required` meant to require it. Only an explicit
52
+ * `required: false` offers the skip.
53
+ */
54
+ export function contactIsRequired(
55
+ cfg: EmailVerificationConfig | PhoneVerificationConfig | undefined,
56
+ ): boolean {
57
+ return cfg?.required !== false;
58
+ }
59
+
60
+ /** Code length, clamped to what the server will accept. */
61
+ export function contactCodeLength(
62
+ cfg: EmailVerificationConfig | PhoneVerificationConfig | undefined,
63
+ ): number {
64
+ const raw = cfg?.codeLength ?? DEFAULT_CODE_LENGTH;
65
+ return Math.min(MAX_CODE_LENGTH, Math.max(MIN_CODE_LENGTH, Math.round(raw)));
66
+ }
67
+
68
+ /**
69
+ * Header title/description for a contact step, read by stepHeaderMeta.
70
+ *
71
+ * Wording matches the web SDK exactly, including the way the description turns
72
+ * from a promise ("we'll send a code…") into an instruction ("enter the code we
73
+ * sent to…") once a code is out. The shell renders the header, so the step
74
+ * publishes its outstanding challenge to the store for this to read.
75
+ *
76
+ * A challenge raised for the OTHER channel is ignored: both steps are the same
77
+ * component, and a leftover email challenge must never caption the phone step.
78
+ */
79
+ export function contactMeta(
80
+ channel: 'email' | 'phone',
81
+ ctx?: { codeLength?: number; challenge?: ContactChallenge | null },
82
+ ): { title: string; description: string } {
83
+ const isEmail = channel === 'email';
84
+ const title = isEmail ? 'Verify your email' : 'Verify your phone number';
85
+ const live = ctx?.challenge?.channel === channel ? ctx.challenge : null;
86
+ const by = !isEmail && live?.via ? ` by ${CHANNEL_LABELS[live.via]}` : '';
87
+
88
+ if (live?.destination) {
89
+ const length = ctx?.codeLength ?? DEFAULT_CODE_LENGTH;
90
+ return {
91
+ title,
92
+ description: `Enter the ${length}-digit code we sent to ${live.destination}${by}.`,
93
+ };
94
+ }
95
+
96
+ return {
97
+ title,
98
+ description: isEmail
99
+ ? "We'll send a one-time code to confirm this email belongs to you."
100
+ : `We'll send a one-time code${by} to confirm this number belongs to you.`,
101
+ };
102
+ }
103
+
104
+ export type PhoneOtpChannel = 'sms' | 'whatsapp';
105
+
106
+ const KNOWN_CHANNELS: PhoneOtpChannel[] = ['sms', 'whatsapp'];
107
+
108
+ export const CHANNEL_LABELS: Record<PhoneOtpChannel, string> = {
109
+ sms: 'SMS',
110
+ whatsapp: 'WhatsApp',
111
+ };
112
+
113
+ /**
114
+ * The delivery channels a phone step offers, normalised.
115
+ *
116
+ * Never empty: an unset, empty or entirely-unrecognised list falls back to SMS,
117
+ * which is what the server defaults to when `via` is omitted. Unknown values are
118
+ * dropped so a channel added server-side cannot render as an unlabelled option
119
+ * in an older build. Order is the workflow's, so its first entry is the default.
120
+ */
121
+ export function offeredPhoneChannels(channels?: string[] | null): PhoneOtpChannel[] {
122
+ const known = (channels ?? []).filter((c): c is PhoneOtpChannel =>
123
+ KNOWN_CHANNELS.includes(c as PhoneOtpChannel),
124
+ );
125
+ const deduped = Array.from(new Set(known));
126
+ return deduped.length > 0 ? deduped : ['sms'];
127
+ }
128
+
129
+ /**
130
+ * Email shape check.
131
+ *
132
+ * Deliberately permissive — the real proof of an address is that a code sent to
133
+ * it arrives, and a strict pattern rejects valid addresses (new TLDs, plus
134
+ * addressing, unicode local parts) far more often than it catches a typo.
135
+ */
136
+ export function isValidContactEmail(value: string): boolean {
137
+ const trimmed = value.trim();
138
+ if (trimmed.length < 5 || trimmed.length > 254) return false;
139
+ if (/\s/.test(trimmed)) return false;
140
+ const at = trimmed.indexOf('@');
141
+ if (at <= 0 || at !== trimmed.lastIndexOf('@')) return false;
142
+ const domain = trimmed.slice(at + 1);
143
+ return domain.includes('.') && !domain.startsWith('.') && !domain.endsWith('.');
144
+ }
145
+
146
+ /**
147
+ * Phone shape check against E.164: a leading `+`, then 8–15 digits.
148
+ *
149
+ * Only the format is checked here. Whether the number is reachable is the OTP's
150
+ * job, and whether the country is plausible is the server's.
151
+ */
152
+ export function isValidContactPhone(value: string): boolean {
153
+ return /^\+[1-9]\d{7,14}$/.test(value.replace(/[\s()-]/g, ''));
154
+ }
155
+
156
+ /** Strips formatting so the submitted number is E.164. */
157
+ export function normalizeContactPhone(value: string): string {
158
+ return value.replace(/[\s()-]/g, '');
159
+ }
@@ -0,0 +1,254 @@
1
+ // GENERATED — do not edit by hand.
2
+ //
3
+ // ISO-3166 alpha-2 country names, generated FROM the Flutter SDK's
4
+ // country_names.g.dart so both SDKs label a country identically by
5
+ // construction rather than by two runtimes happening to agree.
6
+ //
7
+ // This exists because `Intl.DisplayNames` is not dependable here: Hermes may
8
+ // ship without the region data, and when it does every row in the picker reads
9
+ // as a bare ISO code ("NG" instead of "Nigeria"). A table is a few KB and it
10
+ // always works.
11
+
12
+ export const COUNTRY_NAMES: Record<string, string> = {
13
+ DZ: 'Algeria',
14
+ AO: 'Angola',
15
+ BJ: 'Benin',
16
+ BW: 'Botswana',
17
+ BF: 'Burkina Faso',
18
+ BI: 'Burundi',
19
+ CV: 'Cape Verde',
20
+ CM: 'Cameroon',
21
+ CF: 'Central African Republic',
22
+ TD: 'Chad',
23
+ KM: 'Comoros',
24
+ CG: 'Congo - Brazzaville',
25
+ CD: 'Congo - Kinshasa',
26
+ CI: 'Côte d’Ivoire',
27
+ DJ: 'Djibouti',
28
+ EG: 'Egypt',
29
+ GQ: 'Equatorial Guinea',
30
+ ER: 'Eritrea',
31
+ SZ: 'Eswatini',
32
+ ET: 'Ethiopia',
33
+ GA: 'Gabon',
34
+ GM: 'Gambia',
35
+ GH: 'Ghana',
36
+ GN: 'Guinea',
37
+ GW: 'Guinea-Bissau',
38
+ KE: 'Kenya',
39
+ LS: 'Lesotho',
40
+ LR: 'Liberia',
41
+ LY: 'Libya',
42
+ MG: 'Madagascar',
43
+ MW: 'Malawi',
44
+ ML: 'Mali',
45
+ MR: 'Mauritania',
46
+ MU: 'Mauritius',
47
+ YT: 'Mayotte',
48
+ MA: 'Morocco',
49
+ MZ: 'Mozambique',
50
+ NA: 'Namibia',
51
+ NE: 'Niger',
52
+ NG: 'Nigeria',
53
+ RE: 'Réunion',
54
+ RW: 'Rwanda',
55
+ SH: 'St. Helena',
56
+ ST: 'São Tomé & Príncipe',
57
+ SN: 'Senegal',
58
+ SC: 'Seychelles',
59
+ SL: 'Sierra Leone',
60
+ SO: 'Somalia',
61
+ ZA: 'South Africa',
62
+ SS: 'South Sudan',
63
+ SD: 'Sudan',
64
+ TZ: 'Tanzania',
65
+ TG: 'Togo',
66
+ TN: 'Tunisia',
67
+ UG: 'Uganda',
68
+ EH: 'Western Sahara',
69
+ ZM: 'Zambia',
70
+ ZW: 'Zimbabwe',
71
+ AE: 'United Arab Emirates',
72
+ BH: 'Bahrain',
73
+ IL: 'Israel',
74
+ IQ: 'Iraq',
75
+ IR: 'Iran',
76
+ JO: 'Jordan',
77
+ KW: 'Kuwait',
78
+ LB: 'Lebanon',
79
+ OM: 'Oman',
80
+ PS: 'Palestinian Territories',
81
+ QA: 'Qatar',
82
+ SA: 'Saudi Arabia',
83
+ SY: 'Syria',
84
+ TR: 'Türkiye',
85
+ YE: 'Yemen',
86
+ AD: 'Andorra',
87
+ AL: 'Albania',
88
+ AT: 'Austria',
89
+ AX: 'Åland Islands',
90
+ BA: 'Bosnia & Herzegovina',
91
+ BE: 'Belgium',
92
+ BG: 'Bulgaria',
93
+ BY: 'Belarus',
94
+ CH: 'Switzerland',
95
+ CY: 'Cyprus',
96
+ CZ: 'Czechia',
97
+ DE: 'Germany',
98
+ DK: 'Denmark',
99
+ EE: 'Estonia',
100
+ ES: 'Spain',
101
+ FI: 'Finland',
102
+ FO: 'Faroe Islands',
103
+ FR: 'France',
104
+ GB: 'United Kingdom',
105
+ GG: 'Guernsey',
106
+ GI: 'Gibraltar',
107
+ GR: 'Greece',
108
+ HR: 'Croatia',
109
+ HU: 'Hungary',
110
+ IE: 'Ireland',
111
+ IM: 'Isle of Man',
112
+ IS: 'Iceland',
113
+ IT: 'Italy',
114
+ JE: 'Jersey',
115
+ LI: 'Liechtenstein',
116
+ LT: 'Lithuania',
117
+ LU: 'Luxembourg',
118
+ LV: 'Latvia',
119
+ MC: 'Monaco',
120
+ MD: 'Moldova',
121
+ ME: 'Montenegro',
122
+ MK: 'North Macedonia',
123
+ MT: 'Malta',
124
+ NL: 'Netherlands',
125
+ NO: 'Norway',
126
+ PL: 'Poland',
127
+ PT: 'Portugal',
128
+ RO: 'Romania',
129
+ RS: 'Serbia',
130
+ RU: 'Russia',
131
+ SE: 'Sweden',
132
+ SI: 'Slovenia',
133
+ SJ: 'Svalbard & Jan Mayen',
134
+ SK: 'Slovakia',
135
+ SM: 'San Marino',
136
+ UA: 'Ukraine',
137
+ VA: 'Vatican City',
138
+ XK: 'Kosovo',
139
+ AG: 'Antigua & Barbuda',
140
+ AI: 'Anguilla',
141
+ AR: 'Argentina',
142
+ AW: 'Aruba',
143
+ BB: 'Barbados',
144
+ BL: 'St. Barthélemy',
145
+ BM: 'Bermuda',
146
+ BO: 'Bolivia',
147
+ BQ: 'Caribbean Netherlands',
148
+ BR: 'Brazil',
149
+ BS: 'Bahamas',
150
+ BZ: 'Belize',
151
+ CA: 'Canada',
152
+ CL: 'Chile',
153
+ CO: 'Colombia',
154
+ CR: 'Costa Rica',
155
+ CU: 'Cuba',
156
+ CW: 'Curaçao',
157
+ DM: 'Dominica',
158
+ DO: 'Dominican Republic',
159
+ EC: 'Ecuador',
160
+ FK: 'Falkland Islands',
161
+ GD: 'Grenada',
162
+ GF: 'French Guiana',
163
+ GL: 'Greenland',
164
+ GP: 'Guadeloupe',
165
+ GT: 'Guatemala',
166
+ GY: 'Guyana',
167
+ HN: 'Honduras',
168
+ HT: 'Haiti',
169
+ JM: 'Jamaica',
170
+ KN: 'St. Kitts & Nevis',
171
+ KY: 'Cayman Islands',
172
+ LC: 'St. Lucia',
173
+ MF: 'St. Martin',
174
+ MQ: 'Martinique',
175
+ MS: 'Montserrat',
176
+ MX: 'Mexico',
177
+ NI: 'Nicaragua',
178
+ PA: 'Panama',
179
+ PE: 'Peru',
180
+ PM: 'St. Pierre & Miquelon',
181
+ PR: 'Puerto Rico',
182
+ PY: 'Paraguay',
183
+ SR: 'Suriname',
184
+ SV: 'El Salvador',
185
+ SX: 'Sint Maarten',
186
+ TC: 'Turks & Caicos Islands',
187
+ TT: 'Trinidad & Tobago',
188
+ US: 'United States',
189
+ UY: 'Uruguay',
190
+ VC: 'St. Vincent & Grenadines',
191
+ VE: 'Venezuela',
192
+ VG: 'British Virgin Islands',
193
+ VI: 'U.S. Virgin Islands',
194
+ AF: 'Afghanistan',
195
+ AM: 'Armenia',
196
+ AS: 'American Samoa',
197
+ AU: 'Australia',
198
+ AZ: 'Azerbaijan',
199
+ BD: 'Bangladesh',
200
+ BN: 'Brunei',
201
+ BT: 'Bhutan',
202
+ CK: 'Cook Islands',
203
+ CN: 'China',
204
+ FJ: 'Fiji',
205
+ FM: 'Micronesia',
206
+ GE: 'Georgia',
207
+ GU: 'Guam',
208
+ HK: 'Hong Kong SAR China',
209
+ ID: 'Indonesia',
210
+ IN: 'India',
211
+ JP: 'Japan',
212
+ KG: 'Kyrgyzstan',
213
+ KH: 'Cambodia',
214
+ KI: 'Kiribati',
215
+ KP: 'North Korea',
216
+ KR: 'South Korea',
217
+ KZ: 'Kazakhstan',
218
+ LA: 'Laos',
219
+ LK: 'Sri Lanka',
220
+ MH: 'Marshall Islands',
221
+ MM: 'Myanmar (Burma)',
222
+ MN: 'Mongolia',
223
+ MO: 'Macao SAR China',
224
+ MP: 'Northern Mariana Islands',
225
+ MV: 'Maldives',
226
+ MY: 'Malaysia',
227
+ NC: 'New Caledonia',
228
+ NF: 'Norfolk Island',
229
+ NP: 'Nepal',
230
+ NR: 'Nauru',
231
+ NU: 'Niue',
232
+ NZ: 'New Zealand',
233
+ PF: 'French Polynesia',
234
+ PG: 'Papua New Guinea',
235
+ PH: 'Philippines',
236
+ PK: 'Pakistan',
237
+ PN: 'Pitcairn Islands',
238
+ PW: 'Palau',
239
+ SB: 'Solomon Islands',
240
+ SG: 'Singapore',
241
+ TH: 'Thailand',
242
+ TJ: 'Tajikistan',
243
+ TK: 'Tokelau',
244
+ TL: 'Timor-Leste',
245
+ TM: 'Turkmenistan',
246
+ TO: 'Tonga',
247
+ TV: 'Tuvalu',
248
+ TW: 'Taiwan',
249
+ UZ: 'Uzbekistan',
250
+ VN: 'Vietnam',
251
+ VU: 'Vanuatu',
252
+ WF: 'Wallis & Futuna',
253
+ WS: 'Samoa',
254
+ };