Skip to content

Commit

Permalink
User lowercase currency symbols, new subscription button logic
Browse files Browse the repository at this point in the history
  • Loading branch information
NikkelM committed Jun 23, 2024
1 parent f5b8fc5 commit d902ca8
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 40 deletions.
5 changes: 3 additions & 2 deletions src/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,16 @@ async function getUserLocaleInfo() {
const response = await fetch(`http://ip-api.com/json/${userIP}?fields=countryCode,currency`);
if (response.ok) {
const data = await response.json();
userCurrency = data.currency;
// Lowercase, as that's the formatting in Stripe
userCurrency = data.currency.toLowerCase();
userCountryCode = data.countryCode;
}
} catch (error) {
console.error("Error fetching user currency:", error);
}

if (!userCurrency) {
userCurrency = countryToCurrency[navigator.language.split("-")[1]];
userCurrency = countryToCurrency[navigator.language.split("-")[1]].toLowerCase();
}
if (!userCountryCode) {
userCountryCode = navigator.language.split("-")[1];
Expand Down
65 changes: 51 additions & 14 deletions src/html/shufflePlus.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
import { setSyncStorageValue } from "../chromeStorage.js";
import { getUser, revokeAccess } from "../googleOauth.js";
import { buildShufflingHints, tryFocusingTab } from "./htmlUtils.js";
import { openStripeCheckout, getSubscriptions, userHasActiveSubscriptionRole } from "../stripe.js";
import { openStripeCheckout, getSubscriptions, getProducts, getPriceFromProducts, userHasActiveSubscriptionRole } from "../stripe.js";

// ----- Setup -----
let user;
let selectedProductInfo;
// TODO: Change default
const defaultProductName = "Shuffle+ (Test)";
const domElements = getDomElements();
await setDomElementValuesFromConfig(domElements);
await setDomElementEventListeners(domElements);
Expand Down Expand Up @@ -93,7 +96,16 @@ async function setDomElementValuesFromConfig(domElements) {
}

// Set the value of the selector to the user currency, or USD if not found
domElements.currencySelectorSelect.value = (await chrome.storage.session.get("userCurrency")).userCurrency || "USD";
const userCurrency = (await chrome.storage.session.get("userCurrency")).userCurrency || "usd";
domElements.currencySelectorSelect.value = userCurrency;

selectedProductInfo = await getProducts(userCurrency, defaultProductName);
let requestedInterval = domElements.switchPlanIntervalToggle.checked ? "year" : "month";
// We offer a 3-monthly and a yearly plan
let requestedIntervalCount = requestedInterval == "year" ? 1 : 3;
const price = getPriceFromProducts(selectedProductInfo, requestedInterval, requestedIntervalCount);

domElements.subscribeButton.textContent = `Subscribe for ${domElements.currencySelectorSelect.value.toUpperCase()} ${price.priceInfo.unit_amount / 100}`;

// This must be last
domElements.loadingOverlay.classList.add("fadeOut");
Expand Down Expand Up @@ -134,23 +146,48 @@ async function setDomElementEventListeners(domElements) {
}
});

// Currency selector
domElements.currencySelectorSelect.addEventListener("change", async function () {
const userCurrency = domElements.currencySelectorSelect.value;
chrome.storage.session.set({ userCurrency: userCurrency });

selectedProductInfo = await getProducts(userCurrency, defaultProductName);
let requestedInterval = domElements.switchPlanIntervalToggle.checked ? "year" : "month";
// We offer a 3-monthly and a yearly plan
let requestedIntervalCount = requestedInterval == "year" ? 1 : 3;
const price = getPriceFromProducts(selectedProductInfo, requestedInterval, requestedIntervalCount);

domElements.subscribeButton.textContent = `Subscribe for ${userCurrency.toUpperCase()} ${price.priceInfo.unit_amount / 100}`;
});

// Plan interval toggle
domElements.switchPlanIntervalToggle.addEventListener("change", async function () {
let requestedInterval = domElements.switchPlanIntervalToggle.checked ? "year" : "month";
// We offer a 3-monthly and a yearly plan
let requestedIntervalCount = requestedInterval == "year" ? 1 : 3;
const price = getPriceFromProducts(selectedProductInfo, requestedInterval, requestedIntervalCount);

domElements.subscribeButton.textContent = `Subscribe for ${domElements.currencySelectorSelect.value.toUpperCase()} ${price.priceInfo.unit_amount / 100}`;
});

// Subscribe button
domElements.subscribeButton.addEventListener("click", async function () {
domElements.subscribeButton.textContent = "Preparing subscription...";
// TODO: Use correct product name
let requestedProduct = "Shuffle+ (Test)";
let requestedCurrency = domElements.currencySelectorSelect.value;
let requestedInterval = domElements.switchPlanIntervalToggle.checked ? "year" : "month";
// We offer a 3-monthly and a yearly plan
let requestedIntervalCount = requestedInterval == "year" ? 1 : 3;

await openStripeCheckout(user, requestedProduct, requestedCurrency, requestedInterval, requestedIntervalCount);
});

