Skip to content

Commit

Permalink
2.4.0 - add free trial support
Browse files Browse the repository at this point in the history
  • Loading branch information
Glench committed Jul 21, 2021
1 parent 48e7c04 commit 4ff23ab
Show file tree
Hide file tree
Showing 8 changed files with 349 additions and 26 deletions.
66 changes: 61 additions & 5 deletions ExtPay.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ if (typeof window !== 'undefined') {
window.addEventListener('message', (event) => {
if (event.origin !== 'http://localhost:3000') return;
if (event.source != window) return;
if (event.data === 'fetch-user') {
if (event.data === 'fetch-user' || event.data === 'trial-start') {
browser.runtime.sendMessage(event.data)
}
}, false);
Expand Down Expand Up @@ -71,7 +71,8 @@ You can copy and paste this to your manifest.json file to fix this error:
await set({'extensionpay_installed_at': date})
})

var paid_callbacks = [];
const paid_callbacks = [];
const trial_callbacks = [];

async function create_key() {
var body = {};
Expand Down Expand Up @@ -139,7 +140,7 @@ You can copy and paste this to your manifest.json file to fix this error:
const parsed_user = {}
for (var [key, value] of Object.entries(user_data)) {
if (value && value.match && value.match(datetime_re)) {
value = new Date()
value = new Date(value)
}
parsed_user[key] = value
}
Expand All @@ -151,6 +152,12 @@ You can copy and paste this to your manifest.json file to fix this error:
paid_callbacks.forEach(cb => cb(parsed_user))
}
}
if (parsed_user.trialStartedAt) {
if (!storage.extensionpay_user || (storage.extensionpay_user && !storage.extensionpay_user.trialStartedAt)) {
trial_callbacks.forEach(cb => cb(parsed_user))
}

}
await set({extensionpay_user: user_data})

return parsed_user;
Expand Down Expand Up @@ -192,9 +199,49 @@ You can copy and paste this to your manifest.json file to fix this error:
}
}

async function open_trial_page(period) {
// let user have period string like '1 week' e.g. "start your 1 week free trial"

var api_key = await get_key();
if (!api_key) {
api_key = await create_key();
}
var url = `${EXTENSION_URL}/trial?api_key=${api_key}`
if (period) {
url += `&period=${period}`
}

if (browser.windows) {
try {
browser.windows.create({
url,
type: "popup",
focused: true,
width: 500,
height: 650,
left: 450
})
} catch(e) {
// firefox doesn't support 'focused'
browser.windows.create({
url,
type: "popup",
width: 500,
height: 650,
left: 450
})
}
} else {
// https://developer.mozilla.org/en-US/docs/Web/API/Window/open
// for opening from a content script
window.open(url, null, "toolbar=no,location=no,directories=no,status=no,menubar=no,width=500,height=800,left=450")
}

}


var polling = false;
async function poll_user() {
async function poll_user_paid() {
// keep trying to fetch user in case stripe webhook is late
if (polling) return;
polling = true;
Expand All @@ -214,7 +261,10 @@ You can copy and paste this to your manifest.json file to fix this error:
if (message == 'fetch-user') {
// Only called via extensionpay.com/extension/[extension-id]/paid -> content_script when user successfully pays.
// It's possible attackers could trigger this but that is basically harmless. It would just query the user.
poll_user()
poll_user_paid()
} else if (message == 'trial-start') {
// no need to poll since the trial confirmation page has already set trialStartedAt
fetch_user()
} else if (message == 'extpay-extinfo' && browser.management) {
// get this message from content scripts which can't access browser.management
return browser.management.getSelf()
Expand Down Expand Up @@ -262,6 +312,12 @@ You can copy and paste this to your manifest.json file to fix this error:
// }
},
openPaymentPage: open_payment_page,
openTrialPage: open_trial_page,
onTrialStarted: {
addListener: function(callback) {
trial_callbacks.push(callback)
}
},
// paymentPageLink: function() {
// return new Promise((resolve, reject) => {
// browser.storage.sync.get(['extensionpay_api_key'], function(storage) {
Expand Down
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Below are directions for using this library in your browser extension. If you le
5. [Use `extpay.openPaymentPage()` to let the user pay](#5-use-extpayopenpaymentpage-to-let-the-user-pay)
6. [Use `extpay.onPaid.addListener()` to run code when the user pays](#6-use-extpayonpaidaddlistener-to-run-code-when-the-user-pays)
7. [Use `extpay.openPaymentPage()` to let the user manage their subscription preferences](#7-use-extpayopenpaymentpage-to-let-the-user-manage-their-subscription-preferences)
8. [Use `extpay.openTrialPage()` to let the user sign up for a free trial](#8-use-extpayopentrialpage-to-let-the-user-sign-up-for-a-free-trial)

**Note**: ExtPay.js doesn't contain malware or track your users in any way. This library only communicates with ExtensionPay.com servers to manage users' paid status.

Expand Down Expand Up @@ -95,6 +96,7 @@ The `user` object returned from `extpay.getUser()` has the following properties:
| `user.paid` | `true` or `false`. `user.paid` is meant to be a simple way to tell if the user should have paid features activated. For subscription payments, `paid` is only true if `subscriptionStatus` is `active`. |
| `user.paidAt` | `Date()` object that the user first paid or `null`.|
| `user.installedAt` | `Date()` object the user installed the extension. |
| `user.trialStartedAt` | `null` or `Date()` object the user confirmed their free trial. |
| **subscription only**| |
| `user.subscriptionStatus` | One of `active`, `past_due`, or `canceled`. `active` means the user's subscription is paid-for. `past_due` means the user's most recent subscription payment has failed (expired card, insufficient funds, etc). `canceled` means that the user has canceled their subscription and the end of their last paid period has passed. [You can read more about how subscriptions work here](/docs/how_subscriptions_work.md). |
| `user.subscriptionCancelAt` | `null` or `Date()` object that the user's subscription is set to cancel or did cancel at. |
Expand Down Expand Up @@ -161,3 +163,44 @@ extpay.openPaymentPage()
The subscription management page looks something like this:

<img src="docs/subscription_management_screenshot.png" alt="Screenshot of example subscription management page." width="400">


## 8. Use `extpay.openTrialPage()` to let the user sign up for a free trial

If you want to give your users a trial period of your extension, you can use `extpay.openTrialPage()`, which looks something like this:

<img src="docs/trial_page_screenshot.png" alt="Screenshot of trial page" width="400">

The user will be sent an email with a link that they can use to start their free trial. Once the user clicks the link, you can use the `trialStartedAt` property from `extpay.getUser()` in your extension to check if the trial has expired.

For example, if you wanted a 7 day trial period, you could use a check like this:

```js
const extpay = ExtPay('sample-extension');
extpay.getUser().then(user => {
const now = new Date();
const sevenDays = 1000*60*60*24*7 // in milliseconds
if (user.trialStartedAt && (now - user.trialStartedAt) < sevenDays) {
// user's trial is active
} else {
// user's trial is not active
}
})
```

Note that `extpay.openTrialPage(displayText)` takes an optional string argument that is displayed to the user on the trial page. For example, `extpay.openTrialPage('7-day')` would change the trial prompt from `Enter an email to start your free trial` to `Enter an email to start your *7-day* free trial`. This is meant to give your users a better idea of what they're signing up for.

You can also use `extpay.onTrialStarted.addListener()` to run functions when the user's trial starts. Like `onPaid`, you need to include the following in your `manifest.json` to make it work:

```json
{
"manifest_version": 2,
"content_scripts": [
{
"matches": ["https://extensionpay.com/*"],
"js": ["ExtPay.js"],
"run_at": "document_start"
}
]
}
```
66 changes: 61 additions & 5 deletions dist/ExtPay.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ if (typeof window !== 'undefined') {
window.addEventListener('message', (event) => {
if (event.origin !== 'https://extensionpay.com') return;
if (event.source != window) return;
if (event.data === 'fetch-user') {
if (event.data === 'fetch-user' || event.data === 'trial-start') {
browser.runtime.sendMessage(event.data);
}
}, false);
Expand Down Expand Up @@ -71,7 +71,8 @@ You can copy and paste this to your manifest.json file to fix this error:
await set({'extensionpay_installed_at': date});
});

var paid_callbacks = [];
const paid_callbacks = [];
const trial_callbacks = [];

async function create_key() {
var body = {};
Expand Down Expand Up @@ -139,7 +140,7 @@ You can copy and paste this to your manifest.json file to fix this error:
const parsed_user = {};
for (var [key, value] of Object.entries(user_data)) {
if (value && value.match && value.match(datetime_re)) {
value = new Date();
value = new Date(value);
}
parsed_user[key] = value;
}
Expand All @@ -151,6 +152,12 @@ You can copy and paste this to your manifest.json file to fix this error:
paid_callbacks.forEach(cb => cb(parsed_user));
}
}
if (parsed_user.trialStartedAt) {
if (!storage.extensionpay_user || (storage.extensionpay_user && !storage.extensionpay_user.trialStartedAt)) {
trial_callbacks.forEach(cb => cb(parsed_user));
}

}
await set({extensionpay_user: user_data});

return parsed_user;
Expand Down Expand Up @@ -192,9 +199,49 @@ You can copy and paste this to your manifest.json file to fix this error:
}
}

async function open_trial_page(period) {
// let user have period string like '1 week' e.g. "start your 1 week free trial"

var api_key = await get_key();
if (!api_key) {
api_key = await create_key();
}
var url = `${EXTENSION_URL}/trial?api_key=${api_key}`;
if (period) {
url += `&period=${period}`;
}

if (browser.windows) {
try {
browser.windows.create({
url,
type: "popup",
focused: true,
width: 500,
height: 650,
left: 450
});
} catch(e) {
// firefox doesn't support 'focused'
browser.windows.create({
url,
type: "popup",
width: 500,
height: 650,
left: 450
});
}
} else {
// https://developer.mozilla.org/en-US/docs/Web/API/Window/open
// for opening from a content script
window.open(url, null, "toolbar=no,location=no,directories=no,status=no,menubar=no,width=500,height=800,left=450");
}

}


var polling = false;
async function poll_user() {
async function poll_user_paid() {
// keep trying to fetch user in case stripe webhook is late
if (polling) return;
polling = true;
Expand All @@ -214,7 +261,10 @@ You can copy and paste this to your manifest.json file to fix this error:
if (message == 'fetch-user') {
// Only called via extensionpay.com/extension/[extension-id]/paid -> content_script when user successfully pays.
// It's possible attackers could trigger this but that is basically harmless. It would just query the user.
poll_user();
poll_user_paid();
} else if (message == 'trial-start') {
// no need to poll since the trial confirmation page has already set trialStartedAt
fetch_user();
} else if (message == 'extpay-extinfo' && browser.management) {
// get this message from content scripts which can't access browser.management
return browser.management.getSelf()
Expand Down Expand Up @@ -262,6 +312,12 @@ You can copy and paste this to your manifest.json file to fix this error:
// }
},
openPaymentPage: open_payment_page,
openTrialPage: open_trial_page,
onTrialStarted: {
addListener: function(callback) {
trial_callbacks.push(callback);
}
},
// paymentPageLink: function() {
// return new Promise((resolve, reject) => {
// browser.storage.sync.get(['extensionpay_api_key'], function(storage) {
Expand Down
Loading

0 comments on commit 4ff23ab

Please sign in to comment.