11open RecoilAtoms
22open Utils
33
4+ // `isVaultCvcFlow` reuses this CVC element for the Hyperswitch-vault saved-card
5+ // (return user) flow. The single submit handler below has two mutually-exclusive
6+ // paths:
7+ // • default (Elements CVC widget): listens for `requestCVCConfirm` and confirms
8+ // the payment-session intent itself with the entered CVC.
9+ // • vault saved-card flow: listens for the forwarded doSubmit, tokenises the CVC
10+ // through the payment-method-session update call, and posts `savedCardCvcTokenEvent`
11+ // back to SavedMethods, which owns the confirm (mirrors the VGS saved-card flow).
412@react.component
5- let make = (~cvcProps : CardUtils .cvcProps , ~paymentType : CardThemeType .mode ) => {
13+ let make = (
14+ ~cvcProps : CardUtils .cvcProps ,
15+ ~paymentType : CardThemeType .mode ,
16+ ~isVaultCvcFlow = false ,
17+ ) => {
618 let {config , localeString } = Recoil .useRecoilValueFromAtom (configAtom )
719 let emitter = SubscriptionEventHooks .useSubscriptionEventEmitter ()
820 let {innerLayout } = config .appearance
921 let keys = Recoil .useRecoilValueFromAtom (keys )
1022 let customPodUri = Recoil .useRecoilValueFromAtom (customPodUri )
1123 let loggerState = Recoil .useRecoilValueFromAtom (loggerAtom )
1224 let redirectionFlags = Recoil .useRecoilValueFromAtom (RecoilAtoms .redirectionFlagsAtom )
25+ // Vault credentials (pmSessionId / sdkAuthorization) for the saved-card tokenise
26+ // call; populated by PaymentMethodsSDK in the inner iframe. Only used in vault mode.
27+ let vaultCredentials = Recoil .useRecoilValueFromAtom (RecoilAtoms .vaultCredentials )
1328
1429 let {
1530 isCVCValid ,
@@ -26,9 +41,67 @@ let make = (~cvcProps: CardUtils.cvcProps, ~paymentType: CardThemeType.mode) =>
2641 let compressedLayoutStyleForCvcError =
2742 innerLayout === Compressed && cvcError -> String .length > 0 ? "!border-l-0" : ""
2843
29- React .useEffect (() => {
44+ // Single submit handler for both modes (mutually exclusive on `isVaultCvcFlow`):
45+ // • vault saved-card flow: SavedMethods forwards the validated doSubmit (carrying
46+ // the selected card's payment_token); we tokenise the CVC via the
47+ // payment-method-session update call and post the token back as
48+ // `savedCardCvcTokenEvent`. SavedMethods builds the vault_card_token_data confirm
49+ // body and calls intent. Validation / tokenisation failures post a failed-submit
50+ // response, which SavedMethods forwards to Hyper.res to reject confirmPayment().
51+ // • Elements CVC widget: confirms the payment-session intent itself on the
52+ // `requestCVCConfirm` message, posting the response back to the parent window.
53+ let submitCallback = React .useCallback ((ev : Window .event ) => {
3054 open Promise
31- let handleRequestCVCConfirm = (ev : Window .event ) => {
55+ if isVaultCvcFlow {
56+ let confirmDict = ev .data -> safeParse -> getDictFromJson
57+ let confirm = confirmDict -> ConfirmType .itemToObjMapper
58+ let isOuterValid = confirmDict -> getBool ("isOuterValid" , true )
59+
60+ if confirm .doSubmit {
61+ let paymentMethodToken = confirmDict -> getString ("paymentToken" , "" )
62+ let (pmSessionId , sdkAuthorization ) = switch vaultCredentials {
63+ | HyperswitchVault (creds ) => (creds .pmSessionId , creds .sdkAuthorization )
64+ | _ => ("" , "" )
65+ }
66+ let isCvcComplete = cvcNumber -> String .length >= 3
67+
68+ if isCvcComplete && isOuterValid {
69+ setCvcError (_ => "" )
70+ PaymentHelpersV2 .updatePaymentMethod (
71+ ~bodyArr = PaymentManagementBody .vaultUpdateCVVBody (~cvcNumber ),
72+ ~pmSessionId ,
73+ ~logger = loggerState ,
74+ ~customPodUri ,
75+ ~sdkAuthorization ,
76+ )
77+ -> then (res => {
78+ let vaultTokenData = VaultHelpers .decodeVaultTokenData (res )
79+ if vaultTokenData .token !== "" {
80+ messageParentWindow ([
81+ ("savedCardCvcTokenEvent" , true -> JSON .Encode .bool ),
82+ ("cvcToken" , vaultTokenData .token -> JSON .Encode .string ),
83+ ])
84+ } else {
85+ postFailedSubmitResponse (~errortype = "server_error" , ~message = "Something went wrong" )
86+ }
87+ resolve ()
88+ })
89+ -> catch (_ => {
90+ postFailedSubmitResponse (~errortype = "server_error" , ~message = "Something went wrong" )
91+ resolve ()
92+ })
93+ -> ignore
94+ } else if isOuterValid {
95+ // Only the CVC is invalid/empty (outer fields are validated upstream).
96+ let errorMsg =
97+ cvcNumber -> String .length == 0
98+ ? localeString .cvcNumberEmptyText
99+ : localeString .inCompleteCVCErrorText
100+ setCvcError (_ => errorMsg )
101+ postFailedSubmitResponse (~errortype = "validation_error" , ~message = errorMsg )
102+ }
103+ }
104+ } else {
32105 let json = ev .data -> safeParse
33106 try {
34107 let dict = json -> getDictFromJson
@@ -127,13 +200,18 @@ let make = (~cvcProps: CardUtils.cvcProps, ~paymentType: CardThemeType.mode) =>
127200 ])
128201 }
129202 }
130- Window .addEventListener ("message" , handleRequestCVCConfirm )
131- Some (
132- () => {
133- Window .removeEventListener ("message" , handleRequestCVCConfirm )
134- },
135- )
136- }, (cvcNumber , keys , paymentType , loggerState , customPodUri , redirectionFlags , localeString ))
203+ }, (
204+ isVaultCvcFlow ,
205+ cvcNumber ,
206+ keys ,
207+ paymentType ,
208+ loggerState ,
209+ customPodUri ,
210+ redirectionFlags ,
211+ localeString ,
212+ vaultCredentials ,
213+ ))
214+ useSubmitPaymentData (submitCallback )
137215
138216 React .useEffect (() => {
139217 SubscriptionEventHooks .emitReady (
@@ -144,18 +222,19 @@ let make = (~cvcProps: CardUtils.cvcProps, ~paymentType: CardThemeType.mode) =>
144222 }, (keys .iframeId , paymentType ))
145223
146224 React .useEffect (() => {
147- let cvcInfoDict = [("isCvcEmpty" , isCvcEmpty -> JSON .Encode .bool )]-> Dict .fromArray
148- Utils .messageParentWindow ([("cvcInfo" , cvcInfoDict -> JSON .Encode .object )])
225+ if ! isVaultCvcFlow {
226+ let cvcInfoDict = [("isCvcEmpty" , isCvcEmpty -> JSON .Encode .bool )]-> Dict .fromArray
227+ Utils .messageParentWindow ([("cvcInfo" , cvcInfoDict -> JSON .Encode .object )])
228+ }
149229 None
150230 }, [isCvcEmpty ])
151-
152231 React .useEffect (() => {
153232 emitter .emitCvcStatus (~iframeId = keys .iframeId , ~isCvcEmpty , ~isCvcComplete )
154233 None
155234 }, (isCvcEmpty , isCvcComplete , keys .iframeId ))
156235
157236 <PaymentInputField
158- fieldName = localeString .cvcTextLabel
237+ fieldName = { isVaultCvcFlow ? "" : localeString .cvcTextLabel }
159238 isValid = isCVCValid
160239 setIsValid = setIsCVCValid
161240 value = cvcNumber
@@ -167,6 +246,7 @@ let make = (~cvcProps: CardUtils.cvcProps, ~paymentType: CardThemeType.mode) =>
167246 maxLength = 4
168247 inputRef = cvcRef
169248 placeholder = "123"
249+ height = {isVaultCvcFlow ? "1.8rem" : "" }
170250 name = TestUtils .cardCVVInputTestId
171251 autocomplete = "cc-csc"
172252 />
0 commit comments