Skip to content

Commit 4193d85

Browse files
feat: added support for collecting_billing_details_from_wallets (#529)
1 parent 23b0596 commit 4193d85

File tree

11 files changed

+308
-73
lines changed

11 files changed

+308
-73
lines changed

src/PaymentElement.res

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,17 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod
148148
~sessions,
149149
)
150150

151+
let dict = sessions->getDictFromJson
152+
let sessionObj = SessionsType.itemToObjMapper(dict, Others)
153+
let applePaySessionObj = SessionsType.itemToObjMapper(dict, ApplePayObject)
154+
let applePayToken = SessionsType.getPaymentSessionObj(applePaySessionObj.sessionsToken, ApplePay)
155+
let gPayToken = SessionsType.getPaymentSessionObj(sessionObj.sessionsToken, Gpay)
156+
let googlePayThirdPartySessionObj = SessionsType.itemToObjMapper(dict, GooglePayThirdPartyObject)
157+
let googlePayThirdPartyToken = SessionsType.getPaymentSessionObj(
158+
googlePayThirdPartySessionObj.sessionsToken,
159+
Gpay,
160+
)
161+
151162
React.useEffect(() => {
152163
switch paymentMethodList {
153164
| Loaded(paymentlist) =>
@@ -317,6 +328,30 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod
317328
<React.Suspense fallback={loader()}>
318329
<BoletoLazy paymentType />
319330
</React.Suspense>
331+
| ApplePay =>
332+
switch applePayToken {
333+
| ApplePayTokenOptional(optToken) =>
334+
<ApplePayLazy sessionObj=optToken walletOptions paymentType />
335+
| _ => React.null
336+
}
337+
| GooglePay =>
338+
<SessionPaymentWrapper type_={Wallet}>
339+
{switch gPayToken {
340+
| OtherTokenOptional(optToken) =>
341+
switch googlePayThirdPartyToken {
342+
| GooglePayThirdPartyTokenOptional(googlePayThirdPartyOptToken) =>
343+
<GPayLazy
344+
sessionObj=optToken
345+
thirdPartySessionObj=googlePayThirdPartyOptToken
346+
walletOptions
347+
paymentType
348+
/>
349+
| _ =>
350+
<GPayLazy sessionObj=optToken thirdPartySessionObj=None walletOptions paymentType />
351+
}
352+
| _ => React.null
353+
}}
354+
</SessionPaymentWrapper>
320355
| _ =>
321356
<React.Suspense fallback={loader()}>
322357
<PaymentMethodsWrapperLazy paymentType paymentMethodName=selectedOption />

src/Payments/ApplePay.res

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
open Utils
22
open Promise
33
@react.component
4-
let make = (~sessionObj: option<JSON.t>) => {
4+
let make = (~sessionObj: option<JSON.t>, ~walletOptions, ~paymentType: CardThemeType.mode) => {
55
let url = RescriptReactRouter.useUrl()
66
let componentName = CardUtils.getQueryParamsDictforKey(url.search, "componentName")
77
let loggerState = Recoil.useRecoilValueFromAtom(RecoilAtoms.loggerAtom)
@@ -21,6 +21,17 @@ let make = (~sessionObj: option<JSON.t>) => {
2121
let areOneClickWalletsRendered = Recoil.useSetRecoilState(RecoilAtoms.areOneClickWalletsRendered)
2222
let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue)
2323

24+
let areRequiredFieldsValid = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsValid)
25+
let areRequiredFieldsEmpty = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsEmpty)
26+
let (requiredFieldsBody, setRequiredFieldsBody) = React.useState(_ => Dict.make())
27+
let isWallet = walletOptions->Array.includes("apple_pay")
28+
29+
UtilityHooks.useHandlePostMessages(
30+
~complete=areRequiredFieldsValid,
31+
~empty=areRequiredFieldsEmpty,
32+
~paymentType="apple_pay",
33+
)
34+
2435
let applePayPaymentMethodType = React.useMemo(() => {
2536
switch PaymentMethodsRecord.getPaymentMethodTypeFromList(
2637
~paymentMethodListValue,
@@ -260,12 +271,15 @@ let make = (~sessionObj: option<JSON.t>) => {
260271
~setApplePayClicked,
261272
~syncPayment,
262273
~isInvokeSDKFlow,
274+
~isWallet,
275+
~requiredFieldsBody,
263276
)
264277

265278
React.useEffect(() => {
266279
if (
267280
(isInvokeSDKFlow || paymentExperience == PaymentMethodsRecord.RedirectToURL) &&
268-
isApplePayReady
281+
isApplePayReady &&
282+
isWallet
269283
) {
270284
setShowApplePay(_ => true)
271285
areOneClickWalletsRendered(prev => {
@@ -275,26 +289,35 @@ let make = (~sessionObj: option<JSON.t>) => {
275289
setIsShowOrPayUsing(_ => true)
276290
}
277291
None
278-
}, (isApplePayReady, isInvokeSDKFlow, paymentExperience))
292+
}, (isApplePayReady, isInvokeSDKFlow, paymentExperience, isWallet))
279293

280-
<RenderIf condition={showApplePay}>
281-
<div>
282-
<style> {React.string(css)} </style>
283-
{if showApplePayLoader {
284-
<div className="apple-pay-loader-div">
285-
<div className="apple-pay-loader" />
286-
</div>
287-
} else {
288-
<button
289-
disabled=applePayClicked
290-
className="apple-pay-button-with-text apple-pay-button-black-with-text"
291-
onClick={_ => onApplePayButtonClicked()}>
292-
<span className="text"> {React.string("Pay with")} </span>
293-
<span className="logo" />
294-
</button>
295-
}}
296-
</div>
297-
</RenderIf>
294+
let submitCallback = ApplePayHelpers.useSubmitCallback(~isWallet, ~sessionObj, ~componentName)
295+
useSubmitPaymentData(submitCallback)
296+
297+
if isWallet {
298+
<RenderIf condition={showApplePay}>
299+
<div>
300+
<style> {React.string(css)} </style>
301+
{if showApplePayLoader {
302+
<div className="apple-pay-loader-div">
303+
<div className="apple-pay-loader" />
304+
</div>
305+
} else {
306+
<button
307+
disabled=applePayClicked
308+
className="apple-pay-button-with-text apple-pay-button-black-with-text"
309+
onClick={_ => onApplePayButtonClicked()}>
310+
<span className="text"> {React.string("Pay with")} </span>
311+
<span className="logo" />
312+
</button>
313+
}}
314+
</div>
315+
</RenderIf>
316+
} else {
317+
<DynamicFields
318+
paymentType paymentMethod="wallet" paymentMethodType="apple_pay" setRequiredFieldsBody
319+
/>
320+
}
298321
}
299322

300323
let default = make

src/Payments/ApplePay.resi

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
@react.component
2-
let default: (~sessionObj: option<JSON.t>) => React.element
2+
let default: (
3+
~sessionObj: option<JSON.t>,
4+
~walletOptions: array<string>,
5+
~paymentType: CardThemeType.mode,
6+
) => React.element

src/Payments/GPay.res

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ open GooglePayType
55
open Promise
66

77
@react.component
8-
let make = (~sessionObj: option<SessionsType.token>, ~thirdPartySessionObj: option<JSON.t>) => {
8+
let make = (
9+
~sessionObj: option<SessionsType.token>,
10+
~thirdPartySessionObj: option<JSON.t>,
11+
~walletOptions,
12+
~paymentType: CardThemeType.mode,
13+
) => {
914
let url = RescriptReactRouter.useUrl()
1015
let componentName = CardUtils.getQueryParamsDictforKey(url.search, "componentName")
1116
let loggerState = Recoil.useRecoilValueFromAtom(loggerAtom)
@@ -28,6 +33,17 @@ let make = (~sessionObj: option<SessionsType.token>, ~thirdPartySessionObj: opti
2833

2934
let areOneClickWalletsRendered = Recoil.useSetRecoilState(RecoilAtoms.areOneClickWalletsRendered)
3035

36+
let areRequiredFieldsValid = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsValid)
37+
let areRequiredFieldsEmpty = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsEmpty)
38+
let (requiredFieldsBody, setRequiredFieldsBody) = React.useState(_ => Dict.make())
39+
let isWallet = walletOptions->Array.includes("google_pay")
40+
41+
UtilityHooks.useHandlePostMessages(
42+
~complete=areRequiredFieldsValid,
43+
~empty=areRequiredFieldsEmpty,
44+
~paymentType="google_pay",
45+
)
46+
3147
let googlePayPaymentMethodType = switch PaymentMethodsRecord.getPaymentMethodTypeFromList(
3248
~paymentMethodListValue,
3349
~paymentMethod="wallet",
@@ -58,7 +74,7 @@ let make = (~sessionObj: option<SessionsType.token>, ~thirdPartySessionObj: opti
5874
->Option.getOr(false)
5975
}, [thirdPartySessionObj])
6076

61-
GooglePayHelpers.useHandleGooglePayResponse(~connectors, ~intent)
77+
GooglePayHelpers.useHandleGooglePayResponse(~connectors, ~intent, ~isWallet, ~requiredFieldsBody)
6278

6379
let (_, buttonType, _) = options.wallets.style.type_
6480
let (_, heightType, _, _) = options.wallets.style.height
@@ -155,9 +171,10 @@ let make = (~sessionObj: option<SessionsType.token>, ~thirdPartySessionObj: opti
155171
React.useEffect(() => {
156172
if (
157173
status == "ready" &&
158-
(isGPayReady ||
159-
isDelayedSessionToken ||
160-
paymentExperience == PaymentMethodsRecord.RedirectToURL)
174+
(isGPayReady ||
175+
isDelayedSessionToken ||
176+
paymentExperience == PaymentMethodsRecord.RedirectToURL) &&
177+
isWallet
161178
) {
162179
setIsShowOrPayUsing(_ => true)
163180
addGooglePayButton()
@@ -190,7 +207,9 @@ let make = (~sessionObj: option<SessionsType.token>, ~thirdPartySessionObj: opti
190207
})
191208

192209
let isRenderGooglePayButton =
193-
isGPayReady || paymentExperience == PaymentMethodsRecord.RedirectToURL || isDelayedSessionToken
210+
(isGPayReady ||
211+
paymentExperience == PaymentMethodsRecord.RedirectToURL ||
212+
isDelayedSessionToken) && isWallet
194213

195214
React.useEffect(() => {
196215
areOneClickWalletsRendered(prev => {
@@ -200,13 +219,22 @@ let make = (~sessionObj: option<SessionsType.token>, ~thirdPartySessionObj: opti
200219
None
201220
}, [isRenderGooglePayButton])
202221

203-
<RenderIf condition={isRenderGooglePayButton}>
204-
<div
205-
style={height: `${height->Int.toString}px`}
206-
id="google-pay-button"
207-
className={`w-full flex flex-row justify-center rounded-md`}
222+
let submitCallback = GooglePayHelpers.useSubmitCallback(~isWallet, ~sessionObj, ~componentName)
223+
useSubmitPaymentData(submitCallback)
224+
225+
if isWallet {
226+
<RenderIf condition={isRenderGooglePayButton}>
227+
<div
228+
style={height: `${height->Int.toString}px`}
229+
id="google-pay-button"
230+
className={`w-full flex flex-row justify-center rounded-md`}
231+
/>
232+
</RenderIf>
233+
} else {
234+
<DynamicFields
235+
paymentType paymentMethod="wallet" paymentMethodType="google_pay" setRequiredFieldsBody
208236
/>
209-
</RenderIf>
237+
}
210238
}
211239

212240
let default = make

src/Payments/GPay.resi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22
let default: (
33
~sessionObj: option<SessionsType.token>,
44
~thirdPartySessionObj: option<JSON.t>,
5+
~walletOptions: array<string>,
6+
~paymentType: CardThemeType.mode,
57
) => React.element

src/Payments/PaymentMethodsRecord.res

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,7 @@ type paymentMethodList = {
802802
mandate_payment: option<mandate>,
803803
payment_type: payment_type,
804804
merchant_name: string,
805+
collect_billing_details_from_wallets: bool,
805806
}
806807

807808
let defaultPaymentMethodType = {
@@ -823,6 +824,7 @@ let defaultList = {
823824
mandate_payment: None,
824825
payment_type: NONE,
825826
merchant_name: "",
827+
collect_billing_details_from_wallets: true,
826828
}
827829
let getMethod = str => {
828830
switch str {
@@ -1030,6 +1032,11 @@ let itemToObjMapper = dict => {
10301032
mandate_payment: getMandate(dict, "mandate_payment"),
10311033
payment_type: getString(dict, "payment_type", "")->paymentTypeMapper,
10321034
merchant_name: getString(dict, "merchant_name", ""),
1035+
collect_billing_details_from_wallets: getBool(
1036+
dict,
1037+
"collect_billing_details_from_wallets",
1038+
true,
1039+
),
10331040
}
10341041
}
10351042

src/Payments/PaymentRequestButtonElement.res

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,15 @@ let make = (~sessions, ~walletOptions, ~paymentType) => {
7777
switch googlePayThirdPartyToken {
7878
| GooglePayThirdPartyTokenOptional(googlePayThirdPartyOptToken) =>
7979
<GPayLazy
80-
sessionObj=optToken thirdPartySessionObj=googlePayThirdPartyOptToken
80+
sessionObj=optToken
81+
thirdPartySessionObj=googlePayThirdPartyOptToken
82+
walletOptions
83+
paymentType
84+
/>
85+
| _ =>
86+
<GPayLazy
87+
sessionObj=optToken thirdPartySessionObj=None walletOptions paymentType
8188
/>
82-
| _ => <GPayLazy sessionObj=optToken thirdPartySessionObj=None />
8389
}
8490
| _ => React.null
8591
}}
@@ -97,7 +103,8 @@ let make = (~sessions, ~walletOptions, ~paymentType) => {
97103
</SessionPaymentWrapper>
98104
| ApplePayWallet =>
99105
switch applePayToken {
100-
| ApplePayTokenOptional(optToken) => <ApplePayLazy sessionObj=optToken />
106+
| ApplePayTokenOptional(optToken) =>
107+
<ApplePayLazy sessionObj=optToken walletOptions paymentType />
101108
| _ => React.null
102109
}
103110
| KlarnaWallet =>

src/Utilities/ApplePayHelpers.res

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ let useHandleApplePayResponse = (
142142
~syncPayment=() => (),
143143
~isInvokeSDKFlow=true,
144144
~isSavedMethodsFlow=false,
145+
~isWallet=true,
146+
~requiredFieldsBody=Dict.make(),
145147
) => {
146148
let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom)
147149
let {publishableKey} = Recoil.useRecoilValueFromAtom(RecoilAtoms.keys)
@@ -187,8 +189,18 @@ let useHandleApplePayResponse = (
187189
~isSavedMethodsFlow,
188190
)
189191

192+
let bodyArr = if isWallet {
193+
applePayBody
194+
} else {
195+
applePayBody
196+
->getJsonFromArrayOfJson
197+
->flattenObject(true)
198+
->mergeTwoFlattenedJsonDicts(requiredFieldsBody)
199+
->getArrayOfTupleFromDict
200+
}
201+
190202
processPayment(
191-
~bodyArr=applePayBody,
203+
~bodyArr,
192204
~isThirdPartyFlow=false,
193205
~isGuestCustomer,
194206
~paymentMethodListValue,
@@ -199,7 +211,7 @@ let useHandleApplePayResponse = (
199211
)
200212
} else if dict->Dict.get("showApplePayButton")->Option.isSome {
201213
setApplePayClicked(_ => false)
202-
if isSavedMethodsFlow {
214+
if isSavedMethodsFlow || !isWallet {
203215
postFailedSubmitResponse(~errortype="server_error", ~message="Something went wrong")
204216
}
205217
} else if dict->Dict.get("applePaySyncPayment")->Option.isSome {
@@ -216,7 +228,14 @@ let useHandleApplePayResponse = (
216228
Window.removeEventListener("message", handleApplePayMessages)
217229
},
218230
)
219-
}, (isInvokeSDKFlow, processPayment, stateJson, isManualRetryEnabled))
231+
}, (
232+
isInvokeSDKFlow,
233+
processPayment,
234+
stateJson,
235+
isManualRetryEnabled,
236+
isWallet,
237+
requiredFieldsBody,
238+
))
220239
}
221240

222241
let handleApplePayButtonClicked = (~sessionObj, ~componentName) => {
@@ -227,3 +246,30 @@ let handleApplePayButtonClicked = (~sessionObj, ~componentName) => {
227246
]
228247
handlePostMessage(message)
229248
}
249+
250+
let useSubmitCallback = (~isWallet, ~sessionObj, ~componentName) => {
251+
let areRequiredFieldsValid = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsValid)
252+
let areRequiredFieldsEmpty = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsEmpty)
253+
let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom)
254+
let {localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom)
255+
256+
React.useCallback((ev: Window.event) => {
257+
if !isWallet {
258+
let json = ev.data->JSON.parseExn
259+
let confirm = json->getDictFromJson->ConfirmType.itemToObjMapper
260+
if confirm.doSubmit && areRequiredFieldsValid && !areRequiredFieldsEmpty {
261+
options.readOnly ? () : handleApplePayButtonClicked(~sessionObj, ~componentName)
262+
} else if areRequiredFieldsEmpty {
263+
postFailedSubmitResponse(
264+
~errortype="validation_error",
265+
~message=localeString.enterFieldsText,
266+
)
267+
} else if !areRequiredFieldsValid {
268+
postFailedSubmitResponse(
269+
~errortype="validation_error",
270+
~message=localeString.enterValidDetailsText,
271+
)
272+
}
273+
}
274+
}, (areRequiredFieldsValid, areRequiredFieldsEmpty, isWallet, sessionObj, componentName))
275+
}

0 commit comments

Comments
 (0)