@revenuecat/purchases-js 0.0.3 β 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +201 -4
- package/dist/entities/offerings.d.ts +34 -0
- package/dist/entities/subscribe-response.d.ts +8 -0
- package/dist/entities/types.d.ts +1 -0
- package/dist/main.d.ts +18 -0
- package/dist/purchases-js.js +144 -0
- package/package.json +2 -1
- package/revenuecat-purchases-js-0.0.5.tgz +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,202 @@
|
|
|
1
|
-
|
|
1
|
+
<h3 align="center">π» In-App Subscriptions Made Easy π»</h3>
|
|
2
|
+
<h4 align="center">πΈοΈ For the web πΈοΈ</h4>
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
RevenueCat is a powerful, reliable, and free to use in-app purchase server with cross-platform support.
|
|
5
|
+
This repository includes all you need to manage your subscriptions on your website or web app using RevenueCat.
|
|
6
|
+
|
|
7
|
+
Sign up to [get started for free](https://app.revenuecat.com/signup).
|
|
8
|
+
|
|
9
|
+
# Prerequisites
|
|
10
|
+
|
|
11
|
+
Login @ [app.revenuecat.com](https://app.revenuecat.com)
|
|
12
|
+
|
|
13
|
+
- Create a Project (if you haven't already)
|
|
14
|
+
|
|
15
|
+
### ======> Only while testing <======
|
|
16
|
+
|
|
17
|
+
- Add the private project id (a.k.a. app_id) to the following feature flags
|
|
18
|
+
- RCBILLING
|
|
19
|
+
- GENERATE_V2_SUBSCRIPTION_MODELS_FOR_RCBILLING
|
|
20
|
+
- GENERATE_V2_SUBSCRIPTION_MODELS
|
|
21
|
+
|
|
22
|
+
### ======> Only while testing <======
|
|
23
|
+
|
|
24
|
+
- Add a new RCBilling app
|
|
25
|
+
- Get the `RC_PUBLISHABLE_API_KEY` (you will need it soon)
|
|
26
|
+
- Connect your Stripe account (More payment gateways are coming soon)
|
|
27
|
+
- Create some products for the RCBilling App
|
|
28
|
+
- Create an offering and add packages with RCBilling products
|
|
29
|
+
- Create the entitlements you need in your app and link them to the RCBilling products
|
|
30
|
+
|
|
31
|
+
# Installation
|
|
32
|
+
|
|
33
|
+
### ======> Only during testing <======
|
|
34
|
+
|
|
35
|
+
- Get a token to download the sdk from our private npm registry
|
|
36
|
+
- Set the environment variable `NODE_AUTH_TOKEN`
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
export NODE_AUTH_TOKEN="the token you got from the npm registry"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### ======> Only during testing <======
|
|
43
|
+
|
|
44
|
+
- Add the library to your project's dependencies
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
npm add --save @revenuecat/purchases-js
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
# Usage
|
|
51
|
+
|
|
52
|
+
## Download the current Offerings
|
|
53
|
+
|
|
54
|
+
By downloading the current Offerings you can easily build a Paywall page using the embedded Packages and their
|
|
55
|
+
associated `rcBillingProduct` and price.
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const purchases = new Purchases("your RC_PUBLISHABLE_API_KEY");
|
|
59
|
+
|
|
60
|
+
purchases.listOfferings().then((offeringsPage) => {
|
|
61
|
+
offeringsPage.offerings.forEach((offering) => {
|
|
62
|
+
console.log(offering);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
This should print the current offerings you have set up in your RC Account.
|
|
68
|
+
|
|
69
|
+
Please check out [this file](https://github.com/RevenueCat/purchases-js/blob/main/src/entities/offerings.ts) for the
|
|
70
|
+
Offering's data structure
|
|
71
|
+
|
|
72
|
+
## Check User Entitlements
|
|
73
|
+
|
|
74
|
+
You can check the entitlements granted to your users throughout all the platforms, now
|
|
75
|
+
also on your website!
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
const appUserId = "the unique id of the user in your systems";
|
|
79
|
+
const entitlementId = "the entitlementId you set up in RC";
|
|
80
|
+
|
|
81
|
+
const purchases = new Purchases("your RC_PUBLISHABLE_API_KEY");
|
|
82
|
+
|
|
83
|
+
purchases.isEntitledTo(appUserId, entitlementId).then((isEntitled) => {
|
|
84
|
+
if (isEntitled == true) {
|
|
85
|
+
console.log(`User ${appUserID} is entitled to ${entitlementId}`);
|
|
86
|
+
} else {
|
|
87
|
+
console.log(`User ${appUserID} is not entitled to ${entitlementId}`);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
As example, you can build a cool React component with it:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
const WithEntitlement = ({ appUserId, entitlementId, children }) => {
|
|
96
|
+
const [isEntitled, setIsEntitled] = useState<boolean | null>(null);
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
const purchases = new Purchases("your RC_PUBLISHABLE_API_KEY");
|
|
100
|
+
purchases.isEntitledTo(appUserId, entitlementId).then((isEntitled) => {
|
|
101
|
+
setIsEntitled(isEntitled);
|
|
102
|
+
});
|
|
103
|
+
}, [appUserId, entitlementId]);
|
|
104
|
+
|
|
105
|
+
if (isEntitled === null) {
|
|
106
|
+
return <>"loading..."</>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (isEntitled === true) {
|
|
110
|
+
return <>{children}</>;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return <>You are not entitled!</>;
|
|
114
|
+
};
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
And then use it in your app:
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
const App = () => (
|
|
121
|
+
<>
|
|
122
|
+
<WithEntitlement appUserId={"user12345"} entitlementId={"functionality5"}>
|
|
123
|
+
<Functionality5 />
|
|
124
|
+
</WithEntitlement>
|
|
125
|
+
</>
|
|
126
|
+
);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Subscribe a User to an entitlement and allow payment with Stripe
|
|
130
|
+
|
|
131
|
+
RCBilling allows you to use your payment gateway for payments.
|
|
132
|
+
In this example we will show Stripe, more will be supported soon!
|
|
133
|
+
|
|
134
|
+
### Context
|
|
135
|
+
|
|
136
|
+
You built your paywall, and your user just clicked on the offer they want to subscribe to.
|
|
137
|
+
|
|
138
|
+
### 1. Call the .subscribe method to initialise the process
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
const purchases = new Purchases("your RC_PUBLISHABLE_API_KEY");
|
|
142
|
+
// You can retrieve this from the offerings you downloaded, as example:
|
|
143
|
+
// offeringsPage.offerings[0].packages[0].rcBillingProduct.identifier
|
|
144
|
+
const rcBillingProductIndentifier =
|
|
145
|
+
"the Product Identifier the user wants to buy";
|
|
146
|
+
const appUserId =
|
|
147
|
+
"the unique id of the user that wants to subscribe to your product";
|
|
148
|
+
|
|
149
|
+
purchase.subscribe(appUserId, rcBillingProductIndentifier).then((response) => {
|
|
150
|
+
if (response.nextAction === "collect_payment_info") {
|
|
151
|
+
// Use the clientSecret to show the StripeElements payment components
|
|
152
|
+
showStripeElements({
|
|
153
|
+
setupIntentClientSecret: response.data.clientSecret,
|
|
154
|
+
});
|
|
155
|
+
} else {
|
|
156
|
+
// No need to collect payment info, just wait for the entitlement to be granted
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 2. [If nextAction === 'collect_payment_info'] Show the Stripe Elements to collect payment
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
// Set up stripe as shown in their docs
|
|
165
|
+
const stripePromise = loadStripe(
|
|
166
|
+
import.meta.env.VITE_RC_STRIPE_PK_KEY as string,
|
|
167
|
+
{ stripeAccount: import.meta.env.VITE_RC_STRIPE_ACCOUNT_ID as string },
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// Use the clientSecret obtained in the step 1. Call the .subscribe method to initialise the process
|
|
171
|
+
const PaymentForm = ({ clientSecret }) => {
|
|
172
|
+
const handleSubmit = async (e: SyntheticEvent) => {
|
|
173
|
+
e.preventDefault();
|
|
174
|
+
stripe
|
|
175
|
+
.confirmSetup({
|
|
176
|
+
elements,
|
|
177
|
+
clientSecret,
|
|
178
|
+
confirmParams: {
|
|
179
|
+
return_url: `${window.location.origin}/success`,
|
|
180
|
+
},
|
|
181
|
+
redirect: "if_required",
|
|
182
|
+
})
|
|
183
|
+
.then((response) => {
|
|
184
|
+
// All is done you can now wait for the entitlement to be granted.
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<form id="payment-form" onSubmit={handleSubmit}>
|
|
190
|
+
<PaymentElement id="payment-element" options={paymentElementOptions} />
|
|
191
|
+
</form>
|
|
192
|
+
);
|
|
193
|
+
};
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 3. Wait for the entitlement to be granted
|
|
197
|
+
|
|
198
|
+
You can now loop and use the `.isEntitledTo` while Stripe communicates with our servers to grant the entitlement.
|
|
199
|
+
As soon as the entitlement is given, your user's payment went through!
|
|
4
200
|
|
|
5
201
|
# Development
|
|
6
202
|
|
|
@@ -15,10 +211,11 @@ npm install
|
|
|
15
211
|
npm run build:dev
|
|
16
212
|
```
|
|
17
213
|
|
|
18
|
-
|
|
214
|
+
To avoid publishing the package you can set it up as a local dependency.
|
|
215
|
+
In your testing project install the library as.
|
|
19
216
|
|
|
20
217
|
```bash
|
|
21
|
-
npm i
|
|
218
|
+
npm i /path/to/rcbilling-js
|
|
22
219
|
```
|
|
23
220
|
|
|
24
221
|
## Running tests
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ServerResponse } from "./types";
|
|
2
|
+
export interface Price {
|
|
3
|
+
amount: number;
|
|
4
|
+
currency: string;
|
|
5
|
+
}
|
|
6
|
+
export interface Product {
|
|
7
|
+
id: string;
|
|
8
|
+
displayName: string;
|
|
9
|
+
identifier: string;
|
|
10
|
+
currentPrice: Price | null;
|
|
11
|
+
normalPeriodDuration: string | null;
|
|
12
|
+
}
|
|
13
|
+
export interface Package {
|
|
14
|
+
id: string;
|
|
15
|
+
identifier: string;
|
|
16
|
+
displayName: string;
|
|
17
|
+
rcBillingProduct: Product | null;
|
|
18
|
+
}
|
|
19
|
+
export interface Offering {
|
|
20
|
+
id: string;
|
|
21
|
+
identifier: string;
|
|
22
|
+
displayName: string;
|
|
23
|
+
packages: Package[];
|
|
24
|
+
}
|
|
25
|
+
export interface OfferingsPage {
|
|
26
|
+
offerings: Offering[];
|
|
27
|
+
priceByPackageId: {
|
|
28
|
+
[packageId: string]: number;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export declare const toPrice: (data: ServerResponse) => Price;
|
|
32
|
+
export declare const toProduct: (data: ServerResponse) => Product;
|
|
33
|
+
export declare const toPackage: (data: ServerResponse) => Package;
|
|
34
|
+
export declare const toOffering: (data: ServerResponse) => Offering;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type ServerResponse = any;
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Offering as InnerOffering, OfferingsPage as InnerOfferingsPage, Package as InnerPackage } from "./entities/offerings";
|
|
2
|
+
import { SubscribeResponse } from "./entities/subscribe-response";
|
|
3
|
+
export type OfferingsPage = InnerOfferingsPage;
|
|
4
|
+
export type Offering = InnerOffering;
|
|
5
|
+
export type Package = InnerPackage;
|
|
6
|
+
export declare class Purchases {
|
|
7
|
+
_API_KEY: string | null;
|
|
8
|
+
_APP_USER_ID: string | null;
|
|
9
|
+
private static readonly _RC_ENDPOINT;
|
|
10
|
+
private static readonly _BASE_PATH;
|
|
11
|
+
constructor(apiKey: string);
|
|
12
|
+
private toOfferingsPage;
|
|
13
|
+
listOfferings(): Promise<OfferingsPage>;
|
|
14
|
+
isEntitledTo(appUserId: string, entitlementIdentifier: string): Promise<boolean>;
|
|
15
|
+
waitForEntitlement(appUserId: string, entitlementIdentifier: string, maxAttempts?: number): Promise<boolean>;
|
|
16
|
+
subscribe(appUserId: string, productId: string, environment?: "sandbox" | "production"): Promise<SubscribeResponse>;
|
|
17
|
+
getPackage(packageIdentifier: string): Promise<Package | null>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
var d = Object.defineProperty;
|
|
2
|
+
var g = (e, t, n) => t in e ? d(e, t, { enumerable: !0, configurable: !0, writable: !0, value: n }) : e[t] = n;
|
|
3
|
+
var l = (e, t, n) => (g(e, typeof t != "symbol" ? t + "" : t, n), n);
|
|
4
|
+
var _ = (e, t, n) => new Promise((i, c) => {
|
|
5
|
+
var s = (o) => {
|
|
6
|
+
try {
|
|
7
|
+
a(n.next(o));
|
|
8
|
+
} catch (u) {
|
|
9
|
+
c(u);
|
|
10
|
+
}
|
|
11
|
+
}, p = (o) => {
|
|
12
|
+
try {
|
|
13
|
+
a(n.throw(o));
|
|
14
|
+
} catch (u) {
|
|
15
|
+
c(u);
|
|
16
|
+
}
|
|
17
|
+
}, a = (o) => o.done ? i(o.value) : Promise.resolve(o.value).then(s, p);
|
|
18
|
+
a((n = n.apply(e, t)).next());
|
|
19
|
+
});
|
|
20
|
+
const P = (e) => ({
|
|
21
|
+
amount: e.amount,
|
|
22
|
+
currency: e.currency
|
|
23
|
+
}), m = (e) => ({
|
|
24
|
+
id: e.id,
|
|
25
|
+
identifier: e.identifier,
|
|
26
|
+
displayName: e.display_name,
|
|
27
|
+
currentPrice: e.current_price ? P(e.current_price) : null,
|
|
28
|
+
normalPeriodDuration: e.normal_period_duration
|
|
29
|
+
}), A = (e) => ({
|
|
30
|
+
id: e.id,
|
|
31
|
+
identifier: e.identifier,
|
|
32
|
+
displayName: e.display_name,
|
|
33
|
+
rcBillingProduct: e.rc_billing_product ? m(e.rc_billing_product) : null
|
|
34
|
+
}), h = (e) => ({
|
|
35
|
+
id: e.id,
|
|
36
|
+
identifier: e.identifier,
|
|
37
|
+
displayName: e.display_name,
|
|
38
|
+
packages: e.packages.map(A)
|
|
39
|
+
}), y = (e) => {
|
|
40
|
+
var t, n;
|
|
41
|
+
return {
|
|
42
|
+
nextAction: e.next_action,
|
|
43
|
+
data: {
|
|
44
|
+
clientSecret: (n = (t = e.data) == null ? void 0 : t.client_secret) != null ? n : void 0
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}, r = class r {
|
|
48
|
+
constructor(t) {
|
|
49
|
+
l(this, "_API_KEY", null);
|
|
50
|
+
l(this, "_APP_USER_ID", null);
|
|
51
|
+
l(this, "toOfferingsPage", (t) => ({
|
|
52
|
+
offerings: t.offerings.map(h),
|
|
53
|
+
priceByPackageId: t.prices_by_package_id
|
|
54
|
+
}));
|
|
55
|
+
this._API_KEY = t, r._RC_ENDPOINT === void 0 && console.error(
|
|
56
|
+
"Project was build without some of the environment variables set"
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
listOfferings() {
|
|
60
|
+
return _(this, null, function* () {
|
|
61
|
+
const n = yield (yield fetch(
|
|
62
|
+
`${r._RC_ENDPOINT}/${r._BASE_PATH}/offerings`,
|
|
63
|
+
{
|
|
64
|
+
headers: {
|
|
65
|
+
Authorization: `Bearer ${this._API_KEY}`,
|
|
66
|
+
"Content-Type": "application/json",
|
|
67
|
+
Accept: "application/json"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
)).json();
|
|
71
|
+
return this.toOfferingsPage(n);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
isEntitledTo(t, n) {
|
|
75
|
+
return _(this, null, function* () {
|
|
76
|
+
const i = yield fetch(
|
|
77
|
+
`${r._RC_ENDPOINT}/${r._BASE_PATH}/entitlements/${t}`,
|
|
78
|
+
{
|
|
79
|
+
headers: {
|
|
80
|
+
Authorization: `Bearer ${this._API_KEY}`,
|
|
81
|
+
"Content-Type": "application/json",
|
|
82
|
+
Accept: "application/json"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
return i.status === 404 ? !1 : (yield i.json()).entitlements.map(
|
|
87
|
+
(a) => a.lookup_key
|
|
88
|
+
).includes(n);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
waitForEntitlement(t, n, i = 10) {
|
|
92
|
+
return new Promise((s, p) => {
|
|
93
|
+
const a = (o = 1) => this.isEntitledTo(t, n).then((u) => {
|
|
94
|
+
if (o > i)
|
|
95
|
+
return s(!1);
|
|
96
|
+
if (u)
|
|
97
|
+
return s(!0);
|
|
98
|
+
setTimeout(
|
|
99
|
+
() => a(o + 1),
|
|
100
|
+
1e3
|
|
101
|
+
);
|
|
102
|
+
}).catch(p);
|
|
103
|
+
a();
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
subscribe(t, n, i = "production") {
|
|
107
|
+
return _(this, null, function* () {
|
|
108
|
+
const c = i === "sandbox", p = yield (yield fetch(
|
|
109
|
+
`${r._RC_ENDPOINT}/${r._BASE_PATH}/subscribe`,
|
|
110
|
+
{
|
|
111
|
+
method: "POST",
|
|
112
|
+
headers: {
|
|
113
|
+
Authorization: `Bearer ${this._API_KEY}`,
|
|
114
|
+
"Content-Type": "application/json",
|
|
115
|
+
Accept: "application/json"
|
|
116
|
+
},
|
|
117
|
+
body: JSON.stringify({
|
|
118
|
+
app_user_id: t,
|
|
119
|
+
product_id: n,
|
|
120
|
+
is_sandbox: c
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
)).json();
|
|
124
|
+
return y(p);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
getPackage(t) {
|
|
128
|
+
return _(this, null, function* () {
|
|
129
|
+
const n = yield this.listOfferings(), i = [];
|
|
130
|
+
n.offerings.forEach(
|
|
131
|
+
(s) => i.push(...s.packages)
|
|
132
|
+
);
|
|
133
|
+
const c = i.filter(
|
|
134
|
+
(s) => s.identifier === t
|
|
135
|
+
);
|
|
136
|
+
return c.length === 0 ? null : c[0];
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
l(r, "_RC_ENDPOINT", "https://api.revenuecat.com"), l(r, "_BASE_PATH", "rcbilling/v1");
|
|
141
|
+
let f = r;
|
|
142
|
+
export {
|
|
143
|
+
f as Purchases
|
|
144
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revenuecat/purchases-js",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.5",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "dist/main.d.ts",
|
|
7
7
|
"main": "dist/purchases-js.js",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"lint": "eslint . --ext .ts",
|
|
15
15
|
"format": "eslint . --ext .ts --fix",
|
|
16
16
|
"test": "vitest",
|
|
17
|
+
"prettier": "prettier --write .",
|
|
17
18
|
"prettier:ci": "prettier --check ."
|
|
18
19
|
},
|
|
19
20
|
"devDependencies": {
|
|
Binary file
|