capacitor-community / stripe

Stripe Mobile SDK wrapper for Capacitor
MIT License
185 stars 74 forks source link

[Android Issue] isGooglePayAvailable method returns "Not implemented on device" and presentGooglePay method does not show any paymentResult promise being logged in the console window even though the paymentintent is being sent, rather the app crashes. #174

Closed rahul-raj07 closed 1 year ago

rahul-raj07 commented 2 years ago

Description of the bug: isGooglePayAvailable method returns "Not implemented on device" and presentGooglePay method does not show any paymentResult promise being logged in the console window even though the paymentIntent is being sent. Rather the app crashes, and I'm returned to the home screen of my android device. Also, after the app crashes, I'm getting a message being logged in the logcat section of Android Studio that says :

To Reproduce: I am trying to implement GooglePay integration in an ionic + react + capacitor mobile application. I have installed and run capacitor-community/stripe plugin using following commands :

npm install @capacitor-community/stripe
npx cap sync

And then I used “CapacitorStripeProvider” to initialize the plugin like so:

import { CapacitorStripeProvider } from ‘@capacitor-community/stripe/dist/esm/react/provider’;

const App: React.FC = () => (
<CapacitorStripeProvider
publishableKey=“Your Publishable Key”
fallback={

Loading…

}
<IonApp>
...
</IonApp>
);
export default App;

And then, I followed the official docs of capacitor-community/stripe to implement the Google Pay functionality. I did everything mentioned there. Called different methods such as isGooglePayAvailable, createGooglePayAvailable and presentGooglePayAvailable.

There are several issues that I’m facing after running the code for GooglePay Integration:

  1. On certain android devices, I’m getting a message that says “Not implemented on device” being logged in the console window from the isGooglePayAvailable method even though I have Gpay app installed and logged in in my android device. On other devices, isGooglePayAvailable methods executes successfully and returns resolve: void response.

  2. Also, after having called presentGooglePay method, my android app crashes and I am returned to the home screen of my android device. Also, I am getting native Stripe.presentGooglePay being logged in the console window but I’m not able to see any “result Stripe.presentGooglePay” in the console window.

  3. Also, I’m getting an error in the logcat of Android Studio that says: Caused by: java.lang.IllegalStateException: presentForPaymentIntent() may only be called when Google Pay is available on this device.

I know all of this must have been quite confusing and cryptic as well to comprehend. So, it would be better if any one could provide me his/her contact details (an email id or anything as such would do), so that I could better make my points clear and share screen to show the code that I’ve used and the errors that I’m getting.

Here's the screenshot of the console messages and errors that I'm getting in the console window.

Screenshot (18)_LI

After clicking the create button createGooglePay method is called and here's the screenshot of the console window that I'm getting:

Screenshot (19)_LI

After clicking on the present button, presentGooglePay method is called and here's the screenshot of the console window:

Screenshot (20)_LI

Here's the code that I've used for GooglePay integration.

import { IonItem, IonLabel, IonList, IonListHeader } from "@ionic/react";
import { useEffect, useState } from "react";
import { Stripe, GooglePayEventsEnum } from "@capacitor-community/stripe";
/**
 * If you use typescript@4.5, you can write:
 * import { useCapacitorStripe } from '@capacitor-community/stripe/react';
 */
// import { useCapacitorStripe } from "@capacitor-community/stripe/dist/esm/react/provider";
import { usePaymentSheet } from "../../hooks/usePaymentSheet";
// import { ProcessStatus } from '../../interfaces';
import React from "react";

const GooglePayStripe: React.FC = () => {
    const [step, setStep] = useState<any>("initial");
    const [isGPayAvailable, setIsGpayAvailable] = useState(false);
    const { createPaymentIntent } = usePaymentSheet();
    console.log("stripe", Stripe);
    console.log(isGPayAvailable);

    const checkIfGooglePlayAvailable = () => {
        const isAvailable = Stripe.isGooglePayAvailable().then((res) => {
            console.log("res", res);
        }).catch(e => {
            console.log("isGooglePayAvailable", e);
        });
        console.log(
            "🚀 ~ file: index.tsx ~ line 30 ~ checkIfGooglePlayAvailable ~ isAvailable",
            isAvailable
        );
        if (isAvailable === undefined) {
            setIsGpayAvailable(false);
            setStep("unavailable");
        } else {
            setIsGpayAvailable(true);
            setStep("ready");
        }
    };

    useEffect(() => {
        checkIfGooglePlayAvailable();

        Stripe.addListener(GooglePayEventsEnum.Completed, () => {
            console.log('GooglePayEventsEnum.Completed');
          });

    }, []);

    console.log(isGPayAvailable);

    return (
        <IonList>
            <IonListHeader>
                <IonLabel>GooglePay</IonLabel>
            </IonListHeader>
            {isGPayAvailable ? (
                <>
                    <IonItem
                        button
                        detail
                        disabled={step !== "ready"}
                        onClick={async e => {
                            e.preventDefault();
                            const { paymentIntent } = await createPaymentIntent();
                            try {
                                await Stripe.createGooglePay({
                                    paymentIntentClientSecret: paymentIntent
                                });
                                setStep("create");
                            } catch (e) {
                                console.log(e);
                            }
                        }}
                    >
                        <IonLabel>Create</IonLabel>
                    </IonItem>
                    <IonItem
                        button
                        detail
                        disabled={step !== "create"}
                        onClick={async e => {
                            e.preventDefault();
                            try {
                              const result = await Stripe.presentGooglePay();
                              if (result.paymentResult === GooglePayEventsEnum.Completed) {
                                console.log('Woohooo!!')
                              }
                                setStep("present");
                            } catch (e) {
                                console.log(e);
                            }
                        }}
                    >
                        <IonLabel>Present</IonLabel>
                    </IonItem>
                </>
            ) : (
                <IonItem button detail disabled={true}>
                    <IonLabel>Your device does not supports GooglePay.</IonLabel>
                </IonItem>
            )}
        </IonList>
    );
};
export default GooglePayStripe;

