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,126 @@ 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 )
215+ // React.useEffect(() => {
216+ // open Promise
217+ // let handleRequestCVCConfirm = (ev: Window.event) => {
218+ // let json = ev.data->safeParse
219+ // try {
220+ // let dict = json->getDictFromJson
221+ // switch dict->Dict.get("requestCVCConfirm") {
222+ // | Some(confirmParams) => {
223+ // let confirmParamsDict = confirmParams->getDictFromJson
224+ // let requiresCvv = confirmParamsDict->getBool("requiresCvv", true)
225+ // if paymentType === CardCVCElement {
226+ // let body = confirmParamsDict->getJsonObjectFromDict("body")
227+ // let bodyArr = body->JSON.Decode.object->Option.getOr(Dict.make())->Dict.toArray
228+ // let payload = confirmParamsDict->getJsonFromDict("payload", JSON.Encode.null)
229+ // let paymentTypeStr = confirmParamsDict->getString("paymentType", "card")
230+ // let publishableKeyVal =
231+ // confirmParamsDict->getString("publishableKey", keys.publishableKey)
232+ // let clientSecretVal =
233+ // confirmParamsDict->getString("clientSecret", keys.clientSecret->Option.getOr(""))
234+
235+ // let isCvcComplete = cvcNumber->String.length >= 3
236+ // if requiresCvv && isCvcComplete {
237+ // setCvcError(_ => "")
238+
239+ // let bodyWithCvc =
240+ // bodyArr->Array.concat([("card_cvc", cvcNumber->JSON.Encode.string)])
241+
242+ // let paymentType = paymentTypeStr->PaymentHelpers.getPaymentType
243+
244+ // PaymentHelpers.paymentIntentForPaymentSession(
245+ // ~body=bodyWithCvc,
246+ // ~paymentType,
247+ // ~payload,
248+ // ~publishableKey=publishableKeyVal,
249+ // ~clientSecret=clientSecretVal,
250+ // ~logger=loggerState,
251+ // ~customPodUri,
252+ // ~redirectionFlags,
253+ // ~sdkAuthorization=keys.sdkAuthorization,
254+ // ~mode=CardCVCElement,
255+ // )
256+ // ->then(response => {
257+ // messageParentWindow([("cvcWidgetConfirmResponse", response)])
258+ // resolve()
259+ // })
260+ // ->catch(err => {
261+ // messageParentWindow([
262+ // (
263+ // "cvcWidgetConfirmErrorResponse",
264+ // err->formatException->JSON.stringify->JSON.Encode.string,
265+ // ),
266+ // ])
267+ // resolve()
268+ // })
269+ // ->ignore
270+ // } else if requiresCvv {
271+ // // Future improvement: We can check if the CVC entered is more than 3 digits and show an appropriate error message. For now, we are just checking if it's less than 3 digits.
272+ // let isEmptyCVC = cvcNumber->String.length == 0
273+
274+ // let errorMsg = if isEmptyCVC {
275+ // localeString.cvcNumberEmptyText
276+ // } else {
277+ // localeString.inCompleteCVCErrorText
278+ // }
279+
280+ // setCvcError(_ => errorMsg)
281+
282+ // messageParentWindow([
283+ // (
284+ // "cvcWidgetConfirmErrorResponse",
285+ // handleFailureResponse(~message=errorMsg, ~errorType="cvc_validation_failed"),
286+ // ),
287+ // ])
288+ // } else {
289+ // messageParentWindow([
290+ // (
291+ // "cvcWidgetConfirmErrorResponse",
292+ // handleFailureResponse(
293+ // ~message="Something went wrong",
294+ // ~errorType="cvc_validation_failed",
295+ // ),
296+ // ),
297+ // ])
298+ // }
299+ // }
300+ // }
301+ // | None => ()
302+ // }
303+ // } catch {
304+ // | _ =>
305+ // messageParentWindow([
306+ // (
307+ // "cvcWidgetConfirmErrorResponse",
308+ // handleFailureResponse(
309+ // ~message="Something went wrong",
310+ // ~errorType="cvc_validation_failed",
311+ // ),
312+ // ),
313+ // ])
314+ // }
315+ // }
316+ // Window.addEventListener("message", handleRequestCVCConfirm)
317+ // Some(
318+ // () => {
319+ // Window.removeEventListener("message", handleRequestCVCConfirm)
320+ // },
321+ // )
322+ // }, (cvcNumber, keys, paymentType, loggerState, customPodUri, redirectionFlags, localeString))
137323
138324 React .useEffect (() => {
139325 SubscriptionEventHooks .emitReady (
@@ -144,18 +330,19 @@ let make = (~cvcProps: CardUtils.cvcProps, ~paymentType: CardThemeType.mode) =>
144330 }, (keys .iframeId , paymentType ))
145331
146332 React .useEffect (() => {
147- let cvcInfoDict = [("isCvcEmpty" , isCvcEmpty -> JSON .Encode .bool )]-> Dict .fromArray
148- Utils .messageParentWindow ([("cvcInfo" , cvcInfoDict -> JSON .Encode .object )])
333+ if ! isVaultCvcFlow {
334+ let cvcInfoDict = [("isCvcEmpty" , isCvcEmpty -> JSON .Encode .bool )]-> Dict .fromArray
335+ Utils .messageParentWindow ([("cvcInfo" , cvcInfoDict -> JSON .Encode .object )])
336+ }
149337 None
150338 }, [isCvcEmpty ])
151-
152339 React .useEffect (() => {
153340 emitter .emitCvcStatus (~iframeId = keys .iframeId , ~isCvcEmpty , ~isCvcComplete )
154341 None
155342 }, (isCvcEmpty , isCvcComplete , keys .iframeId ))
156343
157344 <PaymentInputField
158- fieldName = localeString .cvcTextLabel
345+ fieldName = { isVaultCvcFlow ? "" : localeString .cvcTextLabel }
159346 isValid = isCVCValid
160347 setIsValid = setIsCVCValid
161348 value = cvcNumber
@@ -167,6 +354,7 @@ let make = (~cvcProps: CardUtils.cvcProps, ~paymentType: CardThemeType.mode) =>
167354 maxLength = 4
168355 inputRef = cvcRef
169356 placeholder = "123"
357+ height = {isVaultCvcFlow ? "1.8rem" : "" }
170358 name = TestUtils .cardCVVInputTestId
171359 autocomplete = "cc-csc"
172360 />
0 commit comments