Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 src/app_store_receipt_verifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const MOCK_TRANSACTION_HISTORY = [{
type: "iap",
id: "1",
start_date: current_time(),
end_date: current_time() + 60 * 60 * 24 * 30,
end_date: current_time() + 365 * 24 * 60 * 60,
purchased_date: current_time(),
duration: null
}];
Expand Down
2 changes: 1 addition & 1 deletion src/router_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ function config_router(app) {
}

const transaction_id = req.body.transaction_id
if (!transaction_id) {
if (!transaction_id && transaction_id != 0) { // Xcode environment sends 0 as a transaction id, so it is not "missing" in that case
invalid_request(res, 'Missing transaction_id')
return
}
Expand Down
28 changes: 28 additions & 0 deletions src/user_management.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,36 @@ function mark_iap_history_was_refreshed(api, pubkey) {
return { account: account, request_error: null }
}

// Helper function to calculate the total days of membership from a transaction history
// @param {Transaction[]} transactions - The transaction history
// @returns {number} - The total membership time in seconds
//
// Implementation note: Measuring in seconds prevents doing divisions, which speeds up the calculation
function total_active_membership_time(transactions) {
return transactions.reduce((acc, transaction) => {
if(transaction.type === "iap" && transaction.end_date !== null && transaction.start_date !== null) {
return acc + (transaction.end_date - transaction.start_date)
}
else if(transaction.type === "legacy" && transaction.end_date !== null && transaction.start_date !== null) {
return acc + (transaction.end_date - transaction.start_date)
}
else if(transaction.type === "ln" && transaction.duration !== null) {
return acc + transaction.duration
}
return acc
}, 0);
}

function get_account_info_payload(subscriber_number, account, authenticated = false) {
if (!account)
return null

const account_active = (account.expiry && current_time() < account.expiry) ? true : false
// We consider one year to be 360 days, to be a bit lenient with users who might have a few days of downtime in their subscription, and make sure everyone who roughly got a year of service gets the benefit during the announcement.
const one_year_in_seconds = 360 * 24 * 60 * 60
// Performance optimization: We only calculate the total membership time if the account is active
const member_for_more_than_one_year = account_active ? total_active_membership_time(account.transactions) > one_year_in_seconds : false


return {
pubkey: account.pubkey,
Expand All @@ -165,6 +190,9 @@ function get_account_info_payload(subscriber_number, account, authenticated = fa
subscriber_number: subscriber_number,
active: account_active,
testflight_url: (authenticated && account_active) ? process.env.TESTFLIGHT_URL : null,
attributes: {
member_for_more_than_one_year: member_for_more_than_one_year,
}
}
}

Expand Down
9 changes: 6 additions & 3 deletions test/router_config.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ const { v4: uuidv4 } = require('uuid')
test('config_router - Account management routes', async (t) => {
const account_info = {
pubkey: 'abc123',
created_at: Date.now() - 60 * 60 * 24 * 30 * 1000, // 30 days ago
expiry: Date.now() + 60 * 60 * 24 * 30 * 1000 // 30 days
created_at: current_time() - 60 * 60 * 24 * 30, // 30 days ago
expiry: current_time() + 60 * 60 * 24 * 30 // 30 days
};
const pubkeys_to_user_ids = {
'abc123': 1
Expand Down Expand Up @@ -86,7 +86,10 @@ test('config_router - Account management routes', async (t) => {
subscriber_number: 1,
expiry: account_info.expiry,
active: true,
testflight_url: null
testflight_url: null,
attributes: {
member_for_more_than_one_year: false
}
};
t.same(res.body, expectedData, 'Response should match expected value');
t.end();
Expand Down
Loading