Skip to content

Instantly share code, notes, and snippets.

@astashov
Last active August 28, 2026 16:14
Show Gist options
  • Select an option

  • Save astashov/705c45e5f8f6c89cc5f968ba128b1800 to your computer and use it in GitHub Desktop.

Select an option

Save astashov/705c45e5f8f6c89cc5f968ba128b1800 to your computer and use it in GitHub Desktop.
const { google } = require("googleapis");
const fs = require("fs");
// You need to go to Google Cloud console (console.cloud.google.com), then APIs
// & Services -> Credentials. There, create a new service account (or reuse
// existing if you have one). Click on a service account, go to Keys, and create
// a new key, and download JSON for it. You'll use path to that JSON for
// SERVICE_ACCOUNT_FILE var.
//
// Then, go to Google Play Console, then "Users and Permissions" on the left
// sidebar, invite new users and use the service account's email (looks like
// something@something.iam.gserviceaccount.com). There, in the Permissions,
// allow "Manage store presence" and also all of the "Financial Data".
const SERVICE_ACCOUNT_FILE = "/Users/anton/projects/liftosaur/liftosaur-google-service-account-key.json";
// That's your app id
const PACKAGE_NAME = "com.liftosaur.www.twa";
// Map subscription / in-app purchase ids to the fields in `IPrices`
const KEYS = {
"com.liftosaur.subscription.and_montly": "monthly",
"com.liftosaur.subscription.and_yearly": "yearly",
"com.liftosaur.subscription.and_lifetime": "lifetime",
};
// We have to specify "default" price for any in-app purchases
const DEFAULT_IN_APP_PRICE = {
priceMicros: "79990000",
currency: "USD",
};
// These countries don't support billing for in-app purchases. But apparently still supported for subscriptions.
const IGNORED_IN_APP_COUNTRIES = [
"IS",
"MT",
"AW",
"BS",
"KN",
"SC",
"UY",
"TT",
"MU",
"AR",
"AG",
"MV",
"LY",
"DO",
"BY",
"MK",
"BA",
"AM",
"AL",
"BW",
"LC",
"AZ",
"SR",
"VE",
"GD",
"GA",
"MD",
"TM",
"NE",
"ML",
"BF",
"BJ",
"TG",
"CF",
"GW",
"CD",
"UG",
"YE",
"AO",
"UZ",
"MZ",
"CI",
"NP",
"CM",
"ZM",
"TD",
"SO",
"SN",
"GT",
"ZW",
"GN",
"RW",
"TN",
"HT",
"HN",
"TJ",
"PG",
"SL",
"LA",
"KG",
"NI",
"CG",
"LR",
"ER",
"NA",
"JM",
"GM",
"DJ",
"FJ",
"KM",
"SB",
"CV",
"BZ",
"VU",
"WS",
"TO",
"DM",
];
const auth = new google.auth.GoogleAuth({
keyFile: SERVICE_ACCOUNT_FILE,
scopes: ["https://www.googleapis.com/auth/androidpublisher"],
});
// It expects the CSV format like in the shared Google Sheet
function getNewPrices() {
return fs
.readFileSync("google_prices.csv", "utf8")
.split("\n")
.slice(1)
.map((line) => {
const [
,
code,
currencyCode,
,
,
,
,
,
,
,
,
,
,
monthlyMarketing,
yearlyMarketing,
lifetimeMarketing,
] = line.split(",").map((v) => v.trim());
return {
countryCode: code.trim(),
currencyCode: currencyCode.trim(),
monthly: parseFloat(monthlyMarketing.trim()),
yearly: parseFloat(yearlyMarketing.trim()),
lifetime: parseFloat(lifetimeMarketing.trim()),
};
});
}
async function updateSubscriptionPrices() {
const androidpublisher = google.androidpublisher({
version: "v3",
auth,
});
const newPrices = getNewPrices();
const currentResponse = await androidpublisher.monetization.subscriptions.list({
packageName: PACKAGE_NAME,
});
const currentSubscriptions = currentResponse.data.subscriptions || [];
for (const subscription of currentSubscriptions) {
const key = KEYS[subscription.productId];
for (const basePlan of subscription.basePlans || []) {
basePlan.regionalConfigs = newPrices.map((c) => {
const units = c[key].toFixed(0);
const nanos = Number(((c[key] % 1) * 1e9).toFixed());
return {
regionCode: c.countryCode,
newSubscriberAvailability: true,
price: { currencyCode: c.currencyCode, units, nanos },
};
});
}
const response = await androidpublisher.monetization.subscriptions.patch({
packageName: PACKAGE_NAME,
productId: subscription.productId,
requestBody: subscription,
"regionsVersion.version": "2022/02",
updateMask: "basePlans",
});
console.log(response);
console.log("Subscription prices updated successfully.");
}
}
async function updateProductPrices() {
const androidpublisher = google.androidpublisher({
version: "v3",
auth,
});
const newPrices = getNewPrices();
const currentResponse = await androidpublisher.inappproducts.list({
packageName: PACKAGE_NAME,
});
const currentProducts = currentResponse.data.inappproduct || [];
for (const product of currentProducts) {
const key = KEYS[product.sku];
product.defaultPrice = DEFAULT_IN_APP_PRICE;
product.prices = newPrices
.filter((c) => IGNORED_IN_APP_COUNTRIES.indexOf(c.countryCode) === -1)
.reduce((memo, c) => {
memo[c.countryCode] = {
priceMicros: (c[key] * 1000000).toFixed(0),
currency: c.currencyCode,
};
return memo;
}, {});
const response = await androidpublisher.inappproducts.patch({
packageName: PACKAGE_NAME,
sku: product.sku,
requestBody: product,
});
console.log(response);
console.log("Product prices updated successfully.");
}
}
async function mainUpdatePrices() {
await updateSubscriptionPrices();
await updateProductPrices();
}
mainUpdatePrices();
@sethmills21

Copy link
Copy Markdown

yo! this rocks!

literally was just about to write a very similar script haha feel free to email me if you want to collab any more I have a few more ideas here (seth@rapchat.com)

@astashov

Copy link
Copy Markdown
Author

Ah, glad you found it helpful! :)

@rajmahajan243

Copy link
Copy Markdown

I was searching for such kind of product and claude landed me on this. Must be a great product.
I have two apps live on play store. I built a product similar to this but bit non tech user friendly. It is desktop app for mac and windows. Since there are people who run apps and handles only business side like my cofounder. Ultimately I converted that app into another product. In case you guys feel need visit SnapParity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment