stripe-react-native icon indicating copy to clipboard operation
stripe-react-native copied to clipboard

stripe-android-issuing-push-provisioning dependency resolution fails in EAS Build due to private play-services-tapandpay SDK

Open DanielOnramp opened this issue 4 months ago • 2 comments

Describe the bug The stripe-android-issuing-push-provisioning:1.1.0 library has a transitive dependency on com.google.android.gms:play-services-tapandpay:17.1.2, which is a private SDK that Google distributes

To Reproduce

  • Add stripe-android-issuing-push-provisioning:1.1.0 as a dependency in an Expo/React Native project
  • Configure EAS Build or any containerized Android build system
  • Run eas build --platform android or equivalent
  • Build fails with dependency resolution error for play-services-tapandpay:17.1.2

Expected behavior The library should build successfully in containerized build environments like EAS Build, GitHub Actions, or other CI/CD systems that don't have access to locally installed private Android SDKs.

Screenshots

Could not find com.google.android.gms:play-services-tapandpay:17.1.2
Searched in: maven.org, google(), jcenter(), etc.
Required by: com.stripe:stripe-android-issuing-push-provisioning:1.1.0

Desktop (please complete the following information):

  • OS: macOS/Linux (build environment)
  • EAS Build: Cloud and local builds
  • Expo SDK: 52.0.x

Smartphone (please complete the following information): N/A

Additional context The stripe-android-issuing-push-provisioning library depends on com.google.android.gms:play-services-tapandpay:17.1.2, which is a private SDK distributed separately by Google outside of public Maven repositories. This prevents builds in containerized environments that can't access locally installed private SDKs.

Local development builds work when the private SDK is manually installed in the Android SDK directory, but EAS build fails, both locally and in the cloud

DanielOnramp avatar Sep 02 '25 16:09 DanielOnramp

I ran into the same issue, my workaround was the following.

Create 2 plugins:

  1. withTapAndPaySDK.js : will download and install the repo during EAS build
const {
  withDangerousMod,
  createRunOncePlugin,
} = require("expo/config-plugins");
const fs = require("fs");
const path = require("path");
const { execSync } = require("child_process");

/**
 * Downloads and sets up the TapAndPay SDK during prebuild
 * This ensures the SDK is available before Gradle runs
 */
function withTapAndPaySDK(config) {
  return withDangerousMod(config, [
    "android",
    async (config) => {
      const projectRoot = config.modRequest.projectRoot;
      const androidDir = path.join(projectRoot, "android");
      const targetDir = path.join(androidDir, "local-m2repo", "m2repository");
      const sentinelFile = path.join(targetDir, ".tapandpay-sdk-installed");

      // Check if SDK is already installed (idempotent)
      if (fs.existsSync(sentinelFile)) {
        console.log("✓ TapAndPay SDK already installed");
        return config;
      }

      console.log("📥 Downloading TapAndPay SDK...");

      const SDK_URL = "DOWNLOAD_AND_HOST_m2repo_2021-07-19_v17.1.2_ZIP_FILE";
      const tempZip = path.join(androidDir, "tapandpay_temp.zip");

      try {
        // Create target directory
        fs.mkdirSync(path.join(androidDir, "local-m2repo"), { recursive: true });

        // Download SDK
        execSync(`curl -fsSL -o "${tempZip}" "${SDK_URL}"`, {
          stdio: "pipe",
          cwd: projectRoot,
        });

        // Extract SDK
        // The zip contains the m2repository structure, so extract to parent
        execSync(`unzip -q -o "${tempZip}" -d "${path.join(androidDir, "local-m2repo")}"`, {
          stdio: "pipe",
          cwd: projectRoot,
        });

        // Clean up zip
        if (fs.existsSync(tempZip)) {
          fs.unlinkSync(tempZip);
        }

        // Check if extraction created nested structure and fix it
        const extractedPath = path.join(androidDir, "local-m2repo", "com");
        const targetComPath = path.join(targetDir, "com");

        if (fs.existsSync(extractedPath) && !fs.existsSync(targetComPath)) {
          // SDK was extracted without m2repository folder, move it
          fs.mkdirSync(targetDir, { recursive: true });
          fs.renameSync(extractedPath, targetComPath);
        }

        // Verify SDK is in place
        const tapandPayPath = path.join(targetDir, "com", "google", "android", "gms", "play-services-tapandpay", "17.1.2");
        if (!fs.existsSync(tapandPayPath)) {
          throw new Error("TapAndPay SDK not found after extraction");
        }

        // Create sentinel file to mark as installed
        fs.writeFileSync(sentinelFile, new Date().toISOString());

        console.log("✅ TapAndPay SDK installed successfully");
        console.log(`   Location: ${targetDir}`);

      } catch (error) {
        console.error("❌ Failed to install TapAndPay SDK:", error.message);
        // Clean up on failure
        if (fs.existsSync(tempZip)) {
          fs.unlinkSync(tempZip);
        }
        throw error;
      }

      return config;
    },
  ]);
}