Here's the code that I've used to get the paymentIntent from the usePaymentSheet hook:

import { useCallback, useMemo } from "react";

export const usePaymentSheet = () => {
  const api = useMemo(() => {
    if (process.env.REACT_APP_API_URL) {
      console.log(
        " process.env.REACT_APP_API_URL",
        process.env.REACT_APP_API_URL
      );
      return process.env.REACT_APP_API_URL;
    }
    return "http://54.162.94.169:5000/";
  }, []);
  const createPaymentIntent = useCallback(async (): Promise<{
    customer: string;
    paymentIntent: string;
    ephemeralKey: string;
  }> => {
    const { customer, paymentIntent, ephemeralKey } = await fetch(
      `${api}intent`,
      {
        method: "POST"
      }
    ).then(res => res.json());
    return {
      customer,
      paymentIntent,
      ephemeralKey
    };
  }, [api]);
  return {
    createPaymentIntent
  };
};

My package.json file:

{
  "name": "RealOrNot",
  "version": "0.0.1",
  "private": true,
  "dependencies": {
    "@awesome-cordova-plugins/core": "^5.39.1",
    "@awesome-cordova-plugins/pdf-generator": "^5.39.1",
    "@awesome-cordova-plugins/photo-viewer": "^5.39.1",
    "@capacitor-community/facebook-login": "^3.3.0",
    "@capacitor-community/stripe": "^3.6.0",
    "@capacitor/android": "^3.4.1",
    "@capacitor/app": "1.1.0",
    "@capacitor/core": "^3.4.1",
    "@capacitor/filesystem": "^1.1.0",
    "@capacitor/haptics": "1.1.4",
    "@capacitor/keyboard": "1.2.2",
    "@capacitor/status-bar": "1.0.8",
    "@codetrix-studio/capacitor-google-auth": "^3.1.0",
    "@emotion/react": "^11.7.1",
    "@emotion/styled": "^11.6.0",
    "@google-pay/button-react": "^2.0.0",
    "@ionic/react": "^6.0.0",
    "@ionic/react-router": "^6.0.0",
    "@mui/material": "^5.4.2",
    "@paypal/paypal-js": "^5.0.3",
    "@paypal/react-paypal-js": "^7.6.0",
    "@testing-library/jest-dom": "^5.11.9",
    "@testing-library/react": "^11.2.5",
    "@testing-library/user-event": "^12.6.3",
    "@types/jest": "^26.0.20",
    "@types/node": "^12.19.15",
    "@types/react": "^16.14.3",
    "@types/react-dom": "^16.9.10",
    "@types/react-router": "^5.1.11",
    "@types/react-router-dom": "^5.1.7",
    "axios": "^0.26.0",
    "com-sarriaroman-photoviewer": "^1.2.5",
    "cordova-pdf-generator": "^2.1.1",
    "firebase": "^8.3.1",
    "formik": "^2.2.9",
    "ionic": "^5.4.16",
    "ionicons": "^5.4.0",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-facebook-login": "^4.1.1",
    "react-google-login": "^5.2.2",
    "react-jwt": "^1.1.4",
    "react-native-crypto-js": "^1.0.0",
    "react-otp-input": "^2.4.0",
    "react-paypal-express-checkout": "^1.0.5",
    "react-phone-input-2": "^2.15.0",
    "react-redux": "^7.2.6",
    "react-router": "^5.2.0",
    "react-router-dom": "^5.2.0",
    "react-scripts": "^5.0.0",
    "react-to-print": "^2.14.4",
    "redux": "^4.1.2",
    "redux-devtools-extension": "^2.13.9",
    "redux-logger": "^3.0.6",
    "redux-persist": "^6.0.0",
    "redux-thunk": "^2.4.1",
    "sass": "^1.49.7",
    "scss": "^0.2.4",
    "typescript": "^4.1.3",
    "web-vitals": "^0.2.4",
    "workbox-background-sync": "^5.1.4",
    "workbox-broadcast-update": "^5.1.4",
    "workbox-cacheable-response": "^5.1.4",
    "workbox-core": "^5.1.4",
    "workbox-expiration": "^5.1.4",
    "workbox-google-analytics": "^5.1.4",
    "workbox-navigation-preload": "^5.1.4",
    "workbox-precaching": "^5.1.4",
    "workbox-range-requests": "^5.1.4",
    "workbox-routing": "^5.1.4",
    "workbox-strategies": "^5.1.4",
    "workbox-streams": "^5.1.4",
    "yup": "^0.32.11"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --transformIgnorePatterns 'node_modules/(?!(@ionic/react|@ionic/react-router|@ionic/core|@stencil/core|ionicons)/)'",
    "eject": "react-scripts eject",
    "lint": "eslint ./src --fix",
    "resources": "cordova-res ios && cordova-res android && node scripts/resources.js"
  },
  "lint-staged": {
    "src/**/*.{ts,tsx}": [
      "eslint --ext .tsx --ext .ts ./src --fix"
    ],
    "./src/**": [
      "prettier --write ."
    ]
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "@capacitor/cli": "^3.4.1",
    "@types/react-facebook-login": "^4.1.4",
    "@types/react-native-crypto-js": "^1.0.0",
    "@types/redux-logger": "^3.0.9",
    "@typescript-eslint/eslint-plugin": "^5.12.0",
    "@typescript-eslint/parser": "^5.12.0",
    "eslint": "^8.9.0",
    "eslint-config-airbnb": "^17.1.0",
    "eslint-config-prettier": "^4.1.0",
    "eslint-config-prettier-react": "0.0.24",
    "eslint-plugin-html": "^5.0.3",
    "eslint-plugin-import": "^2.16.0",
    "eslint-plugin-jsx-a11y": "^6.2.1",
    "eslint-plugin-prettier": "^3.0.1",
    "eslint-plugin-react": "^7.12.4",
    "eslint-plugin-react-hooks": "^1.3.0",
    "husky": "^4.3.8",
    "lint-staged": "^12.3.4",
    "prettier": "^1.19.1",
    "pretty-quick": "^3.1.3"
  },
  "description": "An Ionic project"
}

