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

Feature/swas/backward compatible product id param #19

Merged
merged 4 commits into from
Nov 26, 2024
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 .env.sample
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This environment file holds a dummy plugin from Freemius for testing purposes.
# PLUGIN ID
VITE_PLUGIN_ID=311
VITE_PRODUCT_ID=311
# PUBLIC KEY
VITE_PUBLIC_KEY="pk_a42d2ee6de0b31c389d5d11e36211"
# PLAN IDs
Expand Down
56 changes: 39 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
- [Instantiate the class](#instantiate-the-class)
- [Calling the method](#calling-the-method)
- [Use with React](#use-with-react)
- [Testing with Sandbox](#testing-with-sandbox)
- [Testing with Sandbox](#testing-with-the-sandbox)
- [Migration guide](#migration-guide)
- [Contributing](#contributing)

Expand Down Expand Up @@ -50,7 +50,7 @@ function getSelectedLicenses() {
}

const handler = new Checkout({
plugin_id: '311',
product_id: '311',
public_key: 'pk_a42d2ee6de0b31c389d5d11e36211',
});

Expand Down Expand Up @@ -104,7 +104,7 @@ need to hook into the `load` event of `window` or use `window.onload`.
<script type="text/javascript">
window.addEventListener('load', () => {
const handler = new FS.Checkout({
plugin_id: '1234',
product_id: '1234',
public_key: 'pk_xxxx',
});

Expand Down Expand Up @@ -179,7 +179,7 @@ available under the global `FS` namespace.

```js
const handler = new FS.Checkout({
plugin_id: '1234',
product_id: '1234',
public_key: 'pk_xxxx',
});
```
Expand All @@ -191,12 +191,12 @@ import { Checkout } from 'freemius-checkout-js';

// instantiate
const handler = new Checkout({
plugin_id: '0001',
product_id: '0001',
public_key: 'pk_xxxx',
});
```

Note that the `plugin_id` and `public_key` are required parameters and must be
Note that the `product_id` and `public_key` are required parameters and must be
supplied during instantiation.

### Calling the method
Expand Down Expand Up @@ -233,8 +233,8 @@ handle.close();

## Use with React

We will make a small react hook. Here we assume the `plugin_id` and `public_key`
are available in
We will make a small react hook. Here we assume the `product_id` and
`public_key` are available in
[some environment variable](https://vite.dev/guide/env-and-mode).

**checkout.ts**
Expand All @@ -244,7 +244,7 @@ import { Checkout, CheckoutOptions } from '@freemius/checkout';
import { useState, useEffect } from 'react';

export const checkoutConfig: CheckoutOptions = {
plugin_id: import.meta.env.VITE_FS_PLUGIN_ID as string,
product_id: import.meta.env.VITE_FS_PLUGIN_ID as string,
public_key: import.meta.env.VITE_FS_PUBLIC_KEY as string,
};

Expand Down Expand Up @@ -296,22 +296,40 @@ export default function App() {

## Testing with the Sandbox

To get the sandbox token and ctx, follow the steps:
This sample code uses PHP to generate the token and timestamp but you can use
the same approach in any server-side environment which will protect the secret
key.

1. Go to the Developer Dashboard.
2. Under Plans click on the "Get Checkout Code" button.
3. Go to the Sandbox tab.
4. Copy the `sandbox_token` and `timestamp` values.

The value of `sandbox` is token and value of `s_ctx_ts` is ctx. So for the above
value, the configuration would look like
4. Copy the code to generate the `sandbox_token` and `timestamp` values and
output them for the Javascript to use.

Example:

```PHP
<?php
$plugin_id = 1; // Change to your product ID
$plugin_public_key = 'pk_00001'; // Your public key
$plugin_secret_key = 'sk_00001'; // Your secret key
$timestamp = time();

$sandbox_token = md5(
$timestamp .
$plugin_id .
$plugin_secret_key .
$plugin_public_key .
'checkout'
);
```

```js
const config = {
// ...
sandbox: {
token: 'xxxxxxxx',
ctx: '00000000',
token: '<?php echo $sandbox_token; ?>',
ctx: '<?php echo $timestamp; ?>',
},
};
```
Expand Down Expand Up @@ -347,6 +365,10 @@ context. In this repository we use the `.env` file for storing sandbox data.

The rest of the code will continue to work exactly as it is with no changes.

Optionally you can also change `plugin_id` to `product_id`, but we support both
(giving preference to `product_id` if both are set) and we don't plan to remove
it the near future.

```js
document.querySelector('#purchase').addEventListener('click', (e) => {
handler.open({
Expand All @@ -372,7 +394,7 @@ page, just create a new checkout:

```js
const anotherHandler = new FS.Checkout({
plugin_id: '4321',
product_id: '4321',
plan_id: '9876',
public_key: 'pk_....nnn',
image: 'https://your-plugin-site.com/logo-100x100.png',
Expand Down
4 changes: 2 additions & 2 deletions cypress/pages/common.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { CheckoutOptions, CheckoutPopupEvents } from '../../src';

export const initOptions: CheckoutOptions = {
plugin_id: Number.parseInt(
(import.meta.env.VITE_PLUGIN_ID as string) ?? '0',
product_id: Number.parseInt(
(import.meta.env.VITE_PRODUCT_ID as string) ?? '0',
10
),
public_key: import.meta.env.VITE_PUBLIC_KEY as string,
Expand Down
4 changes: 2 additions & 2 deletions src/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Backward compatible adapter for the checkout service.
*/
import './global';
import { CheckoutOptions, Checkout } from '.';
import { Checkout, CheckoutOptions } from '.';
import { IFSOldCheckout } from './lib/contracts/IFSOldCheckout';

class FSOldCheckout implements IFSOldCheckout {
Expand Down Expand Up @@ -38,7 +38,7 @@ class FSOldCheckout implements IFSOldCheckout {

checkout.open({
...(this.options ?? {}),
...options,
...(options as Omit<CheckoutOptions, 'plugin_id' | 'product_id'>),
});
}

Expand Down
8 changes: 4 additions & 4 deletions src/demo.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Checkout, CheckoutOptions } from '.';
import { Checkout, CheckoutOptions, CheckoutPopupOptions } from '.';

import './style.css';

const fsCheckout = new Checkout(
{
plugin_id: Number.parseInt(
(import.meta.env.VITE_PLUGIN_ID as string) ?? '0',
product_id: Number.parseInt(
(import.meta.env.VITE_PRODUCT_ID as string) ?? '0',
10
),
public_key: import.meta.env.VITE_PUBLIC_KEY as string,
Expand Down Expand Up @@ -39,7 +39,7 @@ document.addEventListener('DOMContentLoaded', () => {
}

function getEventLoggers(): Pick<
CheckoutOptions,
CheckoutPopupOptions,
| 'cancel'
| 'purchaseCompleted'
| 'success'
Expand Down
69 changes: 63 additions & 6 deletions src/lib/checkout.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import { screen } from '@testing-library/dom';
import { Checkout } from './checkout';
import { Checkout, CheckoutOptions } from './checkout';
import { sendMockedCanceledEvent } from '../../tests/utils';
import { createHydratedMock } from 'ts-auto-mock';
import {
CheckoutPopupOptions,
CheckoutPopupParams,
} from './contracts/CheckoutPopupOptions';
import { CheckoutPopupParams } from './contracts/CheckoutPopupOptions';
import { getQueryValueFromItem } from './utils/ops';

describe('CheckoutPopup', () => {
Expand Down Expand Up @@ -145,7 +142,7 @@ describe('CheckoutPopup', () => {
});

test('passes all undocumented query parameters', () => {
const option: CheckoutPopupOptions = {
const option: CheckoutOptions = {
plugin_id: 1,
public_key: 'pk_123456',
foo: 'bar',
Expand Down Expand Up @@ -186,4 +183,64 @@ describe('CheckoutPopup', () => {
expect(iFrame.src).toContain(`${key}=`);
});
});

test('properly encodes the query parameters', () => {
const checkout = new Checkout({
plugin_id: 1,
public_key: 'pk_123456',
license_key: 'sk_R-5E2+%20BD:.kp*(Oq2aodhzZ1Jw',
});
checkout.open();

const guid = checkout.getGuid();

const iFrame = screen.queryByTestId(
`fs-checkout-page-${guid}`
) as HTMLIFrameElement;

expect(iFrame).toBeInTheDocument();

expect(
new URL(iFrame.src).searchParams.get('license_key')
).toMatchInlineSnapshot(`"sk_R-5E2+%20BD:.kp*(Oq2aodhzZ1Jw"`);
});

test('supports product_id instead of plugin_id', () => {
const checkout = new Checkout({
product_id: 1,
public_key: 'pk_123456',
license_key: 'sk_R-5E2+%20BD:.kp*(Oq2aodhzZ1Jw',
});
checkout.open();

const guid = checkout.getGuid();

const iFrame = screen.queryByTestId(
`fs-checkout-page-${guid}`
) as HTMLIFrameElement;

expect(iFrame).toBeInTheDocument();

expect(new URL(iFrame.src).searchParams.get('plugin_id')).toBe('1');
});

test('prefers product_id over plugin_id', () => {
const checkout = new Checkout({
plugin_id: 1,
product_id: 2,
public_key: 'pk_123456',
license_key: 'sk_R-5E2+%20BD:.kp*(Oq2aodhzZ1Jw',
} as any);
checkout.open();

const guid = checkout.getGuid();

const iFrame = screen.queryByTestId(
`fs-checkout-page-${guid}`
) as HTMLIFrameElement;

expect(iFrame).toBeInTheDocument();

expect(new URL(iFrame.src).searchParams.get('plugin_id')).toBe('2');
});
});
31 changes: 21 additions & 10 deletions src/lib/checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import type { CheckoutOptions } from './types';
import { Style } from './services/style';
import { Loader } from './services/loader';
import { CheckoutPopup } from './services/checkout-popup';
import { CheckoutPopupOptions } from './contracts/CheckoutPopupOptions';
import {
CheckoutPopupArbitraryParams,
CheckoutPopupOptions,
} from './contracts/CheckoutPopupOptions';
import { ExitIntent } from './services/exit-intent';
import { ILoader } from './contracts/ILoader';
import { IExitIntent } from './contracts/IExitIntent';
Expand All @@ -14,7 +17,8 @@ import { Cart } from './services/cart';
export type { PostmanEvents, CheckoutOptions };

export class Checkout {
private readonly options: CheckoutOptions = {
private readonly options: CheckoutPopupOptions &
CheckoutPopupArbitraryParams = {
plugin_id: 0,
public_key: '',
};
Expand All @@ -36,14 +40,22 @@ export class Checkout {
recoverCart: boolean = true,
private readonly baseUrl: string = 'https://checkout.freemius.com'
) {
if (!options.plugin_id) {
throw new Error('Must provide a plugin_id to options.');
const { plugin_id, product_id, public_key, ...popupOptions } = options;

if (!plugin_id && !product_id) {
throw new Error('Must provide a product_id to options.');
}
if (!options.public_key) {

if (!public_key) {
throw new Error('Must provide the public_key to options.');
}

this.options = options;
this.options = {
...popupOptions,
public_key,
plugin_id: product_id ?? plugin_id,
};

this.guid = generateGuid();

if (isSsr()) {
Expand All @@ -54,9 +66,8 @@ export class Checkout {

this.loader = new Loader(
this.style,
this.options.loadingImageUrl ??
`${this.baseUrl}/assets/img/spinner.svg`,
this.options.loadingImageAlt
options.loadingImageUrl ?? `${this.baseUrl}/assets/img/spinner.svg`,
options.loadingImageAlt
);

this.exitIntent = new ExitIntent(this.style);
Expand Down Expand Up @@ -84,7 +95,7 @@ export class Checkout {
*/
public open(
options?: Partial<
Omit<CheckoutPopupOptions, 'plugin_id' | 'public_key'>
Omit<CheckoutOptions, 'plugin_id' | 'public_key' | 'product_id'>
>
) {
if (isSsr()) {
Expand Down
Loading