module.exports = createRunOncePlugin(
  withTapAndPaySDK,
  "tapandpay-sdk-installer",
  "1.0.0"
);
  1. withStripePushProvisioning.js : link dependencies
const {
  withAppBuildGradle,
  withProjectBuildGradle,
  createRunOncePlugin,
} = require("expo/config-plugins");

const REPO_SENTINEL = "// expoTapAndPayLocalRepo";
const DEP_SENTINEL = "// expoStripePushProvisioning";

/**
 * Adds Stripe Android Issuing Push Provisioning dependency to app/build.gradle
 * and adds the TapAndPay SDK maven repository to project build.gradle
 * This is required for adding Stripe Issuing cards to Google Wallet
 */
function withStripePushProvisioning(config) {
  const tapAndPaySdkPath = "local-m2repo/m2repository"; // Relative to android/ folder

  // Step 1: Add the maven repository to project build.gradle
  config = withProjectBuildGradle(config, (config) => {
    let buildGradle = config.modResults.contents;

    // Check if repository already exists (idempotent)
    if (buildGradle.includes(REPO_SENTINEL)) {
      console.log("✓ TapAndPay SDK maven repository already added");
      return config;
    }

    // Add the local maven repo using file: URL scheme (standard Gradle approach)
    // ${rootDir} is resolved at Gradle execution time, works in monorepos and EAS Build
    const repoBlock = `        ${REPO_SENTINEL}
        maven { url "file:\${rootDir}/${tapAndPaySdkPath}" }`;

    // Insert at the beginning of repositories block (first priority)
    const modifiedBuildGradle = buildGradle.replace(
      /(allprojects\s*\{[\s\S]*?repositories\s*\{)/,
      (match) => `${match}\n${repoBlock}\n`
    );

    if (modifiedBuildGradle !== buildGradle) {
      config.modResults.contents = modifiedBuildGradle;
      console.log(
        "✓ TapAndPay SDK maven repository added to project build.gradle"
      );
    } else {
      console.warn(
        "⚠️ Could not find allprojects.repositories block in project build.gradle"
      );
    }

    return config;
  });

  // Step 2: Add dependencies to app/build.gradle
  config = withAppBuildGradle(config, (config) => {
    let buildGradle = config.modResults.contents;

    // Check if dependency already exists (idempotent)
    if (buildGradle.includes(DEP_SENTINEL)) {
      console.log("✓ Stripe push provisioning dependencies already added");
      return config;
    }

    // Add dependencies: TapAndPay MUST be declared explicitly before Stripe
    // to force Gradle to resolve it from our local repo instead of searching public repos
    const depsBlock = `    ${DEP_SENTINEL}
    implementation 'com.google.android.gms:play-services-tapandpay:17.1.2'
    implementation 'com.stripe:stripe-android-issuing-push-provisioning:1.1.0'`;

    // Insert after react-android dependency
    const modifiedBuildGradle = buildGradle.replace(
      /dependencies\s*\{[\s\S]*?implementation\(["']com\.facebook\.react:react-android["']\)/,
      (match) => `${match}\n${depsBlock}\n`
    );

    if (modifiedBuildGradle !== buildGradle) {
      config.modResults.contents = modifiedBuildGradle;
      console.log(
        "✓ Stripe push provisioning dependencies added to app/build.gradle"
      );
    } else {
      console.warn("⚠️ Could not find dependencies block in app/build.gradle");
    }

    return config;
  });

  return config;
}

module.exports = createRunOncePlugin(
  withStripePushProvisioning,
  "stripe-push-provisioning",
  "1.0.0"
);

app.json make sure to add plugins like this

      "./plugins/withTapAndPaySDK.js",
      "./plugins/withStripePushProvisioning.js"

Finally in my example it was version m2repo_2021-07-19_v17.1.2, change it to yours if you have to.

edeuxk avatar Oct 15 '25 19:10 edeuxk

some of our users of the Flutter sdk are reporting the same issue. See above link.

remonh87 avatar Oct 30 '25 11:10 remonh87