@shopify/shopify-app-remix 4.1.1 → 4.2.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.
- package/dist/cjs/react/components/AppProvider/AppProvider.js.map +1 -1
- package/dist/cjs/react/components/AppProxyLink/AppProxyLink.js.map +1 -1
- package/dist/cjs/server/authenticate/admin/strategies/auth-code-flow.js.map +1 -1
- package/dist/cjs/server/authenticate/admin/strategies/merchant-custom-app.js.map +1 -1
- package/dist/cjs/server/authenticate/admin/strategies/token-exchange.js.map +1 -1
- package/dist/cjs/server/authenticate/webhooks/authenticate.js +34 -10
- package/dist/cjs/server/authenticate/webhooks/authenticate.js.map +1 -1
- package/dist/cjs/server/version.js +1 -1
- package/dist/cjs/server/version.js.map +1 -1
- package/dist/esm/react/components/AppProvider/AppProvider.mjs.map +1 -1
- package/dist/esm/react/components/AppProxyLink/AppProxyLink.mjs.map +1 -1
- package/dist/esm/server/authenticate/admin/strategies/auth-code-flow.mjs.map +1 -1
- package/dist/esm/server/authenticate/admin/strategies/merchant-custom-app.mjs.map +1 -1
- package/dist/esm/server/authenticate/admin/strategies/token-exchange.mjs.map +1 -1
- package/dist/esm/server/authenticate/webhooks/authenticate.mjs +35 -11
- package/dist/esm/server/authenticate/webhooks/authenticate.mjs.map +1 -1
- package/dist/esm/server/version.mjs +1 -1
- package/dist/esm/server/version.mjs.map +1 -1
- package/dist/ts/react/components/AppProvider/AppProvider.d.ts.map +1 -1
- package/dist/ts/react/components/AppProxyLink/AppProxyLink.d.ts.map +1 -1
- package/dist/ts/server/authenticate/admin/billing/types.d.ts.map +1 -1
- package/dist/ts/server/authenticate/admin/strategies/auth-code-flow.d.ts.map +1 -1
- package/dist/ts/server/authenticate/admin/strategies/merchant-custom-app.d.ts.map +1 -1
- package/dist/ts/server/authenticate/admin/strategies/token-exchange.d.ts.map +1 -1
- package/dist/ts/server/authenticate/admin/types.d.ts.map +1 -1
- package/dist/ts/server/authenticate/public/extension/types.d.ts.map +1 -1
- package/dist/ts/server/authenticate/webhooks/authenticate.d.ts.map +1 -1
- package/dist/ts/server/authenticate/webhooks/types.d.ts +34 -2
- package/dist/ts/server/authenticate/webhooks/types.d.ts.map +1 -1
- package/dist/ts/server/config-types.d.ts.map +1 -1
- package/dist/ts/server/version.d.ts +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +18 -14
- package/src/react/components/AppProvider/AppProvider.doc.ts +35 -0
- package/src/react/components/AppProvider/AppProvider.tsx +147 -0
- package/src/react/components/AppProvider/index.ts +1 -0
- package/src/react/components/AppProxyForm/AppProxyForm.doc.ts +28 -0
- package/src/react/components/AppProxyForm/AppProxyForm.tsx +82 -0
- package/src/react/components/AppProxyForm/index.ts +1 -0
- package/src/react/components/AppProxyLink/AppProxyLink.doc.ts +28 -0
- package/src/react/components/AppProxyLink/AppProxyLink.tsx +58 -0
- package/src/react/components/AppProxyLink/index.ts +1 -0
- package/src/react/components/AppProxyProvider/AppProxyProvider.doc.ts +40 -0
- package/src/react/components/AppProxyProvider/AppProxyProvider.tsx +100 -0
- package/src/react/components/AppProxyProvider/index.ts +5 -0
- package/src/react/components/RemixPolarisLink.tsx +14 -0
- package/src/react/components/index.ts +4 -0
- package/src/react/const.ts +2 -0
- package/src/react/index.ts +1 -0
- package/src/server/__test-helpers/const.ts +14 -0
- package/src/server/__test-helpers/expect-admin-api-client.ts +51 -0
- package/src/server/__test-helpers/expect-begin-auth-redirect.ts +22 -0
- package/src/server/__test-helpers/expect-document-request-headers.ts +25 -0
- package/src/server/__test-helpers/expect-exit-iframe.ts +37 -0
- package/src/server/__test-helpers/expect-login-redirect.ts +8 -0
- package/src/server/__test-helpers/expect-storefront-api-client.ts +49 -0
- package/src/server/__test-helpers/expect-token-refresh.ts +188 -0
- package/src/server/__test-helpers/get-hmac.ts +7 -0
- package/src/server/__test-helpers/get-jwt.ts +10 -0
- package/src/server/__test-helpers/get-thrown-error.ts +19 -0
- package/src/server/__test-helpers/get-thrown-response.ts +17 -0
- package/src/server/__test-helpers/index.ts +21 -0
- package/src/server/__test-helpers/mock-graphql-request.ts +60 -0
- package/src/server/__test-helpers/request-mock.ts +169 -0
- package/src/server/__test-helpers/setup-embedded-flow.ts +30 -0
- package/src/server/__test-helpers/setup-fetch-flow.ts +29 -0
- package/src/server/__test-helpers/setup-non-embedded-flow.ts +31 -0
- package/src/server/__test-helpers/setup-valid-request.ts +78 -0
- package/src/server/__test-helpers/setup-valid-session.ts +28 -0
- package/src/server/__test-helpers/sign-request-cookie.ts +21 -0
- package/src/server/__test-helpers/test-config.ts +56 -0
- package/src/server/adapters/node/index.ts +13 -0
- package/src/server/adapters/vercel/index.ts +13 -0
- package/src/server/authenticate/admin/authenticate.admin.doc.example.ts +34 -0
- package/src/server/authenticate/admin/authenticate.admin.doc.ts +51 -0
- package/src/server/authenticate/admin/authenticate.ts +232 -0
- package/src/server/authenticate/admin/billing/authenticate.admin.billing.doc.ts +29 -0
- package/src/server/authenticate/admin/billing/cancel.ts +38 -0
- package/src/server/authenticate/admin/billing/check.ts +41 -0
- package/src/server/authenticate/admin/billing/create-usage-record.ts +36 -0
- package/src/server/authenticate/admin/billing/helpers.ts +42 -0
- package/src/server/authenticate/admin/billing/index.ts +6 -0
- package/src/server/authenticate/admin/billing/request.ts +64 -0
- package/src/server/authenticate/admin/billing/require.ts +58 -0
- package/src/server/authenticate/admin/billing/types.ts +613 -0
- package/src/server/authenticate/admin/billing/update-usage-subscription-capped-amount.ts +55 -0
- package/src/server/authenticate/admin/helpers/begin-auth.ts +17 -0
- package/src/server/authenticate/admin/helpers/create-admin-api-context.ts +20 -0
- package/src/server/authenticate/admin/helpers/ensure-app-is-embedded-if-required.ts +18 -0
- package/src/server/authenticate/admin/helpers/ensure-session-token-search-param-if-required.ts +25 -0
- package/src/server/authenticate/admin/helpers/handle-client-error.ts +45 -0
- package/src/server/authenticate/admin/helpers/index.ts +14 -0
- package/src/server/authenticate/admin/helpers/redirect-to-auth-page.ts +28 -0
- package/src/server/authenticate/admin/helpers/redirect-to-bounce-page.ts +23 -0
- package/src/server/authenticate/admin/helpers/redirect-to-install-page.ts +53 -0
- package/src/server/authenticate/admin/helpers/redirect-to-shopify-or-app-root.ts +21 -0
- package/src/server/authenticate/admin/helpers/redirect-with-app-bridge-headers.ts +13 -0
- package/src/server/authenticate/admin/helpers/redirect-with-exitiframe.ts +28 -0
- package/src/server/authenticate/admin/helpers/redirect.ts +164 -0
- package/src/server/authenticate/admin/helpers/render-app-bridge.ts +46 -0
- package/src/server/authenticate/admin/helpers/trigger-after-auth-hook.ts +29 -0
- package/src/server/authenticate/admin/helpers/validate-redirect-url.ts +67 -0
- package/src/server/authenticate/admin/helpers/validate-shop-and-host-params.ts +46 -0
- package/src/server/authenticate/admin/scope/authenticate.admin.scopes.doc.ts +28 -0
- package/src/server/authenticate/admin/scope/client/fetch-scopes-details.ts +43 -0
- package/src/server/authenticate/admin/scope/client/revoke-scopes.ts +38 -0
- package/src/server/authenticate/admin/scope/factory.ts +21 -0
- package/src/server/authenticate/admin/scope/query.ts +51 -0
- package/src/server/authenticate/admin/scope/request.ts +32 -0
- package/src/server/authenticate/admin/scope/revoke.ts +48 -0
- package/src/server/authenticate/admin/scope/types.ts +126 -0
- package/src/server/authenticate/admin/strategies/auth-code-flow.ts +332 -0
- package/src/server/authenticate/admin/strategies/merchant-custom-app.ts +65 -0
- package/src/server/authenticate/admin/strategies/token-exchange.ts +188 -0
- package/src/server/authenticate/admin/strategies/types.ts +29 -0
- package/src/server/authenticate/admin/types.ts +216 -0
- package/src/server/authenticate/const.ts +15 -0
- package/src/server/authenticate/flow/authenticate.flow.doc.example.ts +39 -0
- package/src/server/authenticate/flow/authenticate.flow.doc.ts +47 -0
- package/src/server/authenticate/flow/authenticate.ts +68 -0
- package/src/server/authenticate/flow/types.ts +85 -0
- package/src/server/authenticate/fulfillment-service/authenticate.fulfillment-service.doc.example.ts +52 -0
- package/src/server/authenticate/fulfillment-service/authenticate.fulfillment-service.doc.ts +48 -0
- package/src/server/authenticate/fulfillment-service/authenticate.ts +82 -0
- package/src/server/authenticate/fulfillment-service/types.ts +82 -0
- package/src/server/authenticate/helpers/add-response-headers.ts +43 -0
- package/src/server/authenticate/helpers/app-bridge-url.ts +10 -0
- package/src/server/authenticate/helpers/ensure-cors-headers.ts +38 -0
- package/src/server/authenticate/helpers/get-session-token-header.ts +11 -0
- package/src/server/authenticate/helpers/get-shop-from-request.ts +4 -0
- package/src/server/authenticate/helpers/idempotent-promise-handler.ts +45 -0
- package/src/server/authenticate/helpers/index.ts +10 -0
- package/src/server/authenticate/helpers/invalidate-access-token.ts +17 -0
- package/src/server/authenticate/helpers/reject-bot-request.ts +27 -0
- package/src/server/authenticate/helpers/respond-to-invalid-session-token.ts +28 -0
- package/src/server/authenticate/helpers/respond-to-options-request.ts +26 -0
- package/src/server/authenticate/helpers/validate-session-token.ts +40 -0
- package/src/server/authenticate/login/login.ts +53 -0
- package/src/server/authenticate/public/appProxy/authenticate.public.app-proxy.doc.example.ts +27 -0
- package/src/server/authenticate/public/appProxy/authenticate.public.app-proxy.doc.ts +55 -0
- package/src/server/authenticate/public/appProxy/authenticate.ts +150 -0
- package/src/server/authenticate/public/appProxy/types.ts +215 -0
- package/src/server/authenticate/public/checkout/authenticate.public.checkout.doc.example.offers.ts +21 -0
- package/src/server/authenticate/public/checkout/authenticate.public.checkout.doc.example.ts +17 -0
- package/src/server/authenticate/public/checkout/authenticate.public.checkout.doc.ts +49 -0
- package/src/server/authenticate/public/checkout/authenticate.ts +13 -0
- package/src/server/authenticate/public/checkout/types.ts +65 -0
- package/src/server/authenticate/public/customer-account/authenticate.public.customerAccount.doc.example.offers.ts +36 -0
- package/src/server/authenticate/public/customer-account/authenticate.public.customerAccount.doc.example.ts +18 -0
- package/src/server/authenticate/public/customer-account/authenticate.public.customerAccount.doc.ts +49 -0
- package/src/server/authenticate/public/customer-account/authenticate.ts +13 -0
- package/src/server/authenticate/public/customer-account/types.ts +65 -0
- package/src/server/authenticate/public/extension/authenticate.ts +54 -0
- package/src/server/authenticate/public/extension/types.ts +11 -0
- package/src/server/authenticate/public/factory.ts +24 -0
- package/src/server/authenticate/public/index.ts +1 -0
- package/src/server/authenticate/public/pos/authenticate.ts +8 -0
- package/src/server/authenticate/public/pos/types.ts +65 -0
- package/src/server/authenticate/public/types.ts +96 -0
- package/src/server/authenticate/webhooks/authenticate.ts +105 -0
- package/src/server/authenticate/webhooks/authenticate.webhooks.doc.example.ts +43 -0
- package/src/server/authenticate/webhooks/authenticate.webhooks.doc.ts +39 -0
- package/src/server/authenticate/webhooks/index.ts +1 -0
- package/src/server/authenticate/webhooks/register.ts +48 -0
- package/src/server/authenticate/webhooks/types.ts +266 -0
- package/src/server/boundary/error.tsx +14 -0
- package/src/server/boundary/headers.ts +15 -0
- package/src/server/boundary/index.ts +39 -0
- package/src/server/boundary/types.ts +4 -0
- package/src/server/clients/admin/authenticate.admin.api.doc.ts +34 -0
- package/src/server/clients/admin/factory.ts +22 -0
- package/src/server/clients/admin/graphql.ts +39 -0
- package/src/server/clients/admin/index.ts +2 -0
- package/src/server/clients/admin/types.ts +120 -0
- package/src/server/clients/index.ts +2 -0
- package/src/server/clients/storefront/authenticate.storefront.api.doc.ts +34 -0
- package/src/server/clients/storefront/factory.ts +32 -0
- package/src/server/clients/storefront/index.ts +2 -0
- package/src/server/clients/storefront/types.ts +78 -0
- package/src/server/clients/types.ts +47 -0
- package/src/server/config-types.ts +309 -0
- package/src/server/errors.ts +3 -0
- package/src/server/future/flags.ts +62 -0
- package/src/server/helpers/create-or-load-offline-session.ts +19 -0
- package/src/server/helpers/ensure-offline-token-is-not-expired.ts +32 -0
- package/src/server/helpers/ensure-valid-offline-session.ts +15 -0
- package/src/server/helpers/index.ts +4 -0
- package/src/server/helpers/refresh-token.ts +35 -0
- package/src/server/index.ts +24 -0
- package/src/server/override-logger.ts +42 -0
- package/src/server/shopify-app.doc.ts +45 -0
- package/src/server/shopify-app.ts +215 -0
- package/src/server/test-helpers/index.ts +1 -0
- package/src/server/test-helpers/test-config.ts +79 -0
- package/src/server/types-contexts.ts +44 -0
- package/src/server/types.ts +507 -0
- package/src/server/unauthenticated/admin/factory.ts +23 -0
- package/src/server/unauthenticated/admin/index.ts +1 -0
- package/src/server/unauthenticated/admin/types.ts +79 -0
- package/src/server/unauthenticated/admin/unauthenticated.admin.doc.ts +40 -0
- package/src/server/unauthenticated/storefront/factory.ts +28 -0
- package/src/server/unauthenticated/storefront/index.ts +1 -0
- package/src/server/unauthenticated/storefront/types.ts +105 -0
- package/src/server/unauthenticated/storefront/unauthenticated.storefront.doc.ts +40 -0
- package/src/server/unauthenticated/types.ts +65 -0
- package/src/server/version.ts +1 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {shopifyApp} from '../shopify-app';
|
|
2
|
+
|
|
3
|
+
import {APP_URL, BASE64_HOST, TEST_SHOP} from './const';
|
|
4
|
+
import {getJwt} from './get-jwt';
|
|
5
|
+
import {setUpValidSession} from './setup-valid-session';
|
|
6
|
+
import {testConfig} from './test-config';
|
|
7
|
+
|
|
8
|
+
export async function setUpEmbeddedFlow() {
|
|
9
|
+
const shopify = shopifyApp({
|
|
10
|
+
...testConfig(),
|
|
11
|
+
future: {
|
|
12
|
+
unstable_newEmbeddedAuthStrategy: false,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
const expectedSession = await setUpValidSession(shopify.sessionStorage);
|
|
16
|
+
|
|
17
|
+
const {token} = await getJwt();
|
|
18
|
+
const request = new Request(
|
|
19
|
+
`${APP_URL}?embedded=1&shop=${TEST_SHOP}&host=${BASE64_HOST}&id_token=${token}`,
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const result = await shopify.authenticate.admin(request);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
shopify,
|
|
26
|
+
expectedSession,
|
|
27
|
+
...result,
|
|
28
|
+
request,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {shopifyApp} from '../shopify-app';
|
|
2
|
+
|
|
3
|
+
import {APP_URL} from './const';
|
|
4
|
+
import {getJwt} from './get-jwt';
|
|
5
|
+
import {setUpValidSession} from './setup-valid-session';
|
|
6
|
+
import {testConfig} from './test-config';
|
|
7
|
+
|
|
8
|
+
export async function setUpFetchFlow(flags?: {
|
|
9
|
+
unstable_newEmbeddedAuthStrategy?: boolean;
|
|
10
|
+
}) {
|
|
11
|
+
const shopify = shopifyApp({
|
|
12
|
+
...testConfig(),
|
|
13
|
+
future: {...flags},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
await setUpValidSession(shopify.sessionStorage);
|
|
17
|
+
|
|
18
|
+
const {token} = await getJwt();
|
|
19
|
+
const request = new Request(APP_URL, {
|
|
20
|
+
headers: {Authorization: `Bearer ${token}`},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const result = await shopify.authenticate.admin(request);
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
shopify,
|
|
27
|
+
...result,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {SESSION_COOKIE_NAME} from '@shopify/shopify-api';
|
|
2
|
+
|
|
3
|
+
import {shopifyApp} from '../shopify-app';
|
|
4
|
+
|
|
5
|
+
import {APP_URL, TEST_SHOP} from './const';
|
|
6
|
+
import {setUpValidSession} from './setup-valid-session';
|
|
7
|
+
import {signRequestCookie} from './sign-request-cookie';
|
|
8
|
+
import {testConfig} from './test-config';
|
|
9
|
+
|
|
10
|
+
export async function setUpNonEmbeddedFlow() {
|
|
11
|
+
const shopify = shopifyApp({
|
|
12
|
+
...testConfig({isEmbeddedApp: false}),
|
|
13
|
+
});
|
|
14
|
+
const session = await setUpValidSession(shopify.sessionStorage);
|
|
15
|
+
|
|
16
|
+
const request = new Request(`${APP_URL}?shop=${TEST_SHOP}`);
|
|
17
|
+
signRequestCookie({
|
|
18
|
+
request,
|
|
19
|
+
cookieName: SESSION_COOKIE_NAME,
|
|
20
|
+
cookieValue: session.id,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const result = await shopify.authenticate.admin(request);
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
shopify,
|
|
27
|
+
...result,
|
|
28
|
+
request,
|
|
29
|
+
session,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RequestType,
|
|
3
|
+
ValidRequestOptions as ValidRequestOptionsImport,
|
|
4
|
+
setUpValidRequest as setUpValidRequestImport,
|
|
5
|
+
} from '@shopify/shopify-api/test-helpers';
|
|
6
|
+
|
|
7
|
+
import {ShopifyApp} from '../types';
|
|
8
|
+
|
|
9
|
+
import {API_KEY, API_SECRET_KEY, APP_URL, TEST_SHOP_NAME} from './const';
|
|
10
|
+
import {setUpValidSession} from './setup-valid-session';
|
|
11
|
+
|
|
12
|
+
interface ValidBaseRequestOptions {
|
|
13
|
+
type: RequestType.Admin | RequestType.Bearer | RequestType.Public;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface ValidExtensionRequestOptions {
|
|
17
|
+
type: RequestType.Extension;
|
|
18
|
+
body?: any;
|
|
19
|
+
headers?: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type ValidRequestOptions =
|
|
23
|
+
| ValidBaseRequestOptions
|
|
24
|
+
| ValidExtensionRequestOptions;
|
|
25
|
+
|
|
26
|
+
export async function setupValidRequest(
|
|
27
|
+
shopify: ShopifyApp<any>,
|
|
28
|
+
options: ValidRequestOptions,
|
|
29
|
+
) {
|
|
30
|
+
const session = await setUpValidSession(shopify.sessionStorage, {
|
|
31
|
+
isOnline: false,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
let decoratedOptions: ValidRequestOptionsImport;
|
|
35
|
+
switch (options.type) {
|
|
36
|
+
case RequestType.Admin:
|
|
37
|
+
decoratedOptions = {
|
|
38
|
+
store: TEST_SHOP_NAME,
|
|
39
|
+
apiKey: API_KEY,
|
|
40
|
+
apiSecretKey: API_SECRET_KEY,
|
|
41
|
+
...options,
|
|
42
|
+
};
|
|
43
|
+
break;
|
|
44
|
+
case RequestType.Bearer:
|
|
45
|
+
decoratedOptions = {
|
|
46
|
+
store: TEST_SHOP_NAME,
|
|
47
|
+
apiKey: API_KEY,
|
|
48
|
+
apiSecretKey: API_SECRET_KEY,
|
|
49
|
+
...options,
|
|
50
|
+
};
|
|
51
|
+
break;
|
|
52
|
+
case RequestType.Extension:
|
|
53
|
+
decoratedOptions = {
|
|
54
|
+
store: TEST_SHOP_NAME,
|
|
55
|
+
apiSecretKey: API_SECRET_KEY,
|
|
56
|
+
...options,
|
|
57
|
+
};
|
|
58
|
+
break;
|
|
59
|
+
case RequestType.Public:
|
|
60
|
+
decoratedOptions = {
|
|
61
|
+
store: TEST_SHOP_NAME,
|
|
62
|
+
apiSecretKey: API_SECRET_KEY,
|
|
63
|
+
...options,
|
|
64
|
+
type: options.type,
|
|
65
|
+
};
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const url = new URL(APP_URL);
|
|
70
|
+
const request: Request = await setUpValidRequestImport(
|
|
71
|
+
decoratedOptions,
|
|
72
|
+
new Request(url),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return {shopify, session, request};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export {RequestType};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {SessionStorage} from '@shopify/shopify-app-session-storage';
|
|
2
|
+
import {Session} from '@shopify/shopify-api';
|
|
3
|
+
import {setUpValidSession as setUpValidSessionImport} from '@shopify/shopify-api/test-helpers';
|
|
4
|
+
|
|
5
|
+
import {TEST_SHOP} from './const';
|
|
6
|
+
|
|
7
|
+
export async function setUpValidSession(
|
|
8
|
+
sessionStorage: SessionStorage,
|
|
9
|
+
sessionParams?: Partial<Session>,
|
|
10
|
+
): Promise<Session> {
|
|
11
|
+
const session = setUpValidSessionImport({
|
|
12
|
+
shop: TEST_SHOP,
|
|
13
|
+
...sessionParams,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
await sessionStorage.storeSession(session);
|
|
17
|
+
|
|
18
|
+
return session;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function setupValidCustomAppSession(shop: string): Session {
|
|
22
|
+
return new Session({
|
|
23
|
+
id: '',
|
|
24
|
+
shop,
|
|
25
|
+
state: '',
|
|
26
|
+
isOnline: false,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {getHmac} from './get-hmac';
|
|
2
|
+
|
|
3
|
+
export function signRequestCookie({
|
|
4
|
+
request,
|
|
5
|
+
cookieName,
|
|
6
|
+
cookieValue,
|
|
7
|
+
}: {
|
|
8
|
+
request: Request;
|
|
9
|
+
cookieName: string;
|
|
10
|
+
cookieValue: string;
|
|
11
|
+
}) {
|
|
12
|
+
const signedCookieValue = getHmac(cookieValue);
|
|
13
|
+
|
|
14
|
+
request.headers.set(
|
|
15
|
+
'Cookie',
|
|
16
|
+
[
|
|
17
|
+
`${cookieName}=${cookieValue}`,
|
|
18
|
+
`${cookieName}.sig=${signedCookieValue}`,
|
|
19
|
+
].join(';'),
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {ApiVersion} from '@shopify/shopify-api';
|
|
2
|
+
import {MemorySessionStorage} from '@shopify/shopify-app-session-storage-memory';
|
|
3
|
+
|
|
4
|
+
import type {FutureFlagOptions, FutureFlags} from '../future/flags';
|
|
5
|
+
import type {TestOverridesArg} from '../test-helpers/test-config';
|
|
6
|
+
import {testConfig as testConfigImport} from '../test-helpers/test-config';
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* This object mandates that all existing future flags be activated for tests. If a new flag is added, this object must
|
|
10
|
+
* be updated to include it, which will also cause all tests to use the new behaviour by default (and likely fail).
|
|
11
|
+
*
|
|
12
|
+
* This way, we'll always ensure our tests are covering all future flags. Please make sure to also have tests for the
|
|
13
|
+
* old behaviour.
|
|
14
|
+
*/
|
|
15
|
+
const TEST_FUTURE_FLAGS: Required<{[key in keyof FutureFlags]: true}> = {
|
|
16
|
+
unstable_newEmbeddedAuthStrategy: true,
|
|
17
|
+
expiringOfflineAccessTokens: true,
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
// Override the helper's future flags and logger settings for our purposes
|
|
21
|
+
const TEST_CONFIG = {
|
|
22
|
+
apiVersion: ApiVersion.July25,
|
|
23
|
+
isEmbeddedApp: true,
|
|
24
|
+
future: TEST_FUTURE_FLAGS,
|
|
25
|
+
logger: {
|
|
26
|
+
log: jest.fn(),
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Reset the config object before each test
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
TEST_CONFIG.logger.log.mockReset();
|
|
33
|
+
(TEST_CONFIG as any).sessionStorage = new MemorySessionStorage();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export function testConfig<
|
|
37
|
+
Overrides extends TestOverridesArg,
|
|
38
|
+
Future extends FutureFlagOptions,
|
|
39
|
+
>(
|
|
40
|
+
{future, ...overrides}: Overrides & {future?: Future} = {} as Overrides & {
|
|
41
|
+
future?: Future;
|
|
42
|
+
},
|
|
43
|
+
) {
|
|
44
|
+
return testConfigImport({
|
|
45
|
+
...TEST_CONFIG,
|
|
46
|
+
...overrides,
|
|
47
|
+
future: {
|
|
48
|
+
...TEST_CONFIG.future,
|
|
49
|
+
...future,
|
|
50
|
+
},
|
|
51
|
+
logger: {
|
|
52
|
+
...TEST_CONFIG.logger,
|
|
53
|
+
...(overrides as NonNullable<Overrides>).logger,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {setAbstractRuntimeString} from '@shopify/shopify-api/runtime';
|
|
2
|
+
|
|
3
|
+
import {setAppBridgeUrlOverride} from '../../authenticate/helpers';
|
|
4
|
+
|
|
5
|
+
setAbstractRuntimeString(() => {
|
|
6
|
+
return `Remix (Node)`;
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
/* eslint-disable no-process-env */
|
|
10
|
+
if (process.env.APP_BRIDGE_URL) {
|
|
11
|
+
setAppBridgeUrlOverride(process.env.APP_BRIDGE_URL);
|
|
12
|
+
}
|
|
13
|
+
/* eslint-enable no-process-env */
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {setAbstractFetchFunc} from '@shopify/shopify-api/runtime';
|
|
2
|
+
import {installGlobals} from '@remix-run/node';
|
|
3
|
+
|
|
4
|
+
import '../node';
|
|
5
|
+
|
|
6
|
+
installGlobals();
|
|
7
|
+
|
|
8
|
+
const fetchFunc = fetch;
|
|
9
|
+
setAbstractFetchFunc(async (...args) => {
|
|
10
|
+
const response = await fetchFunc(...args);
|
|
11
|
+
|
|
12
|
+
return response as any;
|
|
13
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {type ActionFunctionArgs, json} from '@remix-run/node';
|
|
2
|
+
import {GraphqlQueryError} from '@shopify/shopify-api';
|
|
3
|
+
|
|
4
|
+
import {authenticate} from '../shopify.server';
|
|
5
|
+
|
|
6
|
+
export const action = async ({request}: ActionFunctionArgs) => {
|
|
7
|
+
const {admin, redirect} = await authenticate.admin(request);
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
await admin.graphql(
|
|
11
|
+
`#graphql
|
|
12
|
+
mutation updateProductTitle($input: ProductInput!) {
|
|
13
|
+
productUpdate(input: $input) {
|
|
14
|
+
product {
|
|
15
|
+
id
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}`,
|
|
19
|
+
{
|
|
20
|
+
variables: {
|
|
21
|
+
input: {id: '123', title: 'New title'},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
return redirect('/app/product-updated');
|
|
27
|
+
} catch (error) {
|
|
28
|
+
if (error instanceof GraphqlQueryError) {
|
|
29
|
+
return json({errors: error.body?.errors}, {status: 500});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return new Response('Failed to update product title', {status: 500});
|
|
33
|
+
}
|
|
34
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs';
|
|
2
|
+
|
|
3
|
+
const data: ReferenceEntityTemplateSchema = {
|
|
4
|
+
name: 'Admin',
|
|
5
|
+
description:
|
|
6
|
+
'Contains methods for authenticating and interacting with the Admin API.\n\nThis function can handle requests for apps embedded in the Admin, Admin extensions, or non-embedded apps.',
|
|
7
|
+
category: 'Authenticate',
|
|
8
|
+
type: 'object',
|
|
9
|
+
isVisualComponent: false,
|
|
10
|
+
definitions: [
|
|
11
|
+
{
|
|
12
|
+
title: 'authenticate.admin',
|
|
13
|
+
description:
|
|
14
|
+
'Authenticates requests coming from the Shopify admin.\n\nThe shape of the returned object changes depending on the `isEmbeddedApp` config.',
|
|
15
|
+
type: 'AuthenticateAdmin',
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
defaultExample: {
|
|
19
|
+
description: 'Authenticate, run API mutation, and redirect',
|
|
20
|
+
codeblock: {
|
|
21
|
+
title: 'Authenticate, run API mutation, and redirect',
|
|
22
|
+
tabs: [
|
|
23
|
+
{
|
|
24
|
+
title: '/app/routes/**.ts',
|
|
25
|
+
language: 'typescript',
|
|
26
|
+
code: './authenticate.admin.doc.example.ts',
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
jsDocTypeExamples: [
|
|
32
|
+
'EmbeddedAdminContext',
|
|
33
|
+
'AdminApiContext',
|
|
34
|
+
'BillingContext',
|
|
35
|
+
'ScopesApiContext',
|
|
36
|
+
],
|
|
37
|
+
related: [
|
|
38
|
+
{
|
|
39
|
+
name: 'API context',
|
|
40
|
+
subtitle: 'Interact with the Admin API.',
|
|
41
|
+
url: '/docs/api/shopify-app-remix/apis/admin-api',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'Billing context',
|
|
45
|
+
subtitle: 'Bill merchants for your app using the Admin API.',
|
|
46
|
+
url: '/docs/api/shopify-app-remix/apis/billing',
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default data;
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import {JwtPayload, Session} from '@shopify/shopify-api';
|
|
2
|
+
|
|
3
|
+
import type {BasicParams} from '../../types';
|
|
4
|
+
import type {AppConfigArg} from '../../config-types';
|
|
5
|
+
import {
|
|
6
|
+
getSessionTokenHeader,
|
|
7
|
+
ensureCORSHeadersFactory,
|
|
8
|
+
getSessionTokenFromUrlParam,
|
|
9
|
+
respondToBotRequest,
|
|
10
|
+
respondToOptionsRequest,
|
|
11
|
+
validateSessionToken,
|
|
12
|
+
getShopFromRequest,
|
|
13
|
+
} from '../helpers';
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
cancelBillingFactory,
|
|
17
|
+
requestBillingFactory,
|
|
18
|
+
requireBillingFactory,
|
|
19
|
+
checkBillingFactory,
|
|
20
|
+
createUsageRecordFactory,
|
|
21
|
+
updateUsageCappedAmountFactory,
|
|
22
|
+
} from './billing';
|
|
23
|
+
import type {
|
|
24
|
+
AdminContext,
|
|
25
|
+
AuthenticateAdmin,
|
|
26
|
+
EmbeddedAdminContext,
|
|
27
|
+
NonEmbeddedAdminContext,
|
|
28
|
+
} from './types';
|
|
29
|
+
import {
|
|
30
|
+
createAdminApiContext,
|
|
31
|
+
ensureAppIsEmbeddedIfRequired,
|
|
32
|
+
ensureSessionTokenSearchParamIfRequired,
|
|
33
|
+
redirectFactory,
|
|
34
|
+
renderAppBridge,
|
|
35
|
+
validateShopAndHostParams,
|
|
36
|
+
} from './helpers';
|
|
37
|
+
import {AuthorizationStrategy} from './strategies/types';
|
|
38
|
+
import {scopesApiFactory} from './scope/factory';
|
|
39
|
+
|
|
40
|
+
export interface SessionTokenContext {
|
|
41
|
+
shop: string;
|
|
42
|
+
sessionId?: string;
|
|
43
|
+
sessionToken?: string;
|
|
44
|
+
payload?: JwtPayload;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface AuthStrategyParams extends BasicParams {
|
|
48
|
+
strategy: AuthorizationStrategy;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function authStrategyFactory<ConfigArg extends AppConfigArg>({
|
|
52
|
+
strategy,
|
|
53
|
+
...params
|
|
54
|
+
}: AuthStrategyParams): AuthenticateAdmin<ConfigArg> {
|
|
55
|
+
const {api, logger, config} = params;
|
|
56
|
+
|
|
57
|
+
async function respondToBouncePageRequest(request: Request) {
|
|
58
|
+
const url = new URL(request.url);
|
|
59
|
+
|
|
60
|
+
if (url.pathname === config.auth.patchSessionTokenPath) {
|
|
61
|
+
logger.debug('Rendering bounce page', {
|
|
62
|
+
shop: getShopFromRequest(request),
|
|
63
|
+
});
|
|
64
|
+
throw renderAppBridge({config, logger, api}, request);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function respondToExitIframeRequest(request: Request) {
|
|
69
|
+
const url = new URL(request.url);
|
|
70
|
+
|
|
71
|
+
if (url.pathname === config.auth.exitIframePath) {
|
|
72
|
+
const destination = url.searchParams.get('exitIframe')!;
|
|
73
|
+
|
|
74
|
+
logger.debug('Rendering exit iframe page', {
|
|
75
|
+
shop: getShopFromRequest(request),
|
|
76
|
+
destination,
|
|
77
|
+
});
|
|
78
|
+
throw renderAppBridge({config, logger, api}, request, {url: destination});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
type AdminContextBase =
|
|
83
|
+
| EmbeddedAdminContext<ConfigArg>
|
|
84
|
+
| NonEmbeddedAdminContext<ConfigArg>;
|
|
85
|
+
|
|
86
|
+
function createContext(
|
|
87
|
+
request: Request,
|
|
88
|
+
session: Session,
|
|
89
|
+
authStrategy: AuthorizationStrategy,
|
|
90
|
+
sessionToken?: JwtPayload,
|
|
91
|
+
): AdminContext<ConfigArg> {
|
|
92
|
+
let context: AdminContextBase = {
|
|
93
|
+
admin: createAdminApiContext(
|
|
94
|
+
session,
|
|
95
|
+
params,
|
|
96
|
+
authStrategy.handleClientError(request),
|
|
97
|
+
),
|
|
98
|
+
billing: {
|
|
99
|
+
require: requireBillingFactory(params, request, session),
|
|
100
|
+
check: checkBillingFactory(params, request, session),
|
|
101
|
+
request: requestBillingFactory(params, request, session),
|
|
102
|
+
cancel: cancelBillingFactory(params, request, session),
|
|
103
|
+
createUsageRecord: createUsageRecordFactory(params, request, session),
|
|
104
|
+
updateUsageCappedAmount: updateUsageCappedAmountFactory(
|
|
105
|
+
params,
|
|
106
|
+
request,
|
|
107
|
+
session,
|
|
108
|
+
),
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
session,
|
|
112
|
+
cors: ensureCORSHeadersFactory(params, request),
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
context = addEmbeddedFeatures(context, request, session, sessionToken);
|
|
116
|
+
context = addScopesFeatures(context);
|
|
117
|
+
|
|
118
|
+
return context as AdminContext<ConfigArg>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function addEmbeddedFeatures(
|
|
122
|
+
context: AdminContextBase,
|
|
123
|
+
request: Request,
|
|
124
|
+
session: Session,
|
|
125
|
+
sessionToken?: JwtPayload,
|
|
126
|
+
) {
|
|
127
|
+
if (config.isEmbeddedApp) {
|
|
128
|
+
return {
|
|
129
|
+
...context,
|
|
130
|
+
sessionToken,
|
|
131
|
+
redirect: redirectFactory(params, request, session.shop),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return context;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function addScopesFeatures(context: AdminContextBase) {
|
|
138
|
+
return {
|
|
139
|
+
...context,
|
|
140
|
+
scopes: scopesApiFactory(params, context.session, context.admin),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return async function authenticateAdmin(request: Request) {
|
|
145
|
+
try {
|
|
146
|
+
respondToBotRequest(params, request);
|
|
147
|
+
respondToOptionsRequest(params, request);
|
|
148
|
+
await respondToBouncePageRequest(request);
|
|
149
|
+
await respondToExitIframeRequest(request);
|
|
150
|
+
await strategy.respondToOAuthRequests(request);
|
|
151
|
+
|
|
152
|
+
// If this is a valid request, but it doesn't have a session token header, this is a document request. We need to
|
|
153
|
+
// ensure we're embedded if needed and we have the information needed to load the session.
|
|
154
|
+
if (!getSessionTokenHeader(request)) {
|
|
155
|
+
validateShopAndHostParams(params, request);
|
|
156
|
+
await ensureAppIsEmbeddedIfRequired(params, request);
|
|
157
|
+
await ensureSessionTokenSearchParamIfRequired(params, request);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
logger.info('Authenticating admin request', {
|
|
161
|
+
shop: getShopFromRequest(request),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const {payload, shop, sessionId, sessionToken} =
|
|
165
|
+
await getSessionTokenContext(params, request);
|
|
166
|
+
|
|
167
|
+
logger.debug('Loading session from storage', {shop, sessionId});
|
|
168
|
+
const existingSession = sessionId
|
|
169
|
+
? await config.sessionStorage!.loadSession(sessionId)
|
|
170
|
+
: undefined;
|
|
171
|
+
|
|
172
|
+
const session = await strategy.authenticate(request, {
|
|
173
|
+
session: existingSession,
|
|
174
|
+
sessionToken,
|
|
175
|
+
shop,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return createContext(request, session, strategy, payload);
|
|
179
|
+
} catch (errorOrResponse) {
|
|
180
|
+
if (errorOrResponse instanceof Response) {
|
|
181
|
+
logger.debug('Authenticate returned a response', {
|
|
182
|
+
shop: getShopFromRequest(request),
|
|
183
|
+
});
|
|
184
|
+
ensureCORSHeadersFactory(params, request)(errorOrResponse);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
throw errorOrResponse;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function getSessionTokenContext(
|
|
193
|
+
params: BasicParams,
|
|
194
|
+
request: Request,
|
|
195
|
+
): Promise<SessionTokenContext> {
|
|
196
|
+
const {api, config, logger} = params;
|
|
197
|
+
|
|
198
|
+
const headerSessionToken = getSessionTokenHeader(request);
|
|
199
|
+
const searchParamSessionToken = getSessionTokenFromUrlParam(request);
|
|
200
|
+
const sessionToken = (headerSessionToken || searchParamSessionToken)!;
|
|
201
|
+
|
|
202
|
+
logger.debug('Attempting to authenticate session token', {
|
|
203
|
+
shop: getShopFromRequest(request),
|
|
204
|
+
sessionToken: JSON.stringify({
|
|
205
|
+
header: headerSessionToken,
|
|
206
|
+
search: searchParamSessionToken,
|
|
207
|
+
}),
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
if (config.isEmbeddedApp) {
|
|
211
|
+
const payload = await validateSessionToken(params, request, sessionToken);
|
|
212
|
+
const dest = new URL(payload.dest);
|
|
213
|
+
const shop = dest.hostname;
|
|
214
|
+
|
|
215
|
+
logger.debug('Session token is valid - authenticated', {shop, payload});
|
|
216
|
+
const sessionId = config.useOnlineTokens
|
|
217
|
+
? api.session.getJwtSessionId(shop, payload.sub)
|
|
218
|
+
: api.session.getOfflineId(shop);
|
|
219
|
+
|
|
220
|
+
return {shop, payload, sessionId, sessionToken};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const url = new URL(request.url);
|
|
224
|
+
const shop = url.searchParams.get('shop')!;
|
|
225
|
+
|
|
226
|
+
const sessionId = await api.session.getCurrentId({
|
|
227
|
+
isOnline: config.useOnlineTokens,
|
|
228
|
+
rawRequest: request,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
return {shop, sessionId, payload: undefined, sessionToken};
|
|
232
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs';
|
|
2
|
+
|
|
3
|
+
const data: ReferenceEntityTemplateSchema = {
|
|
4
|
+
name: 'Billing',
|
|
5
|
+
description:
|
|
6
|
+
'Contains function used to bill merchants for your app with the Billing API.\n\nThis object is returned on authenticated Admin requests.' +
|
|
7
|
+
'\n\n> Note: [Managed App Pricing](/docs/apps/launch/billing/managed-pricing) is now available. Define your app’s pricing plans directly in the Shopify Partner Dashboard, without needing to use the Billing API.',
|
|
8
|
+
category: 'APIs',
|
|
9
|
+
type: 'object',
|
|
10
|
+
isVisualComponent: false,
|
|
11
|
+
definitions: [
|
|
12
|
+
{
|
|
13
|
+
title: 'billing',
|
|
14
|
+
description:
|
|
15
|
+
'Provides utilities that apps can use to request billing for the app using the Admin API.',
|
|
16
|
+
type: 'BillingContext',
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
jsDocTypeExamples: ['BillingContext'],
|
|
20
|
+
related: [
|
|
21
|
+
{
|
|
22
|
+
name: 'Admin context',
|
|
23
|
+
subtitle: 'Authenticate requests from Shopify Admin.',
|
|
24
|
+
url: '/docs/api/shopify-app-remix/authenticate/admin',
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default data;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {HttpResponseError, Session} from '@shopify/shopify-api';
|
|
2
|
+
|
|
3
|
+
import type {BasicParams} from '../../../types';
|
|
4
|
+
import {redirectToAuthPage} from '../helpers';
|
|
5
|
+
import {invalidateAccessToken} from '../../helpers';
|
|
6
|
+
|
|
7
|
+
import type {CancelBillingOptions} from './types';
|
|
8
|
+
|
|
9
|
+
export function cancelBillingFactory(
|
|
10
|
+
params: BasicParams,
|
|
11
|
+
request: Request,
|
|
12
|
+
session: Session,
|
|
13
|
+
) {
|
|
14
|
+
return async function cancelBilling(options: CancelBillingOptions) {
|
|
15
|
+
const {api, logger} = params;
|
|
16
|
+
|
|
17
|
+
logger.debug('Cancelling billing', {shop: session.shop, ...options});
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
return await api.billing.cancel({
|
|
21
|
+
session,
|
|
22
|
+
subscriptionId: options.subscriptionId,
|
|
23
|
+
isTest: options.isTest,
|
|
24
|
+
prorate: options.prorate,
|
|
25
|
+
});
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (error instanceof HttpResponseError && error.response.code === 401) {
|
|
28
|
+
logger.debug('API token was invalid, redirecting to OAuth', {
|
|
29
|
+
shop: session.shop,
|
|
30
|
+
});
|
|
31
|
+
await invalidateAccessToken(params, session);
|
|
32
|
+
throw await redirectToAuthPage(params, request, session.shop);
|
|
33
|
+
} else {
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|