diff --git a/CHANGELOG.md b/CHANGELOG.md index 2409ede02..cba71aa67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +## [0.36.1](https://github.com/juspay/hyperswitch-web/compare/v0.36.0...v0.36.1) (2024-04-04) + + +### Bug Fixes + +* added Loader and Error Handling for TrustPay GooglePay ([#268](https://github.com/juspay/hyperswitch-web/issues/268)) ([1f082eb](https://github.com/juspay/hyperswitch-web/commit/1f082ebe2031a10129b24238f1c60fabd6f5e2e0)) + +# [0.36.0](https://github.com/juspay/hyperswitch-web/compare/v0.35.6...v0.36.0) (2024-04-04) + + +### Features + +* **3ds:** three DS SDK - adding logs to track milestone events ([#265](https://github.com/juspay/hyperswitch-web/issues/265)) ([ceab161](https://github.com/juspay/hyperswitch-web/commit/ceab1614e80d8cfb96ac3eea04486ebd509e0770)) +* giropay dynamic fields added ([#267](https://github.com/juspay/hyperswitch-web/issues/267)) ([ad2fa63](https://github.com/juspay/hyperswitch-web/commit/ad2fa630c639c7b246176f1d2683050a58ad3e36)) + ## [0.35.6](https://github.com/juspay/hyperswitch-web/compare/v0.35.5...v0.35.6) (2024-04-02) diff --git a/package-lock.json b/package-lock.json index 4eb12e8cd..101fe6213 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "orca-payment-page", - "version": "0.35.6", + "version": "0.36.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e11908492..de47c185c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "orca-payment-page", - "version": "0.35.6", + "version": "0.36.1", "main": "index.js", "private": true, "dependencies": { diff --git a/src/Components/DynamicFields.res b/src/Components/DynamicFields.res index 1784479e3..bef9f90ae 100644 --- a/src/Components/DynamicFields.res +++ b/src/Components/DynamicFields.res @@ -318,425 +318,421 @@ let make = ( dynamicFieldsToRenderInsideBilling->Array.length > 0 && (dynamicFieldsToRenderInsideBilling->Array.length > 1 || !isOnlyInfoElementPresent) - { - fieldsArr->Array.length > 0 - ? <> - {dynamicFieldsToRenderOutsideBilling - ->Array.mapWithIndex((item, index) => { -
Int.toString}`} - className="flex flex-col w-full place-content-between" - style={ReactDOMStyle.make( - ~marginTop=index !== 0 || paymentMethod === "card" - ? themeObj.spacingGridColumn - : "", - ~gridColumnGap=themeObj.spacingGridRow, - (), - )}> - {switch item { - | CardNumber => - - | CardExpiryMonth - | CardExpiryYear - | CardExpiryMonthAndYear => - - | CardCvc => - - | CardExpiryAndCvc => -
- Array.length > 0}> + {<> + {dynamicFieldsToRenderOutsideBilling + ->Array.mapWithIndex((item, index) => { +
Int.toString}`} + className="flex flex-col w-full place-content-between" + style={ReactDOMStyle.make( + ~marginTop=index !== 0 || paymentMethod === "card" ? themeObj.spacingGridColumn : "", + ~gridColumnGap=themeObj.spacingGridRow, + (), + )}> + {switch item { + | CardNumber => + + | CardExpiryMonth + | CardExpiryYear + | CardExpiryMonthAndYear => + + | CardCvc => + + | CardExpiryAndCvc => +
+ + +
+ | Currency(currencyArr) => + + | FullName => + getCustomFieldName} + optionalRequiredFields={Some(requiredFields)} + /> + | Email + | InfoElement + | Country + | Bank + | None + | BillingName + | PhoneNumber + | AddressLine1 + | AddressLine2 + | AddressCity + | StateAndCity + | AddressPincode + | AddressState + | BlikCode + | SpecialField(_) + | CountryAndPincode(_) + | AddressCountry(_) => React.null + }} +
+ }) + ->React.array} + +
+ {React.string(localeString.billingDetailsText)} +
+ {dynamicFieldsToRenderInsideBilling + ->Array.mapWithIndex((item, index) => { +
Int.toString}`} + className="flex flex-col w-full place-content-between"> + {switch item { + | BillingName => + | Email => + | PhoneNumber => + | StateAndCity => +
+ { + let value = ReactEvent.Form.target(ev)["value"] + setCity(prev => { + isValid: value !== "" ? Some(true) : Some(false), + value, + errorString: value !== "" ? "" : prev.errorString, + }) + }} + onBlur={ev => { + let value = ReactEvent.Focus.target(ev)["value"] + setCity(prev => { + ...prev, + isValid: Some(value !== ""), + }) + }} + paymentType + type_="text" + name="city" + inputRef=cityRef + placeholder=localeString.cityLabel + /> + {switch stateJson { + | Some(options) => + Utils.getStateNames({ + value: country, + isValid: None, + errorString: "", + })} + /> + | None => React.null + }} +
+ | CountryAndPincode(countryArr) => +
+ + { + let value = ReactEvent.Focus.target(ev)["value"] + setPostalCode(prev => { + ...prev, + isValid: Some(value !== ""), + }) + }} + onChange=onPostalChange + paymentType + name="postal" + inputRef=postalRef + placeholder=localeString.postalCodeLabel + /> +
+ | AddressLine1 => + { + let value = ReactEvent.Form.target(ev)["value"] + setLine1(prev => { + isValid: value !== "" ? Some(true) : Some(false), + value, + errorString: value !== "" ? "" : prev.errorString, + }) + }} + onBlur={ev => { + let value = ReactEvent.Focus.target(ev)["value"] + setLine1(prev => { + ...prev, + isValid: Some(value !== ""), + }) + }} paymentType - type_="tel" - appearance=config.appearance - maxLength=7 - inputRef=expiryRef - placeholder="MM / YY" + type_="text" + name="line1" + inputRef=line1Ref + placeholder=localeString.line1Placeholder + /> + | AddressLine2 => + { + let value = ReactEvent.Form.target(ev)["value"] + setLine2(prev => { + isValid: value !== "" ? Some(true) : Some(false), + value, + errorString: value !== "" ? "" : prev.errorString, + }) + }} + onBlur={ev => { + let value = ReactEvent.Focus.target(ev)["value"] + setLine2(prev => { + ...prev, + isValid: Some(value !== ""), + }) + }} + paymentType + type_="text" + name="line2" + inputRef=line2Ref + placeholder=localeString.line2Placeholder + /> + | AddressCity => + { + let value = ReactEvent.Form.target(ev)["value"] + setCity(prev => { + isValid: value !== "" ? Some(true) : Some(false), + value, + errorString: value !== "" ? "" : prev.errorString, + }) + }} + onBlur={ev => { + let value = ReactEvent.Focus.target(ev)["value"] + setCity(prev => { + ...prev, + isValid: Some(value !== ""), + }) + }} + paymentType + type_="text" + name="city" + inputRef=cityRef + placeholder=localeString.cityLabel /> - + switch stateJson { + | Some(options) => + Utils.getStateNames({ + value: country, + isValid: None, + errorString: "", + })} + /> + | None => React.null + } + | AddressPincode => + { + let value = ReactEvent.Focus.target(ev)["value"] + setPostalCode(prev => { + ...prev, + isValid: Some(value !== ""), + }) + }} + onChange=onPostalChange paymentType - rightIcon={CardUtils.setRightIconForCvc( - ~cardEmpty, - ~cardInvalid, - ~color=themeObj.colorIconCardCvcError, - ~cardComplete, - )} + name="postal" + inputRef=postalRef + placeholder=localeString.postalCodeLabel + /> + | BlikCode => + | Country => + -
- | Currency(currencyArr) => - - | FullName => - getCustomFieldName} - optionalRequiredFields={Some(requiredFields)} - /> - | Email - | InfoElement - | Country - | Bank - | None - | BillingName - | PhoneNumber - | AddressLine1 - | AddressLine2 - | AddressCity - | StateAndCity - | AddressPincode - | AddressState - | BlikCode - | SpecialField(_) - | CountryAndPincode(_) - | AddressCountry(_) => React.null - }} -
- }) - ->React.array} - -
- {React.string(localeString.billingDetailsText)} -
- {dynamicFieldsToRenderInsideBilling - ->Array.mapWithIndex((item, index) => { -
Int.toString}`} - className="flex flex-col w-full place-content-between"> - {switch item { - | BillingName => - | Email => - | PhoneNumber => - | StateAndCity => -
- { - let value = ReactEvent.Form.target(ev)["value"] - setCity(prev => { - isValid: value !== "" ? Some(true) : Some(false), - value, - errorString: value !== "" ? "" : prev.errorString, - }) - }} - onBlur={ev => { - let value = ReactEvent.Focus.target(ev)["value"] - setCity(prev => { - ...prev, - isValid: Some(value !== ""), - }) - }} - paymentType - type_="text" - name="city" - inputRef=cityRef - placeholder=localeString.cityLabel - /> - {switch stateJson { - | Some(options) => - Utils.getStateNames({ - value: country, - isValid: None, - errorString: "", - })} - /> - | None => React.null - }} -
- | CountryAndPincode(countryArr) => -
- - { - let value = ReactEvent.Focus.target(ev)["value"] - setPostalCode(prev => { - ...prev, - isValid: Some(value !== ""), - }) - }} - onChange=onPostalChange - paymentType - name="postal" - inputRef=postalRef - placeholder=localeString.postalCodeLabel - /> -
- | AddressLine1 => - { - let value = ReactEvent.Form.target(ev)["value"] - setLine1(prev => { - isValid: value !== "" ? Some(true) : Some(false), - value, - errorString: value !== "" ? "" : prev.errorString, - }) - }} - onBlur={ev => { - let value = ReactEvent.Focus.target(ev)["value"] - setLine1(prev => { - ...prev, - isValid: Some(value !== ""), - }) - }} - paymentType - type_="text" - name="line1" - inputRef=line1Ref - placeholder=localeString.line1Placeholder - /> - | AddressLine2 => - { - let value = ReactEvent.Form.target(ev)["value"] - setLine2(prev => { - isValid: value !== "" ? Some(true) : Some(false), - value, - errorString: value !== "" ? "" : prev.errorString, - }) - }} - onBlur={ev => { - let value = ReactEvent.Focus.target(ev)["value"] - setLine2(prev => { - ...prev, - isValid: Some(value !== ""), - }) - }} - paymentType - type_="text" - name="line2" - inputRef=line2Ref - placeholder=localeString.line2Placeholder - /> - | AddressCity => - { - let value = ReactEvent.Form.target(ev)["value"] - setCity(prev => { - isValid: value !== "" ? Some(true) : Some(false), - value, - errorString: value !== "" ? "" : prev.errorString, - }) - }} - onBlur={ev => { - let value = ReactEvent.Focus.target(ev)["value"] - setCity(prev => { - ...prev, - isValid: Some(value !== ""), - }) - }} - paymentType - type_="text" - name="city" - inputRef=cityRef - placeholder=localeString.cityLabel - /> - | AddressState => - switch stateJson { - | Some(options) => - Utils.getStateNames({ - value: country, - isValid: None, - errorString: "", - })} - /> - | None => React.null - } - | AddressPincode => - { - let value = ReactEvent.Focus.target(ev)["value"] - setPostalCode(prev => { - ...prev, - isValid: Some(value !== ""), - }) - }} - onChange=onPostalChange - paymentType - name="postal" - inputRef=postalRef - placeholder=localeString.postalCodeLabel - /> - | BlikCode => - | Country => - - | AddressCountry(countryArr) => - - | Bank => - - | SpecialField(element) => element - | InfoElement => - <> - - {if fieldsArr->Array.length > 1 { - bottomElement - } else { - - }} - - | CardNumber - | CardExpiryMonth - | CardExpiryYear - | CardExpiryMonthAndYear - | CardCvc - | CardExpiryAndCvc - | Currency(_) - | FullName - | None => React.null + | AddressCountry(countryArr) => + + | Bank => + + | SpecialField(element) => element + | InfoElement => + <> + + {if fieldsArr->Array.length > 1 { + bottomElement + } else { + }} -
- }) - ->React.array} + + | CardNumber + | CardExpiryMonth + | CardExpiryYear + | CardExpiryMonthAndYear + | CardCvc + | CardExpiryAndCvc + | Currency(_) + | FullName + | None => React.null + }}
-
-
- - {<> - - {if fieldsArr->Array.length > 1 { - bottomElement - } else { - - }} - } - - - - - - : React.null - } + }) + ->React.array} +
+
+ + + {<> + + {if fieldsArr->Array.length > 1 { + bottomElement + } else { + + }} + } + + + + + } + } diff --git a/src/Payments/GPay.res b/src/Payments/GPay.res index 03d5cc210..5cc0bbfba 100644 --- a/src/Payments/GPay.res +++ b/src/Payments/GPay.res @@ -151,6 +151,11 @@ let make = ( if result { if isInvokeSDKFlow { if isDelayedSessionToken { + handlePostMessage([ + ("fullscreen", true->JSON.Encode.bool), + ("param", "paymentloader"->JSON.Encode.string), + ("iframeId", iframeId->JSON.Encode.string), + ]) let bodyDict = PaymentBody.gPayThirdPartySdkBody(~connectors) processPayment(bodyDict) } else { diff --git a/src/Payments/PaymentMethodsRecord.res b/src/Payments/PaymentMethodsRecord.res index 143e0413e..b77b5ddff 100644 --- a/src/Payments/PaymentMethodsRecord.res +++ b/src/Payments/PaymentMethodsRecord.res @@ -223,7 +223,7 @@ let paymentMethodsFields = [ paymentMethodName: "giropay", icon: Some(icon("giropay", ~size=19, ~width=25)), displayName: "GiroPay", - fields: [FullName, InfoElement], + fields: [InfoElement], miniIcon: None, }, { @@ -578,6 +578,7 @@ let dynamicFieldsEnabledPaymentMethods = [ "ideal", "sofort", "pix_transfer", + "giropay", ] let getIsBillingField = requiredFieldType => { diff --git a/src/Payments/PaymentMethodsWrapper.res b/src/Payments/PaymentMethodsWrapper.res index 7ee67cc8c..2c7d1710f 100644 --- a/src/Payments/PaymentMethodsWrapper.res +++ b/src/Payments/PaymentMethodsWrapper.res @@ -115,7 +115,7 @@ let make = ( )) useSubmitPaymentData(submitCallback)
Utils.handlePostMessage([("fullscreen", false->JSON.Encode.bool)]) -let retrievePaymentIntent = (clientSecret, headers, ~optLogger, ~switchToCustomPod) => { +let retrievePaymentIntent = ( + clientSecret, + headers, + ~optLogger, + ~switchToCustomPod, + ~isForceSync=false, +) => { open Promise let paymentIntentID = String.split(clientSecret, "_secret_")->Array.get(0)->Option.getOr("") let endpoint = ApiEndpoint.getApiEndPoint() - let uri = `${endpoint}/payments/${paymentIntentID}?client_secret=${clientSecret}` + let forceSync = isForceSync ? "&force_sync=true" : "" + let uri = `${endpoint}/payments/${paymentIntentID}?client_secret=${clientSecret}${forceSync}` logApi( ~optLogger, @@ -209,9 +216,15 @@ let threeDsAuth = (~clientSecret, ~optLogger, ~threeDsMethodComp, ~headers) => { }) } -let rec pollRetrievePaymentIntent = (clientSecret, headers, ~optLogger, ~switchToCustomPod) => { +let rec pollRetrievePaymentIntent = ( + clientSecret, + headers, + ~optLogger, + ~switchToCustomPod, + ~isForceSync=false, +) => { open Promise - retrievePaymentIntent(clientSecret, headers, ~optLogger, ~switchToCustomPod) + retrievePaymentIntent(clientSecret, headers, ~optLogger, ~switchToCustomPod, ~isForceSync) ->then(json => { let dict = json->JSON.Decode.object->Option.getOr(Dict.make()) let status = dict->getString("status", "") @@ -220,13 +233,19 @@ let rec pollRetrievePaymentIntent = (clientSecret, headers, ~optLogger, ~switchT resolve(json) } else { delay(2000)->then(_val => { - pollRetrievePaymentIntent(clientSecret, headers, ~optLogger, ~switchToCustomPod) + pollRetrievePaymentIntent( + clientSecret, + headers, + ~optLogger, + ~switchToCustomPod, + ~isForceSync, + ) }) } }) ->catch(e => { Console.log2("Unable to retrieve payment due to following error", e) - pollRetrievePaymentIntent(clientSecret, headers, ~optLogger, ~switchToCustomPod) + pollRetrievePaymentIntent(clientSecret, headers, ~optLogger, ~switchToCustomPod, ~isForceSync) }) } diff --git a/src/orca-loader/Elements.res b/src/orca-loader/Elements.res index d0ade9433..b5164d21d 100644 --- a/src/orca-loader/Elements.res +++ b/src/orca-loader/Elements.res @@ -305,40 +305,79 @@ let make = ( paymentDataRequest->GooglePayType.jsonToPaymentRequestDataType( googlePayThirdPartySession, ) - let secrets = googlePayThirdPartySession->getJsonFromDict("secrets", JSON.Encode.null) - let payment = secrets->getDictFromJson->getString("payment", "") + let headers = [("Content-Type", "application/json"), ("api-key", publishableKey)] + + let connector = + googlePayThirdPartySession + ->Dict.get("connector") + ->Option.getOr(JSON.Encode.null) + ->JSON.Decode.string + ->Option.getOr("") try { - let trustpay = trustPayApi(secrets) - trustpay.executeGooglePayment(payment, googlePayRequest) - ->then(res => { - logger.setLogInfo( - ~value="TrustPay GooglePay Success Response", - ~internalMetadata=res->JSON.stringify, - ~eventName=GOOGLE_PAY_FLOW, - ~paymentMethod="GOOGLE_PAY", - (), - ) - let msg = [("googlePaySyncPayment", true->JSON.Encode.bool)]->Dict.fromArray - mountedIframeRef->Window.iframePostMessage(msg) - resolve() - }) - ->catch(err => { - let exceptionMessage = err->formatException->JSON.stringify - logger.setLogInfo( - ~value=exceptionMessage, - ~eventName=GOOGLE_PAY_FLOW, - ~paymentMethod="GOOGLE_PAY", - ~logType=ERROR, - ~logCategory=USER_ERROR, - (), - ) - let msg = [("googlePaySyncPayment", true->JSON.Encode.bool)]->Dict.fromArray - mountedIframeRef->Window.iframePostMessage(msg) - resolve() - }) - ->ignore + switch connector { + | "trustpay" => { + let secrets = + googlePayThirdPartySession->Utils.getJsonFromDict("secrets", JSON.Encode.null) + + let payment = secrets->Utils.getDictFromJson->Utils.getString("payment", "") + + let trustpay = trustPayApi(secrets) + + let polling = + Utils.delay(2000)->then(_ => + PaymentHelpers.pollRetrievePaymentIntent( + clientSecret, + headers, + ~optLogger=Some(logger), + ~switchToCustomPod, + ~isForceSync=true, + ) + ) + let executeGooglePayment = trustpay.executeGooglePayment( + payment, + googlePayRequest, + ) + let timeOut = Utils.delay(600000)->then(_ => { + let errorMsg = + [("error", "Request Timed Out"->JSON.Encode.string)] + ->Dict.fromArray + ->JSON.Encode.object + reject(Exn.anyToExnInternal(errorMsg)) + }) + + Promise.race([polling, executeGooglePayment, timeOut]) + ->then(res => { + logger.setLogInfo( + ~value="TrustPay GooglePay Response", + ~internalMetadata=res->JSON.stringify, + ~eventName=GOOGLE_PAY_FLOW, + ~paymentMethod="GOOGLE_PAY", + (), + ) + let msg = [("googlePaySyncPayment", true->JSON.Encode.bool)]->Dict.fromArray + mountedIframeRef->Window.iframePostMessage(msg) + resolve() + }) + ->catch(err => { + let exceptionMessage = err->Utils.formatException->JSON.stringify + logger.setLogInfo( + ~value=exceptionMessage, + ~eventName=GOOGLE_PAY_FLOW, + ~paymentMethod="GOOGLE_PAY", + ~logType=ERROR, + ~logCategory=USER_ERROR, + (), + ) + let msg = [("googlePaySyncPayment", true->JSON.Encode.bool)]->Dict.fromArray + mountedIframeRef->Window.iframePostMessage(msg) + resolve() + }) + ->ignore + } + | _ => () + } } catch { | err => { let exceptionMessage = err->formatException->JSON.stringify