// Manage subscription button
domElements.manageSubscriptionButton.addEventListener("click", async function () {
// TODO: This is the test URL
const url = `https://billing.stripe.com/p/login/test_7sI5lw95Afu5fzqbII?prefilled_email=${user.userInfo.email}`;
await chrome.tabs.create({ url });

// TODO: Move to new button
// domElements.manageSubscriptionButton.textContent = "Preparing subscription...";

// // Get configuration from UI or use defaults
// // TODO: Use correct product name
// let requestedProduct = "Shuffle+ (Test)";
// let requestedCurrency = domElements.currencySelectorSelect.value;
// let requestedInterval = "year";
// let requestedIntervalCount = 1; // Unused with yearly interval in this context

// await openStripeCheckout(user, requestedProduct, requestedCurrency, requestedInterval, requestedIntervalCount);
});

// Forget me button
Expand Down
42 changes: 22 additions & 20 deletions src/stripe.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ const app = !getApps().length ? initializeApp(firebaseConfig) : getApp();
const firestore = getFirestore(app);

// Get products and pricing information from Firestore/Stripe
async function getProducts(currency) {
export async function getProducts(currency, productName) {
const currencyRef = new FieldPath("metadata", "currency");
const productCurrencyQuery = query(
collection(firestore, "products"),
where("active", "==", true),
where("name", "==", productName),
where(currencyRef, "==", currency)
);

Expand All @@ -23,7 +24,7 @@ async function getProducts(currency) {
const productsPromises = productSnapshot.docs.map(async (productDoc) => {
let productInfo = productDoc.data();

// Fetch prices subcollection per product
// Fetch prices sub-collection per product
const priceQuerySnapshot = await getDocs(collection(productDoc.ref, "prices"));

// Iterate over all price documents and filter by currency
Expand All @@ -42,35 +43,36 @@ async function getProducts(currency) {
// If no products with the requested currency are found, return products with USD pricing
if (products.length == 0) {
console.log(`No products found with currency ${currency}, trying to get products with USD pricing.`);
return getProducts("usd");
if (currency == "usd") {
console.log("No products found with USD pricing, returning empty products array.");
return { products: [], currency: null };
}
return getProducts("usd", productName);
}

return {
products: products.filter(product => product !== null),
currency: currency
};
return products.filter(product => product !== null)[0];
}

export function getPriceFromProducts(products, requestedInterval, requestedIntervalCount) {
return products.prices.find(
p =>
p.priceInfo.active &&
p.priceInfo.type == "recurring" &&
p.priceInfo.recurring.interval == requestedInterval &&
// If using the monthly interval, get the requested kind
(requestedInterval == "year" || p.priceInfo.recurring.interval_count == requestedIntervalCount)
);
}

export async function openStripeCheckout(user, requestedProduct, requestedCurrency, requestedInterval, requestedIntervalCount) {
user ??= await getUser(false, true, true);
// TODO: Do we want to scope to requestedProduct in getProducts as well?
// In case the requested currency is not available, we default to USD
const { products, currency } = await getProducts(requestedCurrency);

// In theory, there should always only be one matching product returned by getProducts
const shufflePlusTestProducts = products.find(p => p.name == requestedProduct);
const products = await getProducts(requestedCurrency, requestedProduct);

let paymentMethods = ["paypal", "card", "link"];

let checkoutSessionData = {
price: shufflePlusTestProducts.prices.find(
p =>
p.priceInfo.active &&
p.priceInfo.type == "recurring" &&
p.priceInfo.recurring.interval == requestedInterval &&
// If using the monthly interval, get the requested kind
(requestedInterval == "year" || p.priceInfo.recurring.interval_count == requestedIntervalCount)
).priceId,
price: getPriceFromProducts(products, requestedInterval, requestedIntervalCount).priceId,
// TODO: Proper success URL, cancellation URL. Redirect to either Github or nikkelm.dev, if redirecting to extension is not possible?
//chrome runtime URL's are not valid for Stripe
// current success_url does nothing after completion (users stays on stripe checkout page)
Expand Down
8 changes: 4 additions & 4 deletions static/html/shufflePlus.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ <h2 id="welcomeHeader">Welcome! Sign in below to get started</h2>
<div id="currencySelectorContainer" class="alignTopRight">
<label for="currencySelector">Currency:</label>
<select id="currencySelectorSelect" class="currencySelector" name="currencySelect">
<option value="USD">USD</option>
<option value="EUR">EUR</option>
<option value="GBP">GBP</option>
<option value="RUB">RUB</option>
<option value="usd">USD</option>
<option value="eur">EUR</option>
<option value="gbp">GBP</option>
<option value="rub">RUB</option>
</select>
</div>

Expand Down

0 comments on commit d902ca8

Please sign in to comment.