@shopify/shopify-api 5.0.0 → 5.1.0

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.
Files changed (62) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +1 -0
  3. package/dist/auth/oauth/oauth.d.ts +2 -0
  4. package/dist/auth/oauth/oauth.d.ts.map +1 -1
  5. package/dist/auth/oauth/oauth.js +120 -95
  6. package/dist/auth/session/storage/redis.d.ts +1 -0
  7. package/dist/auth/session/storage/redis.d.ts.map +1 -1
  8. package/dist/auth/session/storage/redis.js +3 -0
  9. package/dist/base-types.d.ts +2 -0
  10. package/dist/base-types.d.ts.map +1 -1
  11. package/dist/billing/check.d.ts +12 -0
  12. package/dist/billing/check.d.ts.map +1 -0
  13. package/dist/billing/check.js +34 -0
  14. package/dist/billing/has_active_payment.d.ts +3 -0
  15. package/dist/billing/has_active_payment.d.ts.map +1 -0
  16. package/dist/billing/has_active_payment.js +91 -0
  17. package/dist/billing/index.d.ts +7 -0
  18. package/dist/billing/index.d.ts.map +1 -0
  19. package/dist/billing/index.js +9 -0
  20. package/dist/billing/is_recurring.d.ts +2 -0
  21. package/dist/billing/is_recurring.d.ts.map +1 -0
  22. package/dist/billing/is_recurring.js +20 -0
  23. package/dist/billing/request_payment.d.ts +3 -0
  24. package/dist/billing/request_payment.d.ts.map +1 -0
  25. package/dist/billing/request_payment.js +134 -0
  26. package/dist/billing/types.d.ts +57 -0
  27. package/dist/billing/types.d.ts.map +1 -0
  28. package/dist/billing/types.js +9 -0
  29. package/dist/clients/graphql/graphql_client.d.ts +1 -1
  30. package/dist/clients/graphql/graphql_client.d.ts.map +1 -1
  31. package/dist/clients/http_client/http_client.d.ts +5 -5
  32. package/dist/clients/http_client/http_client.d.ts.map +1 -1
  33. package/dist/clients/http_client/types.d.ts +2 -2
  34. package/dist/clients/http_client/types.d.ts.map +1 -1
  35. package/dist/clients/rest/rest_client.d.ts +1 -1
  36. package/dist/clients/rest/rest_client.d.ts.map +1 -1
  37. package/dist/clients/rest/rest_client.js +1 -1
  38. package/dist/clients/rest/types.d.ts +1 -1
  39. package/dist/clients/rest/types.d.ts.map +1 -1
  40. package/dist/context.d.ts.map +1 -1
  41. package/dist/context.js +3 -0
  42. package/dist/error.d.ts +7 -0
  43. package/dist/error.d.ts.map +1 -1
  44. package/dist/error.js +13 -1
  45. package/dist/index.d.ts +4 -0
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +2 -0
  48. package/dist/types.d.ts +1 -0
  49. package/dist/types.d.ts.map +1 -1
  50. package/dist/types.js +1 -0
  51. package/dist/utils/decode-session-token.js +1 -1
  52. package/dist/utils/get-embedded-app-url.d.ts +1 -0
  53. package/dist/utils/get-embedded-app-url.d.ts.map +1 -1
  54. package/dist/utils/get-embedded-app-url.js +8 -2
  55. package/dist/utils/setup-jest.d.ts.map +1 -1
  56. package/dist/utils/setup-jest.js +1 -0
  57. package/dist/utils/shop-validator.d.ts.map +1 -1
  58. package/dist/utils/shop-validator.js +1 -1
  59. package/dist/version.d.ts +1 -1
  60. package/dist/version.js +1 -1
  61. package/dist/webhooks/registry.js +1 -1
  62. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -7,6 +7,17 @@ and adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## [5.1.0] - 2022-09-19
