Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

move to production #4183

Merged
merged 44 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
e2abefe
chores for the payments submodules
Baalmart Dec 4, 2024
021da4e
undoing a small removal
Baalmart Dec 4, 2024
ecc3ef8
fixing runtime errors
Baalmart Dec 4, 2024
4429e1d
merge device-monitoring into device-registry microservice
Baalmart Dec 6, 2024
eda3f60
adding the collocations route path
Baalmart Dec 6, 2024
2ba0634
Merge branch 'staging' into paddle-payments
Baalmart Dec 8, 2024
68d4762
jobs for subscriptions and transactions
Baalmart Dec 8, 2024
6cb9601
fixing Paddle configuration
Baalmart Dec 8, 2024
7dc8aab
enhancing the controllers and utils for transactions
Baalmart Dec 8, 2024
ff9925c
adding more User fiels to support Transaction processing
Baalmart Dec 8, 2024
1b0a76f
more routes for transaction processing
Baalmart Dec 8, 2024
6ae1340
Merge branch 'staging' into migrating-device-monitoring
Baalmart Dec 9, 2024
58290b8
Merge branch 'staging' into migrating-device-monitoring
Baalmart Dec 11, 2024
534690c
fixing runtime errors
Baalmart Dec 11, 2024
77b148f
Merge branch 'staging' into paddle-payments
Baalmart Dec 28, 2024
472d515
Merge branch 'staging' into paddle-payments
Baalmart Dec 30, 2024
e0381c2
more hotfixes during QA tests for the checkout endpoint
Baalmart Jan 1, 2025
ffb077f
Merge branch 'staging' into paddle-payments
Baalmart Jan 1, 2025
7996630
fixing the Paddle configuration issues
Baalmart Jan 2, 2025
2b7b70e
just refactoring the route and validation functions
Baalmart Jan 2, 2025
7f657c8
Merge branch 'staging' into migrating-device-monitoring
Baalmart Jan 3, 2025
5f1dea6
Merge branch 'staging' into migrating-device-monitoring
Baalmart Jan 4, 2025
727716b
refactoring the cron jobs to align with new database configs
Baalmart Jan 4, 2025
f0c19ba
adjustments to the hoourly status job to use internal functions
Baalmart Jan 4, 2025
fb24bca
introducing a new use case for creating feeds from thingspeak
Baalmart Jan 4, 2025
df2e24d
small adjustments to support the new usecase
Baalmart Jan 4, 2025
9debac1
fixing a runtime error for routes
Baalmart Jan 4, 2025
05dcac2
enhancing the utils and controllers
Baalmart Jan 4, 2025
f496978
Merge branch 'staging' into migrating-device-monitoring
Baalmart Jan 5, 2025
db75ec3
enhancements to the schemas
Baalmart Jan 5, 2025
3f8ccc7
Merge branch 'staging' into paddle-payments
Baalmart Jan 5, 2025
ebb3063
Update AirQo exceedance production image tag to prod-a8a141f1-1736326087
github-actions[bot] Jan 8, 2025
f9e74b5
Update KCCA exceedance production image tag to prod-a8a141f1-1736326087
github-actions[bot] Jan 8, 2025
9181c55
Update workflows staging image tag to stage-c223ba1b-1736326045
github-actions[bot] Jan 8, 2025
00aa1c2
Update auth service production image tag to prod-a8a141f1-1736326087
github-actions[bot] Jan 8, 2025
6dba68c
Update device registry production image tag to prod-a8a141f1-1736326087
github-actions[bot] Jan 8, 2025
7789969
Update meta-data production image tag to prod-a8a141f1-1736326087
github-actions[bot] Jan 8, 2025
2d24089
Update workflows prod image tag to prod-a8a141f1-1736326087
github-actions[bot] Jan 8, 2025
91b6366
Update spatial production image tag to prod-a8a141f1-1736326087
github-actions[bot] Jan 8, 2025
8a7ae52
Update predict production image tag to prod-a8a141f1-1736326087
github-actions[bot] Jan 8, 2025
7edfc2f
Merge pull request #3998 from airqo-platform/paddle-payments
Baalmart Jan 9, 2025
a890194
Update auth service staging image tag to stage-7edfc2f7-1736401595
github-actions[bot] Jan 9, 2025
4a24d39
Merge pull request #4017 from airqo-platform/migrating-device-monitoring
Baalmart Jan 9, 2025
b7daa28
Update device registry staging image tag to stage-4a24d391-1736401971
github-actions[bot] Jan 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion k8s/auth-service/values-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ app:
replicaCount: 3
image:
repository: eu.gcr.io/airqo-250220/airqo-auth-api
tag: prod-d831e59b-1736322897
tag: prod-a8a141f1-1736326087
nameOverride: ''
fullnameOverride: ''
podAnnotations: {}
Expand Down
2 changes: 1 addition & 1 deletion k8s/auth-service/values-stage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ app:
replicaCount: 2
image:
repository: eu.gcr.io/airqo-250220/airqo-stage-auth-api
tag: stage-5d451842-1736194286
tag: stage-7edfc2f7-1736401595
nameOverride: ''
fullnameOverride: ''
podAnnotations: {}
Expand Down
2 changes: 1 addition & 1 deletion k8s/device-registry/values-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ app:
replicaCount: 3
image:
repository: eu.gcr.io/airqo-250220/airqo-device-registry-api
tag: prod-d831e59b-1736322897
tag: prod-a8a141f1-1736326087
nameOverride: ''
fullnameOverride: ''
podAnnotations: {}
Expand Down
2 changes: 1 addition & 1 deletion k8s/device-registry/values-stage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ app:
replicaCount: 2
image:
repository: eu.gcr.io/airqo-250220/airqo-stage-device-registry-api
tag: stage-4ca1c803-1736183154
tag: stage-4a24d391-1736401971
nameOverride: ''
fullnameOverride: ''
podAnnotations: {}
Expand Down
2 changes: 1 addition & 1 deletion k8s/exceedance/values-prod-airqo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ app:
configmap: env-exceedance-production
image:
repository: eu.gcr.io/airqo-250220/airqo-exceedance-job
tag: prod-d831e59b-1736322897
tag: prod-a8a141f1-1736326087
nameOverride: ''
fullnameOverride: ''
2 changes: 1 addition & 1 deletion k8s/exceedance/values-prod-kcca.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ app:
configmap: env-exceedance-production
image:
repository: eu.gcr.io/airqo-250220/kcca-exceedance-job
tag: prod-d831e59b-1736322897
tag: prod-a8a141f1-1736326087
nameOverride: ''
fullnameOverride: ''
2 changes: 1 addition & 1 deletion k8s/meta-data/values-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ images:
repositories:
api: eu.gcr.io/airqo-250220/airqo-meta-data-api
sitesConsumer: eu.gcr.io/airqo-250220/airqo-meta-data-sites-consumer
tag: prod-d831e59b-1736322897
tag: prod-a8a141f1-1736326087
nameOverride: ''
fullnameOverride: ''
podAnnotations: {}
Expand Down
2 changes: 1 addition & 1 deletion k8s/predict/values-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ images:
predictJob: eu.gcr.io/airqo-250220/airqo-predict-job
trainJob: eu.gcr.io/airqo-250220/airqo-train-job
predictPlaces: eu.gcr.io/airqo-250220/airqo-predict-places-air-quality
tag: prod-d831e59b-1736322897
tag: prod-a8a141f1-1736326087
api:
name: airqo-prediction-api
label: prediction-api
Expand Down
2 changes: 1 addition & 1 deletion k8s/spatial/values-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ app:
replicaCount: 3
image:
repository: eu.gcr.io/airqo-250220/airqo-spatial-api
tag: prod-d831e59b-1736322897
tag: prod-a8a141f1-1736326087
nameOverride: ''
fullnameOverride: ''
podAnnotations: {}
Expand Down
2 changes: 1 addition & 1 deletion k8s/workflows/values-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ images:
initContainer: eu.gcr.io/airqo-250220/airqo-workflows-xcom
redisContainer: eu.gcr.io/airqo-250220/airqo-redis
containers: eu.gcr.io/airqo-250220/airqo-workflows
tag: prod-d831e59b-1736322897
tag: prod-a8a141f1-1736326087
nameOverride: ''
fullnameOverride: ''
podAnnotations: {}
Expand Down
2 changes: 1 addition & 1 deletion k8s/workflows/values-stage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ images:
initContainer: eu.gcr.io/airqo-250220/airqo-stage-workflows-xcom
redisContainer: eu.gcr.io/airqo-250220/airqo-stage-redis
containers: eu.gcr.io/airqo-250220/airqo-stage-workflows
tag: stage-36d65c6f-1736322817
tag: stage-c223ba1b-1736326045
nameOverride: ''
fullnameOverride: ''
podAnnotations: {}
Expand Down
79 changes: 79 additions & 0 deletions src/auth-service/bin/jobs/check-subscription-status.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// @utils/subscriptionUtils.js
const paddleClient = require("@config/paddle");
const TransactionModel = require("@models/Transaction");
const UserModel = require("@models/User");
const constants = require("@config/constants");
const log4js = require("log4js");
const stringify = require("@utils/stringify");
const httpStatus = require("http-status");

const logger = log4js.getLogger(
`${constants.ENVIRONMENT} -- subscription-utils`
);

const checkSubscriptionStatuses = async () => {
try {
const batchSize = 100;
let skip = 0;

while (true) {
// Find users with active subscriptions
const users = await UserModel("airqo")
.find({
subscriptionStatus: "active",
isActive: true,
})
.limit(batchSize)
.skip(skip)
.select("_id email currentSubscriptionId")
.lean();

if (users.length === 0) break;

for (const user of users) {
try {
// Fetch subscription status from Paddle
const subscriptionStatus = await paddleClient.subscriptions.get(
user.currentSubscriptionId
);

// Update local user record based on Paddle subscription status
await UserModel("airqo").findByIdAndUpdate(user._id, {
$set: {
subscriptionStatus: subscriptionStatus.status,
lastSubscriptionCheck: new Date(),
},
});

// Log significant status changes or actions needed
if (subscriptionStatus.status === "past_due") {
// Send reminder email or notification
await mailer.sendSubscriptionReminderEmail({
email: user.email,
subscriptionId: user.currentSubscriptionId,
});
}
} catch (error) {
logger.error(
`Failed to check subscription for user ${
user.email
} --- ${stringify(error)}`
);
}
}

skip += batchSize;
}
} catch (error) {
logger.error(`Subscription status check error --- ${stringify(error)}`);
}
};

