Skip to content

Instantly share code, notes, and snippets.

@astashov
Last active August 29, 2024 18:07
Show Gist options
  • Save astashov/705c45e5f8f6c89cc5f968ba128b1800 to your computer and use it in GitHub Desktop.
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

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
Author

Ah, glad you found it helpful! :)

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