@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.
Files changed (206) hide show
  1. package/dist/cjs/react/components/AppProvider/AppProvider.js.map +1 -1
  2. package/dist/cjs/react/components/AppProxyLink/AppProxyLink.js.map +1 -1
  3. package/dist/cjs/server/authenticate/admin/strategies/auth-code-flow.js.map +1 -1
  4. package/dist/cjs/server/authenticate/admin/strategies/merchant-custom-app.js.map +1 -1
  5. package/dist/cjs/server/authenticate/admin/strategies/token-exchange.js.map +1 -1
  6. package/dist/cjs/server/authenticate/webhooks/authenticate.js +34 -10
  7. package/dist/cjs/server/authenticate/webhooks/authenticate.js.map +1 -1
  8. package/dist/cjs/server/version.js +1 -1
  9. package/dist/cjs/server/version.js.map +1 -1
  10. package/dist/esm/react/components/AppProvider/AppProvider.mjs.map +1 -1
  11. package/dist/esm/react/components/AppProxyLink/AppProxyLink.mjs.map +1 -1
  12. package/dist/esm/server/authenticate/admin/strategies/auth-code-flow.mjs.map +1 -1
  13. package/dist/esm/server/authenticate/admin/strategies/merchant-custom-app.mjs.map +1 -1
  14. package/dist/esm/server/authenticate/admin/strategies/token-exchange.mjs.map +1 -1
  15. package/dist/esm/server/authenticate/webhooks/authenticate.mjs +35 -11
  16. package/dist/esm/server/authenticate/webhooks/authenticate.mjs.map +1 -1
  17. package/dist/esm/server/version.mjs +1 -1
  18. package/dist/esm/server/version.mjs.map +1 -1
  19. package/dist/ts/react/components/AppProvider/AppProvider.d.ts.map +1 -1
  20. package/dist/ts/react/components/AppProxyLink/AppProxyLink.d.ts.map +1 -1
  21. package/dist/ts/server/authenticate/admin/billing/types.d.ts.map +1 -1
  22. package/dist/ts/server/authenticate/admin/strategies/auth-code-flow.d.ts.map +1 -1
  23. package/dist/ts/server/authenticate/admin/strategies/merchant-custom-app.d.ts.map +1 -1
  24. package/dist/ts/server/authenticate/admin/strategies/token-exchange.d.ts.map +1 -1
  25. package/dist/ts/server/authenticate/admin/types.d.ts.map +1 -1
  26. package/dist/ts/server/authenticate/public/extension/types.d.ts.map +1 -1
  27. package/dist/ts/server/authenticate/webhooks/authenticate.d.ts.map +1 -1
  28. package/dist/ts/server/authenticate/webhooks/types.d.ts +34 -2
  29. package/dist/ts/server/authenticate/webhooks/types.d.ts.map +1 -1
  30. package/dist/ts/server/config-types.d.ts.map +1 -1
  31. package/dist/ts/server/version.d.ts +1 -1
  32. package/dist/tsconfig.build.tsbuildinfo +1 -1
  33. package/package.json +18 -14
  34. package/src/react/components/AppProvider/AppProvider.doc.ts +35 -0
  35. package/src/react/components/AppProvider/AppProvider.tsx +147 -0
  36. package/src/react/components/AppProvider/index.ts +1 -0
  37. package/src/react/components/AppProxyForm/AppProxyForm.doc.ts +28 -0
  38. package/src/react/components/AppProxyForm/AppProxyForm.tsx +82 -0
  39. package/src/react/components/AppProxyForm/index.ts +1 -0
  40. package/src/react/components/AppProxyLink/AppProxyLink.doc.ts +28 -0
  41. package/src/react/components/AppProxyLink/AppProxyLink.tsx +58 -0
  42. package/src/react/components/AppProxyLink/index.ts +1 -0
  43. package/src/react/components/AppProxyProvider/AppProxyProvider.doc.ts +40 -0
  44. package/src/react/components/AppProxyProvider/AppProxyProvider.tsx +100 -0
  45. package/src/react/components/AppProxyProvider/index.ts +5 -0
  46. package/src/react/components/RemixPolarisLink.tsx +14 -0
  47. package/src/react/components/index.ts +4 -0
  48. package/src/react/const.ts +2 -0
  49. package/src/react/index.ts +1 -0
  50. package/src/server/__test-helpers/const.ts +14 -0
  51. package/src/server/__test-helpers/expect-admin-api-client.ts +51 -0
  52. package/src/server/__test-helpers/expect-begin-auth-redirect.ts +22 -0
  53. package/src/server/__test-helpers/expect-document-request-headers.ts +25 -0
  54. package/src/server/__test-helpers/expect-exit-iframe.ts +37 -0
  55. package/src/server/__test-helpers/expect-login-redirect.ts +8 -0
  56. package/src/server/__test-helpers/expect-storefront-api-client.ts +49 -0
  57. package/src/server/__test-helpers/expect-token-refresh.ts +188 -0
  58. package/src/server/__test-helpers/get-hmac.ts +7 -0
  59. package/src/server/__test-helpers/get-jwt.ts +10 -0
  60. package/src/server/__test-helpers/get-thrown-error.ts +19 -0
  61. package/src/server/__test-helpers/get-thrown-response.ts +17 -0
  62. package/src/server/__test-helpers/index.ts +21 -0
  63. package/src/server/__test-helpers/mock-graphql-request.ts +60 -0
  64. package/src/server/__test-helpers/request-mock.ts +169 -0
  65. package/src/server/__test-helpers/setup-embedded-flow.ts +30 -0
  66. package/src/server/__test-helpers/setup-fetch-flow.ts +29 -0
  67. package/src/server/__test-helpers/setup-non-embedded-flow.ts +31 -0
  68. package/src/server/__test-helpers/setup-valid-request.ts +78 -0
  69. package/src/server/__test-helpers/setup-valid-session.ts +28 -0
  70. package/src/server/__test-helpers/sign-request-cookie.ts +21 -0
  71. package/src/server/__test-helpers/test-config.ts +56 -0
  72. package/src/server/adapters/node/index.ts +13 -0
  73. package/src/server/adapters/vercel/index.ts +13 -0
  74. package/src/server/authenticate/admin/authenticate.admin.doc.example.ts +34 -0
  75. package/src/server/authenticate/admin/authenticate.admin.doc.ts +51 -0
  76. package/src/server/authenticate/admin/authenticate.ts +232 -0
  77. package/src/server/authenticate/admin/billing/authenticate.admin.billing.doc.ts +29 -0
  78. package/src/server/authenticate/admin/billing/cancel.ts +38 -0
  79. package/src/server/authenticate/admin/billing/check.ts +41 -0
  80. package/src/server/authenticate/admin/billing/create-usage-record.ts +36 -0
  81. package/src/server/authenticate/admin/billing/helpers.ts +42 -0
  82. package/src/server/authenticate/admin/billing/index.ts +6 -0
  83. package/src/server/authenticate/admin/billing/request.ts +64 -0
  84. package/src/server/authenticate/admin/billing/require.ts +58 -0
  85. package/src/server/authenticate/admin/billing/types.ts +613 -0
  86. package/src/server/authenticate/admin/billing/update-usage-subscription-capped-amount.ts +55 -0
  87. package/src/server/authenticate/admin/helpers/begin-auth.ts +17 -0
  88. package/src/server/authenticate/admin/helpers/create-admin-api-context.ts +20 -0
  89. package/src/server/authenticate/admin/helpers/ensure-app-is-embedded-if-required.ts +18 -0
  90. package/src/server/authenticate/admin/helpers/ensure-session-token-search-param-if-required.ts +25 -0
  91. package/src/server/authenticate/admin/helpers/handle-client-error.ts +45 -0
  92. package/src/server/authenticate/admin/helpers/index.ts +14 -0
  93. package/src/server/authenticate/admin/helpers/redirect-to-auth-page.ts +28 -0
  94. package/src/server/authenticate/admin/helpers/redirect-to-bounce-page.ts +23 -0
  95. package/src/server/authenticate/admin/helpers/redirect-to-install-page.ts +53 -0
  96. package/src/server/authenticate/admin/helpers/redirect-to-shopify-or-app-root.ts +21 -0
  97. package/src/server/authenticate/admin/helpers/redirect-with-app-bridge-headers.ts +13 -0
  98. package/src/server/authenticate/admin/helpers/redirect-with-exitiframe.ts +28 -0
  99. package/src/server/authenticate/admin/helpers/redirect.ts +164 -0
  100. package/src/server/authenticate/admin/helpers/render-app-bridge.ts +46 -0
  101. package/src/server/authenticate/admin/helpers/trigger-after-auth-hook.ts +29 -0
  102. package/src/server/authenticate/admin/helpers/validate-redirect-url.ts +67 -0
  103. package/src/server/authenticate/admin/helpers/validate-shop-and-host-params.ts +46 -0
  104. package/src/server/authenticate/admin/scope/authenticate.admin.scopes.doc.ts +28 -0
  105. package/src/server/authenticate/admin/scope/client/fetch-scopes-details.ts +43 -0
  106. package/src/server/authenticate/admin/scope/client/revoke-scopes.ts +38 -0
  107. package/src/server/authenticate/admin/scope/factory.ts +21 -0
  108. package/src/server/authenticate/admin/scope/query.ts +51 -0
  109. package/src/server/authenticate/admin/scope/request.ts +32 -0
  110. package/src/server/authenticate/admin/scope/revoke.ts +48 -0
  111. package/src/server/authenticate/admin/scope/types.ts +126 -0
  112. package/src/server/authenticate/admin/strategies/auth-code-flow.ts +332 -0
  113. package/src/server/authenticate/admin/strategies/merchant-custom-app.ts +65 -0
  114. package/src/server/authenticate/admin/strategies/token-exchange.ts +188 -0
  115. package/src/server/authenticate/admin/strategies/types.ts +29 -0
  116. package/src/server/authenticate/admin/types.ts +216 -0
  117. package/src/server/authenticate/const.ts +15 -0
  118. package/src/server/authenticate/flow/authenticate.flow.doc.example.ts +39 -0
  119. package/src/server/authenticate/flow/authenticate.flow.doc.ts +47 -0
  120. package/src/server/authenticate/flow/authenticate.ts +68 -0
  121. package/src/server/authenticate/flow/types.ts +85 -0
  122. package/src/server/authenticate/fulfillment-service/authenticate.fulfillment-service.doc.example.ts +52 -0
  123. package/src/server/authenticate/fulfillment-service/authenticate.fulfillment-service.doc.ts +48 -0
  124. package/src/server/authenticate/fulfillment-service/authenticate.ts +82 -0
  125. package/src/server/authenticate/fulfillment-service/types.ts +82 -0
  126. package/src/server/authenticate/helpers/add-response-headers.ts +43 -0
  127. package/src/server/authenticate/helpers/app-bridge-url.ts +10 -0
  128. package/src/server/authenticate/helpers/ensure-cors-headers.ts +38 -0
  129. package/src/server/authenticate/helpers/get-session-token-header.ts +11 -0
  130. package/src/server/authenticate/helpers/get-shop-from-request.ts +4 -0
  131. package/src/server/authenticate/helpers/idempotent-promise-handler.ts +45 -0
  132. package/src/server/authenticate/helpers/index.ts +10 -0
  133. package/src/server/authenticate/helpers/invalidate-access-token.ts +17 -0
  134. package/src/server/authenticate/helpers/reject-bot-request.ts +27 -0
  135. package/src/server/authenticate/helpers/respond-to-invalid-session-token.ts +28 -0
  136. package/src/server/authenticate/helpers/respond-to-options-request.ts +26 -0
  137. package/src/server/authenticate/helpers/validate-session-token.ts +40 -0
  138. package/src/server/authenticate/login/login.ts +53 -0
  139. package/src/server/authenticate/public/appProxy/authenticate.public.app-proxy.doc.example.ts +27 -0
  140. package/src/server/authenticate/public/appProxy/authenticate.public.app-proxy.doc.ts +55 -0
  141. package/src/server/authenticate/public/appProxy/authenticate.ts +150 -0
  142. package/src/server/authenticate/public/appProxy/types.ts +215 -0
  143. package/src/server/authenticate/public/checkout/authenticate.public.checkout.doc.example.offers.ts +21 -0
  144. package/src/server/authenticate/public/checkout/authenticate.public.checkout.doc.example.ts +17 -0
  145. package/src/server/authenticate/public/checkout/authenticate.public.checkout.doc.ts +49 -0
  146. package/src/server/authenticate/public/checkout/authenticate.ts +13 -0
  147. package/src/server/authenticate/public/checkout/types.ts +65 -0
  148. package/src/server/authenticate/public/customer-account/authenticate.public.customerAccount.doc.example.offers.ts +36 -0
  149. package/src/server/authenticate/public/customer-account/authenticate.public.customerAccount.doc.example.ts +18 -0
  150. package/src/server/authenticate/public/customer-account/authenticate.public.customerAccount.doc.ts +49 -0
  151. package/src/server/authenticate/public/customer-account/authenticate.ts +13 -0
  152. package/src/server/authenticate/public/customer-account/types.ts +65 -0
  153. package/src/server/authenticate/public/extension/authenticate.ts +54 -0
  154. package/src/server/authenticate/public/extension/types.ts +11 -0
  155. package/src/server/authenticate/public/factory.ts +24 -0
  156. package/src/server/authenticate/public/index.ts +1 -0
  157. package/src/server/authenticate/public/pos/authenticate.ts +8 -0
  158. package/src/server/authenticate/public/pos/types.ts +65 -0
  159. package/src/server/authenticate/public/types.ts +96 -0
  160. package/src/server/authenticate/webhooks/authenticate.ts +105 -0
  161. package/src/server/authenticate/webhooks/authenticate.webhooks.doc.example.ts +43 -0
  162. package/src/server/authenticate/webhooks/authenticate.webhooks.doc.ts +39 -0
  163. package/src/server/authenticate/webhooks/index.ts +1 -0
  164. package/src/server/authenticate/webhooks/register.ts +48 -0
  165. package/src/server/authenticate/webhooks/types.ts +266 -0
  166. package/src/server/boundary/error.tsx +14 -0
  167. package/src/server/boundary/headers.ts +15 -0
  168. package/src/server/boundary/index.ts +39 -0
  169. package/src/server/boundary/types.ts +4 -0
  170. package/src/server/clients/admin/authenticate.admin.api.doc.ts +34 -0
  171. package/src/server/clients/admin/factory.ts +22 -0
  172. package/src/server/clients/admin/graphql.ts +39 -0
  173. package/src/server/clients/admin/index.ts +2 -0
  174. package/src/server/clients/admin/types.ts +120 -0
  175. package/src/server/clients/index.ts +2 -0
  176. package/src/server/clients/storefront/authenticate.storefront.api.doc.ts +34 -0
  177. package/src/server/clients/storefront/factory.ts +32 -0
  178. package/src/server/clients/storefront/index.ts +2 -0
  179. package/src/server/clients/storefront/types.ts +78 -0
  180. package/src/server/clients/types.ts +47 -0
  181. package/src/server/config-types.ts +309 -0
  182. package/src/server/errors.ts +3 -0
  183. package/src/server/future/flags.ts +62 -0
  184. package/src/server/helpers/create-or-load-offline-session.ts +19 -0
  185. package/src/server/helpers/ensure-offline-token-is-not-expired.ts +32 -0
  186. package/src/server/helpers/ensure-valid-offline-session.ts +15 -0
  187. package/src/server/helpers/index.ts +4 -0
  188. package/src/server/helpers/refresh-token.ts +35 -0
  189. package/src/server/index.ts +24 -0
  190. package/src/server/override-logger.ts +42 -0
  191. package/src/server/shopify-app.doc.ts +45 -0
  192. package/src/server/shopify-app.ts +215 -0
  193. package/src/server/test-helpers/index.ts +1 -0
  194. package/src/server/test-helpers/test-config.ts +79 -0
  195. package/src/server/types-contexts.ts +44 -0
  196. package/src/server/types.ts +507 -0
  197. package/src/server/unauthenticated/admin/factory.ts +23 -0
  198. package/src/server/unauthenticated/admin/index.ts +1 -0
  199. package/src/server/unauthenticated/admin/types.ts +79 -0
  200. package/src/server/unauthenticated/admin/unauthenticated.admin.doc.ts +40 -0
  201. package/src/server/unauthenticated/storefront/factory.ts +28 -0
  202. package/src/server/unauthenticated/storefront/index.ts +1 -0
  203. package/src/server/unauthenticated/storefront/types.ts +105 -0
  204. package/src/server/unauthenticated/storefront/unauthenticated.storefront.doc.ts +40 -0
  205. package/src/server/unauthenticated/types.ts +65 -0
  206. package/src/server/version.ts +1 -0
