diff --git a/CHANGELOG.md b/CHANGELOG.md index 3274590d..0f136238 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,45 @@ +## [0.105.2](https://github.com/juspay/hyperswitch-web/compare/v0.105.1...v0.105.2) (2024-12-05) + + +### Bug Fixes + +* re render issue of card nickname ([#823](https://github.com/juspay/hyperswitch-web/issues/823)) ([a4691b0](https://github.com/juspay/hyperswitch-web/commit/a4691b09f8dba1fa3ebaa8a56b910206296b899d)) + +## [0.105.1](https://github.com/juspay/hyperswitch-web/compare/v0.105.0...v0.105.1) (2024-12-05) + +# [0.105.0](https://github.com/juspay/hyperswitch-web/compare/v0.104.6...v0.105.0) (2024-12-04) + + +### Features + +* add traditional chinese locale ([#822](https://github.com/juspay/hyperswitch-web/issues/822)) ([2707923](https://github.com/juspay/hyperswitch-web/commit/27079236edef9bdd776753b67a70c474a794dd72)) + +## [0.104.6](https://github.com/juspay/hyperswitch-web/compare/v0.104.5...v0.104.6) (2024-12-04) + +## [0.104.5](https://github.com/juspay/hyperswitch-web/compare/v0.104.4...v0.104.5) (2024-12-03) + +## [0.104.4](https://github.com/juspay/hyperswitch-web/compare/v0.104.3...v0.104.4) (2024-12-03) + + +### Bug Fixes + +* show card form by default rendering issue ([#817](https://github.com/juspay/hyperswitch-web/issues/817)) ([00e8562](https://github.com/juspay/hyperswitch-web/commit/00e8562b97c20ed02dc86eaf139ffafb822be075)) + +## [0.104.3](https://github.com/juspay/hyperswitch-web/compare/v0.104.2...v0.104.3) (2024-12-02) + +## [0.104.2](https://github.com/juspay/hyperswitch-web/compare/v0.104.1...v0.104.2) (2024-12-02) + +## [0.104.1](https://github.com/juspay/hyperswitch-web/compare/v0.104.0...v0.104.1) (2024-12-02) + +# [0.104.0](https://github.com/juspay/hyperswitch-web/compare/v0.103.2...v0.104.0) (2024-11-26) + + +### Features + +* samsung pay added ([#806](https://github.com/juspay/hyperswitch-web/issues/806)) ([01db104](https://github.com/juspay/hyperswitch-web/commit/01db104a2512e99eb38305751a9e9395e0812940)) + +## [0.103.2](https://github.com/juspay/hyperswitch-web/compare/v0.103.1...v0.103.2) (2024-11-26) + ## [0.103.1](https://github.com/juspay/hyperswitch-web/compare/v0.103.0...v0.103.1) (2024-11-25) diff --git a/Hyperswitch-React-Demo-App/package.json b/Hyperswitch-React-Demo-App/package.json index 475db9f9..1cf70312 100644 --- a/Hyperswitch-React-Demo-App/package.json +++ b/Hyperswitch-React-Demo-App/package.json @@ -4,7 +4,6 @@ "private": true, "dependencies": { "@juspay-tech/hyper-js": "^1.6.0", - "@juspay-tech/hyperswitch-node": "^1.0.1", "@juspay-tech/react-hyper-js": "^1.0.2", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^12.1.5", diff --git a/Hyperswitch-React-Demo-App/server.js b/Hyperswitch-React-Demo-App/server.js index 514948ce..833639ae 100644 --- a/Hyperswitch-React-Demo-App/server.js +++ b/Hyperswitch-React-Demo-App/server.js @@ -2,14 +2,11 @@ const fetch = require("node-fetch"); const express = require("express"); const { resolve } = require("path"); const dotenv = require("dotenv"); -const hyper = require("@juspay-tech/hyperswitch-node"); dotenv.config({ path: "./.env" }); const app = express(); const PORT = 5252; -const hyperswitch = hyper(process.env.HYPERSWITCH_SECRET_KEY); - function getUrl(envVar, selfHostedValue) { return process.env[envVar] === selfHostedValue ? "" : process.env[envVar]; } @@ -95,7 +92,7 @@ const paymentData = { country_code: "+91", }, }, -} +}; const profileId = process.env.PROFILE_ID; if (profileId) { @@ -122,29 +119,25 @@ app.get("/create-payment-intent", async (_, res) => { }); async function createPaymentIntent(request) { - if (SERVER_URL) { - const url = - process.env.HYPERSWITCH_SERVER_URL_FOR_DEMO_APP || - process.env.HYPERSWITCH_SERVER_URL; - const apiResponse = await fetch(`${url}/payments`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - "api-key": process.env.HYPERSWITCH_SECRET_KEY, - }, - body: JSON.stringify(request), - }); - const paymentIntent = await apiResponse.json(); + const url = + process.env.HYPERSWITCH_SERVER_URL_FOR_DEMO_APP || + process.env.HYPERSWITCH_SERVER_URL; + const apiResponse = await fetch(`${url}/payments`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + "api-key": process.env.HYPERSWITCH_SECRET_KEY, + }, + body: JSON.stringify(request), + }); + const paymentIntent = await apiResponse.json(); - if (paymentIntent.error) { - console.error("Error - ", paymentIntent.error); - throw new Error(paymentIntent?.error?.message ?? "Something went wrong."); - } - return paymentIntent; - } else { - return await hyperswitch?.paymentIntents?.create(request); + if (paymentIntent.error) { + console.error("Error - ", paymentIntent.error); + throw new Error(paymentIntent?.error?.message ?? "Something went wrong."); } + return paymentIntent; } app.listen(PORT, () => { diff --git a/package-lock.json b/package-lock.json index 46a50c8f..8526311c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "orca-payment-page", - "version": "0.103.1", + "version": "0.105.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "orca-payment-page", - "version": "0.103.1", + "version": "0.105.2", "hasInstallScript": true, "dependencies": { "@glennsl/rescript-fetch": "^0.2.0", diff --git a/package.json b/package.json index 09a1f897..87455838 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "orca-payment-page", - "version": "0.103.1", + "version": "0.105.2", "main": "index.js", "private": true, "dependencies": { diff --git a/src/App.res b/src/App.res index b1ff9712..aab35134 100644 --- a/src/App.res +++ b/src/App.res @@ -57,7 +57,7 @@ let make = () => { switch fullscreenMode { | "paymentloader" => | "plaidSDK" => - | "pazeWallet" => + | "pazeWallet" => | "fullscreen" =>
diff --git a/src/CardPattern.res b/src/CardPattern.res index 765d4bff..ab814e31 100644 --- a/src/CardPattern.res +++ b/src/CardPattern.res @@ -19,7 +19,7 @@ let cardPatterns = [ { issuer: "Maestro", pattern: %re( - "/^(5018|5081|5044|504681|504993|5020|502260|5038|603845|603123|6304|6759|676[1-3]|6220|504834|504817|504645|504775|600206|627741)/" + "/^(5018|5081|5044|504681|504993|5020|502260|5038|5893|603845|603123|6304|6759|676[1-3]|6220|504834|504817|504645|504775|600206|627741)/" ), cvcLength: [3, 4], length: [12, 13, 14, 15, 16, 17, 18, 19], @@ -29,7 +29,7 @@ let cardPatterns = [ { issuer: "RuPay", pattern: %re( - "/^(508227|508[5-9]|603741|60698[5-9]|60699|607[0-8]|6079[0-7]|60798[0-4]|60800[1-9]|6080[1-9]|608[1-4]|608500|6521[5-9]|652[2-9]|6530|6531[0-4]|817290|817368|817378|353800)/" + "/^(508227|508[5-9]|603741|60698[5-9]|60699|607[0-8]|6079[0-7]|60798[0-4]|60800[1-9]|6080[1-9]|608[1-4]|608500|6521[5-9]|652[2-9]|6530|6531[0-4]|817290|817368|817378|353800|82)/" ), cvcLength: [3], length: [16], @@ -38,7 +38,7 @@ let cardPatterns = [ }, { issuer: "DinersClub", - pattern: %re("/^(36|38|30[0-5])/"), + pattern: %re("/^(36|38|39|30[0-5])/"), cvcLength: [3], maxCVCLength: 3, length: [14, 15, 16, 17, 18, 19], @@ -46,7 +46,7 @@ let cardPatterns = [ }, { issuer: "Discover", - pattern: %re("/^(6011|65|64[4-9]|622)/"), + pattern: %re("/^(6011|64[4-9]|65|622126|622[1-9][0-9][0-9]|6229[0-1][0-9]|622925)/"), cvcLength: [3], length: [16], maxCVCLength: 3, @@ -54,7 +54,7 @@ let cardPatterns = [ }, { issuer: "Mastercard", - pattern: %re("/^5[1-5]/"), + pattern: %re("/^(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[0-1][0-9]|2720|5[1-5])/"), cvcLength: [3], maxCVCLength: 3, length: [16], @@ -104,7 +104,7 @@ let cardPatterns = [ }, { issuer: "JCB", - pattern: %re("/^35/"), + pattern: %re("/^35(2[89]|[3-8][0-9])/"), cvcLength: [3], maxCVCLength: 3, length: [16], diff --git a/src/CardTheme.res b/src/CardTheme.res index c5cd8eb1..ebbff5e5 100644 --- a/src/CardTheme.res +++ b/src/CardTheme.res @@ -97,6 +97,7 @@ let getLocaleObject = async string => { | NL => Js.import(DutchLocale.localeStrings) | SV => Js.import(SwedishLocale.localeStrings) | RU => Js.import(RussianLocale.localeStrings) + | ZH_HANT => Js.import(TraditionalChineseLocale.localeStrings) } let awaitedLocaleValue = await promiseLocale diff --git a/src/CardUtils.res b/src/CardUtils.res index 8f6e8707..3301b78d 100644 --- a/src/CardUtils.res +++ b/src/CardUtils.res @@ -176,8 +176,7 @@ let formatCardNumber = (val, cardType) => { let clearValue = val->clearSpaces let formatedCard = switch cardType { | AMEX => `${clearValue->slice(0, 4)} ${clearValue->slice(4, 10)} ${clearValue->slice(10, 15)}` - | DINERSCLUB => - `${clearValue->slice(0, 4)} ${clearValue->slice(4, 10)} ${clearValue->slice(10, 14)}` + | DINERSCLUB | MASTERCARD | DISCOVER | SODEXO @@ -362,6 +361,7 @@ let getCardBrandIcon = (cardType, paymentType) => { | GooglePayElement | PayPalElement | ApplePayElement + | SamsungPayElement | KlarnaElement | ExpressCheckoutElement | PaymentMethodsManagement @@ -379,7 +379,10 @@ let getExpiryValidity = cardExpiry => { let valid = if currentYear == year->toInt && month->toInt >= currentMonth && month->toInt <= 12 { true } else if ( - year->toInt > currentYear && year->toInt < 2075 && month->toInt >= 1 && month->toInt <= 12 + year->toInt > currentYear && + year->toInt < Date.getFullYear(Js.Date.fromFloat(Date.now())) + 100 && + month->toInt >= 1 && + month->toInt <= 12 ) { true } else { @@ -453,12 +456,27 @@ let generateFontsLink = (fonts: array) => { ->ignore } } + let maxCardLength = cardBrand => { let obj = getobjFromCardPattern(cardBrand) Array.reduce(obj.length, 0, (acc, val) => max(acc, val)) } +let isCardLengthValid = (cardBrand, cardNumberLength) => { + let obj = getobjFromCardPattern(cardBrand) + Array.includes(obj.length, cardNumberLength) +} + let cardValid = (cardNumber, cardBrand) => { + let clearValueLength = cardNumber->clearSpaces->String.length + if cardBrand == "" && (GlobalVars.isInteg || GlobalVars.isSandbox) { + Utils.checkIsTestCardWildcard(cardNumber) + } else { + isCardLengthValid(cardBrand, clearValueLength) && calculateLuhn(cardNumber) + } +} + +let focusCardValid = (cardNumber, cardBrand) => { let clearValueLength = cardNumber->clearSpaces->String.length if cardBrand == "" && (GlobalVars.isInteg || GlobalVars.isSandbox) { Utils.checkIsTestCardWildcard(cardNumber) @@ -467,6 +485,7 @@ let cardValid = (cardNumber, cardBrand) => { (cardBrand === "Visa" && clearValueLength == 16)) && calculateLuhn(cardNumber) } } + let blurRef = (ref: React.ref>) => { ref.current->Nullable.toOption->Option.forEach(input => input->blur)->ignore } @@ -565,10 +584,10 @@ let setCardValid = (cardnumber, setIsCardValid) => { if cardValid(cardnumber, cardBrand) { setIsCardValid(_ => Some(true)) } else if ( - !cardValid(cardnumber, cardBrand) && cardnumber->String.length == maxCardLength(cardBrand) + !cardValid(cardnumber, cardBrand) && isCardLengthValid(cardBrand, cardnumber->String.length) ) { setIsCardValid(_ => Some(false)) - } else if !(cardnumber->String.length == maxCardLength(cardBrand)) { + } else if !isCardLengthValid(cardBrand, cardnumber->String.length) { setIsCardValid(_ => None) } } @@ -677,10 +696,7 @@ let getCardBrandFromStates = (cardBrand, cardScheme, showFields) => { !showFields ? cardScheme : cardBrand } -let getCardBrandInvalidError = ( - ~cardNumber, - ~localeString: OrcaPaymentPage.LocaleStringTypes.localeStrings, -) => { +let getCardBrandInvalidError = (~cardNumber, ~localeString: LocaleStringTypes.localeStrings) => { switch cardNumber->getCardBrand { | "" => localeString.enterValidCardNumberErrorText | cardBrandValue => localeString.cardBrandConfiguredErrorText(cardBrandValue) diff --git a/src/Components/SurchargeUtils.res b/src/Components/SurchargeUtils.res index e97445d4..ab6cc677 100644 --- a/src/Components/SurchargeUtils.res +++ b/src/Components/SurchargeUtils.res @@ -4,6 +4,7 @@ type oneClickWallets = { } let oneClickWallets = [ {paymentMethodType: "apple_pay", displayName: "ApplePay"}, + {paymentMethodType: "samsung_pay", displayName: "SamsungPay"}, {paymentMethodType: "paypal", displayName: "Paypal"}, {paymentMethodType: "google_pay", displayName: "GooglePay"}, {paymentMethodType: "klarna", displayName: "Klarna"}, @@ -23,6 +24,7 @@ let useSurchargeDetailsForOneClickWallets = (~paymentMethodListValue) => { oneClickWallets->Array.reduce([], (acc, wallet) => { let (isWalletBtnRendered, paymentMethod) = switch wallet.paymentMethodType { | "apple_pay" => (areOneClickWalletsRendered.isApplePay, "wallet") + | "samsung_pay" => (areOneClickWalletsRendered.isSamsungPay, "wallet") | "paypal" => (areOneClickWalletsRendered.isPaypal, "wallet") | "google_pay" => (areOneClickWalletsRendered.isGooglePay, "wallet") | "klarna" => (areOneClickWalletsRendered.isKlarna, "pay_later") diff --git a/src/LoaderController.res b/src/LoaderController.res index 57b6d4b8..b2a35c77 100644 --- a/src/LoaderController.res +++ b/src/LoaderController.res @@ -14,6 +14,7 @@ let make = (~children, ~paymentMode, ~setIntegrateErrorError, ~logger, ~initTime let setCustomPodUri = Recoil.useSetRecoilState(customPodUri) let setIsGooglePayReady = Recoil.useSetRecoilState(isGooglePayReady) let setIsApplePayReady = Recoil.useSetRecoilState(isApplePayReady) + let setIsSamsungPayReady = Recoil.useSetRecoilState(isSamsungPayReady) let (divH, setDivH) = React.useState(_ => 0.0) let (launchTime, setLaunchTime) = React.useState(_ => 0.0) let {showCardFormByDefault, paymentMethodOrder} = optionsPayment @@ -96,6 +97,7 @@ let make = (~children, ~paymentMode, ~setIntegrateErrorError, ~logger, ~initTime | GooglePayElement | PayPalElement | ApplePayElement + | SamsungPayElement | KlarnaElement | PazeElement | ExpressCheckoutElement @@ -385,6 +387,9 @@ let make = (~children, ~paymentMode, ~setIntegrateErrorError, ~logger, ~initTime dict->getJsonObjectFromDict("isReadyToPay")->JSON.Decode.bool->Option.getOr(false) ) } + if dict->getDictIsSome("isSamsungPayReady") { + setIsSamsungPayReady(_ => dict->getBool("isSamsungPayReady", false)) + } if ( dict->getDictIsSome("customBackendUrl") && dict diff --git a/src/LocaleStrings/ChineseLocale.res b/src/LocaleStrings/ChineseLocale.res index 4115df3f..31d1471e 100644 --- a/src/LocaleStrings/ChineseLocale.res +++ b/src/LocaleStrings/ChineseLocale.res @@ -1,7 +1,7 @@ let localeStrings: LocaleStringTypes.localeStrings = { locale: `zh`, localeDirection: `ltr`, - cardNumberLabel: `卡号`, + cardNumberLabel: `卡號`, inValidCardErrorText: `卡号无效。`, inCompleteCVCErrorText: `您的卡片安全码不完整。`, inCompleteExpiryErrorText: `您的卡片到期日期不完整。`, diff --git a/src/LocaleStrings/LocaleStringHelper.res b/src/LocaleStrings/LocaleStringHelper.res index 7b63c205..002f81db 100644 --- a/src/LocaleStrings/LocaleStringHelper.res +++ b/src/LocaleStrings/LocaleStringHelper.res @@ -17,6 +17,7 @@ let mapLocalStringToTypeLocale = val => { | "sv" => SV | "ru" => RU | "zh" => ZH + | "zh-Hant" => ZH_HANT | "en" | _ => EN diff --git a/src/LocaleStrings/LocaleStringTypes.res b/src/LocaleStrings/LocaleStringTypes.res index 3f605a15..ce484063 100644 --- a/src/LocaleStrings/LocaleStringTypes.res +++ b/src/LocaleStrings/LocaleStringTypes.res @@ -1,5 +1,5 @@ type locale = - EN | HE | FR | EN_GB | AR | JA | DE | FR_BE | ES | CA | PT | IT | PL | NL | SV | RU | ZH + EN | HE | FR | EN_GB | AR | JA | DE | FR_BE | ES | CA | PT | IT | PL | NL | SV | RU | ZH | ZH_HANT type localeStrings = { locale: string, diff --git a/src/LocaleStrings/TraditionalChineseLocale.res b/src/LocaleStrings/TraditionalChineseLocale.res new file mode 100644 index 00000000..ce153915 --- /dev/null +++ b/src/LocaleStrings/TraditionalChineseLocale.res @@ -0,0 +1,154 @@ +let localeStrings: LocaleStringTypes.localeStrings = { + locale: `zh-HANT`, + localeDirection: `ltr`, + cardNumberLabel: "卡號", + inValidCardErrorText: "卡號無效。", + inCompleteCVCErrorText: "您的卡片安全碼不完整。", + inCompleteExpiryErrorText: "您的卡片到期日期不完整。", + enterValidCardNumberErrorText: "請輸入有效的卡號。", + pastExpiryErrorText: "您的卡片到期年份已過期。", + poweredBy: "技術支持:Hyperswitch", + validThruText: "有效期限", + sortCodeText: "排序碼", + cvcTextLabel: "安全碼", + line1Label: "地址第一行", + line1Placeholder: "街道地址", + line1EmptyText: "地址第一行不能為空", + line2Label: "地址第二行", + line2Placeholder: "公寓、單元號等(可選)", + line2EmptyText: "地址第二行不能為空", + cityLabel: "城市", + cityEmptyText: "城市不能為空", + postalCodeLabel: "郵遞區號", + postalCodeEmptyText: "郵遞區號不能為空", + postalCodeInvalidText: "無效的郵遞區號", + stateLabel: "州/省", + stateEmptyText: "州/省不能為空", + accountNumberText: "帳戶號碼", + emailLabel: "電子郵件", + ibanEmptyText: "IBAN不能為空", + emailEmptyText: "電子郵件不能為空", + emailInvalidText: "無效的電子郵件地址", + fullNameLabel: "全名", + fullNamePlaceholder: "名字和姓氏", + countryLabel: "國家", + currencyLabel: "貨幣", + bankLabel: "選擇銀行", + redirectText: "提交訂單後,您將被重定向以安全完成購買。", + bankDetailsText: "提交這些詳細信息後,您將獲得銀行帳戶信息以進行付款。請確保記下這些信息。", + orPayUsing: "或使用以下方式付款", + addNewCard: "添加信用卡/借記卡", + useExisitingSavedCards: "使用已保存的信用卡/借記卡", + saveCardDetails: "保存卡片詳細信息", + addBankAccount: "添加銀行帳戶", + achBankDebitTerms: _ => + `您的ACH借記授權現在將被設置,我們會在未來扣款前確認金額並通知您。`, + sepaDebitTerms: str => + `通過提供您的付款信息並確認此授權書,您授權(A)${str}、債權人和/或我們的支付服務提供商向您的銀行發出指令以扣取您的帳戶資金,(B)您的銀行根據${str}的指令扣取您的帳戶資金。作為您的權利的一部分,根據您與銀行的協議條款,您有權向銀行申請退款。退款必須在帳戶扣款日起的8週內申請。您的權利在您可以從銀行獲得的聲明中進行了解釋。`, + becsDebitTerms: `通過提供您的銀行帳戶詳細信息並確認此付款,您同意此直接借記請求和直接借記請求服務協議,並授權Hyperswitch Payments Australia Pty Ltd ACN 160 180 343,直接借記用戶ID號507156(“Hyperswitch”)通過批量電子清算系統(BECS)代表Hyperswitch Payment Widget(“商戶”)從您的帳戶中扣款,金額由商戶另行通知您。您保證您是上述帳戶的持有人或授權簽署人。`, + cardTerms: str => + `通過提供您的卡片信息,您允許${str}根據其條款從您的卡片中扣款以進行未來的付款。`, + payNowButton: "立即付款", + cardNumberEmptyText: "卡號不能為空", + cardExpiryDateEmptyText: "卡片到期日期不能為空", + cvcNumberEmptyText: "安全碼不能為空", + enterFieldsText: "請填寫所有欄位", + enterValidDetailsText: "請輸入有效的詳細信息", + selectPaymentMethodText: "請選擇一種付款方式並重試", + card: "卡片", + surchargeMsgAmount: (currency, str) => <> + {React.string(`此交易將收取附加費${Utils.nbsp}`)} + {React.string(`${currency} ${str}`)} + {React.string(`${Utils.nbsp}的金額`)} + , + surchargeMsgAmountForCard: (currency, str) => <> + {React.string(`此交易將收取最高附加費${Utils.nbsp}`)} + {React.string(`${currency} ${str}`)} + {React.string(`${Utils.nbsp}的金額`)} + , + surchargeMsgAmountForOneClickWallets: "適用額外費用", + billingNameLabel: "帳單姓名", + billingNamePlaceholder: "名字和姓氏", + cardHolderName: "持卡人姓名", + on: "於", + \"and": "及", + nameEmptyText: str => `請提供您的${str}`, + completeNameEmptyText: str => `請提供您的完整${str}`, + billingDetailsText: "帳單詳細信息", + socialSecurityNumberLabel: "社會安全號碼", + saveWalletDetails: "選擇後,錢包詳細信息將被保存", + morePaymentMethods: "更多付款方式", + useExistingPaymentMethods: "使用已保存的付款方式", + cardNickname: "卡片暱稱", + nicknamePlaceholder: "卡片暱稱(可選)", + cardExpiredText: "此卡已過期", + cardHeader: "卡片信息", + cardBrandConfiguredErrorText: str => `${str} 暫時不支援。`, + currencyNetwork: "貨幣網絡", + expiryPlaceholder: "月 / 年", + dateOfBirth: "出生日期", + vpaIdLabel: "虛擬支付地址(VPA)", + vpaIdEmptyText: "虛擬支付地址不能為空", + vpaIdInvalidText: "無效的虛擬支付地址", + dateofBirthRequiredText: "必須提供出生日期", + dateOfBirthInvalidText: "年齡應大於或等於 18 歲", + dateOfBirthPlaceholderText: "輸入出生日期", + formFundsInfoText: "資金將存入此帳戶", + formFundsCreditInfoText: pmLabel => `您的資金將存入選定的 ${pmLabel}。`, + formEditText: "編輯", + formSaveText: "保存", + formSubmitText: "提交", + formSubmittingText: "提交中", + formSubheaderBillingDetailsText: "輸入您的帳單地址", + formSubheaderCardText: "您的卡片詳細信息", + formSubheaderAccountText: pmLabel => `您的 ${pmLabel}`, + formHeaderReviewText: "審核", + formHeaderReviewTabLayoutText: pmLabel => `審核您的 ${pmLabel} 詳細信息`, + formHeaderBankText: bankTransferType => `輸入 ${bankTransferType} 銀行詳細信息`, + formHeaderWalletText: walletTransferType => `輸入${walletTransferType}錢包詳細信息`, + formHeaderEnterCardText: "輸入卡信息", + formHeaderSelectBankText: "選擇一種銀行方法", + formHeaderSelectWalletText: "選擇一個錢包", + formHeaderSelectAccountText: "選擇一個帳戶進行付款", + formFieldACHRoutingNumberLabel: "路由號碼", + formFieldSepaIbanLabel: "國際銀行帳戶號碼 (IBAN)", + formFieldSepaBicLabel: "銀行識別碼 (可選)", + formFieldPixIdLabel: "Pix ID", + formFieldBankAccountNumberLabel: "銀行帳戶號碼", + formFieldPhoneNumberLabel: "電話號碼", + formFieldCountryCodeLabel: "國家代碼 (可選)", + formFieldBankNameLabel: "銀行名稱 (可選)", + formFieldBankCityLabel: "銀行城市 (可選)", + formFieldCardHoldernamePlaceholder: "您的姓名", + formFieldBankNamePlaceholder: "銀行名稱", + formFieldBankCityPlaceholder: "銀行城市", + formFieldEmailPlaceholder: "您的電子郵件", + formFieldPhoneNumberPlaceholder: "您的電話", + formFieldInvalidRoutingNumber: "路由號碼無效。", + infoCardRefId: `參考編號`, + infoCardErrCode: `錯誤代碼`, + infoCardErrMsg: `錯誤訊息`, + infoCardErrReason: `原因`, + linkRedirectionText: seconds => `在 ${seconds->Int.toString} 秒後重新導向 ...`, + linkExpiryInfo: expiry => `鏈接將於 ${expiry} 到期`, + payoutFromText: merchant => `來自 ${merchant} 的付款`, + payoutStatusFailedMessage: `處理您的付款失敗。請聯絡您的服務提供商了解更多詳情。`, + payoutStatusPendingMessage: `您的付款應在 2-3 個工作日內處理。`, + payoutStatusSuccessMessage: `您的付款成功。資金已存入您選擇的付款方式。`, + payoutStatusFailedText: `付款失敗`, + payoutStatusPendingText: `付款處理中`, + payoutStatusSuccessText: `付款成功`, + pixCNPJInvalidText: `無效的 Pix CNPJ`, + pixCNPJEmptyText: `Pix CNPJ 不能為空`, + pixCNPJLabel: `Pix CNPJ`, + pixCNPJPlaceholder: `輸入 Pix CNPJ`, + pixCPFInvalidText: `無效的 Pix CPF`, + pixCPFEmptyText: `Pix CPF 不能為空`, + pixCPFLabel: `Pix CPF`, + pixCPFPlaceholder: `輸入 Pix CPF`, + pixKeyEmptyText: `Pix 金鑰不能為空`, + pixKeyPlaceholder: `輸入 Pix 金鑰`, + pixKeyLabel: `Pix 金鑰`, + invalidCardHolderNameError: `持卡人姓名不能包含數字`, + invalidNickNameError: `暱稱不能包含超過兩個數字`, +} diff --git a/src/Payment.res b/src/Payment.res index 22418293..befaab78 100644 --- a/src/Payment.res +++ b/src/Payment.res @@ -11,14 +11,13 @@ let setUserError = message => { @react.component let make = (~paymentMode, ~integrateError, ~logger) => { let {localeString} = Recoil.useRecoilValueFromAtom(configAtom) - let keys = Recoil.useRecoilValueFromAtom(keys) + let {iframeId} = Recoil.useRecoilValueFromAtom(keys) let cardScheme = Recoil.useRecoilValueFromAtom(cardBrand) let showFields = Recoil.useRecoilValueFromAtom(showCardFieldsAtom) let selectedOption = Recoil.useRecoilValueFromAtom(selectedOptionAtom) let isManualRetryEnabled = Recoil.useRecoilValueFromAtom(isManualRetryEnabled) let paymentToken = Recoil.useRecoilValueFromAtom(paymentTokenAtom) let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue) - let {iframeId} = keys let (cardNumber, setCardNumber) = React.useState(_ => "") let (cardExpiry, setCardExpiry) = React.useState(_ => "") @@ -89,7 +88,7 @@ let make = (~paymentMode, ~integrateError, ~logger) => { let clearValue = card->clearSpaces setCardValid(clearValue, setIsCardValid) if ( - cardValid(clearValue, cardBrand) && + focusCardValid(clearValue, cardBrand) && (PaymentUtils.checkIsCardSupported(clearValue, supportedCardBrands)->Option.getOr(false) || Utils.checkIsTestCardWildcard(clearValue)) ) { @@ -112,6 +111,9 @@ let make = (~paymentMode, ~integrateError, ~logger) => { let formattedExpiry = val->formatCardExpiryNumber if isExipryValid(formattedExpiry) { handleInputFocus(~currentRef=expiryRef, ~destinationRef=cvcRef) + + // * Sending card expiry to handle cases where the card expires before the use date. + Utils.messageParentWindow([("expiryDate", formattedExpiry->JSON.Encode.string)]) } setExpiryValid(formattedExpiry, setIsExpiryValid) setCardExpiry(_ => formattedExpiry) diff --git a/src/PaymentElement.res b/src/PaymentElement.res index d5fd4364..e97420f6 100644 --- a/src/PaymentElement.res +++ b/src/PaymentElement.res @@ -168,9 +168,12 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod | Loaded(paymentlist) => let plist = paymentlist->getDictFromJson->PaymentMethodsRecord.itemToObjMapper - setPaymentOptions(_ => { - paymentOptionsList - }) + setPaymentOptions(_ => + [ + ...showCardFormByDefault && checkPriorityList(paymentMethodOrder) ? ["card"] : [], + ...paymentOptionsList, + ]->removeDuplicate + ) setWalletOptions(_ => walletList) setPaymentMethodListValue(_ => plist) showCardFormByDefault @@ -199,7 +202,7 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod | _ => () } None - }, (paymentMethodList, walletList, paymentOptionsList, actualList)) + }, (paymentMethodList, walletList, paymentOptionsList, actualList, showCardFormByDefault)) React.useEffect(() => { switch sessionsObj { | Loaded(ssn) => setSessions(_ => ssn) @@ -275,7 +278,13 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod } ) None - }, (layoutClass.defaultCollapsed, paymentOptions, paymentMethodList, selectedOption)) + }, ( + layoutClass.defaultCollapsed, + paymentOptions, + paymentMethodList, + selectedOption, + showCardFormByDefault, + )) let checkRenderOrComp = () => { walletOptions->Array.includes("paypal") || isShowOrPayUsing } diff --git a/src/Payments/CardPayment.res b/src/Payments/CardPayment.res index 7af2f4b2..2758da9e 100644 --- a/src/Payments/CardPayment.res +++ b/src/Payments/CardPayment.res @@ -154,9 +154,6 @@ let make = ( defaultCardBody } if confirm.doSubmit { - // * Sending card expiry to handle cases where the card expires before the use date. - messageParentWindow([("expiryDate", cardExpiry->JSON.Encode.string)]) - let isCardDetailsValid = isCVCValid->Option.getOr(false) && isCardValid->Option.getOr(false) && diff --git a/src/Payments/GPay.res b/src/Payments/GPay.res index d9e0c69d..7ec6ec08 100644 --- a/src/Payments/GPay.res +++ b/src/Payments/GPay.res @@ -77,8 +77,8 @@ let make = ( GooglePayHelpers.useHandleGooglePayResponse(~connectors, ~intent, ~isWallet, ~requiredFieldsBody) - let (_, buttonType, _) = options.wallets.style.type_ - let (_, heightType, _, _) = options.wallets.style.height + let (_, buttonType, _, _) = options.wallets.style.type_ + let (_, heightType, _, _, _) = options.wallets.style.height let height = switch heightType { | GooglePay(val) => val | _ => 48 @@ -190,7 +190,13 @@ let make = ( syncPayment() } } catch { - | _ => logInfo(Console.log("Error in syncing GooglePay Payment")) + | err => + loggerState.setLogError( + ~value="Error in syncing GooglePay Payment", + ~eventName=GOOGLE_PAY_FLOW, + ~internalMetadata=err->formatException->JSON.stringify, + ~paymentMethod="GOOGLE_PAY", + ) } } Window.addEventListener("message", handleGooglePayMessages) diff --git a/src/Payments/KlarnaSDK.res b/src/Payments/KlarnaSDK.res index ea7ab530..8cdb4b18 100644 --- a/src/Payments/KlarnaSDK.res +++ b/src/Payments/KlarnaSDK.res @@ -24,7 +24,7 @@ let make = (~sessionObj: SessionsType.token) => { let setAreOneClickWalletsRendered = Recoil.useSetRecoilState(areOneClickWalletsRendered) let (stateJson, setStatesJson) = React.useState(_ => JSON.Encode.null) - let (_, _, _, heightType) = options.wallets.style.height + let (_, _, _, heightType, _) = options.wallets.style.height let height = switch heightType { | Klarna(val) => val | _ => 48 diff --git a/src/Payments/PayPal.res b/src/Payments/PayPal.res index 32e27aaa..0e494e8f 100644 --- a/src/Payments/PayPal.res +++ b/src/Payments/PayPal.res @@ -22,12 +22,12 @@ let make = (~paymentType, ~walletOptions) => { let isWallet = walletOptions->Array.includes("paypal") let (requiredFieldsBody, setRequiredFieldsBody) = React.useState(_ => Dict.make()) - let (_, _, labelType) = options.wallets.style.type_ + let (_, _, labelType, _) = options.wallets.style.type_ let _label = switch labelType { | Paypal(val) => val->PaypalSDKTypes.getLabel | _ => Paypal->PaypalSDKTypes.getLabel } - let (_, _, heightType, _) = options.wallets.style.height + let (_, _, heightType, _, _) = options.wallets.style.height let height = switch heightType { | Paypal(val) => val | _ => 48 diff --git a/src/Payments/PaymentRequestButtonElement.res b/src/Payments/PaymentRequestButtonElement.res index 7ea12207..04a521a2 100644 --- a/src/Payments/PaymentRequestButtonElement.res +++ b/src/Payments/PaymentRequestButtonElement.res @@ -1,4 +1,5 @@ -type wallet = GPayWallet | PaypalWallet | ApplePayWallet | KlarnaWallet | PazeWallet | NONE +type wallet = + GPayWallet | PaypalWallet | ApplePayWallet | KlarnaWallet | SamsungPayWallet | PazeWallet | NONE let paymentMode = str => { switch str { | "gpay" @@ -8,6 +9,9 @@ let paymentMode = str => { | "applepay" | "apple_pay" => ApplePayWallet + | "samsungpay" + | "samsung_pay" => + SamsungPayWallet | "klarna" => KlarnaWallet | "paze" => PazeWallet | _ => NONE @@ -18,7 +22,7 @@ module WalletsSaveDetailsText = { @react.component let make = (~paymentType) => { open RecoilAtoms - let {isGooglePay, isApplePay, isPaypal} = Recoil.useRecoilValueFromAtom( + let {isGooglePay, isApplePay, isPaypal, isSamsungPay} = Recoil.useRecoilValueFromAtom( areOneClickWalletsRendered, ) let {localeString} = Recoil.useRecoilValueFromAtom(configAtom) @@ -26,7 +30,7 @@ module WalletsSaveDetailsText = { + (isGooglePay || isApplePay || isPaypal || isSamsungPay)}>
@@ -55,6 +59,9 @@ let make = (~sessions, ~walletOptions, ~paymentType) => { let applePaySessionObj = itemToObjMapper(dict, ApplePayObject) let applePayToken = getPaymentSessionObj(applePaySessionObj.sessionsToken, ApplePay) + let samsungPaySessionObj = itemToObjMapper(dict, SamsungPayObject) + let samsungPayToken = getPaymentSessionObj(samsungPaySessionObj.sessionsToken, SamsungPay) + let googlePayThirdPartySessionObj = itemToObjMapper(dict, GooglePayThirdPartyObject) let googlePayThirdPartyToken = getPaymentSessionObj( googlePayThirdPartySessionObj.sessionsToken, @@ -122,6 +129,12 @@ let make = (~sessions, ~walletOptions, ~paymentType) => { | _ => React.null } + | SamsungPayWallet => + switch samsungPayToken { + | SamsungPayTokenOptional(optToken) => + + | _ => React.null + } | KlarnaWallet => {switch klarnaTokenObj { diff --git a/src/Payments/PaypalSDK.res b/src/Payments/PaypalSDK.res index 5825e5fd..82abecc8 100644 --- a/src/Payments/PaypalSDK.res +++ b/src/Payments/PaypalSDK.res @@ -32,8 +32,8 @@ let make = (~sessionObj: SessionsType.token, ~paymentType: CardThemeType.mode) = let (stateJson, setStatesJson) = React.useState(_ => JSON.Encode.null) let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom) - let (_, _, buttonType) = options.wallets.style.type_ - let (_, _, heightType, _) = options.wallets.style.height + let (_, _, buttonType, _) = options.wallets.style.type_ + let (_, _, heightType, _, _) = options.wallets.style.height let buttonStyle = { layout: "vertical", color: options.wallets.style.theme == Outline @@ -140,7 +140,13 @@ let make = (~sessionObj: SessionsType.token, ~paymentType: CardThemeType.mode) = } } } catch { - | _err => Utils.logInfo(Console.log("Error loading Paypal")) + | err => + loggerState.setLogError( + ~value="Error loading Paypal", + ~eventName=PAYPAL_SDK_FLOW, + ~internalMetadata=err->Utils.formatException->JSON.stringify, + ~paymentMethod="PAYPAL_SDK", + ) } None }, [stateJson]) diff --git a/src/Payments/PazeButton.res b/src/Payments/PazeButton.res index e0dcd5cd..4ffdfa63 100644 --- a/src/Payments/PazeButton.res +++ b/src/Payments/PazeButton.res @@ -16,6 +16,11 @@ let make = (~token: SessionsType.token) => { let paymentIntentID = clientSecret->Option.getOr("")->getPaymentId let (showLoader, setShowLoader) = React.useState(() => false) let onClick = _ => { + loggerState.setLogInfo( + ~value="Paze SDK Button Clicked", + ~eventName=PAZE_SDK_FLOW, + ~paymentMethod="PAZE", + ) setShowLoader(_ => true) let metadata = [ @@ -45,6 +50,11 @@ let make = (~token: SessionsType.token) => { let dict = json->Utils.getDictFromJson->getDictFromDict("data") if dict->getBool("isPaze", false) { setShowLoader(_ => false) + messageParentWindow([ + ("fullscreen", true->JSON.Encode.bool), + ("param", "paymentloader"->JSON.Encode.string), + ("iframeId", iframeId->JSON.Encode.string), + ]) if dict->getOptionString("completeResponse")->Option.isSome { let completeResponse = dict->getString("completeResponse", "") intent( diff --git a/src/Payments/PazeWallet.res b/src/Payments/PazeWallet.res index 6f70bce8..e96fa21d 100644 --- a/src/Payments/PazeWallet.res +++ b/src/Payments/PazeWallet.res @@ -4,7 +4,7 @@ open PazeTypes external digitalWalletSdk: digitalWalletSdk = "DIGITAL_WALLET_SDK" @react.component -let make = () => { +let make = (~logger: HyperLogger.loggerMake) => { open Promise open Utils @@ -97,7 +97,13 @@ let make = () => { resolve() } catch { - | _ => + | err => + logger.setLogError( + ~value=err->formatException->JSON.stringify, + ~eventName=PAZE_SDK_FLOW, + ~paymentMethod="PAZE", + ~logType=ERROR, + ) messageParentWindow([ ("fullscreen", false->JSON.Encode.bool), ("isPaze", true->JSON.Encode.bool), @@ -107,14 +113,21 @@ let make = () => { resolve() } } - + logger.setLogInfo(~value="PAZE SDK Script Loading", ~eventName=PAZE_SDK_FLOW) let pazeScript = Window.createElement("script") pazeScript->Window.elementSrc(pazeScriptURL) pazeScript->Window.elementOnerror(exn => { - let err = exn->Identity.anyTypeToJson->JSON.stringify - Console.log2("PAZE --- errrorrr", err) + logger.setLogError( + ~value=`Error During Loading PAZE SDK Script: ${exn + ->Identity.anyTypeToJson + ->JSON.stringify}`, + ~eventName=PAZE_SDK_FLOW, + ) + }) + pazeScript->Window.elementOnload(_ => { + logger.setLogInfo(~value="PAZE SDK Script Loaded", ~eventName=PAZE_SDK_FLOW) + loadPazeSDK()->ignore }) - pazeScript->Window.elementOnload(_ => loadPazeSDK()->ignore) Window.body->Window.appendChild(pazeScript) } diff --git a/src/Payments/PlaidSDKIframe.res b/src/Payments/PlaidSDKIframe.res index 45b9c1b3..e69a3612 100644 --- a/src/Payments/PlaidSDKIframe.res +++ b/src/Payments/PlaidSDKIframe.res @@ -78,7 +78,13 @@ let make = () => { } messageParentWindow([("fullscreen", false->JSON.Encode.bool)]) } catch { - | e => logInfo(Console.log2("Retrieve Failed", e)) + | err => + logger.setLogError( + ~value="Retrieve failed via Plaid", + ~eventName=PLAID_SDK, + ~internalMetadata=err->formatException->JSON.stringify, + ~paymentMethod="PLAID", + ) } } diff --git a/src/Payments/SamsungPayComponent.res b/src/Payments/SamsungPayComponent.res new file mode 100644 index 00000000..b31fee14 --- /dev/null +++ b/src/Payments/SamsungPayComponent.res @@ -0,0 +1,110 @@ +@react.component +let make = (~sessionObj: option, ~walletOptions) => { + open Utils + open RecoilAtoms + + let url = RescriptReactRouter.useUrl() + let isSamsungPayReady = Recoil.useRecoilValueFromAtom(isSamsungPayReady) + let loggerState = Recoil.useRecoilValueFromAtom(loggerAtom) + let options = Recoil.useRecoilValueFromAtom(optionAtom) + let isManualRetryEnabled = Recoil.useRecoilValueFromAtom(isManualRetryEnabled) + let setIsShowOrPayUsing = Recoil.useSetRecoilState(isShowOrPayUsing) + let areOneClickWalletsRendered = Recoil.useSetRecoilState(areOneClickWalletsRendered) + let {publishableKey, iframeId} = Recoil.useRecoilValueFromAtom(keys) + let status = CommonHooks.useScript("https://img.mpay.samsung.com/gsmpi/sdk/samsungpay_web_sdk.js") + let isWallet = walletOptions->Array.includes("samsung_pay") + let componentName = CardUtils.getQueryParamsDictforKey(url.search, "componentName") + let intent = PaymentHelpers.usePaymentIntent(Some(loggerState), Samsungpay) + + let (_, _, _, _, heightType) = options.wallets.style.height + let height = switch heightType { + | SamsungPay(val) => val + | _ => 48 + } + + let getSamsungPaymentsClient = _ => + SamsungPayType.samsung({ + environment: "PRODUCTION", + }) + + let onSamsungPaymentButtonClick = _ => { + loggerState.setLogInfo( + ~value="SamsungPay Button Clicked", + ~eventName=SAMSUNG_PAY, + ~paymentMethod="SAMSUNG_PAY", + ) + SamsungPayHelpers.handleSamsungPayClicked( + ~sessionObj=sessionObj->Option.getOr(JSON.Encode.null)->getDictFromJson, + ~componentName, + ~iframeId, + ~readOnly=options.readOnly, + ) + } + + let buttonStyle = { + "onClick": onSamsungPaymentButtonClick, + "buttonStyle": "black", + "type": "buy", + }->Identity.anyTypeToJson + + let addSamsungPayButton = _ => { + let paymentClient = getSamsungPaymentsClient() + let button = paymentClient.createButton(buttonStyle) + let spayWrapper = GooglePayType.getElementById(GooglePayType.document, "samsungpay-container") + spayWrapper.innerHTML = "" + spayWrapper.appendChild(button) + } + + React.useEffect(() => { + if status == "ready" && isSamsungPayReady && isWallet { + setIsShowOrPayUsing(_ => true) + addSamsungPayButton() + } + None + }, (status, sessionObj, isSamsungPayReady)) + + React.useEffect0(() => { + let handleSamsung = (ev: Window.event) => { + let json = ev.data->safeParse + let dict = json->getDictFromJson + if dict->Dict.get("samsungPayResponse")->Option.isSome { + let metadata = dict->getJsonObjectFromDict("samsungPayResponse") + let getBody = SamsungPayHelpers.getSamsungPayBodyFromResponse(~sPayResponse=metadata) + let body = PaymentBody.samsungPayBody( + ~metadata=getBody.paymentMethodData->Identity.anyTypeToJson, + ) + + intent( + ~bodyArr=body, + ~confirmParam={ + return_url: options.wallets.walletReturnUrl, + publishableKey, + }, + ~handleUserError=false, + ~manualRetry=isManualRetryEnabled, + ) + } + if dict->Dict.get("samsungPayError")->Option.isSome { + messageParentWindow([("fullscreen", false->JSON.Encode.bool)]) + } + } + Window.addEventListener("message", handleSamsung) + Some(() => {Window.removeEventListener("message", handleSamsung)}) + }) + + let isRenderSamsungPayButton = isSamsungPayReady && isWallet + + React.useEffect(() => { + areOneClickWalletsRendered(prev => { + ...prev, + isSamsungPay: isRenderSamsungPayButton, + }) + None + }, [isRenderSamsungPayButton]) + +
Int.toString}px`} + id="samsungpay-container" + className={`w-full flex flex-row justify-center rounded-md [&>*]:w-full [&>button]:!bg-contain`} + /> +} diff --git a/src/RenderPaymentMethods.res b/src/RenderPaymentMethods.res index 4efbd2d8..4296496c 100644 --- a/src/RenderPaymentMethods.res +++ b/src/RenderPaymentMethods.res @@ -85,6 +85,7 @@ let make = ( | GooglePayElement | PayPalElement | ApplePayElement + | SamsungPayElement | KlarnaElement | PazeElement | ExpressCheckoutElement diff --git a/src/Types/CardThemeType.res b/src/Types/CardThemeType.res index 6a0a5592..2584d8a6 100644 --- a/src/Types/CardThemeType.res +++ b/src/Types/CardThemeType.res @@ -14,6 +14,7 @@ type mode = | GooglePayElement | PayPalElement | ApplePayElement + | SamsungPayElement | KlarnaElement | PazeElement | ExpressCheckoutElement @@ -104,6 +105,7 @@ let getPaymentMode = val => { | "payPal" => PayPalElement | "applePay" => ApplePayElement | "paymentMethodCollect" => PaymentMethodCollectElement + | "samsungPay" => SamsungPayElement | "klarna" => KlarnaElement | "expressCheckout" => ExpressCheckoutElement | "paze" => PazeElement diff --git a/src/Types/PaymentModeType.res b/src/Types/PaymentModeType.res index 074f872f..e8a06fd0 100644 --- a/src/Types/PaymentModeType.res +++ b/src/Types/PaymentModeType.res @@ -18,6 +18,7 @@ type payment = | BanContactCard | GooglePay | ApplePay + | SamsungPay | Boleto | PayPal | NONE @@ -43,6 +44,7 @@ let paymentMode = str => { | "bancontact_card" => BanContactCard | "google_pay" => GooglePay | "apple_pay" => ApplePay + | "samsung_pay" => SamsungPay | "boleto" => Boleto | "paypal" => PayPal | _ => NONE @@ -55,6 +57,7 @@ let defaultOrder = [ "google_pay", "paypal", "klarna", + "samsung_pay", "affirm", "afterpay_clearpay", "ach_transfer", diff --git a/src/Types/PaymentType.res b/src/Types/PaymentType.res index 11d4b36f..346efa08 100644 --- a/src/Types/PaymentType.res +++ b/src/Types/PaymentType.res @@ -60,8 +60,10 @@ type terms = { usBankAccount: showTerms, } type buttonHeight = Default | Custom -type heightType = ApplePay(int) | GooglePay(int) | Paypal(int) | Klarna(int) +type heightType = ApplePay(int) | GooglePay(int) | Paypal(int) | Klarna(int) | SamsungPay(int) type googlePayStyleType = Default | Buy | Donate | Checkout | Subscribe | Book | Pay | Order +type samsungPayStyleType = Buy + type paypalStyleType = Paypal | Checkout | Buynow | Pay | Installment type applePayStyleType = | Default @@ -81,12 +83,13 @@ type styleType = | ApplePay(applePayStyleType) | GooglePay(googlePayStyleType) | Paypal(paypalStyleType) -type styleTypeArray = (styleType, styleType, styleType) + | SamsungPay(samsungPayStyleType) +type styleTypeArray = (styleType, styleType, styleType, styleType) type theme = Dark | Light | Outline type style = { type_: styleTypeArray, theme: theme, - height: (heightType, heightType, heightType, heightType), + height: (heightType, heightType, heightType, heightType, heightType), buttonRadius: int, } type wallets = { @@ -96,6 +99,7 @@ type wallets = { payPal: showType, klarna: showType, paze: showType, + samsungPay: showType, style: style, } type business = {name: string} @@ -273,9 +277,9 @@ let defaultFields = { billingDetails: JSONObject(defaultBilling), } let defaultStyle = { - type_: (ApplePay(Default), GooglePay(Default), Paypal(Paypal)), + type_: (ApplePay(Default), GooglePay(Default), Paypal(Paypal), SamsungPay(Buy)), theme: Light, - height: (ApplePay(48), GooglePay(48), Paypal(48), Klarna(48)), + height: (ApplePay(48), GooglePay(48), Paypal(48), Klarna(48), SamsungPay(48)), buttonRadius: 2, } let defaultWallets = { @@ -285,6 +289,7 @@ let defaultWallets = { payPal: Auto, klarna: Auto, paze: Auto, + samsungPay: Auto, style: defaultStyle, } let defaultBillingAddress = { @@ -467,6 +472,11 @@ let getGooglePayType = str => { GooglePay(Default) } } +let getSamsungPayType = str => { + switch str { + | _ => SamsungPay(Buy) + } +} let getPayPalType = str => { switch str { | "check-out" @@ -505,7 +515,7 @@ let getTypeArray = (str, logger) => { if !Array.includes(goodVals, str) { str->unknownPropValueWarning(goodVals, "options.wallets.style.type", ~logger) } - (str->getApplePayType, str->getGooglePayType, str->getPayPalType) + (str->getApplePayType, str->getGooglePayType, str->getPayPalType, str->getSamsungPayType) } let getShowDetails = (~billingDetails, ~logger) => { @@ -688,86 +698,72 @@ let getTerms = (dict, str, logger) => { }) ->Option.getOr(defaultTerms) } -let getApplePayHeight = (val, logger) => { - let val: heightType = - val >= 45 - ? ApplePay(val) - : { - valueOutRangeWarning( - val, - "options.style.height", - "[h>=45] - ApplePay. Value set to min", - ~logger, - ) - ApplePay(48) - } - val -} -let getGooglePayHeight = (val, logger) => { - let val: heightType = - val >= 48 - ? GooglePay(val) - : { - valueOutRangeWarning( - val, - "options.style.height", - "[h>=48] - GooglePay. Value set to min", - ~logger, - ) - GooglePay(48) - } - val -} -let getPaypalHeight = (val, logger) => { - let val: heightType = - val < 25 - ? { - valueOutRangeWarning( - val, - "options.style.height", - "[25-55] - Paypal. Value set to min", - ~logger, - ) - Paypal(25) - } - : val > 55 - ? { - valueOutRangeWarning( - val, - "options.style.height", - "[25-55] - Paypal. Value set to max", - ~logger, - ) - Paypal(55) - } - : Paypal(val) - val -} -let getKlarnaHeight = (val, logger) => { - let val: heightType = - val < 40 - ? { - valueOutRangeWarning( - val, - "options.style.height", - "[40-60] - Klarna. Value set to min", - ~logger, - ) - Klarna(40) - } - : val > 60 - ? { - valueOutRangeWarning( - val, - "options.style.height", - "[40-60] - Paypal. Value set to max", - ~logger, - ) - Klarna(60) - } - : Klarna(val) - val +let getApplePayHeight: (int, 'a) => heightType = (val, logger) => { + if val >= 45 { + ApplePay(val) + } else { + valueOutRangeWarning( + val, + "options.style.height", + "[h>=45] - ApplePay. Value set to min", + ~logger, + ) + ApplePay(48) + } } + +let getGooglePayHeight: (int, 'a) => heightType = (val, logger) => { + if val >= 45 { + GooglePay(val) + } else { + valueOutRangeWarning( + val, + "options.style.height", + "[h>=45] - GooglePay. Value set to min", + ~logger, + ) + GooglePay(48) + } +} + +let getSamsungPayHeight: (int, 'a) => heightType = (val, logger) => { + if val >= 45 { + SamsungPay(val) + } else { + valueOutRangeWarning( + val, + "options.style.height", + "[h>=45] - SamsungPay. Value set to min", + ~logger, + ) + SamsungPay(48) + } +} + +let getPaypalHeight: (int, 'a) => heightType = (val, logger) => { + if val < 25 { + valueOutRangeWarning(val, "options.style.height", "[25-55] - Paypal. Value set to min", ~logger) + Paypal(25) + } else if val > 55 { + valueOutRangeWarning(val, "options.style.height", "[25-55] - Paypal. Value set to max", ~logger) + Paypal(55) + } else { + Paypal(val) + } +} + +let getKlarnaHeight: (int, 'a) => heightType = (val, logger) => { + if val < 40 { + valueOutRangeWarning(val, "options.style.height", "[40-60] - Klarna. Value set to min", ~logger) + Klarna(40) + } else if val > 60 { + valueOutRangeWarning(val, "options.style.height", "[40-60] - Paypal. Value set to max", ~logger) + Klarna(60) + } else { + Klarna(val) + } +} + let getTheme = (str, logger) => { switch str { | "outline" => Outline @@ -784,6 +780,7 @@ let getHeightArray = (val, logger) => { val->getGooglePayHeight(logger), val->getPaypalHeight(logger), val->getKlarnaHeight(logger), + val->getSamsungPayHeight(logger), ) } let getStyle = (dict, str, logger) => { @@ -808,7 +805,7 @@ let getWallets = (dict, str, logger) => { ->Option.flatMap(JSON.Decode.object) ->Option.map(json => { unknownKeysWarning( - ["applePay", "googlePay", "style", "walletReturnUrl", "payPal", "klarna"], + ["applePay", "googlePay", "style", "walletReturnUrl", "payPal", "klarna", "samsungPay"], json, "options.wallets", ~logger, @@ -836,6 +833,10 @@ let getWallets = (dict, str, logger) => { "options.wallets.paze", logger, ), + samsungPay: getWarningString(json, "samsungPay", "auto", ~logger)->getShowType( + "options.wallets.samsungPay", + logger, + ), style: getStyle(json, "style", logger), } }) diff --git a/src/Types/SamsungPayType.res b/src/Types/SamsungPayType.res new file mode 100644 index 00000000..78a503db --- /dev/null +++ b/src/Types/SamsungPayType.res @@ -0,0 +1,46 @@ +type client = { + isReadyToPay: JSON.t => promise, + createButton: JSON.t => Dom.element, + loadPaymentSheet: (JSON.t, JSON.t) => promise, + notify: JSON.t => unit, +} + +type env = {environment: string} + +@new external samsung: env => client = "SamsungPay.PaymentClient" + +type merchant = { + name: string, + url: string, + countryCode: string, +} +type amount = { + option: string, + currency: string, + total: string, +} +type transactionDetail = { + orderNumber: string, + merchant: merchant, + amount: amount, +} +type paymentMethods = { + version: string, + serviceId: string, + protocol: string, + allowedBrands: array, +} + +type threeDS = { + \"type": string, + version: string, + data: string, +} +type paymentMethodData = { + method: string, + recurring_payment: bool, + card_brand: string, + card_last4digits: string, + @as("3_d_s") threeDS: threeDS, +} +type paymentData = {paymentMethodData: paymentMethodData} diff --git a/src/Types/SessionsType.res b/src/Types/SessionsType.res index 07d0396c..27673c70 100644 --- a/src/Types/SessionsType.res +++ b/src/Types/SessionsType.res @@ -1,8 +1,8 @@ open Utils -type wallet = Gpay | Paypal | Klarna | ApplePay | Paze | NONE - -type tokenCategory = ApplePayObject | GooglePayThirdPartyObject | PazeObject | Others +type wallet = Gpay | Paypal | Klarna | ApplePay | SamsungPay | Paze | NONE +type tokenCategory = + ApplePayObject | GooglePayThirdPartyObject | SamsungPayObject | PazeObject | Others type paymentType = Wallet | Others @@ -30,12 +30,14 @@ type tokenType = | ApplePayToken(array) | GooglePayThirdPartyToken(array) | PazeToken(array) + | SamsungPayToken(array) | OtherToken(array) type optionalTokenType = | ApplePayTokenOptional(option) | GooglePayThirdPartyTokenOptional(option) | PazeTokenOptional(option) + | SamsungPayTokenOptional(option) | OtherTokenOptional(option) type sessions = { @@ -67,6 +69,7 @@ let getWallet = str => { | "apple_pay" => ApplePay | "paypal" => Paypal | "klarna" => Klarna + | "samsung_pay" => SamsungPay | "google_pay" => Gpay | "paze" => Paze | _ => NONE @@ -124,6 +127,11 @@ let itemToObjMapper = (dict, returnType) => { clientSecret: getString(dict, "client_secret", ""), sessionsToken: PazeToken(getSessionsTokenJson(dict, "session_token")), } + | SamsungPayObject => { + paymentId: getString(dict, "payment_id", ""), + clientSecret: getString(dict, "client_secret", ""), + sessionsToken: SamsungPayToken(getSessionsTokenJson(dict, "session_token")), + } | Others => { paymentId: getString(dict, "payment_id", ""), @@ -149,5 +157,6 @@ let getPaymentSessionObj = (tokenType, val) => | GooglePayThirdPartyToken(arr) => GooglePayThirdPartyTokenOptional(getWalletFromTokenType(arr, val)) | PazeToken(arr) => PazeTokenOptional(getWalletFromTokenType(arr, val)) + | SamsungPayToken(arr) => SamsungPayTokenOptional(getWalletFromTokenType(arr, val)) | OtherToken(arr) => OtherTokenOptional(arr->Array.find(item => item.walletName == val)) } diff --git a/src/Utilities/ApplePayHelpers.res b/src/Utilities/ApplePayHelpers.res index 3a342cac..cee2cef9 100644 --- a/src/Utilities/ApplePayHelpers.res +++ b/src/Utilities/ApplePayHelpers.res @@ -189,10 +189,9 @@ let startApplePaySession = ( let payment = event.payment payment->callBackFunc } - ssn.oncancel = _ev => { + ssn.oncancel = _ => { applePaySessionRef := Nullable.null - logInfo(Console.log("Apple Pay Payment Cancelled")) - logger.setLogInfo( + logger.setLogError( ~value="Apple Pay Payment Cancelled", ~eventName=APPLE_PAY_FLOW, ~paymentMethod="APPLE_PAY", @@ -218,6 +217,7 @@ let useHandleApplePayResponse = ( let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom) let {publishableKey} = Recoil.useRecoilValueFromAtom(RecoilAtoms.keys) let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue) + let logger = Recoil.useRecoilValueFromAtom(RecoilAtoms.loggerAtom) let (stateJson, setStatesJson) = React.useState(_ => JSON.Encode.null) @@ -279,7 +279,13 @@ let useHandleApplePayResponse = ( syncPayment() } } catch { - | _ => logInfo(Console.log("Error in parsing Apple Pay Data")) + | err => + logger.setLogError( + ~value="Error in parsing Apple Pay Data", + ~eventName=APPLE_PAY_FLOW, + ~paymentMethod="APPLE_PAY", + ~internalMetadata=err->formatException->JSON.stringify, + ) } } Window.addEventListener("message", handleApplePayMessages) diff --git a/src/Utilities/PaymentBody.res b/src/Utilities/PaymentBody.res index 3774f02a..9b6a9aaa 100644 --- a/src/Utilities/PaymentBody.res +++ b/src/Utilities/PaymentBody.res @@ -386,6 +386,18 @@ let paypalSdkBody = (~token, ~connectors) => [ ), ] +let samsungPayBody = (~metadata) => { + let paymentCredential = [("payment_credential", metadata)]->Utils.getJsonFromArrayOfJson + let spayBody = [("samsung_pay", paymentCredential)]->Utils.getJsonFromArrayOfJson + let paymentMethodData = [("wallet", spayBody)]->Utils.getJsonFromArrayOfJson + + [ + ("payment_method", "wallet"->JSON.Encode.string), + ("payment_method_type", "samsung_pay"->JSON.Encode.string), + ("payment_method_data", paymentMethodData), + ] +} + let gpayBody = (~payObj: GooglePayType.paymentData, ~connectors: array) => { let gPayBody = [ ("payment_method", "wallet"->JSON.Encode.string), diff --git a/src/Utilities/PaymentHelpers.res b/src/Utilities/PaymentHelpers.res index 39fb85d2..9340e664 100644 --- a/src/Utilities/PaymentHelpers.res +++ b/src/Utilities/PaymentHelpers.res @@ -7,6 +7,7 @@ open URLModule let getPaymentType = paymentMethodType => switch paymentMethodType { | "apple_pay" => Applepay + | "samsung_pay" => Samsungpay | "google_pay" => Gpay | "paze" => Paze | "debit" @@ -339,6 +340,7 @@ let rec intentCall = ( let url = makeUrl(confirmParam.return_url) url.searchParams.set("payment_intent_client_secret", clientSecret) url.searchParams.set("status", "failed") + url.searchParams.set("payment_id", clientSecret->getPaymentId) messageParentWindow([("confirmParams", confirmParam->anyTypeToJson)]) if statusCode->String.charAt(0) !== "2" { @@ -484,6 +486,7 @@ let rec intentCall = ( let url = makeUrl(confirmParam.return_url) url.searchParams.set("payment_intent_client_secret", clientSecret) + url.searchParams.set("payment_id", clientSecret->getPaymentId) url.searchParams.set("status", intent.status) let handleProcessingStatus = (paymentType, sdkHandleOneClickConfirmPayment) => { @@ -826,6 +829,7 @@ let rec intentCall = ( try { let url = makeUrl(confirmParam.return_url) url.searchParams.set("payment_intent_client_secret", clientSecret) + url.searchParams.set("payment_id", clientSecret->getPaymentId) url.searchParams.set("status", "failed") let exceptionMessage = err->formatException logApi( diff --git a/src/Utilities/PaymentHelpersTypes.res b/src/Utilities/PaymentHelpersTypes.res index 64ff0425..614d8ad3 100644 --- a/src/Utilities/PaymentHelpersTypes.res +++ b/src/Utilities/PaymentHelpersTypes.res @@ -1,5 +1,14 @@ type payment = - Card | BankTransfer | BankDebits | KlarnaRedirect | Gpay | Applepay | Paypal | Paze | Other + | Card + | BankTransfer + | BankDebits + | KlarnaRedirect + | Gpay + | Applepay + | Paypal + | Samsungpay + | Paze + | Other type paymentIntent = ( ~handleUserError: bool=?, diff --git a/src/Utilities/PaymentUtils.res b/src/Utilities/PaymentUtils.res index 4a4c8a92..cb1c91bf 100644 --- a/src/Utilities/PaymentUtils.res +++ b/src/Utilities/PaymentUtils.res @@ -28,7 +28,6 @@ let paymentListLookupNew = ( "gcash", "momo", "touch_n_go", - "samsung_pay", "mifinity", ] let otherPaymentList = [] @@ -454,6 +453,7 @@ let useGetPaymentMethodList = (~paymentOptions, ~paymentType, ~sessions) => { areAllGooglePayRequiredFieldsPrefilled, isApplePayReady, isGooglePayReady, + showCardFormByDefault, )) } diff --git a/src/Utilities/RecoilAtoms.res b/src/Utilities/RecoilAtoms.res index 5281dc20..b8438084 100644 --- a/src/Utilities/RecoilAtoms.res +++ b/src/Utilities/RecoilAtoms.res @@ -53,6 +53,7 @@ let userPhoneNumber = Recoil.atom( let userCardNickName = Recoil.atom("userCardNickName", defaultFieldValues) let isGooglePayReady = Recoil.atom("isGooglePayReady", false) let isApplePayReady = Recoil.atom("isApplePayReady", false) +let isSamsungPayReady = Recoil.atom("isSamsungPayReady", false) let userCountry = Recoil.atom("userCountry", "") let userBank = Recoil.atom("userBank", "") let userAddressline1 = Recoil.atom("userAddressline1", defaultFieldValues) @@ -84,6 +85,7 @@ type areOneClickWalletsRendered = { isApplePay: bool, isPaypal: bool, isKlarna: bool, + isSamsungPay: bool, } let defaultAreOneClickWalletsRendered = { @@ -91,6 +93,7 @@ let defaultAreOneClickWalletsRendered = { isApplePay: false, isPaypal: false, isKlarna: false, + isSamsungPay: false, } let areOneClickWalletsRendered = Recoil.atom( diff --git a/src/Utilities/SamsungPayHelpers.res b/src/Utilities/SamsungPayHelpers.res new file mode 100644 index 00000000..d00e86e7 --- /dev/null +++ b/src/Utilities/SamsungPayHelpers.res @@ -0,0 +1,60 @@ +open SamsungPayType +open Utils + +let getTransactionDetail = dict => { + let amountDict = dict->getDictFromDict("amount") + let merchantDict = dict->getDictFromDict("merchant") + { + orderNumber: dict->getString("order_number", ""), + amount: { + option: amountDict->getString("option", ""), + currency: amountDict->getString("currency_code", ""), + total: amountDict->getString("total", ""), + }, + merchant: { + name: merchantDict->getString("name", ""), + countryCode: merchantDict->getString("country_code", ""), + url: merchantDict->getString("url", ""), + }, + } +} + +let handleSamsungPayClicked = (~sessionObj, ~componentName, ~iframeId, ~readOnly) => { + messageParentWindow([ + ("fullscreen", true->JSON.Encode.bool), + ("param", "paymentloader"->JSON.Encode.string), + ("iframeId", iframeId->JSON.Encode.string), + ("componentName", componentName->JSON.Encode.string), + ]) + + if !readOnly { + messageParentWindow([ + ("SamsungPayClicked", true->JSON.Encode.bool), + ("SPayPaymentDataRequest", getTransactionDetail(sessionObj)->Identity.anyTypeToJson), + ]) + } +} + +let getPaymentMethodData = dict => { + let threeDSDict = dict->getDictFromDict("3DS") + + { + method: dict->getString("method", ""), + recurring_payment: dict->getBool("recurring_payment", false), + card_brand: dict->getString("card_brand", ""), + card_last4digits: dict->getString("card_last4digits", ""), + threeDS: { + \"type": threeDSDict->getString("type", ""), + version: threeDSDict->getString("version", ""), + data: threeDSDict->getString("data", ""), + }, + } +} + +let itemToObjMapper = dict => { + paymentMethodData: getPaymentMethodData(dict), +} + +let getSamsungPayBodyFromResponse = (~sPayResponse) => { + sPayResponse->getDictFromJson->itemToObjMapper +} diff --git a/src/Utilities/Utils.res b/src/Utilities/Utils.res index 745478b0..c2c0cd32 100644 --- a/src/Utilities/Utils.res +++ b/src/Utilities/Utils.res @@ -764,10 +764,6 @@ let snakeToTitleCase = str => { ->Array.joinWith(" ") } -let logInfo = log => { - Window.isProd ? () : log -} - let formatIBAN = iban => { let formatted = iban->String.replaceRegExp(%re(`/[^a-zA-Z0-9]/g`), "") let countryCode = formatted->String.substring(~start=0, ~end=2)->String.toUpperCase @@ -1285,6 +1281,7 @@ let getWalletPaymentMethod = (wallets, paymentType: CardThemeType.mode) => { | ApplePayElement => wallets->Array.filter(item => item === "apple_pay") | KlarnaElement => wallets->Array.filter(item => item === "klarna") | PazeElement => wallets->Array.filter(item => item === "paze") + | SamsungPayElement => wallets->Array.filter(item => item === "samsung_pay") | _ => wallets } } @@ -1295,6 +1292,7 @@ let expressCheckoutComponents = [ "applePay", "klarna", "paze", + "samsungPay", "expressCheckout", ] @@ -1317,6 +1315,7 @@ let walletElementPaymentType: array = [ GooglePayElement, PayPalElement, ApplePayElement, + SamsungPayElement, KlarnaElement, PazeElement, ExpressCheckoutElement, diff --git a/src/hyper-loader/Elements.res b/src/hyper-loader/Elements.res index 4eda0f49..911290ff 100644 --- a/src/hyper-loader/Elements.res +++ b/src/hyper-loader/Elements.res @@ -101,7 +101,7 @@ let make = ( let preMountLoaderMountedPromise = Promise.make((resolve, _reject) => { let preMountLoaderIframeCallback = (ev: Types.event) => { - let json = ev.data->Identity.anyTypeToJson + let json = ev.data->anyTypeToJson let dict = json->getDictFromJson if dict->Dict.get("preMountLoaderIframeMountedCallback")->Option.isSome { resolve(true->JSON.Encode.bool) @@ -118,7 +118,7 @@ let make = ( let onPlaidCallback = mountedIframeRef => { (ev: Types.event) => { - let json = ev.data->Identity.anyTypeToJson + let json = ev.data->anyTypeToJson let dict = json->getDictFromJson let isPlaidExist = dict->getBool("isPlaid", false) if isPlaidExist { @@ -131,7 +131,7 @@ let make = ( let onPazeCallback = mountedIframeRef => { (event: Types.event) => { - let json = event.data->Identity.anyTypeToJson + let json = event.data->anyTypeToJson let dict = json->getDictFromJson if dict->getBool("isPaze", false) { let componentName = dict->getString("componentName", "payment") @@ -143,7 +143,7 @@ let make = ( let fetchPaymentsList = (mountedIframeRef, componentType) => { let handlePaymentMethodsLoaded = (event: Types.event) => { - let json = event.data->Identity.anyTypeToJson + let json = event.data->anyTypeToJson let dict = json->getDictFromJson let isPaymentMethodsData = dict->getString("data", "") === "payment_methods" if isPaymentMethodsData { @@ -182,7 +182,11 @@ let make = ( logger.setLogInfo(~value="TrustPay Script Loading", ~eventName=TRUSTPAY_SCRIPT) trustPayScript->Window.elementSrc(trustPayScriptURL) trustPayScript->Window.elementOnerror(err => { - logInfo(Console.log2("ERROR DURING LOADING TRUSTPAY APPLE PAY", err)) + logger.setLogError( + ~value="ERROR DURING LOADING TRUSTPAY APPLE PAY", + ~eventName=TRUSTPAY_SCRIPT, + ~internalMetadata=err->formatException->JSON.stringify, + ) }) trustPayScript->Window.elementOnload(_ => { logger.setLogInfo(~value="TrustPay Script Loaded", ~eventName=TRUSTPAY_SCRIPT) @@ -210,7 +214,7 @@ let make = ( ) => { if !disableSavedPaymentMethods { let handleCustomerPaymentMethodsLoaded = (event: Types.event) => { - let json = event.data->Identity.anyTypeToJson + let json = event.data->anyTypeToJson let dict = json->getDictFromJson let isCustomerPaymentMethodsData = dict->getString("data", "") === "customer_payment_methods" @@ -293,6 +297,7 @@ let make = ( | "klarna" | "expressCheckout" | "paze" + | "samsungPay" | "paymentMethodsManagement" | "payment" => () | str => manageErrorWarning(UNKNOWN_KEY, ~dynamicStr=`${str} type in create`, ~logger) @@ -539,7 +544,7 @@ let make = ( } let handleApplePayThirdPartyFlow = (event: Types.event) => { - let json = event.data->Identity.anyTypeToJson + let json = event.data->anyTypeToJson let dict = json->getDictFromJson switch dict->Dict.get("applePayButtonClicked") { | Some(val) => @@ -702,7 +707,7 @@ let make = ( } messageCurrentWindow([ ("submitSuccessful", false->JSON.Encode.bool), - ("error", err->Identity.anyTypeToJson), + ("error", err->anyTypeToJson), ("url", url->JSON.Encode.string), ]) } @@ -727,7 +732,7 @@ let make = ( ->then(json => json->handleRetrievePaymentResponse) ->catch(err => { err->handleErrorResponse - resolve(err->Identity.anyTypeToJson) + resolve(err->anyTypeToJson) }) ->ignore ->resolve @@ -767,10 +772,10 @@ let make = ( } else { messageCurrentWindow([ ("submitSuccessful", false->JSON.Encode.bool), - ("error", err->Identity.anyTypeToJson), + ("error", err->anyTypeToJson), ("url", redirectUrl), ]) - resolve(err->Identity.anyTypeToJson) + resolve(err->anyTypeToJson) } }) ->finally(_ => messageCurrentWindow([("fullscreen", false->JSON.Encode.bool)])) @@ -787,7 +792,7 @@ let make = ( let fetchSessionTokens = mountedIframeRef => { let handleSessionTokensLoaded = (event: Types.event) => { - let json = event.data->Identity.anyTypeToJson + let json = event.data->anyTypeToJson let dict = json->getDictFromJson let sessionTokensData = dict->getString("data", "") === "session_tokens" if sessionTokensData { @@ -827,17 +832,21 @@ let make = ( ->Option.getOr("") x === "google_pay" || x === "googlepay" }) + let samsungPayPresent = sessionsArr->Array.find(item => { + let walletName = item->getDictFromJson->getString("wallet_name", "") + walletName === "samsung_pay" || walletName === "samsungpay" + }) - (json, applePayPresent, googlePayPresent)->resolve + (json, applePayPresent, googlePayPresent, samsungPayPresent)->resolve } ->then(res => { - let (json, applePayPresent, googlePayPresent) = res + let (json, applePayPresent, googlePayPresent, samsungPayPresent) = res if ( componentType->getIsComponentTypeForPaymentElementCreate && applePayPresent->Option.isSome ) { let handleApplePayMessages = (applePayEvent: Types.event) => { - let json = applePayEvent.data->Identity.anyTypeToJson + let json = applePayEvent.data->anyTypeToJson let dict = json->getDictFromJson let componentName = dict->getString("componentName", "payment") @@ -930,10 +939,10 @@ let make = ( let payRequest = GooglePayType.assign( Dict.make()->JSON.Encode.object, - GooglePayType.baseRequest->Identity.anyTypeToJson, + GooglePayType.baseRequest->anyTypeToJson, { "allowedPaymentMethods": gpayobj.allowed_payment_methods->arrayJsonToCamelCase, - }->Identity.anyTypeToJson, + }->anyTypeToJson, ) try { @@ -1034,13 +1043,13 @@ let make = ( "paymentDataCallbacks": { "onPaymentDataChanged": onPaymentDataChanged, }, - }->Identity.anyTypeToJson + }->anyTypeToJson } else { { "environment": publishableKey->String.startsWith("pk_prd_") ? "PRODUCTION" : "TEST", - }->Identity.anyTypeToJson + }->anyTypeToJson } let gPayClient = GooglePayType.google(gpayClientRequest) @@ -1054,7 +1063,7 @@ let make = ( }) ->catch(err => { logger.setLogInfo( - ~value=err->Identity.anyTypeToJson->JSON.stringify, + ~value=err->anyTypeToJson->JSON.stringify, ~eventName=GOOGLE_PAY_FLOW, ~paymentMethod="GOOGLE_PAY", ~logType=DEBUG, @@ -1122,6 +1131,122 @@ let make = ( ~logType=INFO, ) } + if ( + componentType->getIsComponentTypeForPaymentElementCreate && + samsungPayPresent->Option.isSome && + wallets.samsungPay === Auto + ) { + let dict = json->getDictFromJson + let sessionObj = SessionsType.itemToObjMapper(dict, SamsungPayObject) + let samsungPayToken = SessionsType.getPaymentSessionObj( + sessionObj.sessionsToken, + SamsungPay, + ) + let tokenObj = switch samsungPayToken { + | SamsungPayTokenOptional(optToken) => optToken + | _ => None + } + + let sessionObject = + tokenObj + ->Option.flatMap(JSON.Decode.object) + ->Option.getOr(Dict.make()) + + let allowedBrands = + sessionObject + ->getStrArray("allowed_brands") + ->Array.map(str => str->String.toLowerCase) + + let payRequest = { + "version": sessionObject->getString("version", ""), + "allowedBrands": allowedBrands, + "protocol": sessionObject->getString("protocol", ""), + "serviceId": sessionObject->getString("service_id", ""), + }->anyTypeToJson + + try { + let samsungPayClient = SamsungPayType.samsung({ + environment: "PRODUCTION", + }) + samsungPayClient.isReadyToPay(payRequest) + ->then(res => { + let dict = res->getDictFromJson + let isReadyToPay = dict->getBool("result", false) + let msg = + [("isSamsungPayReady", isReadyToPay->JSON.Encode.bool)]->Dict.fromArray + mountedIframeRef->Window.iframePostMessage(msg) + resolve() + }) + ->catch(err => { + logger.setLogError( + ~value=`SAMSUNG PAY not ready ${err->formatException->JSON.stringify}`, + ~eventName=SAMSUNG_PAY, + ~paymentMethod="SAMSUNG_PAY", + ~logType=ERROR, + ) + resolve() + }) + ->ignore + + let handleSamsungPayMessages = (event: Types.event) => { + let evJson = event.data->anyTypeToJson + let samsungPayClicked = + evJson + ->getOptionalJsonFromJson("SamsungPayClicked") + ->getBoolFromOptionalJson(false) + + let paymentDataRequest = + evJson + ->getOptionalJsonFromJson("SPayPaymentDataRequest") + ->Option.getOr(JSON.Encode.null) + + if samsungPayClicked && paymentDataRequest !== JSON.Encode.null { + samsungPayClient.loadPaymentSheet(payRequest, paymentDataRequest) + ->then(json => { + let msg = [("samsungPayResponse", json->anyTypeToJson)]->Dict.fromArray + event.source->Window.sendPostMessage(msg) + resolve() + }) + ->catch(err => { + logger.setLogError( + ~value=`SAMSUNG PAY Initialization fail ${err + ->formatException + ->JSON.stringify}`, + ~eventName=SAMSUNG_PAY, + ~paymentMethod="SAMSUNG_PAY", + ~logType=ERROR, + ) + event.source->Window.sendPostMessage( + [("samsungPayError", err->anyTypeToJson)]->Dict.fromArray, + ) + resolve() + }) + ->ignore + } + } + addSmartEventListener( + "message", + handleSamsungPayMessages, + "onSamsungPayMessages", + ) + } catch { + | err => + logger.setLogError( + ~value=`SAMSUNG PAY Not Ready - ${err->formatException->JSON.stringify}`, + ~eventName=SAMSUNG_PAY, + ~paymentMethod="SAMSUNG_PAY", + ~logType=ERROR, + ) + Console.log("Error loading Samsung Pay") + } + } else if wallets.samsungPay === Never { + logger.setLogInfo( + ~value="SAMSUNG PAY is set as never by merchant", + ~eventName=SAMSUNG_PAY, + ~paymentMethod="SAMSUNG_PAY", + ~logType=INFO, + ) + } json->resolve }) diff --git a/src/hyper-loader/Hyper.res b/src/hyper-loader/Hyper.res index eac314c9..f8750ac6 100644 --- a/src/hyper-loader/Hyper.res +++ b/src/hyper-loader/Hyper.res @@ -139,26 +139,25 @@ let make = (publishableKey, options: option, analyticsInfo: optionOption.getOr(JSON.Encode.null) - ->Utils.getDictFromJson - ->Utils.getBool("isPreloadEnabled", true) + ->getDictFromJson + ->getBool("isPreloadEnabled", true) let shouldUseTopRedirection = options ->Option.getOr(JSON.Encode.null) - ->Utils.getDictFromJson - ->Utils.getBool("shouldUseTopRedirection", false) + ->getDictFromJson + ->getBool("shouldUseTopRedirection", false) let analyticsMetadata = options ->Option.getOr(JSON.Encode.null) - ->Utils.getDictFromJson - ->Utils.getDictFromObj("analytics") - ->Utils.getJsonObjectFromDict("metadata") + ->getDictFromJson + ->getDictFromObj("analytics") + ->getJsonObjectFromDict("metadata") if isPreloadEnabled { preloader() } let analyticsInfoDict = analyticsInfo->Option.flatMap(JSON.Decode.object)->Option.getOr(Dict.make()) - let sessionID = - analyticsInfoDict->getString("sessionID", "hyp_" ++ Utils.generateRandomString(8)) + let sessionID = analyticsInfoDict->getString("sessionID", "hyp_" ++ generateRandomString(8)) let sdkTimestamp = analyticsInfoDict->getString("timeStamp", Date.now()->Float.toString) let logger = HyperLogger.make( ~sessionId=sessionID, @@ -266,12 +265,37 @@ let make = (publishableKey, options: option, analyticsInfo: optionWindow.elementSrc(googlePayScriptURL) googlePayScript->Window.elementOnerror(err => { - Utils.logInfo(Console.log2("ERROR DURING LOADING GOOGLE PAY SCRIPT", err)) + logger.setLogError( + ~value="ERROR DURING LOADING GOOGLE PAY SCRIPT", + ~eventName=GOOGLE_PAY_SCRIPT, + ~internalMetadata=err->formatException->JSON.stringify, + ~paymentMethod="GOOGLE_PAY", + ) }) Window.body->Window.appendChild(googlePayScript) logger.setLogInfo(~value="GooglePay Script Loaded", ~eventName=GOOGLE_PAY_SCRIPT) } + if ( + Window.querySelectorAll(`script[src="https://img.mpay.samsung.com/gsmpi/sdk/samsungpay_web_sdk.js"]`)->Array.length === 0 + ) { + let samsungPayScriptUrl = "https://img.mpay.samsung.com/gsmpi/sdk/samsungpay_web_sdk.js" + let samsungPayScript = Window.createElement("script") + samsungPayScript->Window.elementSrc(samsungPayScriptUrl) + samsungPayScript->Window.elementOnerror(err => { + logger.setLogError( + ~value="ERROR DURING LOADING SAMSUNG PAY SCRIPT", + ~eventName=SAMSUNG_PAY_SCRIPT, + ~internalMetadata=err->formatException->JSON.stringify, + ~paymentMethod="SAMSUNG_PAY", + ) + }) + Window.body->Window.appendChild(samsungPayScript) + samsungPayScript->Window.elementOnload(_ => + logger.setLogInfo(~value="SamsungPay Script Loaded", ~eventName=SAMSUNG_PAY_SCRIPT) + ) + } + let iframeRef = ref([]) let clientSecret = ref("") let ephemeralKey = ref("") diff --git a/src/hyper-log-catcher/HyperLogger.res b/src/hyper-log-catcher/HyperLogger.res index 715ed560..a0f5851f 100644 --- a/src/hyper-log-catcher/HyperLogger.res +++ b/src/hyper-log-catcher/HyperLogger.res @@ -86,6 +86,9 @@ type eventName = | EXTERNAL_TAX_CALCULATION | POST_SESSION_TOKENS_CALL | POST_SESSION_TOKENS_CALL_INIT + | PAZE_SDK_FLOW + | SAMSUNG_PAY_SCRIPT + | SAMSUNG_PAY let eventNameToStrMapper = (eventName: eventName) => (eventName :> string) @@ -104,9 +107,6 @@ let toSnakeCaseWithSeparator = (str, separator) => { type maskableDetails = Email | CardDetails type source = Loader | Elements(CardThemeType.mode) | Headless -let logInfo = log => { - Window.isProd ? () : log -} type logFile = { timestamp: string, diff --git a/webpack.common.js b/webpack.common.js index c3cea6c8..18a9015f 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -50,7 +50,6 @@ const getEnvironmentDomain = (prodDomain, integDomain, defaultDomain) => { const backendDomain = getEnvironmentDomain("checkout", "dev", "beta"); const confirmDomain = getEnvironmentDomain("api", "integ-api", "sandbox"); -const logDomain = getEnvironmentDomain("api", "integ-api", "sandbox"); const backendEndPoint = envBackendUrl || `https://${backendDomain}.hyperswitch.io/api`; @@ -58,8 +57,7 @@ const backendEndPoint = const confirmEndPoint = envBackendUrl || `https://${confirmDomain}.hyperswitch.io`; -const logEndpoint = - envLoggingUrl || `https://${logDomain}.hyperswitch.io/logs/sdk`; +const logEndpoint = envLoggingUrl; const loggingLevel = "DEBUG"; const maxLogsPushedPerEventName = 100;