// Schedule the subscription status check job
const schedule = "0 1 * * *"; // every day at 1 AM
cron.schedule(schedule, checkSubscriptionStatuses, {
scheduled: true,
timezone: "Africa/Nairobi",
});

module.exports = subscriptionUtils;
204 changes: 204 additions & 0 deletions src/auth-service/bin/jobs/subscription-renewal-job.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
// @utils/subscriptionRenewalUtils.js
const paddleClient = require("@config/paddle");
const TransactionModel = require("@models/Transaction");
const UserModel = require("@models/User");
const constants = require("@config/constants");
const log4js = require("log4js");
const stringify = require("@utils/stringify");
const httpStatus = require("http-status");
const cron = require("node-cron");
const mailer = require("@utils/mailer");

const logger = log4js.getLogger(
`${constants.ENVIRONMENT} -- subscription-renewal-utils`
);

const subscriptionRenewalUtils = {
/**
* Automatically renew subscriptions and process transactions
*/
processAutomaticRenewal: async () => {
try {
const batchSize = 100;
let skip = 0;

while (true) {
// Find users eligible for automatic renewal
const users = await UserModel("airqo")
.find({
subscriptionStatus: "active",
isActive: true,
automaticRenewal: true,
nextBillingDate: { $lte: new Date() },
})
.limit(batchSize)
.skip(skip)
.select("_id email currentSubscriptionId currentPlanDetails")
.lean();

if (users.length === 0) break;

for (const user of users) {
try {
// Fetch current subscription details
const subscriptionDetails = await paddleClient.subscriptions.get(
user.currentSubscriptionId
);

// Prepare transaction data
const transactionData = {
customerId: subscriptionDetails.customerId,
items: [
{
priceId: user.currentPlanDetails.priceId,
quantity: 1,
},
],
currency: user.currentPlanDetails.currency || "USD",
};

// Create renewal transaction
const renewalTransaction = await paddleClient.transactions.create(
transactionData
);

// Record transaction in local database
const transactionRecord = await TransactionModel("airqo").register({
paddle_transaction_id: renewalTransaction.id,
paddle_event_type: "transaction.completed",
user_id: user._id,
paddle_customer_id: subscriptionDetails.customerId,
amount: renewalTransaction.total,
currency: transactionData.currency,
status: "completed",
payment_method: renewalTransaction.paymentMethod,
items: transactionData.items,
description: "Subscription Automatic Renewal",
metadata: {
subscriptionId: user.currentSubscriptionId,
originalTransactionData: renewalTransaction,
},
});

// Update user's next billing date
const nextBillingDate = new Date();
nextBillingDate.setMonth(nextBillingDate.getMonth() + 1); // Assuming monthly subscription

await UserModel("airqo").findByIdAndUpdate(user._id, {
$set: {
nextBillingDate: nextBillingDate,
lastRenewalDate: new Date(),
},
});

// Send successful renewal notification
await mailer.sendSubscriptionRenewalConfirmation({
email: user.email,
transactionId: renewalTransaction.id,
amount: renewalTransaction.total,
currency: transactionData.currency,
});
} catch (error) {
// Handle renewal failure
logger.error(
`Automatic renewal failed for user ${user.email} --- ${stringify(
error
)}`
);

// Send failure notification
await mailer.sendSubscriptionRenewalFailure({
email: user.email,
subscriptionId: user.currentSubscriptionId,
errorMessage: error.message,
});

// Update user status if multiple renewal attempts fail
await UserModel("airqo").findByIdAndUpdate(user._id, {
$set: {
subscriptionStatus: "past_due",
automaticRenewal: false,
},
});
}
}

skip += batchSize;
}
} catch (error) {
logger.error(
`Subscription renewal process error --- ${stringify(error)}`
);
}
},

/**
* Notify users about upcoming renewals
*/
sendUpcomingRenewalNotifications: async () => {
try {
const batchSize = 100;
let skip = 0;

// Find users with renewals in next 3 days
const threeeDaysFromNow = new Date();
threeeDaysFromNow.setDate(threeeDaysFromNow.getDate() + 3);

while (true) {
const users = await UserModel("airqo")
.find({
subscriptionStatus: "active",
isActive: true,
automaticRenewal: true,
nextBillingDate: {
$gte: new Date(),
$lte: threeeDaysFromNow,
},
})
.limit(batchSize)
.skip(skip)
.select("_id email nextBillingDate currentPlanDetails")
.lean();

if (users.length === 0) break;

for (const user of users) {
await mailer.sendUpcomingRenewalNotification({
email: user.email,
nextBillingDate: user.nextBillingDate,
planDetails: user.currentPlanDetails,
});
}

skip += batchSize;
}
} catch (error) {
logger.error(
`Upcoming renewal notifications error --- ${stringify(error)}`
);
}
},
};

// Schedule jobs
const renewalSchedule = "0 2 * * *"; // every day at 2 AM
cron.schedule(
renewalSchedule,
subscriptionRenewalUtils.processAutomaticRenewal,
{
scheduled: true,
timezone: "Africa/Nairobi",
}
);

const notificationSchedule = "0 9 * * *"; // every day at 9 AM
cron.schedule(
notificationSchedule,
subscriptionRenewalUtils.sendUpcomingRenewalNotifications,
{
scheduled: true,
timezone: "Africa/Nairobi",
}
);

module.exports = subscriptionRenewalUtils;
Loading
Loading