-
-
Save rossbulat/dfab0da57e8a1c239e12cbacef27c622 to your computer and use it in GitHub Desktop.
Automatic Subscription Renewal / Cancellation Service via Receipt Validation
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 moment = require('moment'); | |
var appleReceiptVerify = require('node-apple-receipt-verify') | |
// initialise receipt verify instance | |
appleReceiptVerify.config({ | |
secret: process.env.APPLE_APP_STORE_SECRET, | |
environment: [process.env.APPLE_APP_STORE_ENVIRONMENT], | |
excludeOldTransactions: true, | |
ignoreExpired: true, | |
}); | |
// define service interval to 1 hour | |
const interval = 60 * 60 * 1000; | |
module.exports = { | |
// service logic - main function of service | |
renewOrCancelSubscriptions: async function (client) { | |
try { | |
const db = client.db('my-app-db'); | |
// get accounts past renewal date | |
const accounts = await db | |
.collection('users') | |
.find({ | |
'plan.nextRenewal': { | |
$lte: moment().unix() | |
} | |
}) | |
.toArray(); | |
if (accounts.length === 0) { | |
return; | |
} | |
// for each over-due account | |
for (let account of accounts) { | |
const expiryTimestamp = account.plan.expiryTimestamp; | |
// get latest transaction from receipt | |
const receipt = await db | |
.collection('iap-receipts') | |
.find({ | |
verified: true, | |
user_id: account._id, | |
}) | |
.sort({ | |
timestamp: -1 | |
}) | |
.toArray(); | |
try { | |
// get latest subscription receipt | |
const record = receipt[0]; | |
// re-verify receipt to get the latest subscription status | |
const purchases = await appleReceiptVerify.validate({ | |
receipt: record.receipt | |
}); | |
// no active transactions (cancelled or expired subscription) | |
if(purchases.length === 0) { | |
// change to free tier and remove plan metadata from user record | |
await db | |
.collection('users') | |
.updateOne({ | |
_id: account._id | |
}, { | |
$unset: { | |
'plan.expiryTimestamp': true, | |
}, | |
$set: { | |
'plan.planId': 'free' | |
} | |
}); | |
} | |
// active purchases returned with latest expiry timestamp | |
if (purchases.length !== 0) { | |
// get the latest purchase from receipt verification | |
const latestPurchase = purchases[0]; | |
// reformat the expiration date as a unix timestamp | |
let latestExpiryTimestamp = latestPurchase.expirationDate; | |
let productId = latestPurchase.productId; | |
latestExpiryTimestamp = Math.round(latestExpiryTimestamp / 1000); | |
// update renewal date if more than current one | |
if (latestExpiryTimestamp > expiryTimestamp) { | |
await db | |
.collection('users') | |
.updateOne({ | |
_id: account._id | |
}, { | |
$set: { | |
'plan.planId': productId, | |
'plan.nextRenewal': latestExpiryTimestamp | |
} | |
}); | |
} | |
} | |
} catch (e) { | |
console.log(e); | |
} | |
} | |
} catch (e) { | |
console.log(e); | |
} | |
}, | |
// define service loop and initiate service | |
init: async function (client) { | |
module.exports.renewOrCancelSubscriptions(client); | |
setInterval(async function () { | |
module.exports.renewOrCancelSubscriptions(client); | |
}, interval) | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment