Skip to content

Commit

Permalink
Add billing api helper to remix package for updating a usage billing …
Browse files Browse the repository at this point in the history
…plan's capped amount (#1691)

* initial commit: remix redirect wrapper and test

* run pnpm build-docs

* add changeset

* change SUBSCRIPTION_LINE_ITEM_ID example to real example + pnpm build-docs
  • Loading branch information
andy-liuu authored Oct 28, 2024
1 parent 4f236d0 commit 9b217e5
Show file tree
Hide file tree
Showing 11 changed files with 775 additions and 53 deletions.
19 changes: 19 additions & 0 deletions .changeset/slow-gorillas-judge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
'@shopify/shopify-app-remix': minor
---

Adds API to update the capped amount for a usage billing plan.

A new billing helper function has been added to update the capped amount for a usage billing plan. This function redirects to a confirmation page where the merchant can confirm the update.

```ts
await billing.updateUsageCappedAmount({
subscriptionLineItemId: "gid://shopify/AppSubscriptionLineItem/12345?v=1&index=1",
cappedAmount: {
amount: 10,
currencyCode: "USD"
},
});
```

Learn more about [App Billing](https://shopify.dev/docs/apps/launch/billing/subscription-billing).

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -568,15 +568,15 @@
{
"id": "guide-webhooks",
"title": "Subscribing to webhooks",
"description": "Your app must respond to [mandatory webhook topics](/docs/apps/webhooks/configuration/mandatory-webhooks). In addition, your app can register [optional webhook topics](/docs/api/admin-rest/current/resources/webhook#event-topics).",
"description": "Your app must respond to [mandatory webhook topics](/docs/apps/webhooks/configuration/mandatory-webhooks). In addition, your app can register [optional webhook topics](/docs/api/admin-rest/current/resources/webhook#event-topics).\n\nThere are app-specific and shop-specific webhooks. We recommend app-specific webhooks for most apps, but there are reasons to register shop-specific webhooks. For more information, please read [App-specific vs shop-specific webhooks](https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-vs-shop-specific-subscriptions).",
"sections": [
{
"type": "Generic",
"anchorLink": "config",
"title": "Subscribe using the app configuration file",
"sectionContent": "The easiest and recommended way to configure your webhooks is to edit your app configuration file. You can find more info in the [webhooks documentation](/docs/apps/webhooks/getting-started-declarative).\n\nTo set up a simple HTTPS webhook subscription, you can follow these steps:\n1. Add in `shopify.app.toml` the topic you want to subscribe to. In this example we subscribe to the `APP_UNINSTALLED` topic.\n1. Review the required scopes for the webhook topics, and update your [app scopes](/docs/apps/tools/cli/configuration#access_scopes) as necessary.\n1. Run `shopify app deploy` from the CLI to save your webhooks configuration.",
"title": "App-specific webhooks (recommended)",
"sectionContent": "The easiest way to configure webhooks is to use app-specific webhooks in `shopify.app.toml`. You can find more info in the [webhooks documentation](/docs/apps/webhooks/getting-started-declarative).\n\nTo set up a simple HTTPS webhook subscription, you can follow these steps:\n1. Add the topic to subscribe to in `shopify.app.toml`. In this example we subscribe to the `APP_UNINSTALLED` topic.\n1. Review the required scopes for the webhook topics, and update your [app scopes](/docs/apps/tools/cli/configuration#access_scopes) as necessary.\n1. Run `shopify app deploy` from the CLI to save your webhooks configuration.",
"codeblock": {
"title": "Configure webhooks subscriptions with the app configuration",
"title": "Configure app-specific webhooks",
"tabs": [
{
"title": "shopify.app.toml",
Expand All @@ -589,14 +589,14 @@
{
"type": "Generic",
"anchorLink": "config",
"title": "Subscribe using the API",
"sectionContent": "Configure `shopifyApp` and setup webhook subscription with the following steps:\n1. The webhooks you want to subscribe to. In this example we subscribe to the `APP_UNINSTALLED` topic.\n1. The code to register the `APP_UNINSTALLED` topic after a merchant installs you app. Here `shopifyApp` provides an `afterAuth` hook.\n1. Review the required scopes for the webhook topics, and update your [app scopes](/docs/apps/tools/cli/configuration#access_scopes) as necessary.\n\n> Note: You can't register mandatory topics using this package, you must [configure those in the Partner Dashboard](/docs/apps/webhooks/configuration/mandatory-webhooks) instead.",
"title": "Shop-specific webhooks",
"sectionContent": "Shop-specific webhooks are useful when you need to subscribe to different webhook topics for different shops, or when a topic is not supported by app-specific webhooks.Configure `shopifyApp` and to setup shop-specific webhook subscriptions with the following steps:\n1. The webhooks you want to subscribe to. In this example we subscribe to the `APP_UNINSTALLED` topic.\n1. The code to register the `APP_UNINSTALLED` topic after a merchant installs you app. Here `shopifyApp` provides an `afterAuth` hook.\n1. Review the required scopes for the webhook topics, and update your [app scopes](/docs/apps/tools/cli/configuration#access_scopes) as necessary.\n\n> Note: You can't register mandatory topics using this package, you must [configure those in the Partner Dashboard](/docs/apps/webhooks/configuration/mandatory-webhooks) instead.",
"codeblock": {
"title": "Configure webhooks subscriptions with the API",
"title": "Configure shop-specific webhooks",
"tabs": [
{
"title": "/app/shopify.server.ts",
"code": "import {shopifyApp, DeliveryMethod} from '@shopify/shopify-app-remix/server';\n\nconst shopify = shopifyApp({\n apiKey: 'abcde1234567890',\n // ...etc\n webhooks: {\n APP_UNINSTALLED: {\n deliveryMethod: DeliveryMethod.Http,\n callbackUrl: '/webhooks',\n },\n },\n hooks: {\n afterAuth: async ({session}) => {\n shopify.registerWebhooks({session});\n },\n },\n});\n\nexport const authenticate = shopify.authenticate;\n",
"code": "import {shopifyApp, DeliveryMethod} from '@shopify/shopify-app-remix/server';\n\nconst shopify = shopifyApp({\n apiKey: 'abcde1234567890',\n // ...etc\n webhooks: {\n APP_UNINSTALLED: {\n deliveryMethod: DeliveryMethod.Http,\n callbackUrl: '/webhooks',\n },\n },\n hooks: {\n afterAuth: async ({session}) => {\n // Register webhooks for the shop\n // In this example, every shop will have these webhooks\n // You could wrap this in some custom shop specific conditional logic if needed\n shopify.registerWebhooks({session});\n },\n },\n});\n\nexport const authenticate = shopify.authenticate;\n",
"language": "tsx"
}
]
Expand All @@ -612,7 +612,7 @@
"tabs": [
{
"title": "/app/routes/webhooks.tsx",
"code": "import {ActionFunctionArgs} from '@remix-run/node';\n\nimport db from '../db.server';\n\nimport {authenticate} from '~/shopify.server';\n\nexport const action = async ({request}: ActionFunctionArgs) => {\n const {topic, shop, session} = await authenticate.webhook(request);\n\n switch (topic) {\n case 'APP_UNINSTALLED':\n if (session) {\n await db.session.deleteMany({where: {shop}});\n }\n break;\n case 'CUSTOMERS_DATA_REQUEST':\n case 'CUSTOMERS_REDACT':\n case 'SHOP_REDACT':\n default:\n throw new Response('Unhandled webhook topic', {status: 404});\n }\n\n throw new Response();\n};\n",
"code": "import {ActionFunctionArgs} from '@remix-run/node';\n\nimport db from '../db.server';\n\nimport {authenticate} from '~/shopify.server';\n\nexport const action = async ({request}: ActionFunctionArgs) => {\n const {topic, shop, session} = await authenticate.webhook(request);\n\n switch (topic) {\n case 'APP_UNINSTALLED':\n // Webhook requests can trigger after an app is uninstalled\n // If the app is already uninstalled, the session may be undefined.\n if (session) {\n await db.session.deleteMany({where: {shop}});\n }\n break;\n case 'CUSTOMERS_DATA_REQUEST':\n case 'CUSTOMERS_REDACT':\n case 'SHOP_REDACT':\n default:\n throw new Response('Unhandled webhook topic', {status: 404});\n }\n\n throw new Response();\n};\n",
"language": "tsx"
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
requireBillingFactory,
checkBillingFactory,
createUsageRecordFactory,
updateUsageCappedAmountFactory,
} from './billing';
import type {
AdminContext,
Expand Down Expand Up @@ -97,6 +98,11 @@ export function authStrategyFactory<
request: requestBillingFactory(params, request, session),
cancel: cancelBillingFactory(params, request, session),
createUsageRecord: createUsageRecordFactory(params, request, session),
updateUsageCappedAmount: updateUsageCappedAmountFactory(
params,
request,
session,
),
},

session,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,36 @@ export const USAGE_RECORD = {
},
},
};

export const UPDATE_USAGE_CAPPED_AMOUNT_SUBSCRIPTION_ID = 'gid://123';
export const APP_SUBSCRIPTION_LINE_ITEM_UPDATE_PAYLOAD = {
userErrors: [],
confirmationUrl: CONFIRMATION_URL,
appSubscription: {
id: UPDATE_USAGE_CAPPED_AMOUNT_SUBSCRIPTION_ID,
name: PLAN_1,
test: true,
status: 'ACTIVE',
},
};

export const UPDATE_CAPPED_AMOUNT_CONFIRMATION_RESPONSE = {
...APP_SUBSCRIPTION_LINE_ITEM_UPDATE_PAYLOAD,
userErrors: undefined,
};

export const USAGE_SUBSRIPTION_CAPPED_AMOUNT_UPDATE_RESPONSE = JSON.stringify({
data: {
appSubscriptionLineItemUpdate: APP_SUBSCRIPTION_LINE_ITEM_UPDATE_PAYLOAD,
},
});

export const USAGE_SUBSRIPTION_CAPPED_AMOUNT_UPDATE_RESPONSE_WITH_USER_ERRORS =
JSON.stringify({
data: {
appSubscriptionLineItemUpdate: {
...APP_SUBSCRIPTION_LINE_ITEM_UPDATE_PAYLOAD,
userErrors: ['Oops, something went wrong'],
},
},
});
Loading

0 comments on commit 9b217e5

Please sign in to comment.