@@ -0,0 +1,613 @@
1
+ import {
2
+ AppSubscription,
3
+ BillingCheckParams,
4
+ BillingCheckResponseObject,
5
+ BillingRequestParams,
6
+ UsageRecord,
7
+ } from '@shopify/shopify-api';
8
+
9
+ import type {AppConfigArg} from '../../../config-types';
10
+
11
+ export interface RequireBillingOptions<
12
+ Config extends AppConfigArg,
13
+ > extends Omit<BillingCheckParams, 'session' | 'plans' | 'returnObject'> {
14
+ /**
15
+ * The plans to check for. Must be one of the values defined in the `billing` config option.
16
+ */
17
+ plans: (keyof Config['billing'])[];
18
+ /**
19
+ * How to handle the request if the shop doesn't have an active payment for any plan.
20
+ */
21
+ onFailure: (error: any) => Promise<Response>;
22
+ }
23
+
24
+ export interface CheckBillingOptions<Config extends AppConfigArg> extends Omit<
25
+ BillingCheckParams,
26
+ 'session' | 'plans' | 'returnObject'
27
+ > {
28
+ /**
29
+ * The plans to check for. Must be one of the values defined in the `billing` config option.
30
+ */
31
+ plans?: (keyof Config['billing'])[];
32
+ }
33
+
34
+ export interface RequestBillingOptions<
35
+ Config extends AppConfigArg,
36
+ > extends Omit<BillingRequestParams, 'session' | 'plan' | 'returnObject'> {
37
+ /**
38
+ * The plan to request. Must be one of the values defined in the `billing` config option.
39
+ */
40
+ plan: keyof Config['billing'];
41
+ /**
42
+ * Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.
43
+ */
44
+ isTest?: boolean;
45
+ /**
46
+ * The URL to return to after the merchant approves the payment.
47
+ */
48
+ returnUrl?: string;
49
+ }
50
+
51
+ export interface CancelBillingOptions {
52
+ /**
53
+ * The ID of the subscription to cancel.
54
+ */
55
+ subscriptionId: string;
56
+ /**
57
+ * Whether to issue prorated credits for the unused portion of the app subscription. There will be a corresponding
58
+ * deduction (based on revenue share) to your Partner account. For example, if a $10.00 app subscription
59
+ * (with 0% revenue share) is cancelled and prorated half way through the billing cycle, then the merchant will be
60
+ * credited $5.00 and that amount will be deducted from your Partner account.
61
+ */
62
+ prorate?: boolean;
63
+ /**
64
+ * Whether to use the test mode. This prevents the credit card from being charged.
65
+ */
66
+ isTest?: boolean;
67
+ }
68
+
69
+ export interface CreateUsageRecordOptions {
70
+ /**
71
+ * The description of the app usage record.
72
+ */
73
+ description: string;
74
+ /**
75
+ * The price of the app usage record.
76
+ */
77
+ price: {
78
+ /**
79
+ * The amount to charge for this usage record.
80
+ */
81
+ amount: number;
82
+ /**
83
+ * The currency code for this usage record.
84
+ */
85
+ currencyCode: string;
86
+ };
87
+ /**
88
+ * Whether to use the test mode. This prevents the credit card from being charged.
89
+ */
90
+ isTest: boolean;
91
+ /*
92
+ * Defines the usage pricing plan the merchant is subscribed to.
93
+ */
94
+ subscriptionLineItemId?: string;
95
+ /*
96
+ * A unique key generated to avoid duplicate charges.
97
+ */
98
+ idempotencyKey?: string;
99
+ }
100
+
101
+ export interface UpdateUsageCappedAmountOptions {
102
+ /**
103
+ * The subscription line item ID to update.
104
+ */
105
+ subscriptionLineItemId: string;
106
+ /**
107
+ * The maximum charge for the usage billing plan.
108
+ */
109
+ cappedAmount: {
110
+ /**
111
+ * The amount to update.
112
+ */
113
+ amount: number;
114
+ /**
115
+ * The currency code to update.
116
+ */
117
+ currencyCode: string;
118
+ };
119
+ }
120
+
121
+ export interface BillingContext<Config extends AppConfigArg> {
122
+ /**
123
+ * Checks if the shop has an active payment for any plan defined in the `billing` config option.
124
+ *
125
+ * @returns A promise that resolves to an object containing the active purchases for the shop.
126
+ *
127
+ * @example
128
+ * <caption>Requesting billing right away.</caption>
129
+ * <description>Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.</description>
130
+ * ```ts
131
+ * // /app/routes/**\/*.ts
132
+ * import { LoaderFunctionArgs } from "@remix-run/node";
133
+ * import { authenticate, MONTHLY_PLAN } from "../shopify.server";
134
+ *
135
+ * export const loader = async ({ request }: LoaderFunctionArgs) => {
136
+ * const { billing } = await authenticate.admin(request);
137
+ * await billing.require({
138
+ * plans: [MONTHLY_PLAN],
139
+ * isTest: true,
140
+ * onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),
141
+ * });
142
+ *
143
+ * // App logic
144
+ * };
145
+ * ```
146
+ * ```ts
147
+ * // shopify.server.ts
148
+ * import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server";
149
+ *
150
+ * export const MONTHLY_PLAN = 'Monthly subscription';
151
+ *
152
+ * const shopify = shopifyApp({
153
+ * // ...etc
154
+ * billing: {
155
+ * [MONTHLY_PLAN]: {
156
+ * lineItems: [
157
+ * {
158
+ * amount: 5,
159
+ * currencyCode: 'USD',
160
+ * interval: BillingInterval.Every30Days,
161
+ * }
162
+ * ],
163
+ * },
164
+ * }
165
+ * });
166
+ * export default shopify;
167
+ * export const authenticate = shopify.authenticate;
168
+ * ```
169
+ *
170
+ * @example
171
+ * <caption>Redirect to a plan selection page.</caption>
172
+ * <description>When the app has multiple plans, create a page in your App that allows the merchant to select a plan. If a merchant does not have the required plan you can redirect them to page in your app to select one.</description>
173
+ * ```ts
174
+ * // /app/routes/**\/*.ts
175
+ * import { LoaderFunctionArgs, redirect } from "@remix-run/node";
176
+ * import { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from "../shopify.server";
177
+ *
178
+ * export const loader = async ({ request }: LoaderFunctionArgs) => {
179
+ * const { billing } = await authenticate.admin(request);
180
+ * const billingCheck = await billing.require({
181
+ * plans: [MONTHLY_PLAN, ANNUAL_PLAN],
182
+ * isTest: true,
183
+ * onFailure: () => redirect('/select-plan'),
184
+ * });
185
+ *
186
+ * const subscription = billingCheck.appSubscriptions[0];
187
+ * console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);
188
+ *
189
+ * // App logic
190
+ * };
191
+ * ```
192
+ * ```ts
193
+ * // shopify.server.ts
194
+ * import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server";
195
+ *
196
+ * export const MONTHLY_PLAN = 'Monthly subscription';
197
+ * export const ANNUAL_PLAN = 'Annual subscription';
198
+ *
199
+ * const shopify = shopifyApp({
200
+ * // ...etc
201
+ * billing: {
202
+ * [MONTHLY_PLAN]: {
203
+ * lineItems: [
204
+ * {
205
+ * amount: 5,
206
+ * currencyCode: 'USD',
207
+ * interval: BillingInterval.Every30Days,
208
+ * }
209
+ * ],
210
+ * },
211
+ * [ANNUAL_PLAN]: {
212
+ * lineItems: [
213
+ * {
214
+ * amount: 50,
215
+ * currencyCode: 'USD',
216
+ * interval: BillingInterval.Annual,
217
+ * }
218
+ * ],
219
+ * },
220
+ * }
221
+ * });
222
+ * export default shopify;
223
+ * export const authenticate = shopify.authenticate;
224
+ * ```
225
+ */
226
+ require: (
227
+ options: RequireBillingOptions<Config>,
228
+ ) => Promise<BillingCheckResponseObject>;
229
+
230
+ /**
231
+ * Checks if the shop has an active payment for any plan defined in the `billing` config option.
232
+ *
233
+ * @returns A promise that resolves to an object containing the active purchases for the shop.
234
+ *
235
+ * @example
236
+ * <caption>Check what billing plans a merchant is subscribed to.</caption>
237
+ * <description>Use billing.check if you want to determine which plans are in use. Unlike `require`, `check` does not
238
+ * throw an error if no active billing plans are present. </description>
239
+ * ```ts
240
+ * // /app/routes/**\/*.ts
241
+ * import { LoaderFunctionArgs } from "@remix-run/node";
242
+ * import { authenticate, MONTHLY_PLAN } from "../shopify.server";
243
+ *
244
+ * export const loader = async ({ request }: LoaderFunctionArgs) => {
245
+ * const { billing } = await authenticate.admin(request);
246
+ * const { hasActivePayment, appSubscriptions } = await billing.check({
247
+ * plans: [MONTHLY_PLAN],
248
+ * isTest: false,
249
+ * });
250
+ * console.log(hasActivePayment);
251
+ * console.log(appSubscriptions);
252
+ * };
253
+ * ```
254
+ * ```ts
255
+ * // shopify.server.ts
256
+ * import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server";
257
+ *
258
+ * export const MONTHLY_PLAN = 'Monthly subscription';
259
+ * export const ANNUAL_PLAN = 'Annual subscription';
260
+ *
261
+ * const shopify = shopifyApp({
262
+ * // ...etc
263
+ * billing: {
264
+ * [MONTHLY_PLAN]: {
265
+ * lineItems: [
266
+ * {
267
+ * amount: 5,
268
+ * currencyCode: 'USD',
269
+ * interval: BillingInterval.Every30Days,
270
+ * }
271
+ * ],
272
+ * },
273
+ * [ANNUAL_PLAN]: {
274
+ * lineItems: [
275
+ * {
276
+ * amount: 50,
277
+ * currencyCode: 'USD',
278
+ * interval: BillingInterval.Annual,
279
+ * }
280
+ * ],
281
+ * },
282
+ * }
283
+ * });
284
+ * export default shopify;
285
+ * export const authenticate = shopify.authenticate;
286
+ * ```
287
+ *
288
+ * @example
289
+ * <caption>Check for payments without filtering.</caption>
290
+ * <description>Use billing.check to see if any payments exist for the store, regardless of whether it's a test or
291
+ * matches one or more plans.</description>
292
+ * ```ts
293
+ * // /app/routes/**\/*.ts
294
+ * import { LoaderFunctionArgs } from "@remix-run/node";
295
+ * import { authenticate, MONTHLY_PLAN } from "../shopify.server";
296
+ *
297
+ * export const loader = async ({ request }: LoaderFunctionArgs) => {
298
+ * const { billing } = await authenticate.admin(request);
299
+ * const { hasActivePayment, appSubscriptions } = await billing.check();
300
+ * // This will be true if any payment is found
301
+ * console.log(hasActivePayment);
302
+ * console.log(appSubscriptions);
303
+ * };
304
+ * ```
305
+ */
306
+ check: <Options extends CheckBillingOptions<Config>>(
307
+ options?: Options,
308
+ ) => Promise<BillingCheckResponseObject>;
309
+
310
+ /**
311
+ * Requests payment for the plan.
312
+ *
313
+ * @returns Redirects to the confirmation URL for the payment.
314
+ *
315
+ * @example
316
+ * <caption>Using a custom return URL.</caption>
317
+ * <description>Change where the merchant is returned to after approving the purchase using the `returnUrl` option.</description>
318
+ * ```ts
319
+ * // /app/routes/**\/*.ts
320
+ * import { LoaderFunctionArgs } from "@remix-run/node";
321
+ * import { authenticate, MONTHLY_PLAN } from "../shopify.server";
322
+ *
323
+ * export const loader = async ({ request }: LoaderFunctionArgs) => {
324
+ * const { billing } = await authenticate.admin(request);
325
+ * await billing.require({
326
+ * plans: [MONTHLY_PLAN],
327
+ * onFailure: async () => billing.request({
328
+ * plan: MONTHLY_PLAN,
329
+ * isTest: true,
330
+ * returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',
331
+ * }),
332
+ * });
333
+ *
334
+ * // App logic
335
+ * };
336
+ * ```
337
+ * ```ts
338
+ * // shopify.server.ts
339
+ * import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server";
340
+ *
341
+ * export const MONTHLY_PLAN = 'Monthly subscription';
342
+ * export const ANNUAL_PLAN = 'Annual subscription';
343
+ *
344
+ * const shopify = shopifyApp({
345
+ * // ...etc
346
+ * billing: {
347
+ * [MONTHLY_PLAN]: {
348
+ * lineItems: [
349
+ * {
350
+ * amount: 5,
351
+ * currencyCode: 'USD',
352
+ * interval: BillingInterval.Every30Days,
353
+ * }
354
+ * ],
355
+ * },
356
+ * [ANNUAL_PLAN]: {
357
+ * lineItems: [
358
+ * {
359
+ * amount: 50,
360
+ * currencyCode: 'USD',
361
+ * interval: BillingInterval.Annual,
362
+ * }
363
+ * ],
364
+ * },
365
+ * }
366
+ * });
367
+ * export default shopify;
368
+ * export const authenticate = shopify.authenticate;
369
+ * ```
370
+ *
371
+ * @example
372
+ * <caption>Overriding plan settings.</caption>
373
+ * <description>Customize the plan for a merchant when requesting billing. Any fields from the plan can be overridden, as long as the billing interval for line items matches the config.</description>
374
+ * ```ts
375
+ * // /app/routes/**\/*.ts
376
+ * import { LoaderFunctionArgs } from "@remix-run/node";
377
+ * import { authenticate, MONTHLY_PLAN } from "../shopify.server";
378
+ *
379
+ * export const loader = async ({ request }: LoaderFunctionArgs) => {
380
+ * const { billing } = await authenticate.admin(request);
381
+ * await billing.require({
382
+ * plans: [MONTHLY_PLAN],
383
+ * onFailure: async () => billing.request({
384
+ * plan: MONTHLY_PLAN,
385
+ * isTest: true,
386
+ * trialDays: 14,
387
+ * lineItems: [
388
+ * {
389
+ * interval: BillingInterval.Every30Days,
390
+ * discount: { value: { percentage: 0.1 } },
391
+ * },
392
+ * ],
393
+ * }),
394
+ * });
395
+ *
396
+ * // App logic
397
+ * };
398
+ * ```
399
+ * ```ts
400
+ * // shopify.server.ts
401
+ * import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server";
402
+ *
403
+ * export const MONTHLY_PLAN = 'Monthly subscription';
404
+ * export const ANNUAL_PLAN = 'Annual subscription';
405
+ *
406
+ * const shopify = shopifyApp({
407
+ * // ...etc
408
+ * billing: {
409
+ * [MONTHLY_PLAN]: {
410
+ * lineItems: [
411
+ * {
412
+ * amount: 5,
413
+ * currencyCode: 'USD',
414
+ * interval: BillingInterval.Every30Days,
415
+ * }
416
+ * ],
417
+ * },
418
+ * [ANNUAL_PLAN]: {
419
+ * lineItems: [
420
+ * {
421
+ * amount: 50,
422
+ * currencyCode: 'USD',
423
+ * interval: BillingInterval.Annual,
424
+ * }
425
+ * ],
426
+ * },
427
+ * }
428
+ * });
429
+ * export default shopify;
430
+ * export const authenticate = shopify.authenticate;
431
+ * ```
432
+ */
433
+ request: (options: RequestBillingOptions<Config>) => Promise<never>;
434
+
435
+ /**
436
+ * Cancels an ongoing subscription, given its ID.
437
+ *
438
+ * @returns The cancelled subscription.
439
+ *
440
+ * @example
441
+ * <caption>Cancelling a subscription.</caption>
442
+ * <description>Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.</description>
443
+ * ```ts
444
+ * // /app/routes/cancel-subscription.ts
445
+ * import { LoaderFunctionArgs } from "@remix-run/node";
446
+ * import { authenticate, MONTHLY_PLAN } from "../shopify.server";
447
+ *
448
+ * export const loader = async ({ request }: LoaderFunctionArgs) => {
449
+ * const { billing } = await authenticate.admin(request);
450
+ * const billingCheck = await billing.require({
451
+ * plans: [MONTHLY_PLAN],
452
+ * onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),
453
+ * });
454
+ *
455
+ * const subscription = billingCheck.appSubscriptions[0];
456
+ * const cancelledSubscription = await billing.cancel({
457
+ * subscriptionId: subscription.id,
458
+ * isTest: true,
459
+ * prorate: true,
460
+ * });
461
+ *
462
+ * // App logic
463
+ * };
464
+ * ```
465
+ * ```ts
466
+ * // shopify.server.ts
467
+ * import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server";
468
+ *
469
+ * export const MONTHLY_PLAN = 'Monthly subscription';
470
+ * export const ANNUAL_PLAN = 'Annual subscription';
471
+ *
472
+ * const shopify = shopifyApp({
473
+ * // ...etc
474
+ * billing: {
475
+ * [MONTHLY_PLAN]: {
476
+ * lineItems: [
477
+ * {
478
+ * amount: 5,
479
+ * currencyCode: 'USD',
480
+ * interval: BillingInterval.Every30Days,
481
+ * }
482
+ * ],
483
+ * },
484
+ * [ANNUAL_PLAN]: {
485
+ * lineItems: [
486
+ * {
487
+ * amount: 50,
488
+ * currencyCode: 'USD',
489
+ * interval: BillingInterval.Annual,
490
+ * }
491
+ * ],
492
+ * },
493
+ * }
494
+ * });
495
+ * export default shopify;
496
+ * export const authenticate = shopify.authenticate;
497
+ * ```
498
+ */
499
+ cancel: (options: CancelBillingOptions) => Promise<AppSubscription>;
500
+
501
+ /**
502
+ * Creates a usage record for an app subscription.
503
+ *
504
+ * @returns Returns a usage record when one was created successfully.
505
+ *
506
+ * @example
507
+ * <caption>Creating a usage record</caption>
508
+ * <description>Create a usage record for the active usage billing plan</description>
509
+ * ```ts
510
+ * // /app/routes/create-usage-record.ts
511
+ * import { ActionFunctionArgs } from "@remix-run/node";
512
+ * import { authenticate, MONTHLY_PLAN } from "../shopify.server";
513
+ *
514
+ * export const action = async ({ request }: ActionFunctionArgs) => {
515
+ * const { billing } = await authenticate.admin(request);
516
+ *
517
+ * const chargeBilling = await billing.createUsageRecord({
518
+ * description: "Usage record for product creation",
519
+ * price: {
520
+ * amount: 1,
521
+ * currencyCode: "USD",
522
+ * },
523
+ * isTest: true,
524
+ * });
525
+ * console.log(chargeBilling);
526
+ *
527
+ * // App logic
528
+ * };
529
+ * ```
530
+ * ```ts
531
+ * // shopify.server.ts
532
+ * import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server";
533
+ *
534
+ * export const USAGE_PLAN = 'Usage subscription';
535
+ *
536
+ * const shopify = shopifyApp({
537
+ * // ...etc
538
+ * billing: {
539
+ * [USAGE_PLAN]: {
540
+ * lineItems: [
541
+ * {
542
+ * amount: 5,
543
+ * currencyCode: 'USD',
544
+ * interval: BillingInterval.Usage,
545
+ * }
546
+ * ],
547
+ * },
548
+ * }
549
+ * });
550
+ * export default shopify;
551
+ * export const authenticate = shopify.authenticate;
552
+ * ```
553
+ */
554
+ createUsageRecord: (
555
+ options: CreateUsageRecordOptions,
556
+ ) => Promise<UsageRecord>;
557
+
558
+ /**
559
+ * Updates the capped amount for a usage billing plan.
560
+ *
561
+ * @returns Redirects to a confirmation page to update the usage billing plan.
562
+ *
563
+ * @example
564
+ * <caption>Updating the capped amount for a usage billing plan.</caption>
565
+ * <description>Update the capped amount for the usage billing plan specified by `subscriptionLineItemId`.</description>
566
+ * ```ts
567
+ * // /app/routes/**\/*.ts
568
+ * import { ActionFunctionArgs } from "@remix-run/node";
569
+ * import { authenticate } from "../shopify.server";
570
+ *
571
+ * export const action = async ({ request }: ActionFunctionArgs) => {
572
+ * const { billing } = await authenticate.admin(request);
573
+ *
574
+ * await billing.updateUsageCappedAmount({
575
+ * subscriptionLineItemId: "gid://shopify/AppSubscriptionLineItem/12345?v=1&index=1",
576
+ * cappedAmount: {
577
+ * amount: 10,
578
+ * currencyCode: "USD"
579
+ * },
580
+ * });
581
+ *
582
+ * // App logic
583
+ * };
584
+ * ```
585
+ * ```ts
586
+ * // shopify.server.ts
587
+ * import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server";
588
+ *
589
+ * export const USAGE_PLAN = 'Usage subscription';
590
+ *
591
+ * const shopify = shopifyApp({
592
+ * // ...etc
593
+ * billing: {
594
+ * [USAGE_PLAN]: {
595
+ * lineItems: [
596
+ * {
597
+ * amount: 5,
598
+ * currencyCode: 'USD',
599
+ * interval: BillingInterval.Usage,
600
+ * terms: "Usage based"
601
+ * }
602
+ * ],
603
+ * },
604
+ * }
605
+ * });
606
+ * export default shopify;
607
+ * export const authenticate = shopify.authenticate;
608
+ * ```
609
+ */
610
+ updateUsageCappedAmount: (
611
+ options: UpdateUsageCappedAmountOptions,
612
+ ) => Promise<never>;
613
+ }
@@ -0,0 +1,55 @@
1
+ import {
2
+ HttpResponseError,
3
+ Session,
4
+ UpdateCappedAmountConfirmation,
5
+ } from '@shopify/shopify-api';
6
+
7
+ import type {BasicParams} from '../../../types';
8
+ import {redirectToAuthPage} from '../helpers';
9
+ import {invalidateAccessToken} from '../../helpers';
10
+
11
+ import {UpdateUsageCappedAmountOptions} from './types';
12
+ import {redirectOutOfApp} from './helpers';
13
+
14
+ export function updateUsageCappedAmountFactory(
15
+ params: BasicParams,
16
+ request: Request,
17
+ session: Session,
18
+ ) {
19
+ return async function updateUsageCappedAmount(
20
+ options: UpdateUsageCappedAmountOptions,
21
+ ): Promise<never> {
22
+ const {api, logger} = params;
23
+
24
+ logger.debug('Updating usage subscription capped amount', {
25
+ shop: session.shop,
26
+ ...options,
27
+ });
28
+
29
+ let result: UpdateCappedAmountConfirmation;
30
+ try {
31
+ result = await api.billing.updateUsageCappedAmount({
32
+ session,
33
+ subscriptionLineItemId: options.subscriptionLineItemId,
34
+ cappedAmount: options.cappedAmount,
35
+ });
36
+ } catch (error) {
37
+ if (error instanceof HttpResponseError && error.response.code === 401) {
38
+ logger.debug('API token was invalid, redirecting to OAuth', {
39
+ shop: session.shop,
40
+ });
41
+ await invalidateAccessToken(params, session);
42
+ throw await redirectToAuthPage(params, request, session.shop);
43
+ } else {
44
+ throw error;
45
+ }
46
+ }
47
+
48
+ throw redirectOutOfApp(
49
+ params,
50
+ request,
51
+ result.confirmationUrl,
52
+ session.shop,
53
+ );
54
+ };
55
+ }
@@ -0,0 +1,17 @@
1
+ import type {BasicParams} from '../../../types';
2
+
3
+ export async function beginAuth(
4
+ params: BasicParams,
5
+ request: Request,
6
+ isOnline: boolean,
7
+ shop: string,
8
+ ): Promise<never> {
9
+ const {api, config} = params;
10
+
11
+ throw await api.auth.begin({
12
+ shop,
13
+ callbackPath: config.auth.callbackPath,
14
+ isOnline,
15
+ rawRequest: request,
16
+ });
17
+ }
@@ -0,0 +1,20 @@
1
+ import {Session} from '@shopify/shopify-api';
2
+
3
+ import type {BasicParams} from '../../../types';
4
+ import {
5
+ AdminApiContext,
6
+ HandleAdminClientError,
7
+ adminClientFactory,
8
+ } from '../../../clients/admin';
9
+
10
+ export function createAdminApiContext(
11
+ session: Session,
12
+ params: BasicParams,
13
+ handleClientError: HandleAdminClientError,
14
+ ): AdminApiContext {
15
+ return adminClientFactory({
16
+ session,
17
+ params,
18
+ handleClientError,
19
+ });
20
+ }