diff --git a/CHANGELOG.md b/CHANGELOG.md index 8803d4779..412e3f73c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,42 @@ +# [0.32.0](https://github.com/juspay/hyperswitch-web/compare/v0.31.6...v0.32.0) (2024-03-13) + + +### Features + +* cvc nickname gpay ([#224](https://github.com/juspay/hyperswitch-web/issues/224)) ([f3f4047](https://github.com/juspay/hyperswitch-web/commit/f3f404733f7c13eb69589779ea2e2c8a6569259d)) + +## [0.31.6](https://github.com/juspay/hyperswitch-web/compare/v0.31.5...v0.31.6) (2024-03-13) + + +### Bug Fixes + +* pay now button text & theme based changes ([#223](https://github.com/juspay/hyperswitch-web/issues/223)) ([993767f](https://github.com/juspay/hyperswitch-web/commit/993767f185a3059ea5cc0652aa4053304e66ba52)) + +## [0.31.5](https://github.com/juspay/hyperswitch-web/compare/v0.31.4...v0.31.5) (2024-03-12) + + +### Bug Fixes + +* disable and enable Pay now button ([#221](https://github.com/juspay/hyperswitch-web/issues/221)) ([3d2e497](https://github.com/juspay/hyperswitch-web/commit/3d2e4971a20b8737820743c848496e4e8bb4a57b)) + +## [0.31.4](https://github.com/juspay/hyperswitch-web/compare/v0.31.3...v0.31.4) (2024-03-12) + + +### Bug Fixes + +* added ordering for saved payment methods ([#222](https://github.com/juspay/hyperswitch-web/issues/222)) ([a7e9f6d](https://github.com/juspay/hyperswitch-web/commit/a7e9f6d48e83ae96395ac19365abf4d757229c9b)) + +## [0.31.3](https://github.com/juspay/hyperswitch-web/compare/v0.31.2...v0.31.3) (2024-03-12) + +## [0.31.2](https://github.com/juspay/hyperswitch-web/compare/v0.31.1...v0.31.2) (2024-03-12) + +## [0.31.1](https://github.com/juspay/hyperswitch-web/compare/v0.31.0...v0.31.1) (2024-03-12) + + +### Bug Fixes + +* card payment customer_acceptance ([#220](https://github.com/juspay/hyperswitch-web/issues/220)) ([c2067e9](https://github.com/juspay/hyperswitch-web/commit/c2067e9c274727eda03686d5986c5071f2da17b2)) + # [0.31.0](https://github.com/juspay/hyperswitch-web/compare/v0.30.0...v0.31.0) (2024-03-11) diff --git a/bsconfig.json b/bsconfig.json index e314bff3a..8083e7e60 100644 --- a/bsconfig.json +++ b/bsconfig.json @@ -19,9 +19,8 @@ }, "bs-dependencies": [ "@rescript/react", - "bs-fetch", "@ryyppy/rescript-promise", - "rescript-webapi", - "@rescript/core" + "@rescript/core", + "@glennsl/rescript-fetch" ] } diff --git a/package-lock.json b/package-lock.json index b6de67564..b14df5ce6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "orca-payment-page", - "version": "0.31.0", + "version": "0.32.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2795,6 +2795,11 @@ "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true }, + "@glennsl/rescript-fetch": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@glennsl/rescript-fetch/-/rescript-fetch-0.2.0.tgz", + "integrity": "sha512-0tsEqJ/6/WBm02prM4RYG+qpnNTaB8QKKIeQHXdDaE4C5YfA/nzjxMNW3CjsGIaEgyrAmmIXFS0kx24UjvOI6A==" + }, "@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -2865,21 +2870,6 @@ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -2897,40 +2887,6 @@ "strip-ansi": "^7.0.1" } }, - "string-width-cjs": { - "version": "npm:string-width@4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, "strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", @@ -2940,23 +2896,6 @@ "ansi-regex": "^6.0.1" } }, - "strip-ansi-cjs": { - "version": "npm:strip-ansi@6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - } - } - }, "wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -2967,60 +2906,6 @@ "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } - }, - "wrap-ansi-cjs": { - "version": "npm:wrap-ansi@7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } } } }, @@ -7004,11 +6889,6 @@ "update-browserslist-db": "^1.0.13" } }, - "bs-fetch": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/bs-fetch/-/bs-fetch-0.6.2.tgz", - "integrity": "sha512-VXEjp8kY3vHPckaoy3d96Bx0KYjJAPLNISBwfpwMN26K6DtuZYwI2HKhx7zeHBajz1bRArfx7O8OOLcdtujMvg==" - }, "bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -19718,11 +19598,6 @@ "integrity": "sha512-FFKlS9AG/XrLepWsyw7B+A9DtQBPWEPDPDKghV831Y2KGbie+eeFBOS0xtRHp0xbt7S0N2Dm6hhX+kTZQ/3Ybg==", "dev": true }, - "rescript-webapi": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/rescript-webapi/-/rescript-webapi-0.7.0.tgz", - "integrity": "sha512-fA1LPXSmQY3HtcIStQ2sHu9Xye1XLxd4JvkGQMupux9Q6Tp/jbcdh7bnCaGwHW6aI1QFmTmA/xWxUMb6oPphsQ==" - }, "resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -20962,6 +20837,17 @@ "strip-ansi": "^6.0.1" } }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, "string.prototype.matchall": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", @@ -21049,6 +20935,15 @@ "ansi-regex": "^5.0.1" } }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, "strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -23620,6 +23515,43 @@ } } }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 9dfc6465b..e2bd39cdf 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,22 @@ { "name": "orca-payment-page", - "version": "0.31.0", + "version": "0.32.0", "main": "index.js", "private": true, "dependencies": { "@aws-sdk/client-cloudfront": "^3.414.0", "@aws-sdk/client-s3": "^3.417.0", + "@glennsl/rescript-fetch": "^0.2.0", "@kount/kount-web-client-sdk": "^1.1.6", "@rescript/core": "^0.7.0", "@rescript/react": "^0.11.0", "@ryyppy/rescript-promise": "^2.1.0", "@sentry/react": "^7.64.0", "@sentry/webpack-plugin": "^2.7.0", - "bs-fetch": "^0.6.2", "fast-glob": "^3.3.2", "react": "^18.2.0", "react-dom": "^18.2.0", "recoil": "^0.1.2", - "rescript-webapi": "^0.7.0", "webpack-merge": "^5.9.0" }, "scripts": { diff --git a/src/BrutalTheme.res b/src/BrutalTheme.res index 723960679..c13da8e99 100644 --- a/src/BrutalTheme.res +++ b/src/BrutalTheme.res @@ -40,7 +40,7 @@ let brutal = { spacingGridRow: "20px", buttonBackgroundColor: "#f5fb1f", buttonHeight: "48px", - buttonWidth: "thin", + buttonWidth: "100%", buttonBorderRadius: "6px", buttonBorderColor: "#566186", buttonTextColor: "#000000", diff --git a/src/CardTheme.res b/src/CardTheme.res index 06f50e513..005251657 100644 --- a/src/CardTheme.res +++ b/src/CardTheme.res @@ -255,34 +255,34 @@ let getVariables = (str, dict, default, logger) => { buttonBackgroundColor: getWarningString( json, "buttonBackgroundColor", - default.spacingGridRow, + default.buttonBackgroundColor, ~logger, ), - buttonHeight: getWarningString(json, "buttonHeight", default.spacingGridRow, ~logger), - buttonWidth: getWarningString(json, "buttonWidth", default.spacingGridRow, ~logger), + buttonHeight: getWarningString(json, "buttonHeight", default.buttonHeight, ~logger), + buttonWidth: getWarningString(json, "buttonWidth", default.buttonWidth, ~logger), buttonBorderRadius: getWarningString( json, "buttonBorderRadius", - default.spacingGridRow, + default.buttonBorderRadius, ~logger, ), buttonBorderColor: getWarningString( json, "buttonBorderColor", - default.spacingGridRow, + default.buttonBorderColor, ~logger, ), - buttonTextColor: getWarningString(json, "buttonTextColor", default.spacingGridRow, ~logger), + buttonTextColor: getWarningString(json, "buttonTextColor", default.buttonTextColor, ~logger), buttonTextFontSize: getWarningString( json, "buttonTextFontSize", - default.spacingGridRow, + default.buttonTextFontSize, ~logger, ), buttonTextFontWeight: getWarningString( json, "buttonTextFontWeight", - default.spacingGridRow, + default.buttonTextFontWeight, ~logger, ), } diff --git a/src/CharcoalTheme.res b/src/CharcoalTheme.res index d42347b9f..2711ef31e 100644 --- a/src/CharcoalTheme.res +++ b/src/CharcoalTheme.res @@ -40,7 +40,7 @@ let charcoal = { spacingGridRow: "20px", buttonBackgroundColor: "#000000", buttonHeight: "48px", - buttonWidth: "thin", + buttonWidth: "100%", buttonBorderRadius: "6px", buttonBorderColor: "#000000", buttonTextColor: "#ffffff", diff --git a/src/Components/DynamicFields.res b/src/Components/DynamicFields.res index 3a3909700..1a5da4ddd 100644 --- a/src/Components/DynamicFields.res +++ b/src/Components/DynamicFields.res @@ -21,16 +21,11 @@ let make = ( let {billingAddress} = Recoil.useRecoilValueFromAtom(optionAtom) //<...>// - let paymentMethodTypes = React.useMemo3(() => { - PaymentMethodsRecord.getPaymentMethodTypeFromList( - ~list, - ~paymentMethod, - ~paymentMethodType=PaymentUtils.getPaymentMethodName( - ~paymentMethodType=paymentMethod, - ~paymentMethodName=paymentMethodType, - ), - )->Option.getOr(PaymentMethodsRecord.defaultPaymentMethodType) - }, (list, paymentMethod, paymentMethodType)) + let paymentMethodTypes = DynamicFieldsUtils.usePaymentMethodTypeFromList( + ~list, + ~paymentMethod, + ~paymentMethodType, + ) let requiredFieldsWithBillingDetails = React.useMemo3(() => { if paymentMethod === "card" { diff --git a/src/Components/NicknamePaymentInput.res b/src/Components/NicknamePaymentInput.res new file mode 100644 index 000000000..941a26df7 --- /dev/null +++ b/src/Components/NicknamePaymentInput.res @@ -0,0 +1,19 @@ +@react.component +let make = (~paymentType: CardThemeType.mode, ~value, ~setValue) => { + let {config, localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom) + + let onChange = ev => { + let val = ReactEvent.Form.target(ev)["value"] + setValue(_ => val) + } + + +} diff --git a/src/Components/PayNowButton.res b/src/Components/PayNowButton.res index def4bbdfb..6de1aa890 100644 --- a/src/Components/PayNowButton.res +++ b/src/Components/PayNowButton.res @@ -19,6 +19,8 @@ let make = ( ~cardProps: CardUtils.cardProps, ~expiryProps: CardUtils.expiryProps, ~selectedOption: PaymentModeType.payment, + ~savedMethods: array, + ~paymentToken, ) => { open RecoilAtoms let {themeObj, localeString} = Recoil.useRecoilValueFromAtom(configAtom) @@ -26,14 +28,32 @@ let make = ( let (showLoader, setShowLoader) = React.useState(() => false) let areRequiredFieldsValidValue = Recoil.useRecoilValueFromAtom(areRequiredFieldsValid) let {sdkHandleConfirmPayment} = Recoil.useRecoilValueFromAtom(optionAtom) + let showFields = Recoil.useRecoilValueFromAtom(showCardFieldsAtom) - let (isCVCValid, _, _, _, _, _, _, _, _, _) = cvcProps + let (isCVCValid, _, cvcNumber, _, _, _, _, _, _, _) = cvcProps let (isCardValid, _, _, _, _, _, _, _, _, _) = cardProps let (isExpiryValid, _, _, _, _, _, _, _, _) = expiryProps + let (token, _) = paymentToken + let customerMethod = + savedMethods + ->Array.filter(savedMethod => { + savedMethod.paymentToken === token + }) + ->Array.get(0) + ->Option.getOr(PaymentType.defaultCustomerMethods) + let isCardPaymentMethod = customerMethod.paymentMethod === "card" + let complete = switch isCVCValid { + | Some(val) => token !== "" && val + | _ => false + } + let empty = cvcNumber == "" + let isSavedMethodCheck = + areRequiredFieldsValidValue && (!isCardPaymentMethod || (complete && !empty)) + let validFormat = - isCVCValid->Option.getOr(false) && isCardValid->Option.getOr(false) && + isCVCValid->Option.getOr(false) && isExpiryValid->Option.getOr(false) && areRequiredFieldsValidValue @@ -44,14 +64,21 @@ let make = ( setShowLoader(_ => true) Utils.handlePostMessage([("handleSdkConfirm", confirmPayload)]) } - React.useEffect3(() => { - if selectedOption === Card { - setIsDisabled(_ => !validFormat) + + let buttonText = sdkHandleConfirmPayment.buttonText->Option.getOr(localeString.payNowButton) + + React.useEffect4(() => { + if showFields { + if selectedOption === Card { + setIsDisabled(_ => !validFormat) + } else { + setIsDisabled(_ => !areRequiredFieldsValidValue) + } } else { - setIsDisabled(_ => !areRequiredFieldsValidValue) + setIsDisabled(_ => !isSavedMethodCheck) } None - }, (validFormat, areRequiredFieldsValidValue, selectedOption)) + }, (validFormat, areRequiredFieldsValidValue, selectedOption, isSavedMethodCheck))
diff --git a/src/Components/SavedCardItem.res b/src/Components/SavedCardItem.res index 318bdb690..eb5b237b1 100644 --- a/src/Components/SavedCardItem.res +++ b/src/Components/SavedCardItem.res @@ -48,6 +48,7 @@ let make = ( }, [isActive]) let isCard = paymentItem.paymentMethod === "card" + let isRenderCvv = isCard && paymentItem.requiresCvv let paymentMethodType = switch paymentItem.paymentMethodType { | Some(paymentMethodType) => paymentMethodType->Utils.snakeToTitleCase @@ -90,15 +91,15 @@ let make = ( border="1px solid currentColor" />
-
- brandIcon -
-
+
brandIcon
+
{isCard - ?
-
{React.string(`****`)}
-
{React.string({paymentItem.card.last4Digits})}
+ ?
+
{React.string(paymentItem.card.nickname)}
+
+
{React.string(`****`)}
+
{React.string(paymentItem.card.last4Digits)}
+
:
{React.string(paymentMethodType)}
} @@ -125,7 +126,7 @@ let make = (
- +
diff --git a/src/Components/SavedMethods.res b/src/Components/SavedMethods.res index 550c9483e..556844a65 100644 --- a/src/Components/SavedMethods.res +++ b/src/Components/SavedMethods.res @@ -83,8 +83,16 @@ let make = ( ->Array.get(0) ->Option.getOr(PaymentType.defaultCustomerMethods) let isCardPaymentMethod = customerMethod.paymentMethod === "card" + let isCardPaymentMethodValid = !customerMethod.requiresCvv || (complete && !empty) + let savedPaymentMethodBody = switch customerMethod.paymentMethod { - | "card" => PaymentBody.savedCardBody(~paymentToken=token, ~customerId, ~cvcNumber) + | "card" => + PaymentBody.savedCardBody( + ~paymentToken=token, + ~customerId, + ~cvcNumber, + ~requiresCvv=customerMethod.requiresCvv, + ) | _ => { let paymentMethodType = switch customerMethod.paymentMethodType { | Some("") @@ -99,8 +107,9 @@ let make = ( ) } } + if confirm.doSubmit { - if areRequiredFieldsValid && (!isCardPaymentMethod || (complete && !empty)) { + if areRequiredFieldsValid && (!isCardPaymentMethod || isCardPaymentMethodValid) { intent( ~bodyArr=savedPaymentMethodBody ->Dict.fromArray diff --git a/src/DefaultTheme.res b/src/DefaultTheme.res index 9077766f3..99dd6bc17 100644 --- a/src/DefaultTheme.res +++ b/src/DefaultTheme.res @@ -40,7 +40,7 @@ let default = { spacingGridRow: "20px", buttonBackgroundColor: "#006df9", buttonHeight: "48px", - buttonWidth: "thin", + buttonWidth: "100%", buttonBorderRadius: "6px", buttonBorderColor: "#ffffff", buttonTextColor: "#ffffff", diff --git a/src/Hooks/Fetcher.res b/src/Hooks/Fetcher.res index 3d2ee2e7c..b6707acdd 100644 --- a/src/Hooks/Fetcher.res +++ b/src/Hooks/Fetcher.res @@ -9,7 +9,7 @@ let useFetcher = fileName => { let (optionalJson, setJson) = React.useState(() => None) React.useEffect1(() => { open Promise - Fetch.fetch(`${hostname}/json/${fileName}.json`) + Fetch.get(`${hostname}/json/${fileName}.json`) ->then(Fetch.Response.json) ->thenResolve(json => { setJson(_ => Some(json)) diff --git a/src/Hooks/OutsideClick.res b/src/Hooks/OutsideClick.res index c24ac04e4..03ccb298a 100644 --- a/src/Hooks/OutsideClick.res +++ b/src/Hooks/OutsideClick.res @@ -1,5 +1,6 @@ -external ffToDomType: Dom.eventTarget => Dom.node_like<'a> = "%identity" -external ffToWebDom: Nullable.t => Nullable.t = "%identity" +external ffToDomType: {..} => Dom.node_like<'a> = "%identity" +@send external contains: (Dom.element, {..}) => bool = "contains" + type ref = | ArrayOfRef(array>>) | RefArray(React.ref>>) @@ -27,14 +28,14 @@ let useOutsideClick = ( let eventCallback = useEvent0(callback) React.useEffect1(() => { if isActive { - let handleClick = (e: Dom.event) => { - let targ = Webapi.Dom.Event.target(e) + let handleClick = (e: ReactEvent.Mouse.t) => { + let targ = e->ReactEvent.Mouse.target let isInsideClick = switch refs { | ArrayOfRef(refs) => refs->Array.reduce(false, (acc, ref: React.ref>) => { - let isClickInsideRef = switch ffToWebDom(ref.current)->Nullable.toOption { - | Some(element) => element->Webapi.Dom.Element.contains(~child=ffToDomType(targ)) + let isClickInsideRef = switch ref.current->Nullable.toOption { + | Some(element) => element->contains(targ) | None => false } acc || isClickInsideRef @@ -43,8 +44,8 @@ let useOutsideClick = ( refs.current ->Array.slice(~start=0, ~end=-1) ->Array.reduce(false, (acc, ref: Nullable.t) => { - let isClickInsideRef = switch ffToWebDom(ref)->Nullable.toOption { - | Some(element) => element->Webapi.Dom.Element.contains(~child=ffToDomType(targ)) + let isClickInsideRef = switch ref->Nullable.toOption { + | Some(element) => element->contains(targ) | None => false } acc || isClickInsideRef @@ -53,8 +54,8 @@ let useOutsideClick = ( let isClickInsideOfContainer = switch containerRefs { | Some(ref) => - switch ffToWebDom(ref.current)->Nullable.toOption { - | Some(element) => element->Webapi.Dom.Element.contains(~child=ffToDomType(targ)) + switch ref.current->Nullable.toOption { + | Some(element) => element->contains(targ) | None => false } | None => true @@ -68,16 +69,16 @@ let useOutsideClick = ( setTimeout(() => { events->Array.forEach( event => { - Webapi.Dom.window->Webapi.Dom.Window.addEventListener(event, handleClick) + Window.addEventListener(event, handleClick) }, ) }, 50)->ignore Some( () => { - events->Array.forEach(event => - Webapi.Dom.window->Webapi.Dom.Window.removeEventListener(event, handleClick) - ) + events->Array.forEach(event => { + Window.removeEventListener(event, handleClick) + }) }, ) } else { diff --git a/src/LocaleString.res b/src/LocaleString.res index 1c89b2324..26de81dfc 100644 --- a/src/LocaleString.res +++ b/src/LocaleString.res @@ -67,6 +67,8 @@ type localeStrings = { useExistingPaymentMethods: string, selectPaymentMethodLabel: string, savedPaymentMethodsLabel: string, + nicknameLabel: string, + nicknamePlaceholder: string, } let defaultLocale = { @@ -149,6 +151,8 @@ let defaultLocale = { useExistingPaymentMethods: "Use saved payment methods", selectPaymentMethodLabel: "Select Payment Method", savedPaymentMethodsLabel: "Saved Payment Methods", + nicknameLabel: "Card Nickname", + nicknamePlaceholder: "Card Nickname (Optional)", } type locale = {localeStrings: array} @@ -233,6 +237,8 @@ let localeStrings = [ useExistingPaymentMethods: "Use saved payment methods", selectPaymentMethodLabel: "Select Payment Method", savedPaymentMethodsLabel: "Saved Payment Methods", + nicknameLabel: "Card Nickname", + nicknamePlaceholder: "Card Nickname (Optional)", }, { locale: "he", @@ -314,6 +320,8 @@ let localeStrings = [ useExistingPaymentMethods: `השתמש באמצעי תשלום שמורים`, selectPaymentMethodLabel: `בחר שיטת תשלום`, savedPaymentMethodsLabel: `אמצעי תשלום שמורים`, + nicknameLabel: `כינוי לכרטיס`, + nicknamePlaceholder: `כינוי לכרטיס (אופציונלי)`, }, { locale: `fr`, @@ -395,6 +403,8 @@ let localeStrings = [ useExistingPaymentMethods: `Utiliser les modes de paiement enregistrés`, selectPaymentMethodLabel: `Sélectionnez le mode de paiement`, savedPaymentMethodsLabel: `Modes de paiement enregistrés`, + nicknameLabel: `Pseudonyme de la carte`, + nicknamePlaceholder: `Surnom de la carte (facultatif)`, }, { locale: "en-GB", @@ -476,6 +486,8 @@ let localeStrings = [ useExistingPaymentMethods: "Use saved payment methods", selectPaymentMethodLabel: "Select Payment Method", savedPaymentMethodsLabel: "Saved Payment Methods", + nicknameLabel: "Card Nickname", + nicknamePlaceholder: "Card Nickname (Optional)", }, { locale: "ar", @@ -557,6 +569,8 @@ let localeStrings = [ useExistingPaymentMethods: `استخدم طرق الدفع المحفوظة`, selectPaymentMethodLabel: `اختار طريقة الدفع`, savedPaymentMethodsLabel: `طرق الدفع المحفوظة`, + nicknameLabel: `الاسم علي الكارت`, + nicknamePlaceholder: `اسم البطاقة (اختياري)`, }, { locale: "ja", @@ -638,6 +652,8 @@ let localeStrings = [ useExistingPaymentMethods: `保存した支払い方法を使用する`, selectPaymentMethodLabel: `支払い方法を選択してください`, savedPaymentMethodsLabel: `保存された支払い方法`, + nicknameLabel: `カードのニックネーム`, + nicknamePlaceholder: `カードニックネーム(任意)`, }, { locale: "de", @@ -719,5 +735,7 @@ let localeStrings = [ useExistingPaymentMethods: `Gespeicherte Zahlungsarten nutzen`, selectPaymentMethodLabel: `Wählen Sie die Zahlungsmethode`, savedPaymentMethodsLabel: `Gespeicherte Zahlungsarten`, + nicknameLabel: `Spitzname der Karte`, + nicknamePlaceholder: `Kartenname (optional)`, }, ] diff --git a/src/MidnightTheme.res b/src/MidnightTheme.res index b46337934..4b6c1201c 100644 --- a/src/MidnightTheme.res +++ b/src/MidnightTheme.res @@ -40,7 +40,7 @@ let midnight = { spacingGridRow: "20px", buttonBackgroundColor: "#85d996", buttonHeight: "48px", - buttonWidth: "thin", + buttonWidth: "100%", buttonBorderRadius: "6px", buttonBorderColor: "#85d996", buttonTextColor: "#000000", diff --git a/src/NoTheme.res b/src/NoTheme.res index 71d873b6d..cc543b9c8 100644 --- a/src/NoTheme.res +++ b/src/NoTheme.res @@ -41,7 +41,7 @@ let nakedValues = { spacingGridRow: "20px", buttonBackgroundColor: "", buttonHeight: "48px", - buttonWidth: "thin", + buttonWidth: "100%", buttonBorderRadius: "6px", buttonBorderColor: "", buttonTextColor: "", diff --git a/src/Payment.res b/src/Payment.res index 35df08639..4c4d75f2a 100644 --- a/src/Payment.res +++ b/src/Payment.res @@ -127,6 +127,12 @@ let make = (~paymentMode, ~integrateError, ~logger) => { if cvc->String.length > 0 && cvcNumberInRange(cvc, cardBrand)->Array.includes(true) { zipRef.current->Nullable.toOption->Option.forEach(input => input->focus)->ignore } + + if cvc->Js.String2.length > 0 && cvcNumberInRange(cvc, cardBrand)->Js.Array2.includes(true) { + setIsCVCValid(_ => Some(true)) + } else { + setIsCVCValid(_ => None) + } } let changeZipCode = ev => { @@ -245,6 +251,7 @@ let make = (~paymentMode, ~integrateError, ~logger) => { ~cardHolderName="", ~cvcNumber, ~cardBrand=cardNetwork, + (), ) | CardNumberElement => let (month, year) = getExpiryDates(getCardElementValue(iframeId, "card-expiry")) @@ -256,6 +263,7 @@ let make = (~paymentMode, ~integrateError, ~logger) => { ~cardHolderName="", ~cvcNumber=localCvcNumber, ~cardBrand=cardNetwork, + (), ) | _ => [] } diff --git a/src/PaymentElement.res b/src/PaymentElement.res index f6a9fef65..ff6870fdd 100644 --- a/src/PaymentElement.res +++ b/src/PaymentElement.res @@ -21,7 +21,7 @@ let make = ( paymentMethodOrder, layout, customerPaymentMethods, - displaySavedPaymentMethodsCheckbox, + displaySavedPaymentMethods, } = Recoil.useRecoilValueFromAtom(optionAtom) let isApplePayReady = Recoil.useRecoilValueFromAtom(isApplePayReady) let isGooglePayReady = Recoil.useRecoilValueFromAtom(isGooglePayReady) @@ -49,34 +49,43 @@ let make = ( setLoadSavedCards: (PaymentType.savedCardsLoadState => PaymentType.savedCardsLoadState) => unit, ) = React.useState(_ => PaymentType.LoadingSavedCards) - React.useEffect1(() => { - switch customerPaymentMethods { - | LoadingSavedCards => () - | LoadedSavedCards(savedCards, isGuestCustomer) => { - setSavedMethods(_ => savedCards) + React.useEffect2(() => { + switch (displaySavedPaymentMethods, customerPaymentMethods) { + | (false, _) => { + setShowFields(._ => true) + setLoadSavedCards(_ => LoadedSavedCards([], true)) + } + | (_, LoadingSavedCards) => () + | (_, LoadedSavedCards(savedPaymentMethods, isGuestCustomer)) => { + let defaultPaymentMethod = + savedPaymentMethods->Array.find(savedCard => savedCard.defaultPaymentMethodSet) + + let savedCardsWithoutDefaultPaymentMethod = savedPaymentMethods->Array.filter(savedCard => { + !savedCard.defaultPaymentMethodSet + }) + + let finalSavedPaymentMethods = switch defaultPaymentMethod { + | Some(defaultPaymentMethod) => + [defaultPaymentMethod]->Array.concat(savedCardsWithoutDefaultPaymentMethod) + | None => savedCardsWithoutDefaultPaymentMethod + } + + setSavedMethods(_ => finalSavedPaymentMethods) setLoadSavedCards(_ => - savedCards->Array.length == 0 + finalSavedPaymentMethods->Array.length == 0 ? NoResult(isGuestCustomer) - : LoadedSavedCards(savedCards, isGuestCustomer) + : LoadedSavedCards(finalSavedPaymentMethods, isGuestCustomer) ) - setShowFields(.prev => savedCards->Array.length == 0 || prev) + setShowFields(.prev => finalSavedPaymentMethods->Array.length == 0 || prev) } - | NoResult(isGuestCustomer) => { + | (_, NoResult(isGuestCustomer)) => { setLoadSavedCards(_ => NoResult(isGuestCustomer)) setShowFields(._ => true) } } None - }, [customerPaymentMethods]) - - React.useEffect1(() => { - if displaySavedPaymentMethodsCheckbox { - setShowFields(._ => true) - setLoadSavedCards(_ => LoadedSavedCards([], true)) - } - None - }, [displaySavedPaymentMethodsCheckbox]) + }, (customerPaymentMethods, displaySavedPaymentMethods)) React.useEffect1(() => { let defaultPaymentMethod = @@ -97,7 +106,19 @@ let make = ( None }, [savedMethods]) - let (walletList, paymentOptionsList, actualList) = React.useMemo4(() => { + let areAllGooglePayRequiredFieldsPrefilled = DynamicFieldsUtils.useAreAllRequiredFieldsPrefilled( + ~list, + ~paymentMethod="wallet", + ~paymentMethodType="google_pay", + ) + + let areAllApplePayRequiredFieldsPrefilled = DynamicFieldsUtils.useAreAllRequiredFieldsPrefilled( + ~list, + ~paymentMethod="wallet", + ~paymentMethodType="apple_pay", + ) + + let (walletList, paymentOptionsList, actualList) = React.useMemo6(() => { switch methodslist { | Loaded(paymentlist) => let paymentOrder = @@ -108,6 +129,8 @@ let make = ( ~order=paymentOrder, ~showApplePay=isApplePayReady, ~showGooglePay=isGooglePayReady, + ~areAllGooglePayRequiredFieldsPrefilled, + ~areAllApplePayRequiredFieldsPrefilled, ) ( wallets->Utils.removeDuplicate, @@ -120,7 +143,14 @@ let make = ( : ([], [], []) | _ => ([], [], []) } - }, (methodslist, paymentMethodOrder, isApplePayReady, isGooglePayReady)) + }, ( + methodslist, + paymentMethodOrder, + isApplePayReady, + isGooglePayReady, + areAllGooglePayRequiredFieldsPrefilled, + areAllApplePayRequiredFieldsPrefilled, + )) React.useEffect4(() => { switch methodslist { @@ -358,13 +388,20 @@ let make = ( } - let paymentLabel = showFields - ? localeString.selectPaymentMethodLabel - : localeString.savedPaymentMethodsLabel + React.useEffect1(() => { + setShowFields(._ => !displaySavedPaymentMethods) + None + }, [displaySavedPaymentMethods]) + + let paymentLabel = if displaySavedPaymentMethods { + showFields ? localeString.selectPaymentMethodLabel : localeString.savedPaymentMethodsLabel + } else { + localeString.selectPaymentMethodLabel + } <>
{React.string(paymentLabel)}
- + @@ -398,6 +435,8 @@ let make = ( cardProps expiryProps selectedOption={selectedOption->PaymentModeType.paymentMode} + savedMethods + paymentToken />
diff --git a/src/PaymentOptions.res b/src/PaymentOptions.res index 6d2f6ebbe..6f2d7ee07 100644 --- a/src/PaymentOptions.res +++ b/src/PaymentOptions.res @@ -59,7 +59,7 @@ let make = ( let (toggleIconElement, setToggleIconElement) = React.useState(_ => false) React.useEffect2(() => { let width = switch payOptionsRef.current->Nullable.toOption { - | Some(ref) => Webapi.Dom.Element.clientWidth(ref) + | Some(ref) => ref->Window.Element.clientWidth | None => 0 } setCardsContainerWidth(_ => width) @@ -85,7 +85,6 @@ let make = ( let intervalId = setInterval(() => { if dropDownOptionsDetails->Array.length > 1 { setMoreIconIndex(prev => mod(prev + 1, dropDownOptionsDetails->Array.length)) - setToggleIconElement(_ => true) setTimeout( () => { diff --git a/src/Payments/ApplePay.res b/src/Payments/ApplePay.res index 420cbea45..b3d98a886 100644 --- a/src/Payments/ApplePay.res +++ b/src/Payments/ApplePay.res @@ -73,7 +73,7 @@ let make = ( ) } else { let requiredFieldsBodyArr = - bodyArr + requestBody ->Dict.fromArray ->JSON.Encode.object ->OrcaUtils.flattenObject(true) @@ -281,7 +281,7 @@ let make = ( ->ignore } - React.useEffect3(() => { + React.useEffect4(() => { let handleApplePayMessages = (ev: Window.event) => { let json = try { ev.data->JSON.parseExn @@ -315,7 +315,7 @@ let make = ( Window.removeEventListener("message", handleApplePayMessages) }, ) - }, (isInvokeSDKFlow, requiredFieldsBody, isWallet)) + }, (isInvokeSDKFlow, requiredFieldsBody, isWallet, processPayment)) React.useEffect4(() => { if ( diff --git a/src/Payments/CardPayment.res b/src/Payments/CardPayment.res index 6ace6f0f6..7ee064e0b 100644 --- a/src/Payments/CardPayment.res +++ b/src/Payments/CardPayment.res @@ -16,6 +16,9 @@ let make = ( let {config, themeObj, localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom) let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom) let loggerState = Recoil.useRecoilValueFromAtom(RecoilAtoms.loggerAtom) + + let (nickname, setNickname) = React.useState(_ => "") + let ( isCardValid, setIsCardValid, @@ -100,7 +103,15 @@ let make = ( ~isCvcValidValue, ) - let submitCallback = React.useCallback5((ev: Window.event) => { + let isCustomerAcceptanceRequired = React.useMemo1(() => { + if displaySavedPaymentMethodsCheckbox { + isSaveCardsChecked || list.payment_type === SETUP_MANDATE + } else { + !(isGuestCustomer || list.payment_type === NORMAL) + } + }, [isSaveCardsChecked]) + + let submitCallback = React.useCallback6((ev: Window.event) => { let json = ev.data->JSON.parseExn let confirm = json->getDictFromJson->ConfirmType.itemToObjMapper let (month, year) = CardUtils.getExpiryDates(cardExpiry) @@ -120,16 +131,14 @@ let make = ( ~cardHolderName="", ~cvcNumber, ~cardBrand=cardNetwork, + ~nickname, + (), ) let banContactBody = PaymentBody.bancontactBody() - let cardBody = if displaySavedPaymentMethodsCheckbox { - if isSaveCardsChecked || list.payment_type === "setup_mandate" { - defaultCardBody->Array.concat(onSessionBody) - } else { - defaultCardBody - } - } else { + let cardBody = if isCustomerAcceptanceRequired { defaultCardBody->Array.concat(onSessionBody) + } else { + defaultCardBody } if confirm.doSubmit { let validFormat = @@ -169,17 +178,26 @@ let make = ( } } } - }, (areRequiredFieldsValid, requiredFieldsBody, empty, complete, isSaveCardsChecked)) + }, ( + areRequiredFieldsValid, + requiredFieldsBody, + empty, + complete, + isCustomerAcceptanceRequired, + nickname, + )) submitPaymentData(submitCallback) let paymentMethod = isBancontact ? "bank_redirect" : "card" let paymentMethodType = isBancontact ? "bancontact_card" : "debit" let conditionsForShowingSaveCardCheckbox = !isGuestCustomer && - list.payment_type !== "setup_mandate" && + list.payment_type !== SETUP_MANDATE && options.displaySavedPaymentMethodsCheckbox && !isBancontact + let nicknameFieldClassName = conditionsForShowingSaveCardCheckbox ? "pt-2" : "pt-5" +
- Array.length > 0 && !isBancontact}> + +
+ +
+
+ Array.length > 0 && + !isBancontact}>
, multi_use: option, } +type payment_type = NORMAL | NEW_MANDATE | SETUP_MANDATE | NONE type list = { redirect_url: string, currency: string, payment_methods: array, mandate_payment: option, - payment_type: string, + payment_type: payment_type, } open Utils @@ -731,7 +732,7 @@ let defaultList = { currency: "", payment_methods: [], mandate_payment: None, - payment_type: "", + payment_type: NONE, } let getMethod = str => { switch str { @@ -925,13 +926,31 @@ let getMandate = (dict, str) => { }) } +let paymentTypeMapper = payment_type => { + switch payment_type { + | "normal" => NORMAL + | "new_mandate" => NEW_MANDATE + | "setup_mandate" => SETUP_MANDATE + | _ => NONE + } +} + +let paymentTypeToStringMapper = payment_type => { + switch payment_type { + | NORMAL => "normal" + | NEW_MANDATE => "new_mandate" + | SETUP_MANDATE => "setup_mandate" + | NONE => "" + } +} + let itemToObjMapper = dict => { { redirect_url: getString(dict, "redirect_url", ""), currency: getString(dict, "currency", ""), payment_methods: getMethodsArr(dict, "payment_methods"), mandate_payment: getMandate(dict, "mandate_payment"), - payment_type: getString(dict, "payment_type", ""), + payment_type: getString(dict, "payment_type", "")->paymentTypeMapper, } } diff --git a/src/SoftTheme.res b/src/SoftTheme.res index 95d4b028d..8e5ffd925 100644 --- a/src/SoftTheme.res +++ b/src/SoftTheme.res @@ -41,7 +41,7 @@ let soft = { spacingGridRow: "20px", buttonBackgroundColor: "#3c3d3e", buttonHeight: "48px", - buttonWidth: "thin", + buttonWidth: "100%", buttonBorderRadius: "6px", buttonBorderColor: "#7d8fff", buttonTextColor: "#7d8fff", diff --git a/src/Types/PaymentType.res b/src/Types/PaymentType.res index 81cd88c5b..8be3f9969 100644 --- a/src/Types/PaymentType.res +++ b/src/Types/PaymentType.res @@ -114,6 +114,7 @@ type customerCard = { expiryYear: string, cardToken: string, cardHolderName: option, + nickname: string, } type customerMethods = { paymentToken: string, @@ -123,6 +124,7 @@ type customerMethods = { card: customerCard, paymentMethodType: option, defaultPaymentMethodSet: bool, + requiresCvv: bool, } type savedCardsLoadState = LoadingSavedCards | LoadedSavedCards(array, bool) | NoResult(bool) @@ -134,6 +136,7 @@ type billingAddress = { type sdkHandleConfirmPayment = { handleConfirm: bool, + buttonText?: string, confirmParams: ConfirmType.confirmParams, } @@ -163,6 +166,7 @@ let defaultCardDetails = { expiryYear: "", cardToken: "", cardHolderName: None, + nickname: "", } let defaultCustomerMethods = { paymentToken: "", @@ -172,6 +176,7 @@ let defaultCustomerMethods = { card: defaultCardDetails, paymentMethodType: None, defaultPaymentMethodSet: false, + requiresCvv: true, } let defaultLayout = { defaultCollapsed: false, @@ -779,6 +784,7 @@ let getCardDetails = (dict, str) => { expiryYear: getString(json, "expiry_year", ""), cardToken: getString(json, "card_token", ""), cardHolderName: Some(getString(json, "card_holder_name", "")), + nickname: getString(json, "nick_name", ""), } }) ->Option.getOr(defaultCardDetails) @@ -819,6 +825,7 @@ let createCustomerObjArr = dict => { card: getCardDetails(dict, "card"), paymentMethodType: getPaymentMethodType(dict), defaultPaymentMethodSet: getBool(dict, "default_payment_method_set", false), + requiresCvv: getBool(dict, "requires_cvv", true), } }) LoadedSavedCards(customerPaymentMethods, isGuestCustomer) @@ -840,6 +847,7 @@ let getCustomerMethods = (dict, str) => { card: getCardDetails(json, "card"), paymentMethodType: getPaymentMethodType(dict), defaultPaymentMethodSet: getBool(dict, "default_payment_method_set", false), + requiresCvv: getBool(dict, "requires_cvv", true), } }) LoadedSavedCards(customerPaymentMethods, false) @@ -895,6 +903,7 @@ let getConfirmParams = dict => { let getSdkHandleConfirmPaymentProps = dict => { handleConfirm: dict->getBool("handleConfirm", false), + buttonText: ?dict->getOptionString("buttonText"), confirmParams: dict->getDictfromDict("confirmParams")->getConfirmParams, } diff --git a/src/Utilities/DynamicFieldsUtils.res b/src/Utilities/DynamicFieldsUtils.res index ea40c55bd..ec2ee4594 100644 --- a/src/Utilities/DynamicFieldsUtils.res +++ b/src/Utilities/DynamicFieldsUtils.res @@ -693,3 +693,24 @@ let useSubmitCallback = () => { } }, (line1, line2, state, city, postalCode)) } + +let usePaymentMethodTypeFromList = (~list, ~paymentMethod, ~paymentMethodType) => { + React.useMemo3(() => { + PaymentMethodsRecord.getPaymentMethodTypeFromList( + ~list, + ~paymentMethod, + ~paymentMethodType=PaymentUtils.getPaymentMethodName( + ~paymentMethodType=paymentMethod, + ~paymentMethodName=paymentMethodType, + ), + )->Belt.Option.getWithDefault(PaymentMethodsRecord.defaultPaymentMethodType) + }, (list, paymentMethod, paymentMethodType)) +} + +let useAreAllRequiredFieldsPrefilled = (~list, ~paymentMethod, ~paymentMethodType) => { + let paymentMethodTypes = usePaymentMethodTypeFromList(~list, ~paymentMethod, ~paymentMethodType) + + paymentMethodTypes.required_fields->Js.Array2.reduce((acc, requiredField) => { + acc && requiredField.value != "" + }, true) +} diff --git a/src/Utilities/PaymentBody.res b/src/Utilities/PaymentBody.res index 5468c8970..44c429a09 100644 --- a/src/Utilities/PaymentBody.res +++ b/src/Utilities/PaymentBody.res @@ -1,29 +1,38 @@ @val @scope("window") external btoa: string => string = "btoa" -let cardPaymentBody = (~cardNumber, ~month, ~year, ~cardHolderName, ~cvcNumber, ~cardBrand) => [ - ("payment_method", "card"->JSON.Encode.string), - ( - "payment_method_data", - [ - ( - "card", - [ - ("card_number", cardNumber->CardUtils.clearSpaces->JSON.Encode.string), - ("card_exp_month", month->JSON.Encode.string), - ("card_exp_year", year->JSON.Encode.string), - ("card_holder_name", cardHolderName->JSON.Encode.string), - ("card_cvc", cvcNumber->JSON.Encode.string), - ("card_issuer", ""->JSON.Encode.string), - ] - ->Array.concat(cardBrand) - ->Dict.fromArray - ->JSON.Encode.object, - ), - ] - ->Dict.fromArray - ->JSON.Encode.object, - ), -] +let cardPaymentBody = ( + ~cardNumber, + ~month, + ~year, + ~cardHolderName, + ~cvcNumber, + ~cardBrand, + ~nickname="", + (), +) => { + let cardBody = [ + ("card_number", cardNumber->CardUtils.clearSpaces->JSON.Encode.string), + ("card_exp_month", month->JSON.Encode.string), + ("card_exp_year", year->JSON.Encode.string), + ("card_holder_name", cardHolderName->JSON.Encode.string), + ("card_cvc", cvcNumber->JSON.Encode.string), + ("card_issuer", ""->JSON.Encode.string), + ] + + if nickname != "" { + cardBody->Array.push(("nick_name", nickname->JSON.Encode.string))->ignore + } + + [ + ("payment_method", "card"->JSON.Encode.string), + ( + "payment_method_data", + [("card", cardBody->Array.concat(cardBrand)->Dict.fromArray->JSON.Encode.object)] + ->Dict.fromArray + ->JSON.Encode.object, + ), + ] +} let bancontactBody = () => [ ("payment_method", "bank_redirect"->JSON.Encode.string), @@ -55,12 +64,19 @@ let boletoBody = (~socialSecurityNumber) => [ ), ] -let savedCardBody = (~paymentToken, ~customerId, ~cvcNumber) => [ - ("payment_method", "card"->JSON.Encode.string), - ("payment_token", paymentToken->JSON.Encode.string), - ("customer_id", customerId->JSON.Encode.string), - ("card_cvc", cvcNumber->JSON.Encode.string), -] +let savedCardBody = (~paymentToken, ~customerId, ~cvcNumber, ~requiresCvv) => { + let savedCardBody = [ + ("payment_method", "card"->JSON.Encode.string), + ("payment_token", paymentToken->JSON.Encode.string), + ("customer_id", customerId->JSON.Encode.string), + ] + + if requiresCvv { + savedCardBody->Array.push(("card_cvc", cvcNumber->JSON.Encode.string))->ignore + } + + savedCardBody +} let customerAcceptanceBody = [ @@ -171,7 +187,7 @@ let achBankDebitBody = ( ->Dict.fromArray ->JSON.Encode.object, ), - ]->Array.concat(mandateBody(paymentType)) + ]->Array.concat(mandateBody(paymentType->PaymentMethodsRecord.paymentTypeToStringMapper)) let sepaBankDebitBody = ( ~fullName, diff --git a/src/Utilities/PaymentHelpers.res b/src/Utilities/PaymentHelpers.res index 939ad46af..4a6b8cadf 100644 --- a/src/Utilities/PaymentHelpers.res +++ b/src/Utilities/PaymentHelpers.res @@ -29,7 +29,7 @@ let retrievePaymentIntent = (clientSecret, headers, ~optLogger, ~switchToCustomP ) fetchApi( uri, - ~method_=Fetch.Get, + ~method=#GET, ~headers=headers->ApiEndpoint.addCustomPodHeader(~switchToCustomPod, ()), (), ) @@ -98,9 +98,9 @@ let rec intentCall = ( string, ~bodyStr: string=?, ~headers: Dict.t=?, - ~method_: Fetch.requestMethod, + ~method: Fetch.method, unit, - ) => OrcaPaymentPage.Promise.t, + ) => OrcaPaymentPage.Promise.t, ~uri, ~headers, ~bodyStr, @@ -135,14 +135,14 @@ let rec intentCall = ( ) let handleOpenUrl = url => { if isPaymentSession { - Window.location.replace(. url) + Window.Location.replace(. url) } else { openUrl(url) } } fetchApi( uri, - ~method_=fetchMethod, + ~method=fetchMethod, ~headers=headers->ApiEndpoint.addCustomPodHeader(~switchToCustomPod, ()), ~bodyStr, (), @@ -257,7 +257,7 @@ let rec intentCall = ( ~handleUserError, ~paymentType, ~iframeId, - ~fetchMethod=Get, + ~fetchMethod=#GET, ~setIsManualRetryEnabled, ~switchToCustomPod, ~sdkHandleOneClickConfirmPayment, @@ -532,7 +532,7 @@ let rec intentCall = ( ~handleUserError, ~paymentType, ~iframeId, - ~fetchMethod=Get, + ~fetchMethod=#GET, ~setIsManualRetryEnabled, ~switchToCustomPod, ~sdkHandleOneClickConfirmPayment, @@ -577,7 +577,7 @@ let usePaymentSync = (optLogger: option, paymentType: pay ~handleUserError, ~paymentType, ~iframeId, - ~fetchMethod=Get, + ~fetchMethod=#GET, ~setIsManualRetryEnabled, ~switchToCustomPod, ~sdkHandleOneClickConfirmPayment=keys.sdkHandleOneClickConfirmPayment, @@ -598,36 +598,26 @@ let usePaymentSync = (optLogger: option, paymentType: pay } } -let rec maskPayload = payloadDict => { - let keys = payloadDict->Dict.keysToArray - let maskedPayload = Dict.make() - keys - ->Array.map(key => { - let value = payloadDict->Dict.get(key)->Option.getOr(JSON.Encode.null) - if value->JSON.Decode.array->Option.isSome { - let arr = value->JSON.Decode.array->Option.getOr([]) - arr->Array.forEachWithIndex((element, index) => { - maskedPayload->Dict.set( - key ++ "[" ++ index->Belt.Int.toString ++ "]", - element->Utils.getDictFromJson->maskPayload->JSON.Encode.string, - ) - }) - } else if value->JSON.Decode.object->Option.isSome { - let valueDict = value->Utils.getDictFromJson - maskedPayload->Dict.set(key, valueDict->maskPayload->JSON.Encode.string) - } else { - maskedPayload->Dict.set( - key, - value - ->JSON.Decode.string - ->Option.getOr("") - ->String.replaceRegExp(%re(`/\S/g`), "x") - ->JSON.Encode.string, - ) - } - }) - ->ignore - maskedPayload->JSON.Encode.object->JSON.stringify +let maskStr = str => str->Js.String2.replaceByRe(%re(`/\S/g`), "x") + +let rec maskPayload = payloadJson => { + switch payloadJson->JSON.Classify.classify { + | Object(valueDict) => + valueDict + ->Dict.toArray + ->Array.map(entry => { + let (key, value) = entry + (key, maskPayload(value)) + }) + ->Dict.fromArray + ->JSON.Encode.object + + | Array(arr) => arr->Array.map(maskPayload)->JSON.Encode.array + | String(valueStr) => valueStr->maskStr->JSON.Encode.string + | Number(float) => Float.toString(float)->maskStr->JSON.Encode.string + | Bool(bool) => (bool ? "true" : "false")->JSON.Encode.string + | Null => JSON.Encode.string("null") + } } let usePaymentIntent = (optLogger: option, paymentType: payment) => { @@ -664,15 +654,10 @@ let usePaymentIntent = (optLogger: option, paymentType: p (), ) let uri = `${endpoint}/payments/${paymentIntentID}/confirm` - let fetchMethod = Fetch.Post let callIntent = body => { let maskedPayload = - body - ->OrcaUtils.safeParseOpt - ->Option.getOr(JSON.Encode.null) - ->Utils.getDictFromJson - ->maskPayload + body->OrcaUtils.safeParseOpt->Option.getOr(JSON.Encode.null)->maskPayload->JSON.stringify let loggerPayload = [ ("payload", maskedPayload->JSON.Encode.string), @@ -729,7 +714,7 @@ let usePaymentIntent = (optLogger: option, paymentType: p ~handleUserError, ~paymentType, ~iframeId, - ~fetchMethod, + ~fetchMethod=#POST, ~setIsManualRetryEnabled, ~switchToCustomPod, ~sdkHandleOneClickConfirmPayment=keys.sdkHandleOneClickConfirmPayment, @@ -753,7 +738,12 @@ let usePaymentIntent = (optLogger: option, paymentType: p let bodyStr = body ->Array.concat( - bodyArr->Array.concatMany([PaymentBody.mandateBody(mandatePaymentType), broswerInfo()]), + bodyArr->Array.concatMany([ + PaymentBody.mandateBody( + mandatePaymentType->PaymentMethodsRecord.paymentTypeToStringMapper, + ), + broswerInfo(), + ]), ) ->Dict.fromArray ->JSON.Encode.object @@ -824,7 +814,7 @@ let useSessions = ( ) fetchApi( uri, - ~method_=Fetch.Post, + ~method=#POST, ~bodyStr=body->JSON.stringify, ~headers=headers->ApiEndpoint.addCustomPodHeader(~switchToCustomPod, ()), (), @@ -899,7 +889,7 @@ let usePaymentMethodList = ( ) fetchApi( uri, - ~method_=Fetch.Get, + ~method=#GET, ~headers=headers->ApiEndpoint.addCustomPodHeader(~switchToCustomPod, ()), (), ) @@ -973,7 +963,7 @@ let useCustomerDetails = ( ) fetchApi( uri, - ~method_=Fetch.Get, + ~method=#GET, ~headers=headers->ApiEndpoint.addCustomPodHeader(~switchToCustomPod, ()), (), ) diff --git a/src/Utilities/PaymentUtils.res b/src/Utilities/PaymentUtils.res index 2d0fe5d2e..c99bd8398 100644 --- a/src/Utilities/PaymentUtils.res +++ b/src/Utilities/PaymentUtils.res @@ -3,6 +3,8 @@ let paymentListLookupNew = ( ~order, ~showApplePay, ~showGooglePay, + ~areAllGooglePayRequiredFieldsPrefilled, + ~areAllApplePayRequiredFieldsPrefilled, ) => { let pmList = list->PaymentMethodsRecord.buildFromPaymentList let walletsList = [] @@ -26,14 +28,14 @@ let paymentListLookupNew = ( let applePayFields = pmList->Array.find(item => item.paymentMethodName === "apple_pay") switch googlePayFields { | Some(val) => - if val.fields->Array.length > 0 && showGooglePay { + if val.fields->Array.length > 0 && showGooglePay && !areAllGooglePayRequiredFieldsPrefilled { walletToBeDisplayedInTabs->Array.push("google_pay")->ignore } | None => () } switch applePayFields { | Some(val) => - if val.fields->Array.length > 0 && showApplePay { + if val.fields->Array.length > 0 && showApplePay && !areAllApplePayRequiredFieldsPrefilled { walletToBeDisplayedInTabs->Array.push("apple_pay")->ignore } | None => () @@ -223,8 +225,11 @@ let getPaymentMethodName = (~paymentMethodType, ~paymentMethodName) => { } } -let isAppendingCustomerAcceptance = (~isGuestCustomer, ~paymentType) => { - !isGuestCustomer && (paymentType === "new_mandate" || paymentType === "setup_mandate") +let isAppendingCustomerAcceptance = ( + ~isGuestCustomer, + ~paymentType: PaymentMethodsRecord.payment_type, +) => { + !isGuestCustomer && (paymentType === NEW_MANDATE || paymentType === SETUP_MANDATE) } let appendedCustomerAcceptance = (~isGuestCustomer, ~paymentType, ~body) => { diff --git a/src/Utilities/Utils.res b/src/Utilities/Utils.res index 7db1cc6f7..256d83216 100644 --- a/src/Utilities/Utils.res +++ b/src/Utilities/Utils.res @@ -810,25 +810,23 @@ let getHeaders = (~uri=?, ~token=?, ~headers=Dict.make(), ()) => { let (x, val) = entries Dict.set(headerObj, x, val) }) - Fetch.HeadersInit.make(headerObj->dictToObj) -} -let fetchApi = ( - uri, - ~bodyStr: string="", - ~headers=Dict.make(), - ~method_: Fetch.requestMethod, - (), -) => { + Fetch.Headers.fromObject(headerObj->dictToObj) +} +let fetchApi = (uri, ~bodyStr: string="", ~headers=Js.Dict.empty(), ~method: Fetch.method, ()) => { open Promise - let body = switch method_ { - | Get => resolve(None) - | _ => resolve(Some(Fetch.BodyInit.make(bodyStr))) + let body = switch method { + | #GET => resolve(None) + | _ => resolve(Some(Fetch.Body.string(bodyStr))) } body->then(body => { - Fetch.fetchWithInit( + Fetch.fetch( uri, - Fetch.RequestInit.make(~method_, ~body?, ~headers=getHeaders(~headers, ~uri, ()), ()), + { + method, + ?body, + headers: getHeaders(~headers, ~uri, ()), + }, ) ->catch(err => { reject(err) diff --git a/src/Window.res b/src/Window.res index e7454b182..8568c1364 100644 --- a/src/Window.res +++ b/src/Window.res @@ -126,5 +126,10 @@ let isInteg = hostname === "dev.hyperswitch.io" let isProd = hostname === "checkout.hyperswitch.io" -type location = {replace: (. string) => unit} -@val @scope("window") external location: location = "location" +module Location = { + @val @scope(("window", "location")) external replace: (. string) => unit = "replace" +} + +module Element = { + @get external clientWidth: Dom.element => int = "clientWidth" +} diff --git a/src/orca-loader/Elements.res b/src/orca-loader/Elements.res index d1fe7d44e..de0df844c 100644 --- a/src/orca-loader/Elements.res +++ b/src/orca-loader/Elements.res @@ -119,7 +119,7 @@ let make = ( // setTimeout(() => { let msg = [("paymentMethodList", json)]->Dict.fromArray mountedIframeRef->Window.iframePostMessage(msg) - let maskedPayload = json->getDictFromJson->PaymentHelpers.maskPayload + let maskedPayload = json->PaymentHelpers.maskPayload->JSON.stringify logger.setLogInfo(~value=maskedPayload, ~eventName=PAYMENT_METHODS_RESPONSE, ()) // }, 5000)->ignore json->resolve @@ -217,7 +217,6 @@ let make = ( mountedIframeRef, selectorString, sdkHandleOneClickConfirmPayment, - displaySavedPaymentMethods, ) => { open Promise @@ -707,9 +706,7 @@ let make = ( }) ->ignore fetchPaymentsList(mountedIframeRef) - if displaySavedPaymentMethods { - fetchCustomerDetails(mountedIframeRef) - } + fetchCustomerDetails(mountedIframeRef) mountedIframeRef->Window.iframePostMessage(message) } diff --git a/src/orca-loader/Hyper.res b/src/orca-loader/Hyper.res index a75ffdc9b..8b93ab9b2 100644 --- a/src/orca-loader/Hyper.res +++ b/src/orca-loader/Hyper.res @@ -196,9 +196,12 @@ let make = (publishableKey, options: option, analyticsInfo: optionthen(resp => { let statusCode = resp->Fetch.Response.status->Int.toString diff --git a/src/orca-loader/LoaderPaymentElement.res b/src/orca-loader/LoaderPaymentElement.res index 18f9350bd..bb50aa838 100644 --- a/src/orca-loader/LoaderPaymentElement.res +++ b/src/orca-loader/LoaderPaymentElement.res @@ -22,12 +22,6 @@ let make = (componentType, options, setIframeRef, iframeRef, mountPostMessage) = true, ) - let displaySavedPaymentMethods = - options->getDecodedBoolFromJson( - callbackFuncForExtractingValFromDict("displaySavedPaymentMethods"), - true, - ) - let on = (eventType, eventHandler) => { switch eventType->eventTypeMapper { | Escape => @@ -184,7 +178,7 @@ let make = (componentType, options, setIframeRef, iframeRef, mountPostMessage) = switch eventDataObject->getOptionalJsonFromJson("openurl") { | Some(val) => { let url = val->getStringfromjson("") - Window.location.replace(. url) + Window.Location.replace(. url) } | None => () } @@ -295,7 +289,6 @@ let make = (componentType, options, setIframeRef, iframeRef, mountPostMessage) = Window.querySelector(`#orca-payment-element-iframeRef-${localSelectorString}`), localSelectorString, sdkHandleOneClickConfirmPayment, - displaySavedPaymentMethods, ) } } diff --git a/src/orca-loader/PaymentSessionMethods.res b/src/orca-loader/PaymentSessionMethods.res index a7b228c64..eaa3b0296 100644 --- a/src/orca-loader/PaymentSessionMethods.res +++ b/src/orca-loader/PaymentSessionMethods.res @@ -120,7 +120,7 @@ let getCustomerSavedPaymentMethods = ( ~handleUserError=false, ~paymentType, ~iframeId="", - ~fetchMethod=Fetch.Post, + ~fetchMethod=#POST, ~setIsManualRetryEnabled={(. _) => ()}, ~switchToCustomPod=false, ~sdkHandleOneClickConfirmPayment=false,