Skip to content

Instantly share code, notes, and snippets.

@rossbulat
Last active August 24, 2020 16:42
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rossbulat/dfab0da57e8a1c239e12cbacef27c622 to your computer and use it in GitHub Desktop.
Save rossbulat/dfab0da57e8a1c239e12cbacef27c622 to your computer and use it in GitHub Desktop.
Automatic Subscription Renewal / Cancellation Service via Receipt Validation
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