stripe-android-issuing-push-provisioning dependency resolution fails in EAS Build due to private play-services-tapandpay SDK
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
I ran into the same issue, my workaround was the following.
Create 2 plugins:
- 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"
);
- 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.
some of our users of the Flutter sdk are reporting the same issue. See above link.