From 55074c6bf9c714c83aef6aaf538e789d923c2343 Mon Sep 17 00:00:00 2001 From: Tharindu Kumarasiri Date: Tue, 13 Aug 2024 16:19:02 +0530 Subject: [PATCH 01/29] MOB-40 and MOB-41 --- .../paymentMethodImages/bank_transfer.png | Bin 1409 -> 24234 bytes .../paymentMethodImages/credit_card.png | Bin 1080 -> 19525 bytes .../images/paymentMethodImages/konbini.png | Bin 1540 -> 33434 bytes payment_sdk/src/assets/languages/ja.json | 6 +++--- payment_sdk/src/components/CardInputGroup.tsx | 1 + 5 files changed, 4 insertions(+), 3 deletions(-) diff --git a/payment_sdk/src/assets/images/paymentMethodImages/bank_transfer.png b/payment_sdk/src/assets/images/paymentMethodImages/bank_transfer.png index d4cb6113aa56d019e50894be76bc7a2d1b75f344..bbfc3c5b1fa03ba3323cd126482b285ff7144fd9 100644 GIT binary patch literal 24234 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≈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 literal 1409 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 CPU*=2 literal 1080 zcmeAS@N?(olHy`uVBq!ia0vp^%|M*Q!3-o_q9+ssDaPU;cPGZ1Cw1z99L@rdNT49M zFbFd;%$g$s6zmP~32_C|Z%G7y{{Drd|4889zklnt?d_YnB)_&hwWRsgn|HcS;T#GU z0vdMGhVEa#eSi4mxv6K2n4ZhoOV@)_OMzO^Y(8}~VI$B%`Xxbr!3=Wc2S5BT7@%)e*ahP_Ig&CX|Pp@}P;Q{)InbXt7F{EP7+u6DOLWTmz({H_V zstD>>VQ9GT;*s|E{{yc`-2Z*7BIbzo!rPg-{WElSpMJaU=1!+KEi6o_ziXSGk$l{@%J<5^pr@oG&OoPrP6&QNgk2lbb^&;E-)~@6~?E1>#GGFheT=}+nRvx$Z zOvsH_Sa|rL_H@2^50{?)!@bzgu7)M_OVST^S+}*(Lbv-DZs$u*{rK-KL(^{S#m_D$ z&fB_uk@Vpe&I=~|+Sh#dgJR$eSBdBC51(t?`?|=sykPpH3q3!%r*3~1@NQPm`iAEZ z;+kXLuIgMA<(Hdy_-8}n?Z53?6h++6t$Qx!Ze(I4e0am_+8*mr>$;4Dh=}%65nFi9 zT~OAY5_sImd3>#ZZ)gEYgqZ?fTP-~ z$^ON>Z83MwbUa)>@o;|jt}6*GMS}M0&$YWVShNI9HTS>rBp~?h838w5@6#`DsZ2Y$ z>*KL=JM6R!|E%YE9&q~CJNFzmo9ww-H`O~l+n+=JgSSu1_HT>Jk39MM qyn^G>TmFSyX(G?<74H4{EMFtE%<51}$VFg&V(@hJb6Mw<&;$S;jUt!; diff --git a/payment_sdk/src/assets/images/paymentMethodImages/konbini.png b/payment_sdk/src/assets/images/paymentMethodImages/konbini.png index e211b6d2e06cb573554da9511dae1585a7e286f7..d55909c894eaf801fe923ee4478d354308b27c22 100644 GIT binary patch literal 33434 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 T=a;AOICfr5TQ&P{tNZ^0se?>Z literal 1540 zcmb7EX*3iH6rO1q#*AhxW#(13iF%V6pM+DXK+|CFE6kB zuh{tsqMtB3|FOEhg;r1&mY72bh`Dg}re|nUdr$x8wXY7OP#2$w;qj^1be4GHKX#eD zN(J*34byYi3aG9SG_fn-_y$g0EsC( zYfD^2{xWMKaLfqN8d+>R5$KuGFA`$ZsB=zJMQY-jrFv(=EU0rytm;LK- zE36JtJw@lU?M}qjz-kYG15g~Ul=5$^#hH0R=x=LYNb4y0hYvASD48k1bU@(Zi}T710#vnVf|e4bKtJqt!HAIbR=;Ql?R$*e`tJ z(*dzZ!9}T+zRqz`%=9?ff$==zuz|?uKpc3Gr zsNU8^kD$sAqtmvJo}-Iz0h@{|H8zNTcK!9kV<>mnaCQVG$#fy_#4x6fn(FzzYAFHv zP;Hl%oD%^bpge8`dG!F>TE1F#i8t`9&npLP(tO~0G&7&#oWd^63%YS-A8zAMHnHB1 z*1C$wU^mhNd@$^bdtNc36+s4l!hiwK(G-_JgL0WYdrn22OZT{PBOpIz=*bGJEOZUG1BwZb z*CSfqcivz{wYe+!L9`P+N_Lu__f)kRYiZ#wJCdIw1%m8nk@B=`zuEJoEjbA4>|jNw z8(5z30oIQ2?Hbp(70x%M zsr4VCjF-KsdtB5_+X(3aR`e?+tG@~5t!y=74I1;sMt#h6R9BuxD^!aXvHzLuNRa@+YS z^Zd@4dFfAnF);Lc#){@0?g7Qcl}AA^g(UIC>~^8fxc9a&@)~$ubH(U=fuLn3J#gdw z4&(0BU31Fz?%4hm;l~B|QZlPZ#Dr7EJbe@>x@%kTWHNXj|H`%fLik~Cruaz$?63~j J53Gps{{rVB4+a1L diff --git a/payment_sdk/src/assets/languages/ja.json b/payment_sdk/src/assets/languages/ja.json index 4dc60b4..e4b00fd 100644 --- a/payment_sdk/src/assets/languages/ja.json +++ b/payment_sdk/src/assets/languages/ja.json @@ -1,11 +1,11 @@ { "translation": { - "credit_card": "クレジットカード", + "credit_card": "カード", "paypay": "PayPay", "linepay": "LINE Pay", "merpay": "メルペイ", "rakutenpay": "楽天ペイ", - "paidy": "ペイディ(Paidy)", + "paidy": "ペイディ", "konbini": "コンビニ", "bank_transfer": "銀行振込", "pay_easy": "Pay Easy", @@ -52,7 +52,7 @@ "CONTINUE_TO_LINE_PAY": "LINE Payに進む", "PAYMENT_VIA_MER_PAY": "メルペイでのお支払い", "MER_PAY_REDIRECT_MESSAGE": "支払いを完了するには、Mer Pay にリダイレクトされます。", - "CONTINUE_TO_MER_PAY": "Mer Pay に進む", + "CONTINUE_TO_MER_PAY": "メルペイに進む", "PAYMENT_VIA_RAKUTEN": "楽天でのお支払い", "RAKUTEN_REDIRECT_MESSAGE": "楽天にリダイレクトされ、支払いが完了します", "CONTINUE_TO_RAKUTEN": "引き続き楽天へ", diff --git a/payment_sdk/src/components/CardInputGroup.tsx b/payment_sdk/src/components/CardInputGroup.tsx index 9356469..ec63d9d 100644 --- a/payment_sdk/src/components/CardInputGroup.tsx +++ b/payment_sdk/src/components/CardInputGroup.tsx @@ -283,6 +283,7 @@ const getStyles = (theme: ThemeSchemeType) => { width: responsiveScale(25), height: responsiveScale(17), marginRight: responsiveScale(2), + resizeMode: "contain", }, }); }; From 3d7c571dd0865d110161b3a437a2758dccc9b0d3 Mon Sep 17 00:00:00 2001 From: Richard Ramsden Date: Fri, 16 Aug 2024 12:52:14 +0900 Subject: [PATCH 02/29] Update translation --- payment_sdk/src/assets/languages/ja.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payment_sdk/src/assets/languages/ja.json b/payment_sdk/src/assets/languages/ja.json index e4b00fd..bdc67a5 100644 --- a/payment_sdk/src/assets/languages/ja.json +++ b/payment_sdk/src/assets/languages/ja.json @@ -38,7 +38,7 @@ "NO": "いいえ", "CANCEL_PAYMENT": "支払いのキャンセル", "PAYMENT_SUCCESS": "支払い完了", - "PAYMENT_FAILED": "おっと、支払いができませんでした", + "PAYMENT_FAILED": "支払いができませんでした", "ORDER_THANK_YOU_NOTE": "ご購入ありがとうございました", "PAYMENT_RE_TRY_MSG": "入力していただいたカードで支払いを行うことができませんでした。別の支払い方法を試してみてください。", "BACK_TO_STORE": "ストアに戻る", From c9a068e933a5aee9a41caf2da8145ad667a1cf65 Mon Sep 17 00:00:00 2001 From: Richard Ramsden Date: Fri, 16 Aug 2024 12:53:21 +0900 Subject: [PATCH 03/29] Update english translation to remove slang --- payment_sdk/src/assets/languages/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payment_sdk/src/assets/languages/en.json b/payment_sdk/src/assets/languages/en.json index b1fba19..c919ffb 100644 --- a/payment_sdk/src/assets/languages/en.json +++ b/payment_sdk/src/assets/languages/en.json @@ -40,7 +40,7 @@ "PAYMENT_SUCCESS": "Payment Success", "PAYMENT_FAILED": "Payment Failed", "ORDER_THANK_YOU_NOTE": "Thank you for your order", - "PAYMENT_RE_TRY_MSG": "Hey there, We tried to charge your card but, something went wrong. Please update your payment method below to continue", + "PAYMENT_RE_TRY_MSG": "We tried to charge your card but, something went wrong. Please update your payment method below to continue", "BACK_TO_STORE": "Back to store", "UPDATE_PAYMENT_METHOD": "Update payment method", From e9cd2e9381e86b776c80a717c462e81b42746e48 Mon Sep 17 00:00:00 2001 From: Richard Ramsden Date: Sat, 17 Aug 2024 13:59:03 +0900 Subject: [PATCH 04/29] Use white for input fields and background --- payment_sdk/src/theme/defaultColorTheme.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/payment_sdk/src/theme/defaultColorTheme.ts b/payment_sdk/src/theme/defaultColorTheme.ts index 14b6aab..10bad57 100644 --- a/payment_sdk/src/theme/defaultColorTheme.ts +++ b/payment_sdk/src/theme/defaultColorTheme.ts @@ -17,10 +17,10 @@ const darkTheme = { const lightTheme = { PRIMARY_COLOR: "#0B82EE", - BACKGROUND_COLOR: "#FBFBFB", + BACKGROUND_COLOR: "#FFFFFF", ERROR: "#fc5d5d", TEXT_COLOR: "#172E44", - INPUT_BACKGROUND: '#F7F8F8', + INPUT_BACKGROUND: '#FFFFFF', INPUT_TEXT: '#172E44', INPUT_PLACEHOLDER: '#ADA4A5', INVERTED_CONTENT: '#fff', @@ -35,4 +35,4 @@ const lightTheme = { export const appTheme = { dark: darkTheme, light: lightTheme -} \ No newline at end of file +} From 7479646ffc7d0dc5b02f97916bc1f85adf786861 Mon Sep 17 00:00:00 2001 From: Tharindu Kumarasiri Date: Mon, 19 Aug 2024 19:28:14 +0530 Subject: [PATCH 05/29] Card validation errors --- payment_sdk/src/assets/languages/en.json | 4 + payment_sdk/src/assets/languages/ja.json | 4 + payment_sdk/src/components/CardInputGroup.tsx | 154 +++++++++++------- payment_sdk/src/components/Input.tsx | 14 +- .../src/components/sections/CardSection.tsx | 1 + payment_sdk/src/util/validator.ts | 40 ++++- 6 files changed, 158 insertions(+), 59 deletions(-) diff --git a/payment_sdk/src/assets/languages/en.json b/payment_sdk/src/assets/languages/en.json index c919ffb..db0fb51 100644 --- a/payment_sdk/src/assets/languages/en.json +++ b/payment_sdk/src/assets/languages/en.json @@ -75,6 +75,10 @@ "FIRST_NAME": "First Name", "LAST_NAME_PHONETIC": "Last Name (Phonetic)", "FIRST_NAME_PHONETIC": "First Name (Phonetic)", + "REQUIRED": "Required", + "CARD_NUMBER_ERROR": "You have entered an invalid card number", + "EXPIRY_DATE_ERROR": "Please input the full expiration date", + "CVV_ERROR": "Please input the CVV", "": "" } } \ No newline at end of file diff --git a/payment_sdk/src/assets/languages/ja.json b/payment_sdk/src/assets/languages/ja.json index bdc67a5..49e3f64 100644 --- a/payment_sdk/src/assets/languages/ja.json +++ b/payment_sdk/src/assets/languages/ja.json @@ -75,6 +75,10 @@ "FIRST_NAME": "名", "LAST_NAME_PHONETIC": "姓 (カタカナ)", "FIRST_NAME_PHONETIC": "名 (カタカナ)", + "REQUIRED": "必須", + "CARD_NUMBER_ERROR": "無効なカード番号を入力しました", + "EXPIRY_DATE_ERROR": "有効期限を完全に入力してください", + "CVV_ERROR": "CVVを入力してください", "": "" } } \ No newline at end of file diff --git a/payment_sdk/src/components/CardInputGroup.tsx b/payment_sdk/src/components/CardInputGroup.tsx index ec63d9d..51fcbcb 100644 --- a/payment_sdk/src/components/CardInputGroup.tsx +++ b/payment_sdk/src/components/CardInputGroup.tsx @@ -131,79 +131,109 @@ const CardInputGroup = ({ inputErrors, resetError }: Props) => { // [] // ); + const RenderCardError = () => { + if (inputErrors.number) + return ( + CARD_NUMBER_ERROR + ); + if (inputErrors.expiry) + return ( + EXPIRY_DATE_ERROR + ); + if (inputErrors.cvv) + return CVV_ERROR; + + return null; + }; + return ( - - - CARD_NUMBER - {/* */} - - - - { - resetError("number"); - if (isCardNumberValid(text)) { - const derivedText = formatCreditCardNumber(text); - dispatch({ - type: Actions.SET_CARD_NUMBER, - payload: derivedText, - }); - // Determine card type and set it - const type = determineCardType(text); - setCardType(type); - } - }} - inputStyle={styles.numberInputStyle} - error={inputErrors.number} - /> - {cardImage()} + <> + + + CARD_NUMBER + {/* */} - - + + { - resetError("expiry"); - if (validateCardExpiry(text)) { + resetError("number"); + if (isCardNumberValid(text)) { + const derivedText = formatCreditCardNumber(text); dispatch({ - type: Actions.SET_CARD_EXPIRED_DATE, - payload: formatExpiry(text), + type: Actions.SET_CARD_NUMBER, + payload: derivedText, }); + // Determine card type and set it + const type = determineCardType(text); + setCardType(type); } }} - inputStyle={styles.expiryInputStyle} - error={inputErrors.expiry} + inputStyle={styles.numberInputStyle} + error={inputErrors.number} /> + {cardImage()} - - { - resetError("cvv"); + + + { + resetError("expiry"); + if (validateCardExpiry(text)) { + dispatch({ + type: Actions.SET_CARD_EXPIRED_DATE, + payload: formatExpiry(text), + }); + } + }} + inputStyle={styles.expiryInputStyle} + error={inputErrors.expiry} + /> + + + { + resetError("cvv"); - if (text?.length < 11) - dispatch({ type: Actions.SET_CARD_CVV, payload: text }); - }} - inputStyle={styles.cvvInputStyle} - error={inputErrors.cvv} - /> - - + if (text?.length < 11) + dispatch({ type: Actions.SET_CARD_CVV, payload: text }); + }} + inputStyle={styles.cvvInputStyle} + error={inputErrors.cvv} + /> + + + - + + ); }; @@ -232,6 +262,9 @@ const getStyles = (theme: ThemeSchemeType) => { marginBottom: -responsiveScale(1), height: responsiveScale(60), }, + errorContainer: { + zIndex: 5, + }, splitRow: { flex: 1, flexDirection: "row", @@ -285,5 +318,12 @@ const getStyles = (theme: ThemeSchemeType) => { marginRight: responsiveScale(2), resizeMode: "contain", }, + errorText: { + color: theme.ERROR, + fontSize: resizeFonts(16), + textAlign: "center", + top: -responsiveScale(10), + marginBottom: responsiveScale(5), + }, }); }; diff --git a/payment_sdk/src/components/Input.tsx b/payment_sdk/src/components/Input.tsx index 3e3f6b3..e291c9e 100644 --- a/payment_sdk/src/components/Input.tsx +++ b/payment_sdk/src/components/Input.tsx @@ -25,6 +25,7 @@ interface InputProps extends TextInputProps { inputStyle?: ViewStyle; testID?: string; error?: boolean; + errorText?: string; keyboardType?: KeyboardTypeOptions; } @@ -36,6 +37,7 @@ const Input: React.FC = ({ placeholder, testID, error = false, + errorText = "", keyboardType, ...rest }: InputProps) => { @@ -61,6 +63,9 @@ const Input: React.FC = ({ testID={testID} {...rest} /> + {error && errorText && ( + {t(errorText)} + )} ); }; @@ -72,6 +77,10 @@ const getStyles = (theme: ThemeSchemeType) => { marginBottom: responsiveScale(8), color: theme.TEXT_COLOR, }, + errorMsg: { + fontSize: resizeFonts(14), + color: theme.ERROR, + }, input: { height: "100%", paddingLeft: responsiveScale(16), @@ -81,12 +90,15 @@ const getStyles = (theme: ThemeSchemeType) => { borderRadius: responsiveScale(8), backgroundColor: theme.INPUT_BACKGROUND, color: theme.INPUT_TEXT, + zIndex: 1, }, withErrorBorder: { borderColor: theme.ERROR, + color: theme.ERROR, + zIndex: 2, }, withBorder: {}, }); -} +}; export default Input; diff --git a/payment_sdk/src/components/sections/CardSection.tsx b/payment_sdk/src/components/sections/CardSection.tsx index 9e1e591..0511d52 100644 --- a/payment_sdk/src/components/sections/CardSection.tsx +++ b/payment_sdk/src/components/sections/CardSection.tsx @@ -84,6 +84,7 @@ const CardSection = (): JSX.Element => { }} inputStyle={styles.inputStyle} error={inputErrors.name} + errorText="REQUIRED" testID="cardHolderName" /> diff --git a/payment_sdk/src/util/validator.ts b/payment_sdk/src/util/validator.ts index 53d504b..91c01b5 100644 --- a/payment_sdk/src/util/validator.ts +++ b/payment_sdk/src/util/validator.ts @@ -99,7 +99,11 @@ export const validateCardFormFields = ({ setInputErrors((pre: object) => ({ ...pre, number: true })); valid = false; } - if (!cardExpiredDate) { + if (!luhnCheck(cardNumber ?? "")) { + setInputErrors((pre: object) => ({ ...pre, number: true })); + valid = false; + } + if (!expiryDateCheck(cardExpiredDate ?? "")) { setInputErrors((pre: object) => ({ ...pre, expiry: true })); valid = false; } @@ -111,6 +115,40 @@ export const validateCardFormFields = ({ return valid; }; +const luhnCheck = (cardNumber: string) => { + // accept only digits and spaces + if (/[^0-9\s]+/.test(cardNumber)) { + return false; + } + + let sum = 0; + let shouldDouble = false; + cardNumber = cardNumber.replace(/\D/g, ""); + const length = cardNumber.length; + + // iterating backwards, double every second digit + for (let i = length - 1; i >= 0; --i) { + let digit = parseInt(cardNumber.charAt(i), 10); + + // double. if doubled digit is > 9, subtract 9 + if (shouldDouble && (digit *= 2) > 9) { + digit -= 9; + } + + sum += digit; + shouldDouble = !shouldDouble; + } + + return sum % 10 === 0; +}; + +const expiryDateCheck = (expiry: string) => { + const derivedExpiry = expiry.replace(" / ", ""); + + if (derivedExpiry.length !== 4) return false; + else return true; +}; + export const validateTransferFormFields = ({ lastName, firstName, From 90e85e65c736427bedda540dbe7cf2fbfa635f48 Mon Sep 17 00:00:00 2001 From: Tharindu Kumarasiri Date: Mon, 19 Aug 2024 19:35:26 +0530 Subject: [PATCH 06/29] paymentMethods rename --- payment_sdk/readme.md | 19 +++++++++++-------- payment_sdk/src/context/KomojuProvider.tsx | 2 +- payment_sdk/src/context/MainStateProvider.tsx | 2 +- payment_sdk/src/index.ts | 4 ++-- payment_sdk/src/util/types.ts | 2 +- readme.md | 19 +++++++++++-------- 6 files changed, 27 insertions(+), 21 deletions(-) diff --git a/payment_sdk/readme.md b/payment_sdk/readme.md index 6dc0d38..cdfdcf3 100644 --- a/payment_sdk/readme.md +++ b/payment_sdk/readme.md @@ -68,14 +68,17 @@ You can [visit our docs](https://doc.komoju.com/reference/createsession) to see Many payment method types require a return URL, so if you fail to provide it, we can’t present those payment methods to your user, even if you’ve enabled them. When a customer exits your app, for example to authenticate in Safari or their banking app, provide a way for them to automatically return to your app afterward. -#### 1. Use `return_url` parameter when creating a session + +#### 1. Use `return_url` parameter when creating a session + #### 2. [Configure the custom URL scheme](https://reactnative.dev/docs/linking) in your AndroidManifest.xml and Info.plist files + > Note: > If you’re using Expo, [set your scheme](https://docs.expo.dev/guides/linking/#in-a-standalone-app) in the app.json file. To initialize Komoju in your React Native app, use the `KomojuSDK.KomojuProvider` component in the root component of your application. -`KomojuProvider` can accept `publicKey`, `payment_methods` and `language` as props. Only `publicKey` is required. +`KomojuProvider` can accept `publicKey`, `paymentMethods` and `language` as props. Only `publicKey` is required. ```tsx import { @@ -99,7 +102,7 @@ function App() { return ( // Your app code here @@ -110,8 +113,8 @@ function App() { ### Properties -| property | type | description | -| ------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------- | -| **publicKey** | `string` | Your publishable key from the KOMOJU [merchant settings page](https://komoju.com/sign_in/) (this is mandtory) | -| **payment_methods** | `Array ` | explicitly set the payment method(s) for purchase. (optional) | -| **language** | `string (LanguageTypes)` | explicitly set the language, if not set language will be picked from your session Id (optional) | +| property | type | description | +| ------------------ | ------------------------ | ------------------------------------------------------------------------------------------------------------- | +| **publicKey** | `string` | Your publishable key from the KOMOJU [merchant settings page](https://komoju.com/sign_in/) (this is mandtory) | +| **paymentMethods** | `Array ` | explicitly set the payment method(s) for purchase. (optional) | +| **language** | `string (LanguageTypes)` | explicitly set the language, if not set language will be picked from your session Id (optional) | diff --git a/payment_sdk/src/context/KomojuProvider.tsx b/payment_sdk/src/context/KomojuProvider.tsx index ec4a834..af24f1b 100644 --- a/payment_sdk/src/context/KomojuProvider.tsx +++ b/payment_sdk/src/context/KomojuProvider.tsx @@ -13,7 +13,7 @@ export const KomojuProvider = (props: KomojuProviderIprops) => { diff --git a/payment_sdk/src/context/MainStateProvider.tsx b/payment_sdk/src/context/MainStateProvider.tsx index 2e93934..6a04e3b 100644 --- a/payment_sdk/src/context/MainStateProvider.tsx +++ b/payment_sdk/src/context/MainStateProvider.tsx @@ -156,7 +156,7 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { // if user provided explicitly payments methods via props, will give priority to that over session payment methods const paymentMethods = parsePaymentMethods( - props?.payment_methods, + props?.paymentMethods, sessionData?.payment_methods ); diff --git a/payment_sdk/src/index.ts b/payment_sdk/src/index.ts index 2a211b4..59a8d7f 100644 --- a/payment_sdk/src/index.ts +++ b/payment_sdk/src/index.ts @@ -26,11 +26,11 @@ export const KomojuSDK = { export { /** - * Supported payment types to parse for payment_methods prop. + * Supported payment types to parse for paymentMethods prop. */ PaymentType as PaymentTypes, /** - * Supported payment types to parse for payment_methods prop. + * Supported languages to parse for language prop. */ LanguageTypes, }; diff --git a/payment_sdk/src/util/types.ts b/payment_sdk/src/util/types.ts index f46503b..001678f 100644 --- a/payment_sdk/src/util/types.ts +++ b/payment_sdk/src/util/types.ts @@ -2,7 +2,7 @@ import { Dispatch, ReactNode, SetStateAction } from "react"; export type InitPrams = { publicKey: string; - payment_methods?: Array; + paymentMethods?: Array; language?: LanguageTypes; useBottomSheet?: boolean; }; diff --git a/readme.md b/readme.md index 6dc0d38..cdfdcf3 100644 --- a/readme.md +++ b/readme.md @@ -68,14 +68,17 @@ You can [visit our docs](https://doc.komoju.com/reference/createsession) to see Many payment method types require a return URL, so if you fail to provide it, we can’t present those payment methods to your user, even if you’ve enabled them. When a customer exits your app, for example to authenticate in Safari or their banking app, provide a way for them to automatically return to your app afterward. -#### 1. Use `return_url` parameter when creating a session + +#### 1. Use `return_url` parameter when creating a session + #### 2. [Configure the custom URL scheme](https://reactnative.dev/docs/linking) in your AndroidManifest.xml and Info.plist files + > Note: > If you’re using Expo, [set your scheme](https://docs.expo.dev/guides/linking/#in-a-standalone-app) in the app.json file. To initialize Komoju in your React Native app, use the `KomojuSDK.KomojuProvider` component in the root component of your application. -`KomojuProvider` can accept `publicKey`, `payment_methods` and `language` as props. Only `publicKey` is required. +`KomojuProvider` can accept `publicKey`, `paymentMethods` and `language` as props. Only `publicKey` is required. ```tsx import { @@ -99,7 +102,7 @@ function App() { return ( // Your app code here @@ -110,8 +113,8 @@ function App() { ### Properties -| property | type | description | -| ------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------- | -| **publicKey** | `string` | Your publishable key from the KOMOJU [merchant settings page](https://komoju.com/sign_in/) (this is mandtory) | -| **payment_methods** | `Array ` | explicitly set the payment method(s) for purchase. (optional) | -| **language** | `string (LanguageTypes)` | explicitly set the language, if not set language will be picked from your session Id (optional) | +| property | type | description | +| ------------------ | ------------------------ | ------------------------------------------------------------------------------------------------------------- | +| **publicKey** | `string` | Your publishable key from the KOMOJU [merchant settings page](https://komoju.com/sign_in/) (this is mandtory) | +| **paymentMethods** | `Array ` | explicitly set the payment method(s) for purchase. (optional) | +| **language** | `string (LanguageTypes)` | explicitly set the language, if not set language will be picked from your session Id (optional) | From 08587202859832348329f0c16d98279e6fb7c47a Mon Sep 17 00:00:00 2001 From: Tharindu Kumarasiri Date: Mon, 19 Aug 2024 19:37:34 +0530 Subject: [PATCH 07/29] Key services integration --- example/App.tsx | 39 ++--- example/PaymentScreen.tsx | 4 +- example/assets/gear_icon.png | Bin 3314 -> 0 bytes example/components/settingsComponent.tsx | 186 ----------------------- example/services/keyService.ts | 16 ++ example/services/sessionService.ts | 21 +-- 6 files changed, 37 insertions(+), 229 deletions(-) delete mode 100644 example/assets/gear_icon.png delete mode 100644 example/components/settingsComponent.tsx create mode 100644 example/services/keyService.ts diff --git a/example/App.tsx b/example/App.tsx index 3ea7ceb..39284f3 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -1,46 +1,37 @@ -import React, {useState} from 'react'; +import React, {useEffect, useState} from 'react'; import {SafeAreaView, StyleSheet} from 'react-native'; import {KomojuSDK, LanguageTypes} from '@komoju/komoju-react-native'; import PaymentScreen from './PaymentScreen'; -import SettingsModal, {SettingsIcon} from './components/settingsComponent'; import LanguageSelectComponent from './components/languageSelectComponet'; +import getPublishableKey from './services/keyService'; /** - * You can get your public key and secret keys from https://komoju.com/en/sign_in + * You can get your Publishable key and Secret keys from https://komoju.com/en/sign_in * Your publishable key is required in order for Fields to access the KOMOJU API. * Your secret key is required in order to create a session. This should be done in your backend on a real world application */ -const PUBLIC_KEY = 'pk_test_bx0nb2z4jcczon39pm4m68l7'; -const SECRET_KEY = 'sk_test_61qdasmogfjxtaco2zocdaaw'; function App(): React.JSX.Element { - const [komojuKeys, setKomojuKeys] = useState({ - PUBLIC_KEY: PUBLIC_KEY, - SECRET_KEY: SECRET_KEY, - }); - const [modalVisible, setModalVisible] = useState(false); + const [publishableKey, setPublishableKey] = useState(''); const [language, setLanguage] = useState(LanguageTypes.ENGLISH); + useEffect(() => { + const keyService = async () => { + const key = await getPublishableKey(); + setPublishableKey(key); + }; + + keyService(); + }, []); + return ( - - - + + - - {modalVisible ? ( - - ) : null} ); } diff --git a/example/PaymentScreen.tsx b/example/PaymentScreen.tsx index e14d106..0d4a655 100644 --- a/example/PaymentScreen.tsx +++ b/example/PaymentScreen.tsx @@ -17,7 +17,7 @@ export enum CurrencyTypes { USD = 'USD', } -const PaymentScreen = ({secretKey}: {secretKey: string}) => { +const PaymentScreen = () => { const [amount, setAmount] = useState(''); const [currency, setCurrency] = useState(CurrencyTypes.JPY); const colorScheme = useColorScheme(); // Detects the color scheme of the device @@ -32,7 +32,7 @@ const PaymentScreen = ({secretKey}: {secretKey: string}) => { } // fetch a session Id to initiate payment - const sessionId = await createSession({amount, currency, secretKey}); + const sessionId = await createSession({amount, currency}); // invoke createPayment method with sessionId as parameters to open the payment portal createPayment({ diff --git a/example/assets/gear_icon.png b/example/assets/gear_icon.png deleted file mode 100644 index ba23f5c9366709c833fca371a4cc2bb2c83f6500..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3314 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 diff --git a/example/components/settingsComponent.tsx b/example/components/settingsComponent.tsx deleted file mode 100644 index 044d067..0000000 --- a/example/components/settingsComponent.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import React, {useState} from 'react'; -import { - Image, - Modal, - Pressable, - StyleSheet, - Text, - TextInput, - useColorScheme, - View, -} from 'react-native'; - -const SettingsModal = ({ - komojuKeys, - setKomojuKeys, - modalVisible, - setModalVisible, -}: { - komojuKeys: {PUBLIC_KEY: string; SECRET_KEY: string}; - setKomojuKeys: (v: any) => void; - modalVisible: boolean; - setModalVisible: (v: any) => void; -}) => { - const [komojuKeyState, setKomojuKeyState] = useState(komojuKeys); - const toggleModal = () => setModalVisible((pre: boolean) => !pre); - - const onChangePublicKey = (val: string) => { - setKomojuKeyState({...komojuKeyState, PUBLIC_KEY: val}); - }; - - const onChangePrivateKey = (val: string) => { - setKomojuKeyState({...komojuKeyState, SECRET_KEY: val}); - }; - - const onSave = () => { - setKomojuKeys(komojuKeyState); - toggleModal(); - }; - - return ( - - - - - Publishable key: - - Secret key: - - - - - - Cancel - - - - Save - - - - - - ); -}; - -export const SettingsIcon = ({ - setModalVisible, -}: { - setModalVisible: (v: any) => void; -}) => { - const toggleModal = () => setModalVisible((pre: boolean) => !pre); - - const colorScheme = useColorScheme(); // Detects the color scheme of the device - - const dynamicStyles = StyleSheet.create({ - container: { - backgroundColor: colorScheme === 'dark' ? '#333' : '#FFF', - }, - }); - - return ( - - - - - - ); -}; - -const styles = StyleSheet.create({ - gearIcon: { - width: 30, - height: 30, - }, - gearIconContainer: { - alignItems: 'flex-end', - paddingHorizontal: 20, - paddingTop: 20, - }, - centeredView: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - marginTop: 22, - }, - modalView: { - margin: 20, - backgroundColor: 'white', - borderRadius: 20, - padding: 35, - alignItems: 'center', - shadowColor: '#000', - shadowOffset: { - width: 0, - height: 2, - }, - shadowOpacity: 0.25, - shadowRadius: 4, - elevation: 5, - }, - modalText: { - marginBottom: 5, - textAlign: 'center', - color: '#000', - }, - inputContainer: { - alignItems: 'flex-start', - }, - buttonContainer: { - flexDirection: 'row', - }, - buttonClose: { - borderColor: '#2196F3', - backgroundColor: 'white', - borderWidth: 1, - marginRight: 10, - }, - buttonSave: { - backgroundColor: '#2196F3', - }, - textStyle: { - color: 'white', - fontWeight: 'bold', - textAlign: 'center', - }, - cancelBtnText: { - color: '#2196F3', - }, - button: { - borderRadius: 20, - padding: 10, - elevation: 2, - }, - input: { - height: 40, - minWidth: 200, - borderColor: 'gray', - borderRadius: 8, - borderWidth: 1, - marginBottom: 20, - paddingHorizontal: 8, - backgroundColor: 'white', - color: '#000', - }, -}); - -export default SettingsModal; diff --git a/example/services/keyService.ts b/example/services/keyService.ts new file mode 100644 index 0000000..969e216 --- /dev/null +++ b/example/services/keyService.ts @@ -0,0 +1,16 @@ +import {Alert} from 'react-native'; + +const getPublishableKey = async () => { + try { + const url = 'https://rn-komoju-app.glitch.me/serve-keys'; + const response = await fetch(url); + const {publishableKey} = await response.json(); + + return publishableKey; + } catch (e) { + Alert.alert('Error', 'Unable to fetch session. Is your server running?'); + return null; + } +}; + +export default getPublishableKey; diff --git a/example/services/sessionService.ts b/example/services/sessionService.ts index d96de67..06e7eba 100644 --- a/example/services/sessionService.ts +++ b/example/services/sessionService.ts @@ -6,44 +6,31 @@ import {Alert} from 'react-native'; type createSessionProps = { amount: string; currency: string; - secretKey: string; }; const createSession = async ({ amount, currency, - secretKey, }: createSessionProps): Promise => { try { - if (!secretKey) { - console.error('Secret Key Not Found'); - throw new Error('Secret Key Required'); - } - - const url = 'https://komoju.com/api/v1/sessions'; + const url = 'https://rn-komoju-app.glitch.me/create-session'; const options = { method: 'POST', headers: { accept: 'application/json', 'content-type': 'application/json', - Authorization: `Basic ${btoa(secretKey + ':')}`, }, body: JSON.stringify({ - default_locale: 'en', amount, currency, - return_url: 'komapp://', }), }; const response = await fetch(url, options); - const {id} = await response.json(); + const {sessionId} = await response.json(); - return id; + return sessionId; } catch (e) { - Alert.alert( - 'Error', - 'Unable to fetch session. Did you set PUBLIC_KEY and SECRET_KEY at App.tsx?', - ); + Alert.alert('Error', 'Unable to fetch session. Is your server running?'); return null; } }; From c7f4c9d26ad4bddf718b0f6c52f150cca0419496 Mon Sep 17 00:00:00 2001 From: Tharindu Kumarasiri Date: Mon, 19 Aug 2024 21:05:48 +0530 Subject: [PATCH 08/29] publishable key rename --- example/App.tsx | 4 +++- example/services/constants.ts | 3 +++ example/services/keyService.ts | 4 ++-- example/services/sessionService.ts | 4 ++-- payment_sdk/readme.md | 16 ++++++++-------- payment_sdk/src/__tests__/CardSection.test.tsx | 2 +- payment_sdk/src/__tests__/paymentService.test.ts | 2 +- payment_sdk/src/context/KomojuProvider.tsx | 2 +- payment_sdk/src/context/MainStateProvider.tsx | 8 ++++---- payment_sdk/src/services/payForSessionService.ts | 6 +++--- payment_sdk/src/services/sessionShow.ts | 6 +++--- payment_sdk/src/util/constants.ts | 9 ++++----- payment_sdk/src/util/types.ts | 4 ++-- readme.md | 16 ++++++++-------- 14 files changed, 45 insertions(+), 41 deletions(-) create mode 100644 example/services/constants.ts diff --git a/example/App.tsx b/example/App.tsx index 39284f3..c1bb450 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -29,7 +29,9 @@ function App(): React.JSX.Element { - + diff --git a/example/services/constants.ts b/example/services/constants.ts new file mode 100644 index 0000000..ccc17cd --- /dev/null +++ b/example/services/constants.ts @@ -0,0 +1,3 @@ +export const PUBLISHABLE_KEY_URL = 'https://rn-komoju-app.glitch.me/serve-keys'; +export const CREATE_SESSION_URL = + 'https://rn-komoju-app.glitch.me/create-session'; diff --git a/example/services/keyService.ts b/example/services/keyService.ts index 969e216..ea654d1 100644 --- a/example/services/keyService.ts +++ b/example/services/keyService.ts @@ -1,9 +1,9 @@ import {Alert} from 'react-native'; +import {PUBLISHABLE_KEY_URL} from './constants'; const getPublishableKey = async () => { try { - const url = 'https://rn-komoju-app.glitch.me/serve-keys'; - const response = await fetch(url); + const response = await fetch(PUBLISHABLE_KEY_URL); const {publishableKey} = await response.json(); return publishableKey; diff --git a/example/services/sessionService.ts b/example/services/sessionService.ts index 06e7eba..a69148f 100644 --- a/example/services/sessionService.ts +++ b/example/services/sessionService.ts @@ -1,4 +1,5 @@ import {Alert} from 'react-native'; +import {CREATE_SESSION_URL} from './constants'; // Refer documentation on https://doc.komoju.com/reference/post_sessions // for creating a session @@ -13,7 +14,6 @@ const createSession = async ({ currency, }: createSessionProps): Promise => { try { - const url = 'https://rn-komoju-app.glitch.me/create-session'; const options = { method: 'POST', headers: { @@ -25,7 +25,7 @@ const createSession = async ({ currency, }), }; - const response = await fetch(url, options); + const response = await fetch(CREATE_SESSION_URL, options); const {sessionId} = await response.json(); return sessionId; diff --git a/payment_sdk/readme.md b/payment_sdk/readme.md index cdfdcf3..57a0963 100644 --- a/payment_sdk/readme.md +++ b/payment_sdk/readme.md @@ -33,7 +33,7 @@ import { KomojuSDK } from "@komoju/komoju-react-native"; function App() { return ( - + ); @@ -78,7 +78,7 @@ When a customer exits your app, for example to authenticate in Safari or their b To initialize Komoju in your React Native app, use the `KomojuSDK.KomojuProvider` component in the root component of your application. -`KomojuProvider` can accept `publicKey`, `paymentMethods` and `language` as props. Only `publicKey` is required. +`KomojuProvider` can accept `publishableKey`, `paymentMethods` and `language` as props. Only `publishableKey` is required. ```tsx import { @@ -88,20 +88,20 @@ import { } from "@komoju/komoju-react-native"; function App() { - const [publicKey, setPublicKey] = useState(""); + const [publishableKey, setpublishableKey] = useState(""); - const fetchPublicKey = async () => { + const fetchpublishableKey = async () => { const key = await fetchKey(); // fetch key from your server here - setPublicKey(key); + setpublishableKey(key); }; useEffect(() => { - fetchPublicKey(); + fetchpublishableKey(); }, []); return ( @@ -115,6 +115,6 @@ function App() { | property | type | description | | ------------------ | ------------------------ | ------------------------------------------------------------------------------------------------------------- | -| **publicKey** | `string` | Your publishable key from the KOMOJU [merchant settings page](https://komoju.com/sign_in/) (this is mandtory) | +| **publishableKey** | `string` | Your publishable key from the KOMOJU [merchant settings page](https://komoju.com/sign_in/) (this is mandtory) | | **paymentMethods** | `Array ` | explicitly set the payment method(s) for purchase. (optional) | | **language** | `string (LanguageTypes)` | explicitly set the language, if not set language will be picked from your session Id (optional) | diff --git a/payment_sdk/src/__tests__/CardSection.test.tsx b/payment_sdk/src/__tests__/CardSection.test.tsx index 4927e07..7bbe1a7 100644 --- a/payment_sdk/src/__tests__/CardSection.test.tsx +++ b/payment_sdk/src/__tests__/CardSection.test.tsx @@ -12,7 +12,7 @@ export const mockState = { cardholderName: "John Doe", cardCVV: "123", cardNumber: "4100000000000100", - cardExpiredDate: "08/25", + cardExpiredDate: "08 / 25", amount: 1000, currency: "USD", }; diff --git a/payment_sdk/src/__tests__/paymentService.test.ts b/payment_sdk/src/__tests__/paymentService.test.ts index 31496c2..f6f4ade 100644 --- a/payment_sdk/src/__tests__/paymentService.test.ts +++ b/payment_sdk/src/__tests__/paymentService.test.ts @@ -5,7 +5,7 @@ import { PaymentType } from "../util/types"; global.fetch = jest.fn(); const paymentDetails = { - publicKey: "samplepublickKey", + publishableKey: "samplepublishablekKey", sessionId: "sessionID123", paymentType: PaymentType.CREDIT, paymentDetails: { diff --git a/payment_sdk/src/context/KomojuProvider.tsx b/payment_sdk/src/context/KomojuProvider.tsx index af24f1b..a78e0d7 100644 --- a/payment_sdk/src/context/KomojuProvider.tsx +++ b/payment_sdk/src/context/KomojuProvider.tsx @@ -12,7 +12,7 @@ export const KomojuProvider = (props: KomojuProviderIprops) => { { const onUserCancel = async () => { if (onDismissCallback.current) { const sessionShowPayload = { - publicKey: props?.publicKey, + publishableKey: props?.publishableKey, sessionId: sessionIdRef.current, }; @@ -135,7 +135,7 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { // Fetching session data from given session ID const sessionData = await sessionShow({ sessionId, - publicKey: props.publicKey, + publishableKey: props.publishableKey, }); // validating the session data and closing the payment gateway if data is not valid @@ -180,7 +180,7 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { // if this is a session flow, check until session response changes from 'pending' to 'completed' or 'error' const sessionShowPayload = { - publicKey: props.publicKey, + publishableKey: props.publishableKey, sessionId: sessionIdRef.current, }; @@ -221,7 +221,7 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { const response = await payForSession({ paymentType, sessionId, - publicKey: props.publicKey, + publishableKey: props.publishableKey, paymentDetails, }); diff --git a/payment_sdk/src/services/payForSessionService.ts b/payment_sdk/src/services/payForSessionService.ts index 96aa0a7..6bacf6a 100644 --- a/payment_sdk/src/services/payForSessionService.ts +++ b/payment_sdk/src/services/payForSessionService.ts @@ -9,7 +9,7 @@ import { /** * Processes a payment for a given session. * @param {object} params - The parameters for processing the payment. - * @param {string} params.publicKey - The public key for authorization. + * @param {string} params.publishableKey - The publishable key for authorization. * @param {string} params.sessionId - The session ID for the payment. * @param {PaymentType} params.paymentType - The type of payment to process. * @param {object} params.paymentDetails - The details of the relevant payment type. @@ -23,7 +23,7 @@ import { */ const payForSession = async ({ - publicKey, + publishableKey, sessionId, paymentType, paymentDetails, @@ -115,7 +115,7 @@ const payForSession = async ({ // payment POST request options of headers and body should be as bellow const options = { method: "POST", - headers: API_HEADER(publicKey), + headers: API_HEADER(publishableKey), body: JSON.stringify({ capture: "auto", payment_details, diff --git a/payment_sdk/src/services/sessionShow.ts b/payment_sdk/src/services/sessionShow.ts index a311aec..63ebb07 100644 --- a/payment_sdk/src/services/sessionShow.ts +++ b/payment_sdk/src/services/sessionShow.ts @@ -4,18 +4,18 @@ import { SessionShowResponseType } from "@util/types"; type SessionShowProps = { sessionId: string; - publicKey: string; + publishableKey: string; }; const sessionShow = async ({ sessionId, - publicKey, + publishableKey, }: SessionShowProps): Promise => { try { const url = `${BASE_URL_API}/sessions/${sessionId}`; const options = { method: "GET", - headers: API_HEADER(publicKey), + headers: API_HEADER(publishableKey), }; const response = await fetch(url, options); diff --git a/payment_sdk/src/util/constants.ts b/payment_sdk/src/util/constants.ts index 889397a..bf3eef7 100644 --- a/payment_sdk/src/util/constants.ts +++ b/payment_sdk/src/util/constants.ts @@ -1,13 +1,13 @@ import { PaymentType } from "./types"; -export const noop = () => { }; +export const noop = () => {}; export const BASE_URL = "https://komoju.com"; export const BASE_URL_API = `${BASE_URL}/api/v1`; -export const API_HEADER = (publicKey: string) => ({ +export const API_HEADER = (publishableKey: string) => ({ accept: "application/json", "content-type": "application/json", "KOMOJU-VIA": "mobile_react", - Authorization: `Basic ${btoa(publicKey + ":")}`, + Authorization: `Basic ${btoa(publishableKey + ":")}`, }); export const paymentSuccessCtaText = "BACK_TO_STORE"; @@ -34,7 +34,7 @@ export enum SimpleRedirectTypeModes { linepay = "LINE_PAY", merpay = "MER_PAY", rakuten = "RAKUTEN", - aupay = "AU_PAY" + aupay = "AU_PAY", } export const LangKeys: { [key in PaymentType]: string } = { @@ -52,5 +52,4 @@ export const LangKeys: { [key in PaymentType]: string } = { [PaymentType.RAKUTEN]: "RAKUTEN", [PaymentType.WEB_MONEY]: "WEB_MONEY", [PaymentType.NET_CASH]: "NET_CASH", - }; diff --git a/payment_sdk/src/util/types.ts b/payment_sdk/src/util/types.ts index 001678f..9ae1955 100644 --- a/payment_sdk/src/util/types.ts +++ b/payment_sdk/src/util/types.ts @@ -1,7 +1,7 @@ import { Dispatch, ReactNode, SetStateAction } from "react"; export type InitPrams = { - publicKey: string; + publishableKey: string; paymentMethods?: Array; language?: LanguageTypes; useBottomSheet?: boolean; @@ -103,7 +103,7 @@ export enum CurrencyTypes { } export type payForSessionProps = { - publicKey: string; + publishableKey: string; sessionId: string; paymentType: PaymentType; paymentDetails?: CardDetailsType & diff --git a/readme.md b/readme.md index cdfdcf3..66a5cfc 100644 --- a/readme.md +++ b/readme.md @@ -33,7 +33,7 @@ import { KomojuSDK } from "@komoju/komoju-react-native"; function App() { return ( - + ); @@ -78,7 +78,7 @@ When a customer exits your app, for example to authenticate in Safari or their b To initialize Komoju in your React Native app, use the `KomojuSDK.KomojuProvider` component in the root component of your application. -`KomojuProvider` can accept `publicKey`, `paymentMethods` and `language` as props. Only `publicKey` is required. +`KomojuProvider` can accept `publishableKey`, `paymentMethods` and `language` as props. Only `publishableKey` is required. ```tsx import { @@ -88,20 +88,20 @@ import { } from "@komoju/komoju-react-native"; function App() { - const [publicKey, setPublicKey] = useState(""); + const [publishableKey, setpublishableKey] = useState(""); - const fetchPublicKey = async () => { + const fetchpublishableKey = async () => { const key = await fetchKey(); // fetch key from your server here - setPublicKey(key); + setpublishableKey(key); }; useEffect(() => { - fetchPublicKey(); + fetchpublishableKey(); }, []); return ( @@ -115,6 +115,6 @@ function App() { | property | type | description | | ------------------ | ------------------------ | ------------------------------------------------------------------------------------------------------------- | -| **publicKey** | `string` | Your publishable key from the KOMOJU [merchant settings page](https://komoju.com/sign_in/) (this is mandtory) | +| **publishableKey** | `string` | Your publishable key from the KOMOJU [merchant settings page](https://komoju.com/sign_in/) (this is mandtory) | | **paymentMethods** | `Array ` | explicitly set the payment method(s) for purchase. (optional) | | **language** | `string (LanguageTypes)` | explicitly set the language, if not set language will be picked from your session Id (optional) | From cd6278e2bef5a9e8656eb9ad32db123cf9d22620 Mon Sep 17 00:00:00 2001 From: Tharindu Kumarasiri Date: Tue, 20 Aug 2024 10:07:41 +0530 Subject: [PATCH 09/29] Added a loader to example app --- example/App.tsx | 7 ++++++- example/PaymentScreen.tsx | 10 +++++++-- example/components/Loader.tsx | 21 +++++++++++++++++++ payment_sdk/src/context/MainStateProvider.tsx | 2 +- 4 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 example/components/Loader.tsx diff --git a/example/App.tsx b/example/App.tsx index c1bb450..96c3ed8 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -5,6 +5,7 @@ import {KomojuSDK, LanguageTypes} from '@komoju/komoju-react-native'; import PaymentScreen from './PaymentScreen'; import LanguageSelectComponent from './components/languageSelectComponet'; import getPublishableKey from './services/keyService'; +import Loader from './components/Loader'; /** * You can get your Publishable key and Secret keys from https://komoju.com/en/sign_in @@ -14,12 +15,14 @@ import getPublishableKey from './services/keyService'; function App(): React.JSX.Element { const [publishableKey, setPublishableKey] = useState(''); + const [loading, setLoading] = useState(true); const [language, setLanguage] = useState(LanguageTypes.ENGLISH); useEffect(() => { const keyService = async () => { const key = await getPublishableKey(); setPublishableKey(key); + setLoading(false); }; keyService(); @@ -32,8 +35,10 @@ function App(): React.JSX.Element { - + + + {loading ? : null} ); } diff --git a/example/PaymentScreen.tsx b/example/PaymentScreen.tsx index 0d4a655..84c6795 100644 --- a/example/PaymentScreen.tsx +++ b/example/PaymentScreen.tsx @@ -1,4 +1,4 @@ -import React, {useState} from 'react'; +import React, {Dispatch, SetStateAction, useState} from 'react'; import { useColorScheme, View, @@ -17,7 +17,11 @@ export enum CurrencyTypes { USD = 'USD', } -const PaymentScreen = () => { +const PaymentScreen = ({ + setLoading, +}: { + setLoading: Dispatch>; +}) => { const [amount, setAmount] = useState(''); const [currency, setCurrency] = useState(CurrencyTypes.JPY); const colorScheme = useColorScheme(); // Detects the color scheme of the device @@ -26,6 +30,7 @@ const PaymentScreen = () => { const {createPayment} = KomojuSDK.useKomoju(); const handleSessionPay = async () => { + setLoading(true); if (!amount) { Alert.alert('Error', 'Please enter an amount to checkout'); return; @@ -33,6 +38,7 @@ const PaymentScreen = () => { // fetch a session Id to initiate payment const sessionId = await createSession({amount, currency}); + setLoading(false); // invoke createPayment method with sessionId as parameters to open the payment portal createPayment({ diff --git a/example/components/Loader.tsx b/example/components/Loader.tsx new file mode 100644 index 0000000..b644656 --- /dev/null +++ b/example/components/Loader.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +import {ActivityIndicator, StyleSheet, View} from 'react-native'; + +const Loader = () => { + return ( + + + + ); +}; + +export default Loader; + +const styles = StyleSheet.create({ + container: { + ...StyleSheet.absoluteFillObject, + alignItems: 'center', + justifyContent: 'center', + }, +}); diff --git a/payment_sdk/src/context/MainStateProvider.tsx b/payment_sdk/src/context/MainStateProvider.tsx index c1a6068..2da5958 100644 --- a/payment_sdk/src/context/MainStateProvider.tsx +++ b/payment_sdk/src/context/MainStateProvider.tsx @@ -54,7 +54,7 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { return () => { subscription.remove(); }; - }, []); + }, [props]); const openPaymentSheet = () => { if (props?.useBottomSheet) { From 424df2c3097fd4a5f638339152aebef5f1c40f89 Mon Sep 17 00:00:00 2001 From: Richard Ramsden Date: Tue, 20 Aug 2024 14:22:08 +0900 Subject: [PATCH 10/29] Update JP translation for redirection --- payment_sdk/src/assets/languages/ja.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/payment_sdk/src/assets/languages/ja.json b/payment_sdk/src/assets/languages/ja.json index 49e3f64..3126511 100644 --- a/payment_sdk/src/assets/languages/ja.json +++ b/payment_sdk/src/assets/languages/ja.json @@ -60,7 +60,7 @@ "PAYMENT_VIA_AU_PAY": "AU Payでのお支払い", "AU_PAY_REDIRECT_MESSAGE": "支払いを完了するには、AU Pay にリダイレクトされます。", "CONTINUE_TO_AU_PAY": "AU Payへ進む", - "LIGHT_BOX_CONTENT": "ブラウザ ウィンドウが開くことに注意してください。購入後はリダイレクトされて戻ります。", + "LIGHT_BOX_CONTENT": "ブラウザのウィンドウが開きます。ご購入後、自動的にこのアプリに戻ります。", "NET_CASH_INPUT_LABEL": "NET CASH情報", "NET_CASH_INPUT_PLACEHOLDER": "NET CASH id", "BIT_CASH_INPUT_LABEL": "BITCASH情報", @@ -81,4 +81,4 @@ "CVV_ERROR": "CVVを入力してください", "": "" } - } \ No newline at end of file + } From f480f4981ce8156f4fe1bc5b8c8750d3ff1d51fd Mon Sep 17 00:00:00 2001 From: Tharindu Kumarasiri Date: Tue, 20 Aug 2024 13:38:47 +0530 Subject: [PATCH 11/29] Input error style fixes --- example/services/keyService.ts | 5 ++++- payment_sdk/src/assets/languages/en.json | 1 + payment_sdk/src/assets/languages/ja.json | 1 + payment_sdk/src/components/CardInputGroup.tsx | 7 +++---- payment_sdk/src/components/Input.tsx | 1 + payment_sdk/src/components/sections/CardSection.tsx | 3 +-- payment_sdk/src/components/sections/KonbiniSection.tsx | 5 +++-- 7 files changed, 14 insertions(+), 9 deletions(-) diff --git a/example/services/keyService.ts b/example/services/keyService.ts index ea654d1..1a14064 100644 --- a/example/services/keyService.ts +++ b/example/services/keyService.ts @@ -8,7 +8,10 @@ const getPublishableKey = async () => { return publishableKey; } catch (e) { - Alert.alert('Error', 'Unable to fetch session. Is your server running?'); + Alert.alert( + 'Error', + 'Unable to fetch publishable Key. Is your server running?', + ); return null; } }; diff --git a/payment_sdk/src/assets/languages/en.json b/payment_sdk/src/assets/languages/en.json index db0fb51..3f515d8 100644 --- a/payment_sdk/src/assets/languages/en.json +++ b/payment_sdk/src/assets/languages/en.json @@ -79,6 +79,7 @@ "CARD_NUMBER_ERROR": "You have entered an invalid card number", "EXPIRY_DATE_ERROR": "Please input the full expiration date", "CVV_ERROR": "Please input the CVV", + "EMAIL_ERROR": "Please enter a valid email address", "": "" } } \ No newline at end of file diff --git a/payment_sdk/src/assets/languages/ja.json b/payment_sdk/src/assets/languages/ja.json index 49e3f64..127b8a8 100644 --- a/payment_sdk/src/assets/languages/ja.json +++ b/payment_sdk/src/assets/languages/ja.json @@ -79,6 +79,7 @@ "CARD_NUMBER_ERROR": "無効なカード番号を入力しました", "EXPIRY_DATE_ERROR": "有効期限を完全に入力してください", "CVV_ERROR": "CVVを入力してください", + "EMAIL_ERROR": "有効なメールアドレスを入力してください", "": "" } } \ No newline at end of file diff --git a/payment_sdk/src/components/CardInputGroup.tsx b/payment_sdk/src/components/CardInputGroup.tsx index 51fcbcb..3e87c5f 100644 --- a/payment_sdk/src/components/CardInputGroup.tsx +++ b/payment_sdk/src/components/CardInputGroup.tsx @@ -320,10 +320,9 @@ const getStyles = (theme: ThemeSchemeType) => { }, errorText: { color: theme.ERROR, - fontSize: resizeFonts(16), - textAlign: "center", - top: -responsiveScale(10), - marginBottom: responsiveScale(5), + fontSize: resizeFonts(14), + marginLeft: responsiveScale(16), + top: -responsiveScale(20), }, }); }; diff --git a/payment_sdk/src/components/Input.tsx b/payment_sdk/src/components/Input.tsx index e291c9e..49397b6 100644 --- a/payment_sdk/src/components/Input.tsx +++ b/payment_sdk/src/components/Input.tsx @@ -80,6 +80,7 @@ const getStyles = (theme: ThemeSchemeType) => { errorMsg: { fontSize: resizeFonts(14), color: theme.ERROR, + marginTop: responsiveScale(5), }, input: { height: "100%", diff --git a/payment_sdk/src/components/sections/CardSection.tsx b/payment_sdk/src/components/sections/CardSection.tsx index 0511d52..afa0ccd 100644 --- a/payment_sdk/src/components/sections/CardSection.tsx +++ b/payment_sdk/src/components/sections/CardSection.tsx @@ -110,8 +110,7 @@ const styles = StyleSheet.create({ }, cardNameContainer: { margin: responsiveScale(16), - marginBottom: responsiveScale(24), - height: responsiveScale(60), + marginBottom: responsiveScale(8), }, btn: { height: responsiveScale(60), diff --git a/payment_sdk/src/components/sections/KonbiniSection.tsx b/payment_sdk/src/components/sections/KonbiniSection.tsx index 18d315b..f64b4b5 100644 --- a/payment_sdk/src/components/sections/KonbiniSection.tsx +++ b/payment_sdk/src/components/sections/KonbiniSection.tsx @@ -108,6 +108,7 @@ const KonbiniSection = (): JSX.Element => { }} inputStyle={styles.inputStyle} error={inputErrors.name} + errorText="REQUIRED" /> @@ -122,6 +123,7 @@ const KonbiniSection = (): JSX.Element => { }} inputStyle={styles.inputStyle} error={inputErrors.email} + errorText="EMAIL_ERROR" inputMode="email" /> @@ -154,8 +156,7 @@ const styles = StyleSheet.create({ }, inputContainer: { margin: responsiveScale(16), - marginBottom: responsiveScale(24), - height: responsiveScale(60), + marginBottom: responsiveScale(8), }, inputStyle: { height: responsiveScale(50), From 3e040d43b1438f9f0003f4eb1d875a9a34be79f6 Mon Sep 17 00:00:00 2001 From: Tharindu Kumarasiri Date: Thu, 22 Aug 2024 10:26:53 +0530 Subject: [PATCH 12/29] selected language passed to session service --- example/App.tsx | 2 +- example/PaymentScreen.tsx | 4 +++- example/services/sessionService.ts | 3 +++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/example/App.tsx b/example/App.tsx index 96c3ed8..307aef2 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -35,7 +35,7 @@ function App(): React.JSX.Element { - + {loading ? : null} diff --git a/example/PaymentScreen.tsx b/example/PaymentScreen.tsx index 84c6795..861a6cb 100644 --- a/example/PaymentScreen.tsx +++ b/example/PaymentScreen.tsx @@ -18,8 +18,10 @@ export enum CurrencyTypes { } const PaymentScreen = ({ + language, setLoading, }: { + language: string; setLoading: Dispatch>; }) => { const [amount, setAmount] = useState(''); @@ -37,7 +39,7 @@ const PaymentScreen = ({ } // fetch a session Id to initiate payment - const sessionId = await createSession({amount, currency}); + const sessionId = await createSession({amount, currency, language}); setLoading(false); // invoke createPayment method with sessionId as parameters to open the payment portal diff --git a/example/services/sessionService.ts b/example/services/sessionService.ts index a69148f..02f6b1e 100644 --- a/example/services/sessionService.ts +++ b/example/services/sessionService.ts @@ -7,11 +7,13 @@ import {CREATE_SESSION_URL} from './constants'; type createSessionProps = { amount: string; currency: string; + language: string; }; const createSession = async ({ amount, currency, + language, }: createSessionProps): Promise => { try { const options = { @@ -23,6 +25,7 @@ const createSession = async ({ body: JSON.stringify({ amount, currency, + language, }), }; const response = await fetch(CREATE_SESSION_URL, options); From d09dde77c17463e59a2d32c08c8380ab4c2418d7 Mon Sep 17 00:00:00 2001 From: chathurapathiranage Date: Thu, 22 Aug 2024 14:38:51 +0530 Subject: [PATCH 13/29] user cancelled on web fix --- payment_sdk/src/assets/languages/en.json | 2 ++ payment_sdk/src/assets/languages/ja.json | 4 +++- payment_sdk/src/components/PaymentModal.tsx | 7 ++++++- payment_sdk/src/components/ResponseScreen.tsx | 10 +++++----- payment_sdk/src/components/Sheet.tsx | 8 ++++++-- payment_sdk/src/context/MainStateProvider.tsx | 16 +++++++++++++++- payment_sdk/src/util/types.ts | 5 +++++ 7 files changed, 42 insertions(+), 10 deletions(-) diff --git a/payment_sdk/src/assets/languages/en.json b/payment_sdk/src/assets/languages/en.json index 3f515d8..38c850e 100644 --- a/payment_sdk/src/assets/languages/en.json +++ b/payment_sdk/src/assets/languages/en.json @@ -39,8 +39,10 @@ "CANCEL_PAYMENT": "Cancel Payment", "PAYMENT_SUCCESS": "Payment Success", "PAYMENT_FAILED": "Payment Failed", + "PAYMENT_CANCELLED": "Payment Canceled", "ORDER_THANK_YOU_NOTE": "Thank you for your order", "PAYMENT_RE_TRY_MSG": "We tried to charge your card but, something went wrong. Please update your payment method below to continue", + "PAYMENT_CANCELLED_MSG": "We noticed that you’ve canceled the payment process. If this was a mistake, you can try again to complete your purchase.", "BACK_TO_STORE": "Back to store", "UPDATE_PAYMENT_METHOD": "Update payment method", diff --git a/payment_sdk/src/assets/languages/ja.json b/payment_sdk/src/assets/languages/ja.json index 38e974e..288744c 100644 --- a/payment_sdk/src/assets/languages/ja.json +++ b/payment_sdk/src/assets/languages/ja.json @@ -38,9 +38,11 @@ "NO": "いいえ", "CANCEL_PAYMENT": "支払いのキャンセル", "PAYMENT_SUCCESS": "支払い完了", - "PAYMENT_FAILED": "支払いができませんでした", + "PAYMENT_FAILED": "支払いができませんでした", + "PAYMENT_CANCELLED": "支払いがキャンセルされました", "ORDER_THANK_YOU_NOTE": "ご購入ありがとうございました", "PAYMENT_RE_TRY_MSG": "入力していただいたカードで支払いを行うことができませんでした。別の支払い方法を試してみてください。", + "PAYMENT_CANCELLED_MSG": "お支払いプロセスがキャンセルされたことを確認しました。もしこれが誤りであれば、再度お試しいただき、購入を完了してください。", "BACK_TO_STORE": "ストアに戻る", "UPDATE_PAYMENT_METHOD": "支払い方法を変更する", diff --git a/payment_sdk/src/components/PaymentModal.tsx b/payment_sdk/src/components/PaymentModal.tsx index 2a382cd..affa94d 100644 --- a/payment_sdk/src/components/PaymentModal.tsx +++ b/payment_sdk/src/components/PaymentModal.tsx @@ -82,6 +82,8 @@ const PaymentModal = ({ return paymentSuccessCtaText; case ResponseScreenStatuses.FAILED: return paymentFailedCtaText; + case ResponseScreenStatuses.CANCELLED: + return paymentSuccessCtaText; default: return ""; } @@ -96,6 +98,8 @@ const PaymentModal = ({ type: Actions.SET_PAYMENT_STATE, payload: "", }); + case ResponseScreenStatuses.CANCELLED: + return closeSheet(false); default: return ""; } @@ -105,6 +109,7 @@ const PaymentModal = ({ closeSheet( !( paymentState === ResponseScreenStatuses.SUCCESS || + paymentState === ResponseScreenStatuses.CANCELLED || // TODO: Fix this type error // @ts-expect-error - Property 'COMPLETE' does not exist on type 'ResponseScreenStatuses'. paymentState === ResponseScreenStatuses.COMPLETE @@ -123,7 +128,7 @@ const PaymentModal = ({ - PAYMENT_OPTIONS + {!paymentState ? 'PAYMENT_OPTIONS' : ''} void; @@ -24,12 +24,12 @@ const ResponseScreen = ({ status, message, onPress, onPressLabel }: Props) => { const renderMessageContent = useMemo(() => { const title = status === ResponseScreenStatuses.SUCCESS - ? "PAYMENT_SUCCESS" - : "PAYMENT_FAILED"; + ? "PAYMENT_SUCCESS" : status === ResponseScreenStatuses.CANCELLED ? + "PAYMENT_CANCELLED" : "PAYMENT_FAILED"; const defaultMessage = status === ResponseScreenStatuses.SUCCESS - ? "ORDER_THANK_YOU_NOTE" - : "PAYMENT_RE_TRY_MSG"; + ? "ORDER_THANK_YOU_NOTE" : status === ResponseScreenStatuses.CANCELLED ? + "PAYMENT_CANCELLED_MSG" : "PAYMENT_RE_TRY_MSG"; const msg = message || defaultMessage; return ( diff --git a/payment_sdk/src/components/Sheet.tsx b/payment_sdk/src/components/Sheet.tsx index d0d2ea4..dfbc945 100644 --- a/payment_sdk/src/components/Sheet.tsx +++ b/payment_sdk/src/components/Sheet.tsx @@ -171,6 +171,8 @@ const Sheet: ForwardRefRenderFunction = ( return paymentSuccessCtaText; case ResponseScreenStatuses.FAILED: return paymentFailedCtaText; + case ResponseScreenStatuses.CANCELLED: + return paymentSuccessCtaText; default: return ""; } @@ -185,6 +187,8 @@ const Sheet: ForwardRefRenderFunction = ( type: Actions.SET_PAYMENT_STATE, payload: "", }); + case ResponseScreenStatuses.CANCELLED: + return closeSheet(false); default: return ""; } @@ -213,6 +217,7 @@ const Sheet: ForwardRefRenderFunction = ( closeSheet( !( paymentState === ResponseScreenStatuses.SUCCESS || + paymentState === ResponseScreenStatuses.CANCELLED || // TODO: Fix this type error // @ts-expect-error - Property 'COMPLETE' does not exist on type 'ResponseScreenStatuses'. paymentState === ResponseScreenStatuses.COMPLETE @@ -227,8 +232,7 @@ const Sheet: ForwardRefRenderFunction = ( { // TODO: Fix this type error - // @ts-expect-error - Property 'COMPLETE' does not exist on type 'ResponseScreenStatuses'. - paymentState && paymentState !== ResponseScreenStatuses.COMPLETE ? ( + paymentState ? ( { payload: ResponseScreenStatuses.FAILED, }); + // when payment is cancelled by the user + const onPaymentCancelled = () => { + dispatch({ + type: Actions.RESET_STATES, + payload: initialState, + }); + dispatch({ + type: Actions.SET_PAYMENT_STATE, + payload: ResponseScreenStatuses.CANCELLED, + }); + } + const onUserCancel = async () => { if (onDismissCallback.current) { const sessionShowPayload = { @@ -188,7 +200,7 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { let sessionResponse = await sessionShow(sessionShowPayload); // Polling until session verification status changes - while (sessionResponse?.status === PaymentStatuses.PENDING) { + while (sessionResponse?.status === PaymentStatuses.PENDING && sessionResponse?.payment?.status !== PaymentStatuses.CANCELLED) { sessionResponse = await sessionShow(sessionShowPayload); } @@ -203,6 +215,8 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { // TODO: Fix this type error // @ts-expect-error - Argument of type 'PaymentSessionResponse' is not assignable to parameter of type 'string'. onCompleteCallback.current(sessionResponse); + } else if (sessionResponse?.payment?.status === PaymentStatuses.CANCELLED) { + onPaymentCancelled(); } else { onPaymentFailed(); } diff --git a/payment_sdk/src/util/types.ts b/payment_sdk/src/util/types.ts index 9ae1955..b95a5b2 100644 --- a/payment_sdk/src/util/types.ts +++ b/payment_sdk/src/util/types.ts @@ -74,6 +74,7 @@ export enum PaymentStatuses { ERROR = "error", SUCCESS = "completed", PENDING = "pending", + CANCELLED = 'cancelled' } export enum TokenResponseStatuses { @@ -90,6 +91,8 @@ export enum ResponseScreenStatuses { FAILED = "failed", /** For displaying payment instruction screens and disabling the cancel payment popup */ COMPLETE = "complete", + /** For displaying payment instruction screens for cancelled by the user */ + CANCELLED = 'cancelled', } export enum CurrencySign { @@ -180,6 +183,7 @@ export type SessionShowResponseType = { payment_details: { instructions_url?: string; }; + status?: string }; }; @@ -239,6 +243,7 @@ export type State = CardDetailsType & paymentState: | ResponseScreenStatuses.SUCCESS | ResponseScreenStatuses.FAILED + | ResponseScreenStatuses.CANCELLED | ""; /** * States of the Bank transfer and Pay Easy fields. From e57fdbb7c0f35be678414cc2148775b214edd43c Mon Sep 17 00:00:00 2001 From: Tharindu Kumarasiri Date: Fri, 23 Aug 2024 09:57:10 +0530 Subject: [PATCH 14/29] Card input style bug fix --- payment_sdk/src/components/CardInputGroup.tsx | 12 ++++++++++-- payment_sdk/src/components/Input.tsx | 3 ++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/payment_sdk/src/components/CardInputGroup.tsx b/payment_sdk/src/components/CardInputGroup.tsx index 3e87c5f..52acafb 100644 --- a/payment_sdk/src/components/CardInputGroup.tsx +++ b/payment_sdk/src/components/CardInputGroup.tsx @@ -222,7 +222,12 @@ const CardInputGroup = ({ inputErrors, resetError }: Props) => { if (text?.length < 11) dispatch({ type: Actions.SET_CARD_CVV, payload: text }); }} - inputStyle={styles.cvvInputStyle} + inputStyle={[ + styles.cvvInputStyle, + inputErrors.cvv && + !inputErrors.expiry && + styles.cvvInputErrorStyle, + ]} error={inputErrors.cvv} /> @@ -286,7 +291,10 @@ const getStyles = (theme: ThemeSchemeType) => { borderTopLeftRadius: 0, borderTopRightRadius: 0, borderBottomLeftRadius: 0, - marginLeft: -responsiveScale(1), + borderLeftWidth: 0, + }, + cvvInputErrorStyle: { + borderLeftWidth: 1, }, titleScanRow: { flexDirection: "row", diff --git a/payment_sdk/src/components/Input.tsx b/payment_sdk/src/components/Input.tsx index 49397b6..fc6af2e 100644 --- a/payment_sdk/src/components/Input.tsx +++ b/payment_sdk/src/components/Input.tsx @@ -8,6 +8,7 @@ import { ViewStyle, KeyboardTypeOptions, TextInputProps, + StyleProp, } from "react-native"; import { useTranslation } from "react-i18next"; @@ -22,7 +23,7 @@ interface InputProps extends TextInputProps { onChangeText: (text: string) => void; label?: string; placeholder?: string; - inputStyle?: ViewStyle; + inputStyle?: StyleProp; testID?: string; error?: boolean; errorText?: string; From e310b73b7c7cf24b1605d63cc70f8001e3032846 Mon Sep 17 00:00:00 2001 From: Tharindu Kumarasiri Date: Fri, 23 Aug 2024 09:57:33 +0530 Subject: [PATCH 15/29] Hardcode the version into API requests --- payment_sdk/src/util/constants.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/payment_sdk/src/util/constants.ts b/payment_sdk/src/util/constants.ts index bf3eef7..52ef1d2 100644 --- a/payment_sdk/src/util/constants.ts +++ b/payment_sdk/src/util/constants.ts @@ -7,6 +7,7 @@ export const API_HEADER = (publishableKey: string) => ({ accept: "application/json", "content-type": "application/json", "KOMOJU-VIA": "mobile_react", + "X-KOMOJU-API-VERSION": "2024-07-15", Authorization: `Basic ${btoa(publishableKey + ":")}`, }); From 177fe5c1c2d09794c57391cfa41bc27540c44db9 Mon Sep 17 00:00:00 2001 From: chathurapathiranage Date: Mon, 26 Aug 2024 08:40:24 +0530 Subject: [PATCH 16/29] response screen changes --- payment_sdk/src/components/ResponseScreen.tsx | 59 +++++++++++-------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/payment_sdk/src/components/ResponseScreen.tsx b/payment_sdk/src/components/ResponseScreen.tsx index 7d08877..e0db85c 100644 --- a/payment_sdk/src/components/ResponseScreen.tsx +++ b/payment_sdk/src/components/ResponseScreen.tsx @@ -1,17 +1,38 @@ import React, { useCallback, useMemo } from "react"; - -import { Image, StyleSheet, View } from "react-native"; - +import { Image, StyleSheet, View, ImageSourcePropType } from "react-native"; import { ResponseScreenStatuses, ThemeSchemeType } from "@util/types"; - import { resizeFonts, responsiveScale } from "@theme/scalling"; import { useCurrentTheme } from "@theme/useCurrentTheme"; - import KomojuText from "./KomojuText"; import SubmitButton from "./SubmitButton"; +type StatusConfig = { + title: string; + defaultMessage: string; + image: ImageSourcePropType; +}; + +// Configuration object for all statuses +const statusConfigs: Partial> = { + [ResponseScreenStatuses.SUCCESS]: { + title: "PAYMENT_SUCCESS", + defaultMessage: "ORDER_THANK_YOU_NOTE", + image: require("../assets/images/success.png"), + }, + [ResponseScreenStatuses.FAILED]: { + title: "PAYMENT_FAILED", + defaultMessage: "PAYMENT_RE_TRY_MSG", + image: require("../assets/images/error.png"), + }, + [ResponseScreenStatuses.CANCELLED]: { + title: "PAYMENT_CANCELLED", + defaultMessage: "PAYMENT_CANCELLED_MSG", + image: require("../assets/images/cancelled.png"), + }, +}; + type Props = { - status: ResponseScreenStatuses.SUCCESS | ResponseScreenStatuses.FAILED | ResponseScreenStatuses.CANCELLED; + status: ResponseScreenStatuses; message?: string; onPressLabel: string; onPress: () => void; @@ -21,32 +42,22 @@ const ResponseScreen = ({ status, message, onPress, onPressLabel }: Props) => { const theme = useCurrentTheme(); const styles = getStyles(theme); + const statusConfig = statusConfigs[status]; + const renderMessageContent = useMemo(() => { - const title = - status === ResponseScreenStatuses.SUCCESS - ? "PAYMENT_SUCCESS" : status === ResponseScreenStatuses.CANCELLED ? - "PAYMENT_CANCELLED" : "PAYMENT_FAILED"; - const defaultMessage = - status === ResponseScreenStatuses.SUCCESS - ? "ORDER_THANK_YOU_NOTE" : status === ResponseScreenStatuses.CANCELLED ? - "PAYMENT_CANCELLED_MSG" : "PAYMENT_RE_TRY_MSG"; - const msg = message || defaultMessage; + const msg = message || statusConfig?.defaultMessage; return ( - {title} + {statusConfig?.title} {msg} ); - }, [status, message]); + }, [status, message, statusConfig]); const renderIcon = useMemo(() => { - const source = - status === ResponseScreenStatuses.SUCCESS - ? require("../assets/images/success.png") - : require("../assets/images/error.png"); - return ; - }, [status]); + return ; + }, [statusConfig]); const memoizedOnPress = useCallback(onPress, [onPress]); @@ -99,4 +110,4 @@ const getStyles = (theme: ThemeSchemeType) => { right: 0, }, }); -}; +}; \ No newline at end of file From d233ecde47adef28060cfa187af4f8b935961bad Mon Sep 17 00:00:00 2001 From: Richard Ramsden Date: Mon, 26 Aug 2024 13:15:49 +0900 Subject: [PATCH 17/29] Updates to JA translations --- payment_sdk/src/assets/languages/ja.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/payment_sdk/src/assets/languages/ja.json b/payment_sdk/src/assets/languages/ja.json index 288744c..3e13d62 100644 --- a/payment_sdk/src/assets/languages/ja.json +++ b/payment_sdk/src/assets/languages/ja.json @@ -39,7 +39,7 @@ "CANCEL_PAYMENT": "支払いのキャンセル", "PAYMENT_SUCCESS": "支払い完了", "PAYMENT_FAILED": "支払いができませんでした", - "PAYMENT_CANCELLED": "支払いがキャンセルされました", + "PAYMENT_CANCELLED": "キャンセルされました", "ORDER_THANK_YOU_NOTE": "ご購入ありがとうございました", "PAYMENT_RE_TRY_MSG": "入力していただいたカードで支払いを行うことができませんでした。別の支払い方法を試してみてください。", "PAYMENT_CANCELLED_MSG": "お支払いプロセスがキャンセルされたことを確認しました。もしこれが誤りであれば、再度お試しいただき、購入を完了してください。", @@ -78,8 +78,8 @@ "LAST_NAME_PHONETIC": "姓 (カタカナ)", "FIRST_NAME_PHONETIC": "名 (カタカナ)", "REQUIRED": "必須", - "CARD_NUMBER_ERROR": "無効なカード番号を入力しました", - "EXPIRY_DATE_ERROR": "有効期限を完全に入力してください", + "CARD_NUMBER_ERROR": "カード番号が無効です", + "EXPIRY_DATE_ERROR": "有効期限を入力してください", "CVV_ERROR": "CVVを入力してください", "EMAIL_ERROR": "有効なメールアドレスを入力してください", "": "" From 72953fb1234da05558b74f3e0f2447933d5848c3 Mon Sep 17 00:00:00 2001 From: Tharindu Kumarasiri Date: Mon, 26 Aug 2024 16:54:16 +0530 Subject: [PATCH 18/29] Awaiting status screen --- .../src/assets/images/awaitingPayment.png | Bin 0 -> 3730 bytes payment_sdk/src/components/PaymentModal.tsx | 36 +++++++------- payment_sdk/src/components/ResponseScreen.tsx | 9 +++- payment_sdk/src/components/Sheet.tsx | 31 ++++++------ payment_sdk/src/context/MainStateProvider.tsx | 44 +++++++++++------- payment_sdk/src/util/types.ts | 8 ++-- 6 files changed, 67 insertions(+), 61 deletions(-) create mode 100644 payment_sdk/src/assets/images/awaitingPayment.png diff --git a/payment_sdk/src/assets/images/awaitingPayment.png b/payment_sdk/src/assets/images/awaitingPayment.png new file mode 100644 index 0000000000000000000000000000000000000000..0b45b26a180a95aed6a7bfdbbb9d7fa057dd41b9 GIT binary patch literal 3730 zcmV;D4sG#?P)xOmG3}3J5C@zdub_0k{Hi1>y?qIb-I$Z}QH0 z8X%pd>!BkVsygSg%H7B?M0^z3kE`o5Fwny`7^$;Px)^_MC#eN8$St%aC{f4 z>(~fSU_n@dg41n15&`_lpFTUK-r7&SBmYN`7eui3gDzh2r%hN83TZh+5PZ9y6X8Z) zmcf#+w$*uTx=1WoH|o_czdVOIfeq@yf_9{Vw>OHXK>pf9@QFO*V8WPwpI^KKdbmJ z6v129om?lyA!hj$&R_CQ;?-?giLnqg(r)N^D_HAO?-SS)^7q^URb|nIAvlHthtO+v z>*02nF1`R^(a<*f7(tG`W0NDum@odR5blzHB1IKX2aamN4T{(hb$py(Ay@WT;7b~Zh#%V�g_ipJ4>?3Se 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 literal 0 HcmV?d00001 diff --git a/payment_sdk/src/components/PaymentModal.tsx b/payment_sdk/src/components/PaymentModal.tsx index affa94d..b30995c 100644 --- a/payment_sdk/src/components/PaymentModal.tsx +++ b/payment_sdk/src/components/PaymentModal.tsx @@ -79,11 +79,11 @@ const PaymentModal = ({ const getCtaText = () => { switch (paymentState) { case ResponseScreenStatuses.SUCCESS: + case ResponseScreenStatuses.COMPLETE: + case ResponseScreenStatuses.CANCELLED: return paymentSuccessCtaText; case ResponseScreenStatuses.FAILED: return paymentFailedCtaText; - case ResponseScreenStatuses.CANCELLED: - return paymentSuccessCtaText; default: return ""; } @@ -92,14 +92,14 @@ const PaymentModal = ({ const ctaOnPress = () => { switch (paymentState) { case ResponseScreenStatuses.SUCCESS: + case ResponseScreenStatuses.COMPLETE: + case ResponseScreenStatuses.CANCELLED: return closeSheet(false); case ResponseScreenStatuses.FAILED: return dispatch({ type: Actions.SET_PAYMENT_STATE, payload: "", }); - case ResponseScreenStatuses.CANCELLED: - return closeSheet(false); default: return ""; } @@ -110,8 +110,6 @@ const PaymentModal = ({ !( paymentState === ResponseScreenStatuses.SUCCESS || paymentState === ResponseScreenStatuses.CANCELLED || - // TODO: Fix this type error - // @ts-expect-error - Property 'COMPLETE' does not exist on type 'ResponseScreenStatuses'. paymentState === ResponseScreenStatuses.COMPLETE ) ); @@ -128,26 +126,24 @@ const PaymentModal = ({ - {!paymentState ? 'PAYMENT_OPTIONS' : ''} + + {!paymentState ? "PAYMENT_OPTIONS" : ""} + - { - // TODO: Fix this type error - // @ts-expect-error - Property 'COMPLETE' does not exist on type 'ResponseScreenStatuses'. - paymentState && paymentState !== ResponseScreenStatuses.COMPLETE ? ( - - ) : ( - - ) - } + {paymentState ? ( + + ) : ( + + )} ); diff --git a/payment_sdk/src/components/ResponseScreen.tsx b/payment_sdk/src/components/ResponseScreen.tsx index e0db85c..761cf46 100644 --- a/payment_sdk/src/components/ResponseScreen.tsx +++ b/payment_sdk/src/components/ResponseScreen.tsx @@ -27,7 +27,12 @@ const statusConfigs: Partial> = { [ResponseScreenStatuses.CANCELLED]: { title: "PAYMENT_CANCELLED", defaultMessage: "PAYMENT_CANCELLED_MSG", - image: require("../assets/images/cancelled.png"), + image: require("../assets/images/error.png"), + }, + [ResponseScreenStatuses.COMPLETE]: { + title: "PAYMENT_WAITING", + defaultMessage: "PAYMENT_CANCELLED_MSG", + image: require("../assets/images/awaitingPayment.png"), }, }; @@ -110,4 +115,4 @@ const getStyles = (theme: ThemeSchemeType) => { right: 0, }, }); -}; \ No newline at end of file +}; diff --git a/payment_sdk/src/components/Sheet.tsx b/payment_sdk/src/components/Sheet.tsx index dfbc945..04df961 100644 --- a/payment_sdk/src/components/Sheet.tsx +++ b/payment_sdk/src/components/Sheet.tsx @@ -168,11 +168,11 @@ const Sheet: ForwardRefRenderFunction = ( const getCtaText = () => { switch (paymentState) { case ResponseScreenStatuses.SUCCESS: + case ResponseScreenStatuses.COMPLETE: + case ResponseScreenStatuses.CANCELLED: return paymentSuccessCtaText; case ResponseScreenStatuses.FAILED: return paymentFailedCtaText; - case ResponseScreenStatuses.CANCELLED: - return paymentSuccessCtaText; default: return ""; } @@ -181,14 +181,14 @@ const Sheet: ForwardRefRenderFunction = ( const ctaOnPress = () => { switch (paymentState) { case ResponseScreenStatuses.SUCCESS: + case ResponseScreenStatuses.COMPLETE: + case ResponseScreenStatuses.CANCELLED: return closeSheet(false); case ResponseScreenStatuses.FAILED: return dispatch({ type: Actions.SET_PAYMENT_STATE, payload: "", }); - case ResponseScreenStatuses.CANCELLED: - return closeSheet(false); default: return ""; } @@ -218,8 +218,6 @@ const Sheet: ForwardRefRenderFunction = ( !( paymentState === ResponseScreenStatuses.SUCCESS || paymentState === ResponseScreenStatuses.CANCELLED || - // TODO: Fix this type error - // @ts-expect-error - Property 'COMPLETE' does not exist on type 'ResponseScreenStatuses'. paymentState === ResponseScreenStatuses.COMPLETE ) ) @@ -230,18 +228,15 @@ const Sheet: ForwardRefRenderFunction = ( /> - { - // TODO: Fix this type error - paymentState ? ( - - ) : ( - - ) - } + {paymentState ? ( + + ) : ( + + )} ); diff --git a/payment_sdk/src/context/MainStateProvider.tsx b/payment_sdk/src/context/MainStateProvider.tsx index 0e74458..f7b6dac 100644 --- a/payment_sdk/src/context/MainStateProvider.tsx +++ b/payment_sdk/src/context/MainStateProvider.tsx @@ -71,12 +71,15 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { setModalVisible(false); }; - // when payment is success global state is rest and invoking the success screen - const onPaymentSuccess = () => { + const resetGlobalStates = () => dispatch({ type: Actions.RESET_STATES, payload: initialState, }); + + // when payment is success global state is rest and invoking the success screen + const onPaymentSuccess = () => { + resetGlobalStates(); dispatch({ type: Actions.SET_PAYMENT_STATE, payload: ResponseScreenStatuses.SUCCESS, @@ -92,15 +95,21 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { // when payment is cancelled by the user const onPaymentCancelled = () => { + resetGlobalStates(); dispatch({ - type: Actions.RESET_STATES, - payload: initialState, + type: Actions.SET_PAYMENT_STATE, + payload: ResponseScreenStatuses.CANCELLED, }); + }; + + // when payment is completed but awaiting payment + const onPaymentAwaiting = () => { + resetGlobalStates(); dispatch({ type: Actions.SET_PAYMENT_STATE, - payload: ResponseScreenStatuses.CANCELLED, + payload: ResponseScreenStatuses.COMPLETE, }); - } + }; const onUserCancel = async () => { if (onDismissCallback.current) { @@ -200,7 +209,10 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { let sessionResponse = await sessionShow(sessionShowPayload); // Polling until session verification status changes - while (sessionResponse?.status === PaymentStatuses.PENDING && sessionResponse?.payment?.status !== PaymentStatuses.CANCELLED) { + while ( + sessionResponse?.status === PaymentStatuses.PENDING && + sessionResponse?.payment?.status !== PaymentStatuses.CANCELLED + ) { sessionResponse = await sessionShow(sessionShowPayload); } @@ -243,14 +255,13 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { if (response?.status === PaymentStatuses.PENDING) { openURL(response.redirect_url); - } else if ( - response?.status === PaymentStatuses.SUCCESS && - response?.payment?.payment_details?.instructions_url - ) { - openURL(response?.payment?.payment_details?.instructions_url); - onPaymentSuccess(); } else if (response?.status === PaymentStatuses.SUCCESS) { - onPaymentSuccess(); + if (response?.payment?.status === PaymentStatuses.SUCCESS) { + onPaymentSuccess(); + } else if (response?.payment?.payment_details?.instructions_url) { + openURL(response?.payment?.payment_details?.instructions_url); + onPaymentAwaiting(); + } } else { onPaymentFailed(); } @@ -259,10 +270,7 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { const createPayment = useCallback( ({ sessionId, onComplete, onDismiss }: CreatePaymentFuncType) => { - dispatch({ - type: Actions.RESET_STATES, - payload: initialState, - }); + resetGlobalStates(); // setting client provided onComplete callback into a ref // TODO: Fix this type error diff --git a/payment_sdk/src/util/types.ts b/payment_sdk/src/util/types.ts index b95a5b2..0d454c3 100644 --- a/payment_sdk/src/util/types.ts +++ b/payment_sdk/src/util/types.ts @@ -74,7 +74,7 @@ export enum PaymentStatuses { ERROR = "error", SUCCESS = "completed", PENDING = "pending", - CANCELLED = 'cancelled' + CANCELLED = "cancelled", } export enum TokenResponseStatuses { @@ -92,7 +92,7 @@ export enum ResponseScreenStatuses { /** For displaying payment instruction screens and disabling the cancel payment popup */ COMPLETE = "complete", /** For displaying payment instruction screens for cancelled by the user */ - CANCELLED = 'cancelled', + CANCELLED = "cancelled", } export enum CurrencySign { @@ -154,6 +154,7 @@ export type SessionPayResponseType = { status: string; payment: { payment_details: { instructions_url: string }; + status?: string; }; }; @@ -183,7 +184,7 @@ export type SessionShowResponseType = { payment_details: { instructions_url?: string; }; - status?: string + status?: string; }; }; @@ -242,6 +243,7 @@ export type State = CardDetailsType & */ paymentState: | ResponseScreenStatuses.SUCCESS + | ResponseScreenStatuses.COMPLETE | ResponseScreenStatuses.FAILED | ResponseScreenStatuses.CANCELLED | ""; From 13f46a54c5d340f037588d4eb16a550b4f0b1ea2 Mon Sep 17 00:00:00 2001 From: Tharindu Kumarasiri Date: Mon, 26 Aug 2024 16:58:03 +0530 Subject: [PATCH 19/29] import oder reformatted --- payment_sdk/src/components/ResponseScreen.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/payment_sdk/src/components/ResponseScreen.tsx b/payment_sdk/src/components/ResponseScreen.tsx index 761cf46..f9a8858 100644 --- a/payment_sdk/src/components/ResponseScreen.tsx +++ b/payment_sdk/src/components/ResponseScreen.tsx @@ -1,8 +1,12 @@ import React, { useCallback, useMemo } from "react"; + import { Image, StyleSheet, View, ImageSourcePropType } from "react-native"; + import { ResponseScreenStatuses, ThemeSchemeType } from "@util/types"; + import { resizeFonts, responsiveScale } from "@theme/scalling"; import { useCurrentTheme } from "@theme/useCurrentTheme"; + import KomojuText from "./KomojuText"; import SubmitButton from "./SubmitButton"; From c42e0546253bf50dffb7d217f8fe2995ba136c29 Mon Sep 17 00:00:00 2001 From: chathurapathiranage Date: Mon, 26 Aug 2024 20:34:42 +0530 Subject: [PATCH 20/29] added background listner for the response --- payment_sdk/src/components/ResponseScreen.tsx | 1 + payment_sdk/src/components/SubmitButton.tsx | 13 +++- payment_sdk/src/context/MainStateProvider.tsx | 64 ++++++++++++++++++- payment_sdk/src/context/state.ts | 6 ++ payment_sdk/src/util/types.ts | 5 ++ 5 files changed, 84 insertions(+), 5 deletions(-) diff --git a/payment_sdk/src/components/ResponseScreen.tsx b/payment_sdk/src/components/ResponseScreen.tsx index f9a8858..ff2ad99 100644 --- a/payment_sdk/src/components/ResponseScreen.tsx +++ b/payment_sdk/src/components/ResponseScreen.tsx @@ -111,6 +111,7 @@ const getStyles = (theme: ThemeSchemeType) => { marginBottom: responsiveScale(16), textAlign: "center", paddingHorizontal: responsiveScale(32), + color: theme.TEXT_COLOR, }, bottomButton: { position: "absolute", diff --git a/payment_sdk/src/components/SubmitButton.tsx b/payment_sdk/src/components/SubmitButton.tsx index 7b73bb5..2ce949b 100644 --- a/payment_sdk/src/components/SubmitButton.tsx +++ b/payment_sdk/src/components/SubmitButton.tsx @@ -1,9 +1,11 @@ -import React from "react"; +import React, { useContext } from "react"; import { StyleSheet, Text, TouchableOpacity } from "react-native"; import { useTranslation } from "react-i18next"; +import { Actions, DispatchContext } from "@context/state"; + import { ThemeSchemeType } from "@util/types"; import { resizeFonts, responsiveScale } from "@theme/scalling"; @@ -20,11 +22,18 @@ const SubmitButton = ({ label, labelSuffix, onPress, testID }: Props) => { const { t } = useTranslation(); const theme = useCurrentTheme(); const styles = getStyles(theme); + const dispatch = useContext(DispatchContext); + + const onSubmit = () => { + dispatch({ type: Actions.SET_PROCEED_PAYMENT, payload: true }); + onPress(); + } + return ( {labelSuffix ? `${t(label)} ${labelSuffix}` : t(label)} diff --git a/payment_sdk/src/context/MainStateProvider.tsx b/payment_sdk/src/context/MainStateProvider.tsx index f7b6dac..76f77da 100644 --- a/payment_sdk/src/context/MainStateProvider.tsx +++ b/payment_sdk/src/context/MainStateProvider.tsx @@ -7,7 +7,7 @@ import React, { useState, } from "react"; -import { Alert, Linking } from "react-native"; +import { Alert, AppState, Linking } from "react-native"; import i18next from "i18next"; @@ -30,11 +30,12 @@ import { import { validateSessionResponse } from "@util/validator"; import "@assets/languages/i18n"; -import { Actions, DispatchContext, KomojuContext } from "./state"; +import { Actions, DispatchContext, KomojuContext, StateContext } from "./state"; export const MainStateProvider = (props: KomojuProviderIprops) => { const dispatch = useContext(DispatchContext); const [modalVisible, setModalVisible] = useState(false); + const { processedPayment } = useContext(StateContext) const sheetRef = useRef(null); // ref to hold client provided onComplete callback @@ -56,6 +57,62 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { }; }, [props]); + useEffect(() => { + // Add event listener for deep links + const subscription = AppState.addEventListener( + "change", + handleBackgroundStateChange + ); + + return () => { + subscription.remove(); + }; + }, [processedPayment]); + + // This callback method is used to check the background state + const handleBackgroundStateChange = async () => { + startLoading(); + + if (processedPayment) { + // if this is a session flow, check until session response changes from 'pending' to 'completed' or 'error' + const sessionShowPayload = { + publishableKey: props.publishableKey, + sessionId: sessionIdRef.current, + }; + + // fetch session status to check if the payment is completed + let sessionResponse = await sessionShow(sessionShowPayload); + + // Polling until session verification status changes + while ( + sessionResponse?.status === PaymentStatuses.PENDING && + sessionResponse?.payment?.status !== PaymentStatuses.CANCELLED + ) { + sessionResponse = await sessionShow(sessionShowPayload); + } + + // if payment success showing success screen or if failed showing error screen + if (sessionResponse?.status === PaymentStatuses.SUCCESS) { + if (sessionResponse?.payment?.payment_details?.instructions_url) { + openURL(sessionResponse?.payment?.payment_details?.instructions_url); + } + onPaymentSuccess(); + // calling user passed onComplete method with session response data + onCompleteCallback.current && + // TODO: Fix this type error + // @ts-expect-error - Argument of type 'PaymentSessionResponse' is not assignable to parameter of type 'string'. + onCompleteCallback.current(sessionResponse); + } else if (sessionResponse?.payment?.status === PaymentStatuses.CANCELLED) { + onPaymentCancelled(); + } else { + onPaymentFailed(); + } + } + dispatch({ type: Actions.SET_PROCEED_PAYMENT, payload: false }); + // after all api calls are done stopping the loading indicator + stopLoading(); + }; + const openPaymentSheet = () => { if (props?.useBottomSheet) { sheetRef?.current?.open(); @@ -233,6 +290,7 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { onPaymentFailed(); } + dispatch({ type: Actions.SET_PROCEED_PAYMENT, payload: false }); // after all api calls are done stopping the loading indicator stopLoading(); }; @@ -302,7 +360,7 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { // TODO: Fix this type error // eslint-disable-next-line @typescript-eslint/no-unused-vars - const initializeKomoju = useCallback((params: InitPrams) => {}, []); + const initializeKomoju = useCallback((params: InitPrams) => { }, []); const renderPaymentUI = useMemo(() => { const UI = props?.useBottomSheet ? ( diff --git a/payment_sdk/src/context/state.ts b/payment_sdk/src/context/state.ts index aea5ef8..6e78765 100644 --- a/payment_sdk/src/context/state.ts +++ b/payment_sdk/src/context/state.ts @@ -32,6 +32,7 @@ export const Actions = { SET_PAYMENT_STATE: "SET_PAYMENT_STATE", SET_PAYMENT_METHODS: "SET_PAYMENT_METHODS", SESSION_PAY: "SESSION_PAY", + SET_PROCEED_PAYMENT: "SET_PROCEED_PAYMENT", }; /** @@ -123,6 +124,11 @@ export function reducer(state: State, action: ActionType) { ...state, paymentMethods: action.payload, }; + case Actions.SET_PROCEED_PAYMENT: + return { + ...state, + processedPayment: action.payload, + }; default: throw new Error(); } diff --git a/payment_sdk/src/util/types.ts b/payment_sdk/src/util/types.ts index 0d454c3..8943485 100644 --- a/payment_sdk/src/util/types.ts +++ b/payment_sdk/src/util/types.ts @@ -219,6 +219,10 @@ export type State = CardDetailsType & * Global loading state. to display loading animation over sdk and disable buttons. */ loading: boolean; + /** + * Global processedPayment state. to indicate whether the payment was processed + */ + processedPayment: boolean; /** * Callback function to call relevant api for each payment type. */ @@ -262,6 +266,7 @@ export type sessionPayProps = { export const initialState: State = { paymentType: PaymentType.CREDIT, loading: false, + processedPayment: false, /** credit card payment related states start */ cardholderName: "", From 626827547e220bd28f87ce8573b37c4eb0495d7d Mon Sep 17 00:00:00 2001 From: chathurapathiranage Date: Mon, 26 Aug 2024 21:54:35 +0530 Subject: [PATCH 21/29] added background listner for the response --- payment_sdk/src/assets/languages/en.json | 2 ++ payment_sdk/src/assets/languages/ja.json | 4 +++- payment_sdk/src/components/PaymentModal.tsx | 6 +++++- payment_sdk/src/components/ResponseScreen.tsx | 5 +++++ payment_sdk/src/context/MainStateProvider.tsx | 12 +++++++++++- payment_sdk/src/util/types.ts | 4 ++++ 6 files changed, 30 insertions(+), 3 deletions(-) diff --git a/payment_sdk/src/assets/languages/en.json b/payment_sdk/src/assets/languages/en.json index 38c850e..08c0eec 100644 --- a/payment_sdk/src/assets/languages/en.json +++ b/payment_sdk/src/assets/languages/en.json @@ -45,6 +45,8 @@ "PAYMENT_CANCELLED_MSG": "We noticed that you’ve canceled the payment process. If this was a mistake, you can try again to complete your purchase.", "BACK_TO_STORE": "Back to store", "UPDATE_PAYMENT_METHOD": "Update payment method", + "SESSION_EXPIRED": "Session Expired", + "SESSION_EXPIRED_MSG": "This session has already expired. Please try again.", "PAYMENT_VIA_ALI_PAY": "Payment via Alipay", "ALI_PAY_REDIRECT_MESSAGE": "You will be redirected to Alipay to complete the payment", diff --git a/payment_sdk/src/assets/languages/ja.json b/payment_sdk/src/assets/languages/ja.json index 3e13d62..98c0a41 100644 --- a/payment_sdk/src/assets/languages/ja.json +++ b/payment_sdk/src/assets/languages/ja.json @@ -44,7 +44,9 @@ "PAYMENT_RE_TRY_MSG": "入力していただいたカードで支払いを行うことができませんでした。別の支払い方法を試してみてください。", "PAYMENT_CANCELLED_MSG": "お支払いプロセスがキャンセルされたことを確認しました。もしこれが誤りであれば、再度お試しいただき、購入を完了してください。", "BACK_TO_STORE": "ストアに戻る", - "UPDATE_PAYMENT_METHOD": "支払い方法を変更する", + "UPDATE_PAYMENT_METHOD": "支払い方法を変更する", + "SESSION_EXPIRED": "セッションが期限切れ", + "SESSION_EXPIRED_MSG": "このセッションは既に期限切れです。再度お試しください。", "PAYMENT_VIA_ALI_PAY": "Alipayによる支払い", "ALI_PAY_REDIRECT_MESSAGE": "支払いを完了するには、Alipay にリダイレクトされます。", diff --git a/payment_sdk/src/components/PaymentModal.tsx b/payment_sdk/src/components/PaymentModal.tsx index b30995c..befbf28 100644 --- a/payment_sdk/src/components/PaymentModal.tsx +++ b/payment_sdk/src/components/PaymentModal.tsx @@ -81,6 +81,7 @@ const PaymentModal = ({ case ResponseScreenStatuses.SUCCESS: case ResponseScreenStatuses.COMPLETE: case ResponseScreenStatuses.CANCELLED: + case ResponseScreenStatuses.EXPIRED: return paymentSuccessCtaText; case ResponseScreenStatuses.FAILED: return paymentFailedCtaText; @@ -95,6 +96,8 @@ const PaymentModal = ({ case ResponseScreenStatuses.COMPLETE: case ResponseScreenStatuses.CANCELLED: return closeSheet(false); + case ResponseScreenStatuses.EXPIRED: + return closeSheet(false); case ResponseScreenStatuses.FAILED: return dispatch({ type: Actions.SET_PAYMENT_STATE, @@ -110,7 +113,8 @@ const PaymentModal = ({ !( paymentState === ResponseScreenStatuses.SUCCESS || paymentState === ResponseScreenStatuses.CANCELLED || - paymentState === ResponseScreenStatuses.COMPLETE + paymentState === ResponseScreenStatuses.COMPLETE || + paymentState === ResponseScreenStatuses.EXPIRED ) ); }; diff --git a/payment_sdk/src/components/ResponseScreen.tsx b/payment_sdk/src/components/ResponseScreen.tsx index ff2ad99..6b1cddb 100644 --- a/payment_sdk/src/components/ResponseScreen.tsx +++ b/payment_sdk/src/components/ResponseScreen.tsx @@ -38,6 +38,11 @@ const statusConfigs: Partial> = { defaultMessage: "PAYMENT_CANCELLED_MSG", image: require("../assets/images/awaitingPayment.png"), }, + [ResponseScreenStatuses.EXPIRED]: { + title: "SESSION_EXPIRED", + defaultMessage: "SESSION_EXPIRED_MSG", + image: require("../assets/images/error.png"), + }, }; type Props = { diff --git a/payment_sdk/src/context/MainStateProvider.tsx b/payment_sdk/src/context/MainStateProvider.tsx index 76f77da..480f28f 100644 --- a/payment_sdk/src/context/MainStateProvider.tsx +++ b/payment_sdk/src/context/MainStateProvider.tsx @@ -86,7 +86,8 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { // Polling until session verification status changes while ( sessionResponse?.status === PaymentStatuses.PENDING && - sessionResponse?.payment?.status !== PaymentStatuses.CANCELLED + sessionResponse?.payment?.status !== PaymentStatuses.CANCELLED && + !sessionResponse?.expired ) { sessionResponse = await sessionShow(sessionShowPayload); } @@ -104,6 +105,8 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { onCompleteCallback.current(sessionResponse); } else if (sessionResponse?.payment?.status === PaymentStatuses.CANCELLED) { onPaymentCancelled(); + } else if (sessionResponse?.expired) { + onSessionExpired() } else { onPaymentFailed(); } @@ -168,6 +171,13 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { }); }; + // when payment is failed invoking the error screen + const onSessionExpired = () => + dispatch({ + type: Actions.SET_PAYMENT_STATE, + payload: ResponseScreenStatuses.EXPIRED, + }); + const onUserCancel = async () => { if (onDismissCallback.current) { const sessionShowPayload = { diff --git a/payment_sdk/src/util/types.ts b/payment_sdk/src/util/types.ts index 8943485..88c3ea9 100644 --- a/payment_sdk/src/util/types.ts +++ b/payment_sdk/src/util/types.ts @@ -75,6 +75,7 @@ export enum PaymentStatuses { SUCCESS = "completed", PENDING = "pending", CANCELLED = "cancelled", + EXPIRED = "expired", } export enum TokenResponseStatuses { @@ -93,6 +94,8 @@ export enum ResponseScreenStatuses { COMPLETE = "complete", /** For displaying payment instruction screens for cancelled by the user */ CANCELLED = "cancelled", + /** For displaying payment instruction screens for expired user session */ + EXPIRED = "expired", } export enum CurrencySign { @@ -250,6 +253,7 @@ export type State = CardDetailsType & | ResponseScreenStatuses.COMPLETE | ResponseScreenStatuses.FAILED | ResponseScreenStatuses.CANCELLED + | ResponseScreenStatuses.EXPIRED | ""; /** * States of the Bank transfer and Pay Easy fields. From e35b73b2134ead9acf52d4ce2a8d3bb9303e836e Mon Sep 17 00:00:00 2001 From: Richard Ramsden Date: Tue, 27 Aug 2024 12:45:51 +0900 Subject: [PATCH 22/29] Update readme.md --- readme.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 66a5cfc..8316230 100644 --- a/readme.md +++ b/readme.md @@ -66,8 +66,7 @@ You can [visit our docs](https://doc.komoju.com/reference/createsession) to see ### Setup a return URL. -Many payment method types require a return URL, so if you fail to provide it, we can’t present those payment methods to your user, even if you’ve enabled them. -When a customer exits your app, for example to authenticate in Safari or their banking app, provide a way for them to automatically return to your app afterward. +Several payment methods require a `return_url`. If this is not provided, we won't be able to display these payment options to your users, even if they are enabled. When a customer exits your app, ensure there is a way for them to automatically return to your app afterward. #### 1. Use `return_url` parameter when creating a session From 5b9d188fc0e841e90ec0f23b82800d1983b2e1f3 Mon Sep 17 00:00:00 2001 From: Tharindu Kumarasiri Date: Tue, 27 Aug 2024 22:39:10 +0530 Subject: [PATCH 23/29] Main state provider functionality update --- example/PaymentScreen.tsx | 4 +- payment_sdk/src/assets/languages/en.json | 2 + payment_sdk/src/assets/languages/ja.json | 6 +- payment_sdk/src/components/SubmitButton.tsx | 15 +--- payment_sdk/src/context/MainStateProvider.tsx | 73 +++++++++---------- payment_sdk/src/context/state.ts | 6 -- payment_sdk/src/index.ts | 10 ++- payment_sdk/src/util/constants.ts | 2 +- payment_sdk/src/util/types.ts | 5 -- 9 files changed, 56 insertions(+), 67 deletions(-) diff --git a/example/PaymentScreen.tsx b/example/PaymentScreen.tsx index 861a6cb..f6d411b 100644 --- a/example/PaymentScreen.tsx +++ b/example/PaymentScreen.tsx @@ -9,7 +9,7 @@ import { Pressable, Alert, } from 'react-native'; -import {KomojuSDK} from '@komoju/komoju-react-native'; +import {KomojuSDK, SessionShowResponseType} from '@komoju/komoju-react-native'; import createSession from './services/sessionService'; export enum CurrencyTypes { @@ -51,7 +51,7 @@ const PaymentScreen = ({ }; // when the payment is complete pass a callback to get the final results of response - const onPaymentComplete = (response: any) => { + const onPaymentComplete = (response: SessionShowResponseType) => { console.log(`Transaction Status: ${response?.status}`); setAmount(''); }; diff --git a/payment_sdk/src/assets/languages/en.json b/payment_sdk/src/assets/languages/en.json index 08c0eec..383cc92 100644 --- a/payment_sdk/src/assets/languages/en.json +++ b/payment_sdk/src/assets/languages/en.json @@ -47,6 +47,8 @@ "UPDATE_PAYMENT_METHOD": "Update payment method", "SESSION_EXPIRED": "Session Expired", "SESSION_EXPIRED_MSG": "This session has already expired. Please try again.", + "PAYMENT_WAITING": "Awaiting Payment", + "PAYMENT_WAITING_MSG": "Your payment has not been completed yet. We have e-mailed you the instructions on how to complete your purchase.", "PAYMENT_VIA_ALI_PAY": "Payment via Alipay", "ALI_PAY_REDIRECT_MESSAGE": "You will be redirected to Alipay to complete the payment", diff --git a/payment_sdk/src/assets/languages/ja.json b/payment_sdk/src/assets/languages/ja.json index 98c0a41..d436d12 100644 --- a/payment_sdk/src/assets/languages/ja.json +++ b/payment_sdk/src/assets/languages/ja.json @@ -47,7 +47,9 @@ "UPDATE_PAYMENT_METHOD": "支払い方法を変更する", "SESSION_EXPIRED": "セッションが期限切れ", "SESSION_EXPIRED_MSG": "このセッションは既に期限切れです。再度お試しください。", - + "PAYMENT_WAITING": "支払い待ち", + "PAYMENT_WAITING_MSG": "お支払いはまだ完了していません。購入を完了するための手順を電子メールで送信しました。", + "PAYMENT_VIA_ALI_PAY": "Alipayによる支払い", "ALI_PAY_REDIRECT_MESSAGE": "支払いを完了するには、Alipay にリダイレクトされます。", "CONTINUE_TO_ALI_PAY": "アリペイに進む", @@ -86,4 +88,4 @@ "EMAIL_ERROR": "有効なメールアドレスを入力してください", "": "" } - } + } \ No newline at end of file diff --git a/payment_sdk/src/components/SubmitButton.tsx b/payment_sdk/src/components/SubmitButton.tsx index 2ce949b..ac32fac 100644 --- a/payment_sdk/src/components/SubmitButton.tsx +++ b/payment_sdk/src/components/SubmitButton.tsx @@ -1,11 +1,9 @@ -import React, { useContext } from "react"; +import React from "react"; import { StyleSheet, Text, TouchableOpacity } from "react-native"; import { useTranslation } from "react-i18next"; -import { Actions, DispatchContext } from "@context/state"; - import { ThemeSchemeType } from "@util/types"; import { resizeFonts, responsiveScale } from "@theme/scalling"; @@ -22,18 +20,12 @@ const SubmitButton = ({ label, labelSuffix, onPress, testID }: Props) => { const { t } = useTranslation(); const theme = useCurrentTheme(); const styles = getStyles(theme); - const dispatch = useContext(DispatchContext); - - const onSubmit = () => { - dispatch({ type: Actions.SET_PROCEED_PAYMENT, payload: true }); - onPress(); - } return ( {labelSuffix ? `${t(label)} ${labelSuffix}` : t(label)} @@ -61,5 +53,4 @@ const getStyles = (theme: ThemeSchemeType) => { fontWeight: "bold", }, }); - -} \ No newline at end of file +}; diff --git a/payment_sdk/src/context/MainStateProvider.tsx b/payment_sdk/src/context/MainStateProvider.tsx index 480f28f..df9e7cf 100644 --- a/payment_sdk/src/context/MainStateProvider.tsx +++ b/payment_sdk/src/context/MainStateProvider.tsx @@ -7,7 +7,7 @@ import React, { useState, } from "react"; -import { Alert, AppState, Linking } from "react-native"; +import { Alert, AppState, AppStateStatus, Linking } from "react-native"; import i18next from "i18next"; @@ -26,16 +26,16 @@ import { PaymentStatuses, ResponseScreenStatuses, sessionPayProps, + TokenResponseStatuses, } from "@util/types"; import { validateSessionResponse } from "@util/validator"; import "@assets/languages/i18n"; -import { Actions, DispatchContext, KomojuContext, StateContext } from "./state"; +import { Actions, DispatchContext, KomojuContext } from "./state"; export const MainStateProvider = (props: KomojuProviderIprops) => { const dispatch = useContext(DispatchContext); const [modalVisible, setModalVisible] = useState(false); - const { processedPayment } = useContext(StateContext) const sheetRef = useRef(null); // ref to hold client provided onComplete callback @@ -52,28 +52,23 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { handleDeepLinkStateChange ); - return () => { - subscription.remove(); - }; - }, [props]); - - useEffect(() => { // Add event listener for deep links - const subscription = AppState.addEventListener( + const windowChangeListener = AppState.addEventListener( "change", handleBackgroundStateChange ); return () => { subscription.remove(); + windowChangeListener.remove(); }; - }, [processedPayment]); + }, [props]); // This callback method is used to check the background state - const handleBackgroundStateChange = async () => { + const handleBackgroundStateChange = async (status: AppStateStatus) => { startLoading(); - if (processedPayment) { + if (status === "active") { // if this is a session flow, check until session response changes from 'pending' to 'completed' or 'error' const sessionShowPayload = { publishableKey: props.publishableKey, @@ -81,37 +76,37 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { }; // fetch session status to check if the payment is completed - let sessionResponse = await sessionShow(sessionShowPayload); - - // Polling until session verification status changes - while ( - sessionResponse?.status === PaymentStatuses.PENDING && - sessionResponse?.payment?.status !== PaymentStatuses.CANCELLED && - !sessionResponse?.expired - ) { - sessionResponse = await sessionShow(sessionShowPayload); - } + const sessionResponse = await sessionShow(sessionShowPayload); // if payment success showing success screen or if failed showing error screen if (sessionResponse?.status === PaymentStatuses.SUCCESS) { - if (sessionResponse?.payment?.payment_details?.instructions_url) { - openURL(sessionResponse?.payment?.payment_details?.instructions_url); - } - onPaymentSuccess(); - // calling user passed onComplete method with session response data + if ( + sessionResponse?.payment?.status === TokenResponseStatuses.CAPTURED + ) { + onPaymentSuccess(); + } else { + onPaymentAwaiting(); + } // calling user passed onComplete method with session response data onCompleteCallback.current && // TODO: Fix this type error // @ts-expect-error - Argument of type 'PaymentSessionResponse' is not assignable to parameter of type 'string'. onCompleteCallback.current(sessionResponse); - } else if (sessionResponse?.payment?.status === PaymentStatuses.CANCELLED) { + } else if ( + sessionResponse?.payment?.status === PaymentStatuses.CANCELLED + ) { onPaymentCancelled(); } else if (sessionResponse?.expired) { - onSessionExpired() - } else { + onSessionExpired(); + } else if ( + sessionResponse?.status === PaymentStatuses.ERROR || + sessionResponse?.payment?.status === PaymentStatuses.ERROR || + sessionResponse?.secure_token?.verification_status === + TokenResponseStatuses.ERROR + ) { onPaymentFailed(); } } - dispatch({ type: Actions.SET_PROCEED_PAYMENT, payload: false }); + // after all api calls are done stopping the loading indicator stopLoading(); }; @@ -278,17 +273,20 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { // Polling until session verification status changes while ( sessionResponse?.status === PaymentStatuses.PENDING && - sessionResponse?.payment?.status !== PaymentStatuses.CANCELLED + sessionResponse?.payment?.status !== PaymentStatuses.CANCELLED && + sessionResponse?.secure_token?.verification_status !== + TokenResponseStatuses.ERROR ) { sessionResponse = await sessionShow(sessionShowPayload); } // if payment success showing success screen or if failed showing error screen if (sessionResponse?.status === PaymentStatuses.SUCCESS) { - if (sessionResponse?.payment?.payment_details?.instructions_url) { - openURL(sessionResponse?.payment?.payment_details?.instructions_url); + if (sessionResponse?.payment?.status === TokenResponseStatuses.CAPTURED) { + onPaymentSuccess(); + } else { + onPaymentAwaiting(); } - onPaymentSuccess(); // calling user passed onComplete method with session response data onCompleteCallback.current && // TODO: Fix this type error @@ -300,7 +298,6 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { onPaymentFailed(); } - dispatch({ type: Actions.SET_PROCEED_PAYMENT, payload: false }); // after all api calls are done stopping the loading indicator stopLoading(); }; @@ -370,7 +367,7 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { // TODO: Fix this type error // eslint-disable-next-line @typescript-eslint/no-unused-vars - const initializeKomoju = useCallback((params: InitPrams) => { }, []); + const initializeKomoju = useCallback((params: InitPrams) => {}, []); const renderPaymentUI = useMemo(() => { const UI = props?.useBottomSheet ? ( diff --git a/payment_sdk/src/context/state.ts b/payment_sdk/src/context/state.ts index 6e78765..aea5ef8 100644 --- a/payment_sdk/src/context/state.ts +++ b/payment_sdk/src/context/state.ts @@ -32,7 +32,6 @@ export const Actions = { SET_PAYMENT_STATE: "SET_PAYMENT_STATE", SET_PAYMENT_METHODS: "SET_PAYMENT_METHODS", SESSION_PAY: "SESSION_PAY", - SET_PROCEED_PAYMENT: "SET_PROCEED_PAYMENT", }; /** @@ -124,11 +123,6 @@ export function reducer(state: State, action: ActionType) { ...state, paymentMethods: action.payload, }; - case Actions.SET_PROCEED_PAYMENT: - return { - ...state, - processedPayment: action.payload, - }; default: throw new Error(); } diff --git a/payment_sdk/src/index.ts b/payment_sdk/src/index.ts index 59a8d7f..e1ae8a3 100644 --- a/payment_sdk/src/index.ts +++ b/payment_sdk/src/index.ts @@ -3,7 +3,11 @@ import { useContext } from "react"; import { KomojuProvider } from "@context/KomojuProvider"; import { KomojuContext } from "@context/state"; -import { LanguageTypes, PaymentType } from "@util/types"; +import { + LanguageTypes, + PaymentType, + SessionShowResponseType, +} from "@util/types"; /** * KomojuSDK provides context utilities for the Komoju payment system. @@ -33,4 +37,8 @@ export { * Supported languages to parse for language prop. */ LanguageTypes, + /** + * Response payload for onComplete and onDismiss callbacks. + */ + SessionShowResponseType, }; diff --git a/payment_sdk/src/util/constants.ts b/payment_sdk/src/util/constants.ts index 52ef1d2..ff5a8d7 100644 --- a/payment_sdk/src/util/constants.ts +++ b/payment_sdk/src/util/constants.ts @@ -14,7 +14,7 @@ export const API_HEADER = (publishableKey: string) => ({ export const paymentSuccessCtaText = "BACK_TO_STORE"; export const paymentFailedCtaText = "UPDATE_PAYMENT_METHOD"; -export const emailRegex = /^[a-zA-Z0–9._-]+@[a-zA-Z0–9.-]+\.[a-zA-Z]{2,4}$/; +export const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/; export const cardTypeRegex = { amex: /^3[47]\d{0,13}/, diner: /^3(?:0([0-5]|9)|[689]\d?)\d{0,11}/, diff --git a/payment_sdk/src/util/types.ts b/payment_sdk/src/util/types.ts index 88c3ea9..c00a29b 100644 --- a/payment_sdk/src/util/types.ts +++ b/payment_sdk/src/util/types.ts @@ -222,10 +222,6 @@ export type State = CardDetailsType & * Global loading state. to display loading animation over sdk and disable buttons. */ loading: boolean; - /** - * Global processedPayment state. to indicate whether the payment was processed - */ - processedPayment: boolean; /** * Callback function to call relevant api for each payment type. */ @@ -270,7 +266,6 @@ export type sessionPayProps = { export const initialState: State = { paymentType: PaymentType.CREDIT, loading: false, - processedPayment: false, /** credit card payment related states start */ cardholderName: "", From 541ecb4573d1aedd3bcfbad93be5f3c9e6e68f7b Mon Sep 17 00:00:00 2001 From: Tharindu Kumarasiri Date: Tue, 27 Aug 2024 23:18:19 +0530 Subject: [PATCH 24/29] Loader zIndex and keyboard dismiss handled --- payment_sdk/src/components/Loader.tsx | 1 + payment_sdk/src/components/SheetContent.tsx | 1 + payment_sdk/src/components/SubmitButton.tsx | 9 +++++++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/payment_sdk/src/components/Loader.tsx b/payment_sdk/src/components/Loader.tsx index b1e4e08..9889bea 100644 --- a/payment_sdk/src/components/Loader.tsx +++ b/payment_sdk/src/components/Loader.tsx @@ -17,5 +17,6 @@ const styles = StyleSheet.create({ ...StyleSheet.absoluteFillObject, alignItems: "center", justifyContent: "center", + zIndex: 6, }, }); diff --git a/payment_sdk/src/components/SheetContent.tsx b/payment_sdk/src/components/SheetContent.tsx index 0cb49c7..54af7d9 100644 --- a/payment_sdk/src/components/SheetContent.tsx +++ b/payment_sdk/src/components/SheetContent.tsx @@ -102,6 +102,7 @@ const SheetContent = () => { } ListFooterComponentStyle={styles.footerContent} + keyboardShouldPersistTaps="handled" /> ); diff --git a/payment_sdk/src/components/SubmitButton.tsx b/payment_sdk/src/components/SubmitButton.tsx index ac32fac..eee11e5 100644 --- a/payment_sdk/src/components/SubmitButton.tsx +++ b/payment_sdk/src/components/SubmitButton.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { StyleSheet, Text, TouchableOpacity } from "react-native"; +import { Keyboard, StyleSheet, Text, TouchableOpacity } from "react-native"; import { useTranslation } from "react-i18next"; @@ -21,11 +21,16 @@ const SubmitButton = ({ label, labelSuffix, onPress, testID }: Props) => { const theme = useCurrentTheme(); const styles = getStyles(theme); + const onSubmit = () => { + Keyboard.dismiss(); + onPress(); + }; + return ( {labelSuffix ? `${t(label)} ${labelSuffix}` : t(label)} From d9625135657be665ca4ceda69679775f982c2e34 Mon Sep 17 00:00:00 2001 From: Tharindu Kumarasiri Date: Wed, 28 Aug 2024 09:11:50 +0530 Subject: [PATCH 25/29] Fixing card payment without 3Ds --- payment_sdk/src/context/MainStateProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payment_sdk/src/context/MainStateProvider.tsx b/payment_sdk/src/context/MainStateProvider.tsx index df9e7cf..3f60051 100644 --- a/payment_sdk/src/context/MainStateProvider.tsx +++ b/payment_sdk/src/context/MainStateProvider.tsx @@ -321,7 +321,7 @@ export const MainStateProvider = (props: KomojuProviderIprops) => { if (response?.status === PaymentStatuses.PENDING) { openURL(response.redirect_url); } else if (response?.status === PaymentStatuses.SUCCESS) { - if (response?.payment?.status === PaymentStatuses.SUCCESS) { + if (response?.payment?.status === TokenResponseStatuses.CAPTURED) { onPaymentSuccess(); } else if (response?.payment?.payment_details?.instructions_url) { openURL(response?.payment?.payment_details?.instructions_url); From eebac628772a8781fcc1f5562d7d1d5065998d89 Mon Sep 17 00:00:00 2001 From: Richard Ramsden Date: Wed, 28 Aug 2024 13:25:58 +0900 Subject: [PATCH 26/29] Update waiting translation --- payment_sdk/src/assets/languages/ja.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/payment_sdk/src/assets/languages/ja.json b/payment_sdk/src/assets/languages/ja.json index d436d12..884555c 100644 --- a/payment_sdk/src/assets/languages/ja.json +++ b/payment_sdk/src/assets/languages/ja.json @@ -48,7 +48,7 @@ "SESSION_EXPIRED": "セッションが期限切れ", "SESSION_EXPIRED_MSG": "このセッションは既に期限切れです。再度お試しください。", "PAYMENT_WAITING": "支払い待ち", - "PAYMENT_WAITING_MSG": "お支払いはまだ完了していません。購入を完了するための手順を電子メールで送信しました。", + "PAYMENT_WAITING_MSG": "支払いは完了していません。支払い方法の詳細をメールアドレス宛にお送りしていますのでご確認ください。", "PAYMENT_VIA_ALI_PAY": "Alipayによる支払い", "ALI_PAY_REDIRECT_MESSAGE": "支払いを完了するには、Alipay にリダイレクトされます。", @@ -88,4 +88,4 @@ "EMAIL_ERROR": "有効なメールアドレスを入力してください", "": "" } - } \ No newline at end of file + } From 26c8cd9d114b0ebdadbd58ffcc0b05ef871c1c12 Mon Sep 17 00:00:00 2001 From: chathurapathiranage Date: Wed, 28 Aug 2024 12:25:17 +0530 Subject: [PATCH 27/29] change failed message of other payment methods --- payment_sdk/src/assets/languages/en.json | 1 + payment_sdk/src/assets/languages/ja.json | 1 + payment_sdk/src/components/PaymentModal.tsx | 3 ++- payment_sdk/src/components/ResponseScreen.tsx | 16 ++++++++++++---- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/payment_sdk/src/assets/languages/en.json b/payment_sdk/src/assets/languages/en.json index 383cc92..73057b7 100644 --- a/payment_sdk/src/assets/languages/en.json +++ b/payment_sdk/src/assets/languages/en.json @@ -42,6 +42,7 @@ "PAYMENT_CANCELLED": "Payment Canceled", "ORDER_THANK_YOU_NOTE": "Thank you for your order", "PAYMENT_RE_TRY_MSG": "We tried to charge your card but, something went wrong. Please update your payment method below to continue", + "PAYMENT_RE_TRY_MSG_OTHERS": "We attempted to process your payment, but something went wrong. Please update your payment method below to continue.", "PAYMENT_CANCELLED_MSG": "We noticed that you’ve canceled the payment process. If this was a mistake, you can try again to complete your purchase.", "BACK_TO_STORE": "Back to store", "UPDATE_PAYMENT_METHOD": "Update payment method", diff --git a/payment_sdk/src/assets/languages/ja.json b/payment_sdk/src/assets/languages/ja.json index 884555c..c052714 100644 --- a/payment_sdk/src/assets/languages/ja.json +++ b/payment_sdk/src/assets/languages/ja.json @@ -42,6 +42,7 @@ "PAYMENT_CANCELLED": "キャンセルされました", "ORDER_THANK_YOU_NOTE": "ご購入ありがとうございました", "PAYMENT_RE_TRY_MSG": "入力していただいたカードで支払いを行うことができませんでした。別の支払い方法を試してみてください。", + "PAYMENT_RE_TRY_MSG_OTHERS": "カードでの支払いを試みましたが、何か問題が発生しました。お手数ですが、下記から別の支払い方法をご入力ください。", "PAYMENT_CANCELLED_MSG": "お支払いプロセスがキャンセルされたことを確認しました。もしこれが誤りであれば、再度お試しいただき、購入を完了してください。", "BACK_TO_STORE": "ストアに戻る", "UPDATE_PAYMENT_METHOD": "支払い方法を変更する", diff --git a/payment_sdk/src/components/PaymentModal.tsx b/payment_sdk/src/components/PaymentModal.tsx index befbf28..653068a 100644 --- a/payment_sdk/src/components/PaymentModal.tsx +++ b/payment_sdk/src/components/PaymentModal.tsx @@ -43,7 +43,7 @@ const PaymentModal = ({ setModalVisible, onDismiss, }: PaymentModalProps) => { - const { paymentState } = useContext(StateContext); + const { paymentState, paymentType } = useContext(StateContext); const dispatch = useContext(DispatchContext); const theme = useCurrentTheme(); @@ -144,6 +144,7 @@ const PaymentModal = ({ status={paymentState} onPress={ctaOnPress} onPressLabel={getCtaText()} + paymentType={paymentType} /> ) : ( diff --git a/payment_sdk/src/components/ResponseScreen.tsx b/payment_sdk/src/components/ResponseScreen.tsx index 6b1cddb..0fbeb4d 100644 --- a/payment_sdk/src/components/ResponseScreen.tsx +++ b/payment_sdk/src/components/ResponseScreen.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useMemo } from "react"; import { Image, StyleSheet, View, ImageSourcePropType } from "react-native"; -import { ResponseScreenStatuses, ThemeSchemeType } from "@util/types"; +import { PaymentType, ResponseScreenStatuses, ThemeSchemeType } from "@util/types"; import { resizeFonts, responsiveScale } from "@theme/scalling"; import { useCurrentTheme } from "@theme/useCurrentTheme"; @@ -50,17 +50,25 @@ type Props = { message?: string; onPressLabel: string; onPress: () => void; + paymentType: PaymentType }; -const ResponseScreen = ({ status, message, onPress, onPressLabel }: Props) => { +const ResponseScreen = ({ status, message, onPress, onPressLabel, paymentType }: Props) => { const theme = useCurrentTheme(); const styles = getStyles(theme); const statusConfig = statusConfigs[status]; const renderMessageContent = useMemo(() => { - const msg = message || statusConfig?.defaultMessage; - + let msg: string = ''; + switch (paymentType) { + case PaymentType.CREDIT: + msg = (message || statusConfig?.defaultMessage) as string; + break; + default: + msg = `${message || statusConfig?.defaultMessage + "_OTHERS"}`; + break; + } return ( {statusConfig?.title} From b1ad2fa369c90cdf55ecf0a8f76c57010108f703 Mon Sep 17 00:00:00 2001 From: chathurapathiranage Date: Wed, 28 Aug 2024 12:52:19 +0530 Subject: [PATCH 28/29] change failed message of other payment methods --- payment_sdk/src/components/ResponseScreen.tsx | 18 ++++++++++-------- payment_sdk/src/components/Sheet.tsx | 3 ++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/payment_sdk/src/components/ResponseScreen.tsx b/payment_sdk/src/components/ResponseScreen.tsx index 0fbeb4d..4106c3a 100644 --- a/payment_sdk/src/components/ResponseScreen.tsx +++ b/payment_sdk/src/components/ResponseScreen.tsx @@ -60,14 +60,16 @@ const ResponseScreen = ({ status, message, onPress, onPressLabel, paymentType }: const statusConfig = statusConfigs[status]; const renderMessageContent = useMemo(() => { - let msg: string = ''; - switch (paymentType) { - case PaymentType.CREDIT: - msg = (message || statusConfig?.defaultMessage) as string; - break; - default: - msg = `${message || statusConfig?.defaultMessage + "_OTHERS"}`; - break; + let msg: string = (message || statusConfig?.defaultMessage) as string; + if (status === ResponseScreenStatuses.FAILED) { + switch (paymentType) { + case PaymentType.CREDIT: + msg = (message || statusConfig?.defaultMessage) as string; + break; + default: + msg = `${message || statusConfig?.defaultMessage + "_OTHERS"}`; + break; + } } return ( diff --git a/payment_sdk/src/components/Sheet.tsx b/payment_sdk/src/components/Sheet.tsx index 04df961..08661e4 100644 --- a/payment_sdk/src/components/Sheet.tsx +++ b/payment_sdk/src/components/Sheet.tsx @@ -67,7 +67,7 @@ const Sheet: ForwardRefRenderFunction = ( const translateYState = useRef(0); const contextState = useRef(0); - const { paymentState } = useContext(StateContext); + const { paymentState, paymentType } = useContext(StateContext); const dispatch = useContext(DispatchContext); const theme = useCurrentTheme(); const styles = getStyles(theme); @@ -233,6 +233,7 @@ const Sheet: ForwardRefRenderFunction = ( status={paymentState} onPress={ctaOnPress} onPressLabel={getCtaText()} + paymentType={paymentType} /> ) : ( From af470db34ef23243f8476654f89aa4c48d78ffe1 Mon Sep 17 00:00:00 2001 From: chathurapathiranage Date: Thu, 29 Aug 2024 08:01:45 +0530 Subject: [PATCH 29/29] payment waiting translation fix --- payment_sdk/src/components/ResponseScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/payment_sdk/src/components/ResponseScreen.tsx b/payment_sdk/src/components/ResponseScreen.tsx index 4106c3a..e25f029 100644 --- a/payment_sdk/src/components/ResponseScreen.tsx +++ b/payment_sdk/src/components/ResponseScreen.tsx @@ -35,7 +35,7 @@ const statusConfigs: Partial> = { }, [ResponseScreenStatuses.COMPLETE]: { title: "PAYMENT_WAITING", - defaultMessage: "PAYMENT_CANCELLED_MSG", + defaultMessage: "PAYMENT_WAITING_MSG", image: require("../assets/images/awaitingPayment.png"), }, [ResponseScreenStatuses.EXPIRED]: {