@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 CHANGED
@@ -1,6 +1,202 @@
1
- # RCBilling JS
1
+ <h3 align="center">😻 In-App Subscriptions Made Easy 😻</h3>
2
+ <h4 align="center">πŸ•ΈοΈ For the web πŸ•ΈοΈ</h4>
2
3
 
3
- JS SDK for RC Billing.
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
- Then in your local project you can do:
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 ../path/to/rcbilling-js
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,8 @@
1
+ import { ServerResponse } from "./types";
2
+ export interface SubscribeResponse {
3
+ nextAction: string;
4
+ data: {
5
+ clientSecret?: string;
6
+ };
7
+ }
8
+ export declare const toSubscribeResponse: (raw: ServerResponse) => SubscribeResponse;
@@ -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.3",
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": {