Skip to content

Commit

Permalink
Merge pull request #4636 from mozilla/MNTOR-3272
Browse files Browse the repository at this point in the history
MNTOR-3272: API to call FxA apply coupon
  • Loading branch information
mansaj authored Jun 12, 2024
2 parents f7f08f4 + 114d8e2 commit 244ac7c
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 5 deletions.
2 changes: 2 additions & 0 deletions .env-dist
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,5 @@ SENTRY_AUTH_TOKEN=

# Whether GA4 sends data or not. NOTE: must be set in build environment.
NEXT_PUBLIC_GA4_DEBUG_MODE=true

CURRENT_COUPON_CODE_ID=
92 changes: 92 additions & 0 deletions src/app/functions/server/applyCoupon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { SubscriberRow } from "knex/types/tables";
import { logger } from "./logging";
import { SerializedSubscriber } from "../../../next-auth";
import {
addCouponForSubscriber,
checkCouponForSubscriber,
} from "../../../db/tables/subscriber_coupons";
import { applyCoupon } from "../../../utils/fxa";

export async function applyCurrentCouponCode(
subscriber: SubscriberRow | SerializedSubscriber,
) {
logger.info("fxa_apply_coupon_code", {
subscriber: subscriber.id,
});

const currentCouponCode = process.env.CURRENT_COUPON_CODE_ID;
if (!currentCouponCode) {
logger.error(
"fxa_apply_coupon_code_failed",
"Coupon code ID is not set. Please set the env var: CURRENT_COUPON_CODE_ID",
);
return {
success: false,
message: "Coupon code not set",
};
}

try {
if (
!(await checkCouponForSubscriber(subscriber.id, currentCouponCode)) &&
subscriber.fxa_access_token
) {
await applyCoupon(subscriber.fxa_access_token, currentCouponCode);
await addCouponForSubscriber(subscriber.id, currentCouponCode);
logger.info("fxa_apply_coupon_code_success");
return {
success: true,
};
}
return {
success: false,
message: "Coupon code already applied for subscriber",
};
} catch (ex) {
logger.error("fxa_apply_coupon_code_failed", {
subscriber_id: subscriber.id,
exception: ex,
});
return {
success: false,
};
}
}

export async function checkCurrentCouponCode(
subscriber: SubscriberRow | SerializedSubscriber,
) {
logger.info("fxa_check_coupon", {
subscriber: subscriber.id,
});

const currentCouponCode = process.env.CURRENT_COUPON_CODE_ID;
if (!currentCouponCode) {
logger.error(
"fxa_check_coupon_failed",
"Coupon code ID is not set. Please set the env var: CURRENT_COUPON_CODE_ID",
);
return {
success: false,
};
}

try {
await checkCouponForSubscriber(subscriber.id, currentCouponCode);
return {
success: true,
};
} catch (ex) {
logger.error("fxa_check_coupon_failed", {
subscriber_id: subscriber.id,
exception: ex,
});
return {
success: false,
};
}
}
5 changes: 2 additions & 3 deletions src/db/tables/subscriber_coupons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import createDbConnection from "../connect.js";
import { logger } from "../../app/functions/server/logging";
import { SubscriberCouponRow } from "knex/types/tables";

const knex = createDbConnection();

Expand All @@ -29,7 +28,7 @@ async function addCouponForSubscriber(

let res;
try {
res = await knex("subscribers_coupon")
res = await knex("subscriber_coupons")
.insert({
subscriber_id: subscriberId,
coupon_code: couponCode,
Expand All @@ -46,7 +45,7 @@ async function addCouponForSubscriber(
}
throw e;
}
return res?.[0] as SubscriberCouponRow;
return res?.[0];
}

export { checkCouponForSubscriber, addCouponForSubscriber };
53 changes: 51 additions & 2 deletions src/utils/fxa.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ async function deleteSubscription(bearerToken) {
}
})
if (!response.ok) {
// throw new InternalServerError(`bad response: ${response.status}`)
throw new Error(await response.text())
} else {
console.info(`delete_fxa_subscription: success - ${JSON.stringify(await response.json())}`)
}
Expand All @@ -144,6 +144,54 @@ async function deleteSubscription(bearerToken) {
}
/* c8 ignore stop */

/**
* @param {string} bearerToken
* @param {string} couponCodeId
* @returns
*/
// Not covered by tests; mostly side-effects. See test-coverage.md#mock-heavy
/* c8 ignore start */
async function applyCoupon(bearerToken, couponCodeId) {
try {
const subs = await getSubscriptions(bearerToken) ?? []
let subscriptionId;
for (const sub of subs) {
if (sub && sub.productId && sub.productId === AppConstants.PREMIUM_PRODUCT_ID) {
subscriptionId = sub.subscriptionId
}
}
if (subscriptionId) {
const applyCouponUrl = `${AppConstants.OAUTH_ACCOUNT_URI}/oauth/subscriptions/coupon/apply`
const response = await fetch(applyCouponUrl, {
method: "PUT",
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Bearer ${bearerToken}`
},
body: JSON.stringify({
promotionId: couponCodeId,
subscriptionId
})
})
if (!response.ok) {
const errMsg = await response.text()
console.info(`apply_coupon: failed - ${errMsg}`)
throw new Error(`apply_coupon: failed - ${errMsg}`)
} else {
console.info(`apply_coupon: success - ${JSON.stringify(await response.json())}`)
}
}
} catch (e) {
if (e instanceof Error) {
console.error('apply_coupon', { stack: e.stack })
}
throw e;
}
}
/* c8 ignore stop */


/**
* @param {crypto.BinaryLike} email
*/
Expand All @@ -158,5 +206,6 @@ export {
revokeOAuthTokens,
getSha1,
getSubscriptions,
deleteSubscription
deleteSubscription,
applyCoupon
}

0 comments on commit 244ac7c

Please sign in to comment.