11
+
12
+ - Increased tolerance for expired JWT session tokens from 5s to 10s [#462](https://github.com/Shopify/shopify-api-node/pull/462)
13
+ - Add support for billing to the library [#449](https://github.com/Shopify/shopify-api-node/pull/449)
14
+ - Allow dynamically typing the body of REST and GraphQL request responses, so callers don't need to cast it [#447](https://github.com/Shopify/shopify-api-node/pull/447)
15
+ - Rather than create a temporary session in order to store a session id in a cookie for the OAuth transaction, we can store the `state` in the cookie instead, that can then be compared against the `state` provided by Shopify in the callback. [#438](https://github.com/Shopify/shopify-api-node/pull/438)
16
+
17
+ ## [5.0.1] - 2022-08-03
18
+
19
+ - Make `sanitizeHost` validation more flexible [#446](https://github.com/Shopify/shopify-api-node/pull/446)
20
+
10
21
  ## [5.0.0] - 2022-07-28
11
22
 
12
23
  - ⚠️ [Breaking] If a response from a GraphQL query contains an `errors` attribute, `GraphqlClient` will now throw a `GraphqlQueryError`. The caller can check the `error.response` attribute to see what was returned from the GraphQL API. [#431](https://github.com/Shopify/shopify-api-node/pull/431)
package/README.md CHANGED
@@ -51,5 +51,6 @@ You can follow our [getting started guide](docs/), which will provide instructio
51
51
  - [Webhooks](docs/usage/webhooks.md)
52
52
  - [Register a Webhook](docs/usage/webhooks.md#register-a-webhook)
53
53
  - [Process a Webhook](docs/usage/webhooks.md#process-a-webhook)
54
+ - [Billing](docs/usage/billing.md)
54
55
  - [Known issues and caveats](docs/issues.md)
55
56
  - [Notes on session handling](docs/issues.md#notes-on-session-handling)
@@ -3,6 +3,7 @@ import { SessionInterface } from '../session/types';
3
3
  import { AuthQuery } from './types';
4
4
  declare const ShopifyOAuth: {
5
5
  SESSION_COOKIE_NAME: string;
6
+ STATE_COOKIE_NAME: string;
6
7
  /**
7
8
  * Initializes a session and cookie for the OAuth process, and returns the necessary authorization url.
8
9
  *
@@ -24,6 +25,7 @@ declare const ShopifyOAuth: {
24
25
  * @param response Current HTTP Response
25
26
  * @param query Current HTTP Request Query, containing the information to be validated.
26
27
  * Depending on framework, this may need to be cast as "unknown" before being passed.
28
+ * @param isOnline Boolean value. Defaults to true (online access).
27
29
  * @returns SessionInterface
28
30
  */
29
31
  validateAuthCallback(request: http.IncomingMessage, response: http.ServerResponse, query: AuthQuery): Promise<SessionInterface>;
@@ -1 +1 @@
1
- {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/oauth.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAexB,OAAO,EAAC,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAGlD,OAAO,EACL,SAAS,EAIV,MAAM,SAAS,CAAC;AAEjB,QAAA,MAAM,YAAY;;IAGhB;;;;;;;;;;OAUG;uBAEQ,KAAK,eAAe,YACnB,KAAK,cAAc,QACvB,MAAM,gBACE,MAAM,uBAEnB,QAAQ,MAAM,CAAC;IAiDlB;;;;;;;;;;OAUG;kCAEQ,KAAK,eAAe,YACnB,KAAK,cAAc,SACtB,SAAS,GACf,QAAQ,gBAAgB,CAAC;IAuG5B;;;;;OAKG;gCAEQ,KAAK,eAAe,YACnB,KAAK,cAAc,GAC5B,MAAM,GAAG,SAAS;IAQrB;;;;;OAKG;0BACmB,MAAM,UAAU,MAAM,GAAG,MAAM;IAIrD;;;;OAIG;8BACuB,MAAM,GAAG,MAAM;IAIzC;;;;;;OAMG;iCAEQ,KAAK,eAAe,YACnB,KAAK,cAAc,uBAE5B,MAAM,GAAG,SAAS;CAiCtB,CAAC;AAcF,OAAO,EAAC,YAAY,EAAC,CAAC"}
1
+ {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/oauth.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAexB,OAAO,EAAC,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAGlD,OAAO,EACL,SAAS,EAIV,MAAM,SAAS,CAAC;AAEjB,QAAA,MAAM,YAAY;;;IAIhB;;;;;;;;;;OAUG;uBAEQ,KAAK,eAAe,YACnB,KAAK,cAAc,QACvB,MAAM,gBACE,MAAM,uBAEnB,QAAQ,MAAM,CAAC;IAkClB;;;;;;;;;;;OAWG;kCAEQ,KAAK,eAAe,YACnB,KAAK,cAAc,SACtB,SAAS,GACf,QAAQ,gBAAgB,CAAC;IAwE5B;;;;;OAKG;gCAEQ,KAAK,eAAe,YACnB,KAAK,cAAc,GAC5B,MAAM,GAAG,SAAS;IAIrB;;;;;OAKG;0BACmB,MAAM,UAAU,MAAM,GAAG,MAAM;IAIrD;;;;OAIG;8BACuB,MAAM,GAAG,MAAM;IAIzC;;;;;;OAMG;iCAEQ,KAAK,eAAe,YACnB,KAAK,cAAc,uBAE5B,MAAM,GAAG,SAAS;CAqCtB,CAAC;AAwGF,OAAO,EAAC,YAAY,EAAC,CAAC"}
@@ -17,6 +17,7 @@ var ShopifyErrors = tslib_1.__importStar(require("../../error"));
17
17
  var shop_validator_1 = require("../../utils/shop-validator");
18
18
  var ShopifyOAuth = {
19
19
  SESSION_COOKIE_NAME: 'shopify_app_session',
20
+ STATE_COOKIE_NAME: 'shopify_app_state',
20
21
  /**
21
22
  * Initializes a session and cookie for the OAuth process, and returns the necessary authorization url.
22
23
  *
@@ -31,41 +32,31 @@ var ShopifyOAuth = {
31
32
  beginAuth: function (request, response, shop, redirectPath, isOnline) {
32
33
  if (isOnline === void 0) { isOnline = true; }
33
34
  return tslib_1.__awaiter(this, void 0, void 0, function () {
34
- var cleanShop, cookies, state, session, sessionStored, query, queryString;
35
+ var cleanShop, cookies, state, query, queryString;
35
36
  return tslib_1.__generator(this, function (_a) {
36
- switch (_a.label) {
37
- case 0:
38
- context_1.Context.throwIfUninitialized();
39
- context_1.Context.throwIfPrivateApp('Cannot perform OAuth for private apps');
40
- cleanShop = (0, shop_validator_1.sanitizeShop)(shop, true);
41
- cookies = new cookies_1.default(request, response, {
42
- keys: [context_1.Context.API_SECRET_KEY],
43
- secure: true,
44
- });
45
- state = (0, nonce_1.default)();
46
- session = new session_1.Session(isOnline ? (0, uuid_1.v4)() : this.getOfflineSessionId(cleanShop), cleanShop, state, isOnline);
47
- return [4 /*yield*/, context_1.Context.SESSION_STORAGE.storeSession(session)];
48
- case 1:
49
- sessionStored = _a.sent();
50
- if (!sessionStored) {
51
- throw new ShopifyErrors.SessionStorageError('OAuth Session could not be saved. Please check your session storage functionality.');
52
- }
53
- cookies.set(ShopifyOAuth.SESSION_COOKIE_NAME, session.id, {
54
- signed: true,
55
- expires: new Date(Date.now() + 60000),
56
- sameSite: 'lax',
57
- secure: true,
58
- });
59
- query = {
60
- client_id: context_1.Context.API_KEY,
61
- scope: context_1.Context.SCOPES.toString(),
62
- redirect_uri: "".concat(context_1.Context.HOST_SCHEME, "://").concat(context_1.Context.HOST_NAME).concat(redirectPath),
63
- state: state,
64
- 'grant_options[]': isOnline ? 'per-user' : '',
65
- };
66
- queryString = querystring_1.default.stringify(query);
67
- return [2 /*return*/, "https://".concat(cleanShop, "/admin/oauth/authorize?").concat(queryString)];
68
- }
37
+ context_1.Context.throwIfUninitialized();
38
+ context_1.Context.throwIfPrivateApp('Cannot perform OAuth for private apps');
39
+ cleanShop = (0, shop_validator_1.sanitizeShop)(shop, true);
40
+ cookies = new cookies_1.default(request, response, {
41
+ keys: [context_1.Context.API_SECRET_KEY],
42
+ secure: true,
43
+ });
44
+ state = isOnline ? "online_".concat((0, nonce_1.default)()) : "offline_".concat((0, nonce_1.default)());
45
+ cookies.set(ShopifyOAuth.STATE_COOKIE_NAME, state, {
46
+ signed: true,
47
+ expires: new Date(Date.now() + 60000),
48
+ sameSite: 'lax',
49
+ secure: true,
50
+ });
51
+ query = {
52
+ client_id: context_1.Context.API_KEY,
53
+ scope: context_1.Context.SCOPES.toString(),
54
+ redirect_uri: "".concat(context_1.Context.HOST_SCHEME, "://").concat(context_1.Context.HOST_NAME).concat(redirectPath),
55
+ state: state,
56
+ 'grant_options[]': isOnline ? 'per-user' : '',
57
+ };
58
+ queryString = querystring_1.default.stringify(query);
59
+ return [2 /*return*/, "https://".concat(cleanShop, "/admin/oauth/authorize?").concat(queryString)];
69
60
  });
70
61
  });
71
62
  },
@@ -78,33 +69,26 @@ var ShopifyOAuth = {
78
69
  * @param response Current HTTP Response
79
70
  * @param query Current HTTP Request Query, containing the information to be validated.
80
71
  * Depending on framework, this may need to be cast as "unknown" before being passed.
72
+ * @param isOnline Boolean value. Defaults to true (online access).
81
73
  * @returns SessionInterface
82
74
  */
83
75
  validateAuthCallback: function (request, response, query) {
84
76
  return tslib_1.__awaiter(this, void 0, void 0, function () {
85
- var cookies, sessionCookie, currentSession, body, postParams, client, postResponse, responseBody, access_token, scope, rest, sessionExpiration, onlineInfo, jwtSessionId, jwtSession, sessionDeleted, responseBody, sessionStored;
77
+ var stateFromCookie, isOnline, body, postParams, cleanShop, client, postResponse, session, cookies, sessionStored;
86
78
  return tslib_1.__generator(this, function (_a) {
87
79
  switch (_a.label) {
88
80
  case 0:
89
81
  context_1.Context.throwIfUninitialized();
90
82
  context_1.Context.throwIfPrivateApp('Cannot perform OAuth for private apps');
91
- cookies = new cookies_1.default(request, response, {
92
- keys: [context_1.Context.API_SECRET_KEY],
93
- secure: true,
94
- });
95
- sessionCookie = this.getCookieSessionId(request, response);
96
- if (!sessionCookie) {
83
+ stateFromCookie = getValueFromCookie(request, response, this.STATE_COOKIE_NAME);
84
+ deleteCookie(request, response, this.STATE_COOKIE_NAME);
85
+ if (!stateFromCookie) {
97
86
  throw new ShopifyErrors.CookieNotFound("Cannot complete OAuth process. Could not find an OAuth cookie for shop url: ".concat(query.shop));
98
87
  }
99
- return [4 /*yield*/, context_1.Context.SESSION_STORAGE.loadSession(sessionCookie)];
100
- case 1:
101
- currentSession = _a.sent();
102
- if (!currentSession) {
103
- throw new ShopifyErrors.SessionNotFound("Cannot complete OAuth process. No session found for the specified shop url: ".concat(query.shop));
104
- }
105
- if (!validQuery(query, currentSession)) {
88
+ if (!validQuery(query, stateFromCookie)) {
106
89
  throw new ShopifyErrors.InvalidOAuthError('Invalid OAuth callback.');
107
90
  }
91
+ isOnline = stateFromCookie.startsWith('online_');
108
92
  body = {
109
93
  client_id: context_1.Context.API_KEY,
110
94
  client_secret: context_1.Context.API_SECRET_KEY,
@@ -115,50 +99,31 @@ var ShopifyOAuth = {
115
99
  type: types_1.DataType.JSON,
116
100
  data: body,
117
101
  };
118
- client = new http_client_1.HttpClient(currentSession.shop);
102
+ cleanShop = (0, shop_validator_1.sanitizeShop)(query.shop, true);
103
+ client = new http_client_1.HttpClient(cleanShop);
119
104
  return [4 /*yield*/, client.post(postParams)];
120
- case 2:
105
+ case 1:
121
106
  postResponse = _a.sent();
122
- if (!currentSession.isOnline) return [3 /*break*/, 5];
123
- responseBody = postResponse.body;
124
- access_token = responseBody.access_token, scope = responseBody.scope, rest = tslib_1.__rest(responseBody, ["access_token", "scope"]);
125
- sessionExpiration = new Date(Date.now() + responseBody.expires_in * 1000);
126
- currentSession.accessToken = access_token;
127
- currentSession.expires = sessionExpiration;
128
- currentSession.scope = scope;
129
- currentSession.onlineAccessInfo = rest;
130
- if (!context_1.Context.IS_EMBEDDED_APP) return [3 /*break*/, 4];
131
- onlineInfo = currentSession.onlineAccessInfo;
132
- jwtSessionId = this.getJwtSessionId(currentSession.shop, "".concat(onlineInfo.associated_user.id));
133
- jwtSession = session_1.Session.cloneSession(currentSession, jwtSessionId);
134
- return [4 /*yield*/, context_1.Context.SESSION_STORAGE.deleteSession(currentSession.id)];
135
- case 3:
136
- sessionDeleted = _a.sent();
137
- if (!sessionDeleted) {
138
- throw new ShopifyErrors.SessionStorageError('OAuth Session could not be deleted. Please check your session storage functionality.');
107
+ session = createSession(postResponse, cleanShop, stateFromCookie, isOnline);
108
+ if (!context_1.Context.IS_EMBEDDED_APP) {
109
+ cookies = new cookies_1.default(request, response, {
110
+ keys: [context_1.Context.API_SECRET_KEY],
111
+ secure: true,
112
+ });
113
+ cookies.set(ShopifyOAuth.SESSION_COOKIE_NAME, session.id, {
114
+ signed: true,
115
+ expires: session.expires,
116
+ sameSite: 'lax',
117
+ secure: true,
118
+ });
139
119
  }
140
- currentSession = jwtSession;
141
- _a.label = 4;
142
- case 4: return [3 /*break*/, 6];
143
- case 5:
144
- responseBody = postResponse.body;
145
- currentSession.accessToken = responseBody.access_token;
146
- currentSession.scope = responseBody.scope;
147
- _a.label = 6;
148
- case 6:
149
- cookies.set(ShopifyOAuth.SESSION_COOKIE_NAME, currentSession.id, {
150
- signed: true,
151
- expires: context_1.Context.IS_EMBEDDED_APP ? new Date() : currentSession.expires,
152
- sameSite: 'lax',
153
- secure: true,
154
- });
155
- return [4 /*yield*/, context_1.Context.SESSION_STORAGE.storeSession(currentSession)];
156
- case 7:
120
+ return [4 /*yield*/, context_1.Context.SESSION_STORAGE.storeSession(session)];
121
+ case 2:
157
122
  sessionStored = _a.sent();
158
123
  if (!sessionStored) {
159
- throw new ShopifyErrors.SessionStorageError('OAuth Session could not be saved. Please check your session storage functionality.');
124
+ throw new ShopifyErrors.SessionStorageError('Session could not be saved. Please check your session storage functionality.');
160
125
  }
161
- return [2 /*return*/, currentSession];
126
+ return [2 /*return*/, session];
162
127
  }
163
128
  });
164
129
  });
@@ -170,11 +135,7 @@ var ShopifyOAuth = {
170
135
  * @param response HTTP response object
171
136
  */
172
137
  getCookieSessionId: function (request, response) {
173
- var cookies = new cookies_1.default(request, response, {
174
- secure: true,
175
- keys: [context_1.Context.API_SECRET_KEY],
176
- });
177
- return cookies.get(this.SESSION_COOKIE_NAME, { signed: true });
138
+ return getValueFromCookie(request, response, this.SESSION_COOKIE_NAME);
178
139
  },
179
140
  /**
180
141
  * Builds a JWT session id from the current shop and user.
@@ -225,7 +186,7 @@ var ShopifyOAuth = {
225
186
  // JWT.
226
187
  if (!currentSessionId) {
227
188
  // We still want to get the offline session id from the cookie to make sure it's validated
228
- currentSessionId = this.getCookieSessionId(request, response);
189
+ currentSessionId = getValueFromCookie(request, response, this.SESSION_COOKIE_NAME);
229
190
  }
230
191
  return currentSessionId;
231
192
  },
@@ -235,8 +196,72 @@ exports.ShopifyOAuth = ShopifyOAuth;
235
196
  * Uses the validation utils validateHmac, and safeCompare to assess whether the callback is valid.
236
197
  *
237
198
  * @param query Current HTTP Request Query
238
- * @param session Current session
199
+ * @param stateFromCookie state value from the current cookie
239
200
  */
240
- function validQuery(query, session) {
241
- return ((0, hmac_validator_1.default)(query) && (0, safe_compare_1.default)(query.state, session.state));
201
+ function validQuery(query, stateFromCookie) {
202
+ return (0, hmac_validator_1.default)(query) && (0, safe_compare_1.default)(query.state, stateFromCookie);
203
+ }
204
+ /**
205
+ * Loads a given value from the cookie
206
+ *
207
+ * @param request HTTP request object
208
+ * @param response HTTP response object
209
+ * @param name Name of the cookie to load
210
+ */
211
+ function getValueFromCookie(request, response, name) {
212
+ var cookies = new cookies_1.default(request, response, {
213
+ secure: true,
214
+ keys: [context_1.Context.API_SECRET_KEY],
215
+ });
216
+ return cookies.get(name, { signed: true });
217
+ }
218
+ /**
219
+ * Loads a given value from the cookie
220
+ *
221
+ * @param request HTTP request object
222
+ * @param response HTTP response object
223
+ * @param name Name of the cookie to load
224
+ */
225
+ function deleteCookie(request, response, name) {
226
+ var cookies = new cookies_1.default(request, response, {
227
+ secure: true,
228
+ keys: [context_1.Context.API_SECRET_KEY],
229
+ });
230
+ cookies.set(name);
231
+ }
232
+ /**
233
+ * Creates a new session from the response from the access token request.
234
+ *
235
+ * @param postResponse Response from the access token request
236
+ * @param shop Shop url: {shop}.myshopify.com
237
+ * @param stateFromCookie State from the cookie received by the OAuth callback
238
+ * @param isOnline Boolean indicating if the access token is for online access
239
+ * @returns SessionInterface
240
+ */
241
+ function createSession(postResponse, shop, stateFromCookie, isOnline) {
242
+ var session;
243
+ if (isOnline) {
244
+ var sessionId = void 0;
245
+ var responseBody = postResponse.body;
246
+ var access_token = responseBody.access_token, scope = responseBody.scope, rest = tslib_1.__rest(responseBody, ["access_token", "scope"]); // eslint-disable-line @typescript-eslint/naming-convention
247
+ var sessionExpiration = new Date(Date.now() + responseBody.expires_in * 1000);
248
+ if (context_1.Context.IS_EMBEDDED_APP) {
249
+ sessionId = ShopifyOAuth.getJwtSessionId(shop, "".concat(rest.associated_user.id));
250
+ }
251
+ else {
252
+ sessionId = (0, uuid_1.v4)();
253
+ }
254
+ session = new session_1.Session(sessionId, shop, stateFromCookie, isOnline);
255
+ session.accessToken = access_token;
256
+ session.scope = scope;
257
+ session.expires = sessionExpiration;
258
+ session.onlineAccessInfo = rest;
259
+ }
260
+ else {
261
+ var responseBody = postResponse.body;
262
+ session = new session_1.Session(ShopifyOAuth.getOfflineSessionId(shop), shop, stateFromCookie, isOnline);
263
+ session.accessToken = responseBody.access_token;
264
+ session.scope = responseBody.scope;
265
+ }
266
+ return session;
242
267
  }
@@ -3,6 +3,7 @@ import { SessionInterface } from '../types';
3
3
  import { SessionStorage } from '../session_storage';
4
4
  export interface RedisSessionStorageOptions extends RedisClientOptions {
5
5
  sessionKeyPrefix: string;
6
+ onError?: (...args: any[]) => void;
6
7
  }
7
8
  export declare class RedisSessionStorage implements SessionStorage {
8
9
  private dbUrl;
@@ -1 +1 @@
1
- {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../../../src/auth/session/storage/redis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,kBAAkB,EAAC,MAAM,OAAO,CAAC;AAExE,OAAO,EAAC,gBAAgB,EAAC,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAC,cAAc,EAAC,MAAM,oBAAoB,CAAC;AAIlD,MAAM,WAAW,0BAA2B,SAAQ,kBAAkB;IACpE,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAKD,qBAAa,mBAAoB,YAAW,cAAc;IAuBtD,OAAO,CAAC,KAAK;IAtBf,MAAM,CAAC,eAAe,CACpB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,CAAC,0BAA0B,CAAC;IAY3C,SAAgB,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,MAAM,CAAkB;gBAGtB,KAAK,EAAE,GAAG,EAClB,IAAI,GAAE,OAAO,CAAC,0BAA0B,CAAM;IASnC,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IAUzD,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC;IAW9D,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAM3C,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAM/C,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAiB7D,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxC,OAAO,CAAC,OAAO;YAID,IAAI;CAOnB"}
1
+ {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../../../src/auth/session/storage/redis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,kBAAkB,EAAC,MAAM,OAAO,CAAC;AAEvD,OAAO,EAAC,gBAAgB,EAAC,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAC,cAAc,EAAC,MAAM,oBAAoB,CAAC;AAKlD,MAAM,WAAW,0BAA2B,SAAQ,kBAAkB;IACpE,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;CACpC;AAKD,qBAAa,mBAAoB,YAAW,cAAc;IAuBtD,OAAO,CAAC,KAAK;IAtBf,MAAM,CAAC,eAAe,CACpB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,CAAC,0BAA0B,CAAC;IAY3C,SAAgB,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,MAAM,CAAc;gBAGlB,KAAK,EAAE,GAAG,EAClB,IAAI,GAAE,OAAO,CAAC,0BAA0B,CAAM;IASnC,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IAUzD,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC;IAW9D,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAM3C,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAM/C,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAiB7D,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxC,OAAO,CAAC,OAAO;YAID,IAAI;CAUnB"}
@@ -157,6 +157,9 @@ var RedisSessionStorage = /** @class */ (function () {
157
157
  switch (_a.label) {
158
158
  case 0:
159
159
  this.client = (0, redis_1.createClient)(tslib_1.__assign(tslib_1.__assign({}, this.options), { url: this.dbUrl.toString() }));
160
+ if (this.options.onError) {
161
+ this.client.on('error', this.options.onError);
162
+ }
160
163
  return [4 /*yield*/, this.client.connect()];
161
164
  case 1:
162
165
  _a.sent();
@@ -1,5 +1,6 @@
1
1
  import { AuthScopes } from './auth/scopes';
2
2
  import { SessionStorage } from './auth/session/session_storage';
3
+ import { BillingSettings } from './billing/types';
3
4
  export interface ContextParams {
4
5
  API_KEY: string;
5
6
  API_SECRET_KEY: string;
@@ -14,6 +15,7 @@ export interface ContextParams {
14
15
  USER_AGENT_PREFIX?: string;
15
16
  PRIVATE_APP_STOREFRONT_ACCESS_TOKEN?: string;
16
17
  CUSTOM_SHOP_DOMAINS?: (RegExp | string)[];
18
+ BILLING?: BillingSettings;
17
19
  }
18
20
  export declare enum ApiVersion {
19
21
  October21 = "2021-10",
@@ -1 +1 @@
1
- {"version":3,"file":"base-types.d.ts","sourceRoot":"","sources":["../src/base-types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAC,MAAM,eAAe,CAAC;AACzC,OAAO,EAAC,cAAc,EAAC,MAAM,gCAAgC,CAAC;AAE9D,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,UAAU,CAAC;IACxB,eAAe,EAAE,OAAO,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mCAAmC,CAAC,EAAE,MAAM,CAAC;IAC7C,mBAAmB,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;CAC3C;AAED,oBAAY,UAAU;IACpB,SAAS,YAAY;IACrB,SAAS,YAAY;IACrB,OAAO,YAAY;IACnB,MAAM,YAAY;IAClB,QAAQ,aAAa;CACtB;AAED,eAAO,MAAM,kBAAkB,oBAAoB,CAAC;AAEpD,oBAAY,aAAa;IACvB,WAAW,2BAA2B;IACtC,qBAAqB,sCAAsC;IAC3D,IAAI,0BAA0B;IAC9B,KAAK,oBAAoB;IACzB,MAAM,0BAA0B;CACjC"}
1
+ {"version":3,"file":"base-types.d.ts","sourceRoot":"","sources":["../src/base-types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAC,MAAM,eAAe,CAAC;AACzC,OAAO,EAAC,cAAc,EAAC,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAAC,eAAe,EAAC,MAAM,iBAAiB,CAAC;AAEhD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,UAAU,CAAC;IACxB,eAAe,EAAE,OAAO,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mCAAmC,CAAC,EAAE,MAAM,CAAC;IAC7C,mBAAmB,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAC1C,OAAO,CAAC,EAAE,eAAe,CAAC;CAC3B;AAED,oBAAY,UAAU;IACpB,SAAS,YAAY;IACrB,SAAS,YAAY;IACrB,OAAO,YAAY;IACnB,MAAM,YAAY;IAClB,QAAQ,aAAa;CACtB;AAED,eAAO,MAAM,kBAAkB,oBAAoB,CAAC;AAEpD,oBAAY,aAAa;IACvB,WAAW,2BAA2B;IACtC,qBAAqB,sCAAsC;IAC3D,IAAI,0BAA0B;IAC9B,KAAK,oBAAoB;IACzB,MAAM,0BAA0B;CACjC"}
@@ -0,0 +1,12 @@
1
+ import { SessionInterface } from '../auth/session/types';
2
+ interface CheckInterface {
3
+ session: SessionInterface;
4
+ isTest?: boolean;
5
+ }
6
+ interface CheckReturn {
7
+ hasPayment: boolean;
8
+ confirmBillingUrl?: string;
9
+ }
10
+ export declare function check({ session, isTest, }: CheckInterface): Promise<CheckReturn>;
11
+ export {};
12
+ //# sourceMappingURL=check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/billing/check.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,gBAAgB,EAAC,MAAM,uBAAuB,CAAC;AAKvD,UAAU,cAAc;IACtB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,UAAU,WAAW;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAsB,KAAK,CAAC,EAC1B,OAAO,EACP,MAAa,GACd,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,CAgBvC"}
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.check = void 0;
4
+ var tslib_1 = require("tslib");
5
+ var context_1 = require("../context");
6
+ var has_active_payment_1 = require("./has_active_payment");
7
+ var request_payment_1 = require("./request_payment");
8
+ function check(_a) {
9
+ var session = _a.session, _b = _a.isTest, isTest = _b === void 0 ? true : _b;
10
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
11
+ var hasPayment, confirmBillingUrl;
12
+ return tslib_1.__generator(this, function (_c) {
13
+ switch (_c.label) {
14
+ case 0:
15
+ if (!context_1.Context.BILLING) {
16
+ return [2 /*return*/, { hasPayment: true, confirmBillingUrl: undefined }];
17
+ }
18
+ return [4 /*yield*/, (0, has_active_payment_1.hasActivePayment)(session, isTest)];
19
+ case 1:
20
+ if (!_c.sent()) return [3 /*break*/, 2];
21
+ hasPayment = true;
22
+ return [3 /*break*/, 4];
23
+ case 2:
24
+ hasPayment = false;
25
+ return [4 /*yield*/, (0, request_payment_1.requestPayment)(session, isTest)];
26
+ case 3:
27
+ confirmBillingUrl = _c.sent();
28
+ _c.label = 4;
29
+ case 4: return [2 /*return*/, { hasPayment: hasPayment, confirmBillingUrl: confirmBillingUrl }];
30
+ }
31
+ });
32
+ });
33
+ }
34
+ exports.check = check;
@@ -0,0 +1,3 @@
1
+ import { SessionInterface } from '../auth/session/types';
2
+ export declare function hasActivePayment(session: SessionInterface, isTest: boolean): Promise<boolean>;
3
+ //# sourceMappingURL=has_active_payment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"has_active_payment.d.ts","sourceRoot":"","sources":["../../src/billing/has_active_payment.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,gBAAgB,EAAC,MAAM,uBAAuB,CAAC;AASvD,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,OAAO,GACd,OAAO,CAAC,OAAO,CAAC,CAMlB"}
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hasActivePayment = void 0;
4
+ var tslib_1 = require("tslib");
5
+ var context_1 = require("../context");
6
+ var error_1 = require("../error");
7
+ var graphql_1 = require("../clients/graphql");
8
+ var is_recurring_1 = require("./is_recurring");
9
+ function hasActivePayment(session, isTest) {
10
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
11
+ return tslib_1.__generator(this, function (_a) {
12
+ if ((0, is_recurring_1.isRecurring)()) {
13
+ return [2 /*return*/, hasActiveSubscription(session, isTest)];
14
+ }
15
+ else {
16
+ return [2 /*return*/, hasActiveOneTimePurchase(session, isTest)];
17
+ }
18
+ return [2 /*return*/];
19
+ });
20
+ });
21
+ }
22
+ exports.hasActivePayment = hasActivePayment;
23
+ function hasActiveSubscription(session, isTest) {
24
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
25
+ var client, currentInstallations;
26
+ return tslib_1.__generator(this, function (_a) {
27
+ switch (_a.label) {
28
+ case 0:
29
+ if (!context_1.Context.BILLING) {
30
+ throw new error_1.BillingError({
31
+ message: 'Attempted to look for subscriptions without billing configs',
32
+ errorData: [],
33
+ });
34
+ }
35
+ client = new graphql_1.GraphqlClient(session.shop, session.accessToken);
36
+ return [4 /*yield*/, client.query({
37
+ data: RECURRING_PURCHASES_QUERY,
38
+ })];
39
+ case 1:
40
+ currentInstallations = _a.sent();
41
+ return [2 /*return*/, currentInstallations.body.data.currentAppInstallation.activeSubscriptions.some(function (subscription) {
42
+ return subscription.name === context_1.Context.BILLING.chargeName &&
43
+ (isTest || !subscription.test);
44
+ })];
45
+ }
46
+ });
47
+ });
48
+ }
49
+ function hasActiveOneTimePurchase(session, isTest) {
50
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
51
+ var client, installation, endCursor, currentInstallations;
52
+ return tslib_1.__generator(this, function (_a) {
53
+ switch (_a.label) {
54
+ case 0:
55
+ if (!context_1.Context.BILLING) {
56
+ throw new error_1.BillingError({
57
+ message: 'Attempted to look for one time purchases without billing configs',
58
+ errorData: [],
59
+ });
60
+ }
61
+ client = new graphql_1.GraphqlClient(session.shop, session.accessToken);
62
+ endCursor = null;
63
+ _a.label = 1;
64
+ case 1: return [4 /*yield*/, client.query({
65
+ data: {
66
+ query: ONE_TIME_PURCHASES_QUERY,
67
+ variables: { endCursor: endCursor },
68
+ },
69
+ })];
70
+ case 2:
71
+ currentInstallations = _a.sent();
72
+ installation = currentInstallations.body.data.currentAppInstallation;
73
+ if (installation.oneTimePurchases.edges.some(function (purchase) {
74
+ return purchase.node.name === context_1.Context.BILLING.chargeName &&
75
+ (isTest || !purchase.node.test) &&
76
+ purchase.node.status === 'ACTIVE';
77
+ })) {
78
+ return [2 /*return*/, true];
79
+ }
80
+ endCursor = installation.oneTimePurchases.pageInfo.endCursor;
81
+ _a.label = 3;
82
+ case 3:
83
+ if (installation === null || installation === void 0 ? void 0 : installation.oneTimePurchases.pageInfo.hasNextPage) return [3 /*break*/, 1];
84
+ _a.label = 4;
85
+ case 4: return [2 /*return*/, false];
86
+ }
87
+ });
88
+ });
89
+ }
90
+ var RECURRING_PURCHASES_QUERY = "\n query appSubscription {\n currentAppInstallation {\n activeSubscriptions {\n name\n test\n }\n }\n }\n";
91
+ var ONE_TIME_PURCHASES_QUERY = "\n query appPurchases($endCursor: String) {\n currentAppInstallation {\n oneTimePurchases(first: 250, sortKey: CREATED_AT, after: $endCursor) {\n edges {\n node {\n name\n test\n status\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n }\n";
@@ -0,0 +1,7 @@
1
+ import { check } from './check';
2
+ declare const ShopifyBilling: {
3
+ check: typeof check;
4
+ };
5
+ export { ShopifyBilling };
6
+ export default ShopifyBilling;
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/billing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,EAAC,MAAM,SAAS,CAAC;AAE9B,QAAA,MAAM,cAAc;;CAEnB,CAAC;AAEF,OAAO,EAAC,cAAc,EAAC,CAAC;AACxB,eAAe,cAAc,CAAC"}
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ShopifyBilling = void 0;
4
+ var check_1 = require("./check");
5
+ var ShopifyBilling = {
6
+ check: check_1.check,
7
+ };
8
+ exports.ShopifyBilling = ShopifyBilling;
9
+ exports.default = ShopifyBilling;
@@ -0,0 +1,2 @@
1
+ export declare function isRecurring(): boolean;
2
+ //# sourceMappingURL=is_recurring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is_recurring.d.ts","sourceRoot":"","sources":["../../src/billing/is_recurring.ts"],"names":[],"mappings":"AAUA,wBAAgB,WAAW,IAAI,OAAO,CASrC"}
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isRecurring = void 0;
4
+ var context_1 = require("../context");
5
+ var error_1 = require("../error");
6
+ var types_1 = require("./types");
7
+ var RECURRING_INTERVALS = [
8
+ types_1.BillingInterval.Every30Days,
9
+ types_1.BillingInterval.Annual,
10
+ ];
11
+ function isRecurring() {
12
+ if (!context_1.Context.BILLING) {
13
+ throw new error_1.BillingError({
14
+ message: 'Attempted to request billing without billing configs',
15
+ errorData: [],
16
+ });
17
+ }
18
+ return RECURRING_INTERVALS.includes(context_1.Context.BILLING.interval);
19
+ }
20
+ exports.isRecurring = isRecurring;
@@ -0,0 +1,3 @@
1
+ import { SessionInterface } from '../auth/session/types';
2
+ export declare function requestPayment(session: SessionInterface, isTest: boolean): Promise<string | undefined>;
3
+ //# sourceMappingURL=request_payment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request_payment.d.ts","sourceRoot":"","sources":["../../src/billing/request_payment.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,gBAAgB,EAAC,MAAM,uBAAuB,CAAC;AAYvD,wBAAsB,cAAc,CAClC,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,OAAO,GACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA8B7B"}