Last active
August 29, 2024 18:07
-
-
Save astashov/705c45e5f8f6c89cc5f968ba128b1800 to your computer and use it in GitHub Desktop.
Script to update Google localized prices for all countries using CSV from https://docs.google.com/spreadsheets/d/1BwSqpkqa98nk9Gh7238U7KQbvdvh_tGlXmQKE0Druwk/ Article: https://www.liftosaur.com/blog/posts/implementing-localized-pricing-for-your-app/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
// [email protected]). 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(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Ah, glad you found it helpful! :)