My capacitor config ---> capacitor.config.json:

{
  "appId": "com.realornot.app",
  "appName": "Real or Not",
  "webDir": "build",
  "bundledWebRuntime": false,
  "plugins": {
    "GoogleAuth": {
      "scopes": ["profile", "email"],
      "serverClientId": "628669220892-fnk0krfnb75e31ckuvl59d012012v01t.apps.googleusercontent.com",
      "forceCodeForRefreshToken": true
    }
  }
}

Thanks, Rahul

nxj1129 commented 2 years ago

Got the same issue, followed the docs and everything compiles successfully. However when I comment out the isGooglePayAvailable() my app crashes. When I uncomment the isGooglePayAvailable() the console says "Not implemented" like in your case. I am also using capacitor 3.6.0. A colleague has the same code as me but on capacitor 2.6.4 and everything works fine, however I cannot change the capacitor versions for obvious reasons

This is console after I comment out the isGooglePayAvailable() => No Error, but App crashes

Selection_027

This is console after I add the isGooglePayAvailable() => Error, but App doesn't crash

Selection_028

The function I use on click button

Selection_029

rdlabo commented 2 years ago

@rahul-raj07 @nxj1129 Please try this code:

const isAvailable = Stripe.isGooglePayAvailable().then(() => true).catch(() => false);

isGooglePayAvailable method is return void(or error).

This is how it is used in the demo.: https://github.com/capacitor-community/stripe/blob/master/demo/angular/src/app/tab1/tab1.page.ts#L135

nxj1129 commented 2 years ago

@rdlabo I tried this code also, but I get the same error unfortunately

rdlabo commented 2 years ago

@rahul-raj07 @nxj1129

In android device(android app)、 isGooglePayAvailable is checked here: https://github.com/capacitor-community/stripe/blob/master/android/src/main/java/com/getcapacitor/community/stripe/StripePlugin.java#L64

If always false, you may not have enough settings. This demo's url will help you: https://github.com/capacitor-community/stripe/blob/master/demo/angular/android/app/src/main/AndroidManifest.xml#L37-L39 https://github.com/capacitor-community/stripe/blob/master/demo/angular/android/app/src/main/res/values/strings.xml#L8-L12

Thanks.

mostafa3ly commented 2 years ago

@rahul-raj07 @nxj1129 have u found a solution fo that issue ??

rdlabo commented 1 year ago

This Issue is closed because there are no updates. If there are any updates, please start a new Issue. Thank you.

jhonnathanH commented 1 year ago

Got the same issue, followed the docs and everything compiles successfully. However when I comment out the isGooglePayAvailable() my app crashes. When I uncomment the isGooglePayAvailable() the console says "Not implemented" like in your case. I am also using capacitor 3.6.0. A colleague has the same code as me but on capacitor 2.6.4 and everything works fine, however I cannot change the capacitor versions for obvious reasons

This is console after I comment out the isGooglePayAvailable() => No Error, but App crashes

Selection_027

This is console after I add the isGooglePayAvailable() => Error, but App doesn't crash

Selection_028

The function I use on click button

Selection_029

Same issue.. any fix?