diff --git a/.github/ISSUE_TEMPLATE/policies-doc.md b/.github/ISSUE_TEMPLATE/policies-doc.md index df994872..dace6984 100644 --- a/.github/ISSUE_TEMPLATE/policies-doc.md +++ b/.github/ISSUE_TEMPLATE/policies-doc.md @@ -1,10 +1,9 @@ --- name: Policies Doc about: File a bug or suggestion for a policy -title: '' -labels: '' -assignees: '' - +title: "" +labels: "" +assignees: "" --- diff --git a/.github/workflows/static.yaml b/.github/workflows/static.yaml index f424468c..139df93f 100644 --- a/.github/workflows/static.yaml +++ b/.github/workflows/static.yaml @@ -1,9 +1,6 @@ name: Static Assets on: push: - branches: - - main - - nextjs_v2 jobs: policies: diff --git a/cspell.json b/cspell.json index a5eb56aa..6261e569 100644 --- a/cspell.json +++ b/cspell.json @@ -4,7 +4,7 @@ // language - current active spelling language "language": "en", // words - list of words to be always considered correct - "words": ["Kubernetes", "Linkerd", "Quickstart", "quickstarts"], + "words": ["Kubernetes", "Linkerd", "Quickstart", "quickstarts", "Zuplo"], // flagWords - list of words to be always considered incorrect // This is useful for offensive words and common spelling errors. // For example "hte" should be "the" diff --git a/docs/articles/background-dispatcher.md b/docs/articles/background-dispatcher.md index d9b725ff..489c37f3 100644 --- a/docs/articles/background-dispatcher.md +++ b/docs/articles/background-dispatcher.md @@ -87,7 +87,7 @@ The `options.msDelay` parameter is required and must be a valid non-zero number. ```ts const backgroundDispatcher = new BackgroundDispatcher( dispatchFunction, - { msDelay: 100 } + { msDelay: 100 }, ); ``` diff --git a/docs/articles/hooks.md b/docs/articles/hooks.md index 93e2958e..14ec82e3 100644 --- a/docs/articles/hooks.md +++ b/docs/articles/hooks.md @@ -29,7 +29,7 @@ import { ZuploContext, ZuploRequest } from "@zuplo/runtime"; export async function tracingPlugin( request: ZuploRequest, context: ZuploContext, - policyName: string + policyName: string, ) { // Get the trace header let traceparent = request.headers.get("traceparent"); @@ -71,7 +71,7 @@ import { ZuploContext, ZuploRequest } from "@zuplo/runtime"; export async function pluginWithHook( request: ZuploRequest, context: ZuploContext, - policyName: string + policyName: string, ) { const cloned = request.clone(); context.addResponseSendingFinalHook( @@ -81,7 +81,7 @@ export async function pluginWithHook( method: "GET", body, }); - } + }, ); return request; diff --git a/docs/articles/monetization-dev-portal-setup.md b/docs/articles/monetization-dev-portal-setup.md new file mode 100644 index 00000000..935db6b9 --- /dev/null +++ b/docs/articles/monetization-dev-portal-setup.md @@ -0,0 +1,120 @@ +--- +title: Step 1 - Set Up Developer Portal +sidebar_label: Step 1 - Set Up Developer Portal +--- + +This guide will walk you through the steps required to set up your Zuplo API's +Developer Portal. This is where your customers will see and subscribe to your +API plans. + +If you don't already have a Zuplo API, you can create a new project using the +"ToDo" sample or following step 1 of the +[Getting Started quickstart](./step-1-setup-basic-gateway.md). + +You'll also need a Stripe account. If you don't already have one, you'll need to +[register](https://dashboard.stripe.com/register) for one. + +::: tip + +You don't need to go through the full set up process in Stripe. You can follow +this tutorial using Stripe's test mode. We recommend using +[Test Mode](https://docs.stripe.com/test-mode) on your Stripe account. This will +allow you to test creating subscriptions with different plans without using real +money. + +::: + +Once you have a working Zuplo API and a Stripe account, we can move forward! + +## 1/ Set Up in Stripe + +You need to have a couple things set up in Stripe before enabling Zuplo +monetization. + +Your API customers will subscribe to a "Plan" in Zuplo. Plans are associated +with [Stripe Products](https://docs.stripe.com/products-prices/getting-started). +The Stripe Product is what determines the price your customers will pay to use +your API. + +![](https://cdn.zuplo.com/assets/aa34975a-2906-4869-9dd6-bd13e5b0dcda.png) + +The "Plan" in Zuplo is where you will define the limits of each plan. For +example, the "Basic" plan might be limited to 10,000 requests per month while +the "Premium" plan may be limited to 1,000,000 requests per month. + +### Creating Stripe Products + +When you create your products in Stripe, set each product as **Recurring** on a +**Monthly** billing cycle. + +![](https://cdn.zuplo.com/assets/0b7bc4e5-9e92-4b24-a4d5-16fe389bec8f.png) + +### Creating a Stripe Pricing Table + +Once you've created your products, you will need to create a Stripe Pricing +table. Follow +[Stripe's guide for creating a Pricing Table](https://docs.stripe.com/payments/checkout/pricing-table). + +## 2/ Set Up in Zuplo Portal + +In order for your Zuplo API to connect to Stripe, you'll need to set your Stripe +API Key as an environment variable. + +1. In Stripe, navigate to the + [API keys section](https://dashboard.stripe.com/apikeys) of Stripe's + developer dashboard. Copy the value of the **Secret Key**. +2. Return to the Zuplo Portal, go to your project and open the **Environment + Variables** section in the **Settings** tab. +3. Click **Add Variable** and name your new environment variable + `STRIPE_SECRET_KEY`. +4. Set this variable as a **secret** and paste the Stripe API Key as the value. + Click **Save**. + +![Save Environment Variable](../../public/media/monetization-dev-portal-setup/image-1a.png) + +### Configure Your Developer Portal + +Next, you will enable the monetization pages in your Developer Portal. + +1. In your Zuplo project, go to the **Code** tab and select the file + `dev-portal.json`. +2. Scroll to the **Monetization Settings** section and select the checkbox + **Enable Monetization**. +3. The values for the fields **Pricing Table ID** and **Publishable Key** can be + found by opening the pricing table in the Stripe Dashboard + [Pricing Table](https://dashboard.stripe.com/pricing-tables) section. +4. Copy the value for `pricing-table-id` (it starts with `prctbl_`) and set it + as the value for **Pricing Table ID** in the form in the Zuplo Portal. +5. Copy the value for `publishable-key` (it starts with `pk_`) and set it as the + value for **Publishable Key** in the form in the Zuplo Portal. + +![Pricing Table](../../public/media/monetization-dev-portal-setup/image-2a.png) + +6. Finally, click the icon next to the **Secret + Key** field and select the environment variable `STRIPE_SECRET_KEY` you + created earlier. This will prefill the form with the value + `$env(STRIPE_SECRET_KEY)`. +7. Click save to publish your changes. + +## 3/ Preview Your Developer Portal + +Now that your Developer Portal is configured for monetization you can open it +and view the pricing page. Click the toolbar on the bottom of the Zuplo Portal +to find the URL of your Developer Portal. + +![Dev Portal Link](../../public/media/monetization-dev-portal-setup/image-3a.png) + +Once you open your Developer Portal, you will find a link to the **Pricing** +page on the top right menu section. Open the **Pricing** page in your Developer +Portal to see the pricing table that you have configured as the **Pricing Table +ID** in the previous step. + +![Pricing Table](../../public/media/monetization-dev-portal-setup/image.png) + +:::caution + +Don't go through the process of subscribing to an API plan yet. In the next step +we will configure the Stripe Webhook that will tell your API that a subscription +was created. + +::: diff --git a/docs/articles/monetization-glossary.md b/docs/articles/monetization-glossary.md new file mode 100644 index 00000000..008c6c2c --- /dev/null +++ b/docs/articles/monetization-glossary.md @@ -0,0 +1,4 @@ +--- +title: Monetization Glossary +sidebar_label: Glossary +--- diff --git a/docs/articles/monetization-policy-setup.md b/docs/articles/monetization-policy-setup.md new file mode 100644 index 00000000..e110ee7a --- /dev/null +++ b/docs/articles/monetization-policy-setup.md @@ -0,0 +1,103 @@ +--- +title: Step 3 - Configure Monetization Policy +sidebar_label: Step 3 - Configure Monetization Policy +--- + +In the previous steps you learned how to set up Stripe and configure your +Developer Portal for monetization. In this step, you will configure your Zuplo +API to ensure that users are allowed to use your API according to the plan they +subscribe to. + +We will configure this using Zuplo's +[API Key Authentication Policy](/docs/policies/api-key-inbound) and the +[Monetization Inbound Policy](/docs/policies/monetization-inbound). + +The Monetization Inbound Policy is what ensures that customers are only able to +call your API withing the quotas set in their Plan. + +## 1/ Add API Key Authentication + +In order to identify customers calling your API, you'll add the +[API Key Authentication Policy](/docs/policies/api-key-inbound). This policy +validates the API Key sent to your API and identifies the user in order to +associate them with the plan which they have subscribed. + +:::tip + +For a full tutorial on the API Key Auth Policy see the +[Getting Started guide](/docs/articles/step-2-add-api-key-auth). + +::: + +1. Open the **Code** section of the Zuplo Portal and select your + `routes.oas.json` file. + +2. Open the route you want to add monetization to and click **Policies**, then + click **+ Add Policy**. + +3. Type `API Key` in the search and select the **API Key Authentication** + policy. + +4. Leave the default configuration and click **OK**. + +Now, all requests to this route will require an API Key to successfully call the +endpoint. + +## 2/ Add the Monetization Policy + +With the user identified via the API Key Authentication policy, next you will +enforce that they are calling the API within the limits of their plan. + +1. Open the **Policies** section of the same route and click **Add Policy**. + +2. Type `Monetization` in the search and select **Monetization**. + +The configuration of this policy is where you specify the meters that are +required in order to call this API. For now, leave the configuration as is, you +can always change it later. + +3. Be sure to save your changes. + +![Policy pipeline](../../public/media/monetization-policy-setup/image.png) + +## 3/ Test the API + +With both policies added to the route, you can now call the API with your +subscription's API Key. + +1. Return to your Developer Portal and open the **API Reference** section. + +2. Find the route you added the policies to in the previous steps. + +3. Above the code sample, next to the route, click the **Test** button. This + will open up the API Playground that you can use to call the API. + +:::note + +The API Key for your subscription will be pre-populated in the `Authorization` +header. + +::: + +4. Edit the API Request if needed and click **Test**. You should see a + successful response. Click **Test** a few more times. + +![Successful response](../../public/media/monetization-policy-setup/image-1.png) + +5. Close the API Playground and click the **Subscription** link in the Dev + Portal header. Notice the Analytics section now shows that you have consumed + some of your request quota from your plan. + +6. Return to the API Playground and click **Test** until you receive an error + response telling you that you have exhausted your quota. + +:::note + +If you set a large number in the **Max Value** of your request quota in the +earlier steps, this is going to take a while. + +::: + +![Max quota status](../../public/media/monetization-policy-setup/image-2.png) + +Congratulations, you are now ready to monetize your API! diff --git a/docs/articles/monetization-programmatic-quotas.md b/docs/articles/monetization-programmatic-quotas.md new file mode 100644 index 00000000..dcecf239 --- /dev/null +++ b/docs/articles/monetization-programmatic-quotas.md @@ -0,0 +1,91 @@ +--- +title: Monetization Programmatic Quotas +sidebar_label: Programmatic Quotas +--- + +Typically, when adding monetization to your API, you set the number of meters a +request will consume in the settings of the +[Monetization Policy](https://zuplo.com/docs/policies/monetization-inbound). For +example, the policy below specifies that the request will consume 1 `requests` +meter and 5 `computeUnits` meters. + +```json +{ + "export": "MonetizationInboundPolicy", + "module": "$import(@zuplo/runtime)", + "options": { + "allowRequestsOverQuota": false, + "allowedSubscriptionStatuses": ["active", "incomplete"], + "meterOnStatusCodes": "200-399", + "meters": { + "requests": 1, + "computeUnits": 5 + } + } +} +``` + +However, in some cases, you may not know up front how many units of a particular +meter will be consumed until after the response is sent. For example, maybe your +backend is responsible for computing the `computeUnits` on a request and send +the result in the response in the `compute-units` header. + +In Zuplo, you can support these dynamic meters by writing a little code. To make +the `computeUnits` meter dynamic, first update the policy by setting the +`computeUnits` meter to `0` as shown below. + +```json +{ + "export": "MonetizationInboundPolicy", + "module": "$import(@zuplo/runtime)", + "options": { + "allowRequestsOverQuota": false, + "allowedSubscriptionStatuses": ["active", "incomplete"], + "meterOnStatusCodes": "200-399", + "meters": { + "requests": 1, + "computeUnits": 0 + } + } +} +``` + +Next you can create a +[custom code outbound policy](/docs/policies/custom-code-outbound) that reads +data from the Response (in this case the `compute-units` header) and sets the +meter programmatically. + +```ts title="/modules/set-compute-units-outbound.ts" +import { + MonetizationInboundPolicy, + ZuploRequest, + ZuploContext, +} from "@zuplo/runtime"; + +export default async function ( + response: Response, + request: ZuploRequest, + context: ZuploContext, + options: any, + policyName: string, +) { + const headerValue = response.headers.get("compute-units"); + let computeUnits; + if (headerValue && typeof headerValue === "string") { + computeUnits = parseInt(headerValue); + } + + // Throw an error if the server doesn't send compute units + // Alternatively, you could have a default value + if (!computeUnits) { + throw new Error("Invalid response, no compute units sent."); + } + + // Set the compute units for the request + MonetizationInboundPolicy.setMeters(context, { + computeUnits, + }); + + return response; +} +``` diff --git a/docs/articles/monetization-webhook-setup.md b/docs/articles/monetization-webhook-setup.md new file mode 100644 index 00000000..fb01ea83 --- /dev/null +++ b/docs/articles/monetization-webhook-setup.md @@ -0,0 +1,167 @@ +--- +title: Step 2 - Configure Subscription Events +sidebar_label: Step 2 - Configure Subscription Events +--- + +In the previous step, you configured the Developer Portal to enable your +customers to subscribe to your Zuplo API. In this step, you will configure +Stripe and your Zuplo API to handle a customer subscribing to your API Plans. + +## 1/ Configure Plans + +Stripe can send subscription events, through Stripe Webhooks, when a customer +subscribes to your API Plans. Your Zuplo API will use those events to connect +the Stripe Subscriptions with the Plans in your Zuplo API. For this to work, +you'll need to create Plans in your Zuplo Metering Service. + +1. In Zuplo Portal, go to your project, select the **Services** tab and click + **Configure** on your "Metering Service". + +![Metering Service](../../public/media/monetization-webhook-setup/image.png) + +> You'll complete the next steps for each Stripe Product you added to your +> Pricing Table. + +2. Click **Create Plan** to create your first Plan. Enter the name of your plan + (i.e. Basic). The name typically will match the name of your Stripe Product. + +3. The value of **External ID** is the ID of the Stripe Product. This value is + found in the Stripe Dashboard in the + [Product Catalog](https://dashboard.stripe.com/products). The value will + start with `prod_`. + +![Plan](../../public/media/monetization-webhook-setup/image-1.png) + +4. Next, set a Meter for the plan. To start, create a single meter called + `Requests`. +5. Set **Max Value** to the number of Requests a user can make against your API + per month. + +:::tip + +During the test phase, set the value of the **Max Value** to a low number like +`10`. This will allow you to test the quota later on in this tutorial. + +::: + +## 2/ Set Up the Zuplo Plugin + +The Zuplo `StripeMonetizationPlugin` enables your Zuplo API to listen to Stripe +Webhook subscription events. This plugin adds an endpoint that is used when +configuring the Stripe Webhook. + +1. To start, navigate to the **Code** section of your Zuplo project. On the + `modules` folder click the **+** button and select **Runtime Extension**. + +:::note + +The **zuplo.runtime.ts** file is where you can +[register global plugins](https://zuplo.com/docs/articles/runtime-extensions#plugin-and-handler-extensions) +and [setup hooks](https://zuplo.com/docs/articles/runtime-extensions#hooks) +within your Zuplo API. + +::: + +2. Add the `StripeMonetizationPlugin` to your `zuplo.runtime.ts` file as shown + below. + +```ts +import { + RuntimeExtensions, + StripeMonetizationPlugin, + environment, +} from "@zuplo/runtime"; + +export function runtimeInit(runtime: RuntimeExtensions) { + // Create the Stripe Plugin + const stripe = new StripeMonetizationPlugin({ + webhooks: { + signingSecret: environment.STRIPE_WEBHOOK_SIGNING_SECRET, + }, + stripeSecretKey: environment.STRIPE_SECRET_KEY, + }); + // Register the plugin + runtime.addPlugin(stripe); +} +``` + +The plugin is using two environment variables. The `STRIPE_SECRET_KEY` is the +same value that was added in the +[Step 1](/docs/articles/monetization-dev-portal-setup.md) of this tutorial. The +`STRIPE_WEBHOOK_SIGNING_SECRET` is a value that you will get after you set up a +new Webhook in Stripe. + +## 3/ Set Up the Stripe Webhook + +1. In Stripe, navigate to the + [Webhooks section](https://dashboard.stripe.com/test/webhooks) and click **+ + Add Endpoint** to register a new webhook. + +2. Add `/__plugins/stripe/webhooks` in **Endpoint + URL**. This URL is your Zuplo API's endpoint to process Stripe Webhook + events. + +3. Click the **+ Select events** button and select the following events: + + - `customer.subscription.created` + - `customer.subscription.updated` + - `customer.subscription.deleted` + + Click **Add events** when done. + +4. Click **Add endpoint** to register your Webhook. + +5. With the Webhook created, find the section called **Signing secret** and + click the **Reveal** link. Copy this value. + +![alt text](../../public/media/monetization-webhook-setup/image-2.png) + +6. Return to the Zuplo Portal, open your project and go to the **Environment + Variables** section under the **Settings** tab. + +7. Create a new Environment Variable. Set it as a **Secret** and name the + variable `STRIPE_WEBHOOK_SIGNING_SECRET`. Paste the value of webhook's + **Signing secret** you copied earlier and click **Save**. + +## 4/ Purchase a Subscription + +With the Zuplo Plans and the Stripe Webhook configured, you can now test the end +to end subscription experience. + +1. Navigate to the **Pricing** page in your Developer Portal. + +2. Select a Plan and click **Subscribe**. If you are not already logged into the + portal, you will be asked to do so first. + +3. The Stripe checkout page will open. If you are using test mode, you can enter + any [test payment method](https://docs.stripe.com/testing) you like. + +:::tip + +In test mode, use Stripe test credit card number **4242 4242 4242 4242**. Set +any future date as the expiration and any three digits as the CVC. + +::: + +4. Once you have completed the checkout, you will be redirected to your + Developer Portal. After a few seconds, you should see your subscription page + with your API Key. + +![Successful subscription](../../public/media/monetization-webhook-setup/image-3.png) + +5. Return to your Zuplo project and open the **Logs** tab. Notice the logs that + show information about the incoming webhook. + +:::tip + +If you are having trouble diagnosing issues with your Webhooks, the logs are a +good place to see what is going on. You can also open the +[**Webhooks**](https://dashboard.stripe.com/webhooks) page in Stripe and see the +history of webhook events sent and their responses. + +::: + +You've successfully subscribed to one of the Plans in your API. You're one step +away from monetizing your API. In the next step you will add policies to your +API, enforce quotas and ensure that users are only allowed to use your API +according to the plan they subscribe to. diff --git a/docs/articles/monetization.md b/docs/articles/monetization.md new file mode 100644 index 00000000..ee2b9379 --- /dev/null +++ b/docs/articles/monetization.md @@ -0,0 +1,50 @@ +--- +title: Introduction +--- + +Zuplo enables you, as an _API provider_, to effortlessly monetize your APIs. It +provides a compelling set of defaults, while also allowing customizations to +suit your company's unique business needs. You can easily create separate plans +for different customer segments, and set up pricing and usage limits for each. + +With Zuplo, your customers can easily sign up for a plan, all within the +[Zuplo Developer Portal](/docs/articles/developer-portal.md). When your +customers visit the Developer Portal, they are presented with a list of plans +that you have created. They can sign up for a plan and start using your API +instantly. + +![Pricing Table](../../public/media/monetization-dev-portal-setup/image.png) + +Zuplo also provides powerful analytics for you as an API Provider. You can +easily track the overall usage of your API, or drill down to see the usage of a +particular customer. + +![Analytics](https://cdn.zuplo.com/assets/353fb3d5-f019-443b-92d6-a4127814b1f0.png) + +## About This Guide + +The guide is divided into the following sections: + +- [Step 1 - Set Up Developer Portal](/docs/articles/monetization-dev-portal-setup.md) +- [Step 2 - Configure Subscription Events](/docs/articles/monetization-webhook-setup.md) +- [Step 3 - Configure Monetization Policy](/docs/articles/monetization-policy-setup.md) + +At the end of this guide, you will have a fully monetized API with Zuplo. The +API will have multiple plans with different limits. + +## Payments + +Zuplo uses [Stripe](https://stripe.com) as the billing and payments processor. +You will need to create a Stripe account to use Zuplo's monetization features. + +:::note + +We exclusively support monthly subscription plans. Please contact us if you need +a different pricing model. + +::: + +If Stripe does +not support your country or currency, please +[contact us](https://discord.zuplo.com) and we will work with you to find a +solution. diff --git a/docs/articles/stripe-monetization-plugin.md b/docs/articles/stripe-monetization-plugin.md new file mode 100644 index 00000000..7b1c087e --- /dev/null +++ b/docs/articles/stripe-monetization-plugin.md @@ -0,0 +1,54 @@ +--- +title: Stripe Monetization Plugin +sidebar_label: Stripe Plugin +--- + +The Stripe Monetization Plugin makes makes it easy to register a Stripe Webhook +in your Zuplo API that will handle Stripe subscription events. + +When you register the Stripe Plugin a new route is configured on your API at the +path `/__plugins/stripe/webhook`. This route is used to receive webhooks sent by +stripe for Stripe subscription events. + +The Plugin is registered in the `zuplo.runtime.ts` extension. It requires +setting the `webhooks.signingSecret` value and the `stripeSecretKey` in order to +function. + +There is additional configuration if you wan to customize the path, etc, but for +most cases no additional configuration is required. + +```ts +import { + RuntimeExtensions, + StripeMonetizationPlugin, + environment, +} from "@zuplo/runtime"; + +export function runtimeInit(runtime: RuntimeExtensions) { + // Create the Stripe Plugin + const stripe = new StripeMonetizationPlugin({ + webhooks: { + signingSecret: environment.STRIPE_WEBHOOK_SIGNING_SECRET, + }, + stripeSecretKey: environment.STRIPE_SECRET_KEY, + }); + // Register the plugin + runtime.addPlugin(stripe); +} +``` + +## Debugging & Troubleshooting + +The Runtime Plugin emits logs that show what the Webhook is doing. For example, +when a new subscription is created, the plugin will log information about the +Stripe subscription, user, etc. + +If are having trouble with the Webhooks, reviewing the logs for the Plugin is +the place to start. + +Additionally, you can use the Stripe +[Webhook logs](https://dashboard.stripe.com/test/webhooks) in the Stripe +Dashboard to view the webhooks that were send and their status. You can also +resend a webhook event. + +![Stripe Webhooks](../../public/media/stripe-monetization-plugin/image.png) diff --git a/docs/articles/support.md b/docs/articles/support.md index b1fe8c6d..c97de566 100644 --- a/docs/articles/support.md +++ b/docs/articles/support.md @@ -48,8 +48,8 @@ offers access to the following channels: ### Premium support Customers on a Zuplo enterprise plan can choose from premium support offerings -that can optionally include specific SLAs for response time as well as additional means of -contact such as a private Slack channel. +that can optionally include specific SLAs for response time as well as +additional means of contact such as a private Slack channel. As part of premium support, Zuplo can also offer: @@ -58,7 +58,7 @@ As part of premium support, Zuplo can also offer: - Advice on best-practices designing your Zuplo API - Troubleshooting -Contact sales to explore adding these options to your agreement. +Contact sales to explore adding these options to your agreement. ## Contact Methods @@ -90,9 +90,10 @@ your support offering. ### Private Discord/Slack Channel -Enterprise support contracts can optionally chat directly with the Zuplo team in a private -Discord or Slack channel. These channels are useful for posting feature -requests, asking questions, or general troubleshooting. Contact sales for more info. +Enterprise support contracts can optionally chat directly with the Zuplo team in +a private Discord or Slack channel. These channels are useful for posting +feature requests, asking questions, or general troubleshooting. Contact sales +for more info. :::caution diff --git a/docs/articles/zuplo-json.md b/docs/articles/zuplo-json.md index b5a20b68..b551b20c 100644 --- a/docs/articles/zuplo-json.md +++ b/docs/articles/zuplo-json.md @@ -13,7 +13,7 @@ of the file is `1`. { "version": 1, "project": "my-project", - "compatibilityDate": "2023-03-14" + "compatibilityDate": "2023-03-14", } ``` diff --git a/policies/custom-code-outbound/doc.md b/policies/custom-code-outbound/doc.md index b7692684..130b0d89 100644 --- a/policies/custom-code-outbound/doc.md +++ b/policies/custom-code-outbound/doc.md @@ -1,7 +1,6 @@ -:::tip -The outbound policy will only execute if the response status code is 'ok' -(e.g. `response.ok === true` or the status code is 200-299) - see -[response.ok on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Response/ok). +:::tip The outbound policy will only execute if the response status code is 'ok' +(e.g. `response.ok === true` or the status code is 200-299) - see +[response.ok on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Response/ok). ::: ### Writing A Policy diff --git a/policies/rbac-policy-inbound/policy.ts b/policies/rbac-policy-inbound/policy.ts index b9540e81..ff896a44 100644 --- a/policies/rbac-policy-inbound/policy.ts +++ b/policies/rbac-policy-inbound/policy.ts @@ -27,8 +27,8 @@ export default async function ( // Check that the user has one of the allowed roles if ( - !options.allowedRoles.some( - (allowedRole) => request.user?.data.roles.includes(allowedRole), + !options.allowedRoles.some((allowedRole) => + request.user?.data.roles.includes(allowedRole), ) ) { context.log.error( diff --git a/postcss.config.js b/postcss.config.js index 33ad091d..12a703d9 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -3,4 +3,4 @@ module.exports = { tailwindcss: {}, autoprefixer: {}, }, -} +}; diff --git a/public/media/monetization-dev-portal-setup/image-1a.png b/public/media/monetization-dev-portal-setup/image-1a.png new file mode 100644 index 00000000..ff8802b4 Binary files /dev/null and b/public/media/monetization-dev-portal-setup/image-1a.png differ diff --git a/public/media/monetization-dev-portal-setup/image-2a.png b/public/media/monetization-dev-portal-setup/image-2a.png new file mode 100644 index 00000000..2883e64b Binary files /dev/null and b/public/media/monetization-dev-portal-setup/image-2a.png differ diff --git a/public/media/monetization-dev-portal-setup/image-3a.png b/public/media/monetization-dev-portal-setup/image-3a.png new file mode 100644 index 00000000..7df23690 Binary files /dev/null and b/public/media/monetization-dev-portal-setup/image-3a.png differ diff --git a/public/media/monetization-dev-portal-setup/image.png b/public/media/monetization-dev-portal-setup/image.png new file mode 100644 index 00000000..9297fd8e Binary files /dev/null and b/public/media/monetization-dev-portal-setup/image.png differ diff --git a/public/media/monetization-policy-setup/image-1.png b/public/media/monetization-policy-setup/image-1.png new file mode 100644 index 00000000..c704a6e2 Binary files /dev/null and b/public/media/monetization-policy-setup/image-1.png differ diff --git a/public/media/monetization-policy-setup/image-2.png b/public/media/monetization-policy-setup/image-2.png new file mode 100644 index 00000000..d6e9a00d Binary files /dev/null and b/public/media/monetization-policy-setup/image-2.png differ diff --git a/public/media/monetization-policy-setup/image.png b/public/media/monetization-policy-setup/image.png new file mode 100644 index 00000000..03311d75 Binary files /dev/null and b/public/media/monetization-policy-setup/image.png differ diff --git a/public/media/monetization-webhook-setup/image-1.png b/public/media/monetization-webhook-setup/image-1.png new file mode 100644 index 00000000..e91a87f5 Binary files /dev/null and b/public/media/monetization-webhook-setup/image-1.png differ diff --git a/public/media/monetization-webhook-setup/image-2.png b/public/media/monetization-webhook-setup/image-2.png new file mode 100644 index 00000000..bfef76fc Binary files /dev/null and b/public/media/monetization-webhook-setup/image-2.png differ diff --git a/public/media/monetization-webhook-setup/image-3.png b/public/media/monetization-webhook-setup/image-3.png new file mode 100644 index 00000000..3d3f7bc5 Binary files /dev/null and b/public/media/monetization-webhook-setup/image-3.png differ diff --git a/public/media/monetization-webhook-setup/image.png b/public/media/monetization-webhook-setup/image.png new file mode 100644 index 00000000..5dea20ad Binary files /dev/null and b/public/media/monetization-webhook-setup/image.png differ diff --git a/public/media/stripe-monetization-plugin/image.png b/public/media/stripe-monetization-plugin/image.png new file mode 100644 index 00000000..904c942e Binary files /dev/null and b/public/media/stripe-monetization-plugin/image.png differ diff --git a/sidebar.json b/sidebar.json index 4bebacc9..dcfad776 100644 --- a/sidebar.json +++ b/sidebar.json @@ -36,6 +36,31 @@ "articles/local-development-troubleshooting" ] }, + { + "type": "category", + "label": "Monetization", + "items": [ + "articles/monetization", + { + "type": "category", + "label": "Quick Start", + "items": [ + "articles/monetization-dev-portal-setup", + "articles/monetization-webhook-setup", + "articles/monetization-policy-setup" + ] + }, + { + "type": "category", + "label": "Reference", + "items": [ + "articles/monetization-glossary", + "articles/monetization-programmatic-quotas", + "articles/stripe-monetization-plugin" + ] + } + ] + }, { "type": "category", "label": "Getting to Production", diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index f2edd03e..07622c39 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -1,4 +1,4 @@ -import Link from 'next/link' +import Link from "next/link"; export default function NotFound() { return ( @@ -21,5 +21,5 @@ export default function NotFound() { - ) + ); } diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 744d9479..bb696471 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -1,30 +1,30 @@ -import Link from 'next/link' -import clsx from 'clsx' +import Link from "next/link"; +import clsx from "clsx"; const variantStyles = { primary: - 'rounded-full bg-pink py-2 px-4 text-sm font-semibold text-gray-900 hover:bg-pink-HOVER focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pink/50 active:bg-pink-HOVER', + "rounded-full bg-pink py-2 px-4 text-sm font-semibold text-gray-900 hover:bg-pink-HOVER focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pink/50 active:bg-pink-HOVER", secondary: - 'rounded-full bg-gray-800 py-2 px-4 text-sm font-medium text-white hover:bg-gray-700 focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/50 active:text-gray-400', -} + "rounded-full bg-gray-800 py-2 px-4 text-sm font-medium text-white hover:bg-gray-700 focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/50 active:text-gray-400", +}; type ButtonProps = { - variant?: keyof typeof variantStyles + variant?: keyof typeof variantStyles; } & ( | React.ComponentPropsWithoutRef - | (React.ComponentPropsWithoutRef<'button'> & { href?: undefined }) -) + | (React.ComponentPropsWithoutRef<"button"> & { href?: undefined }) +); export function Button({ - variant = 'primary', + variant = "primary", className, ...props }: ButtonProps) { - className = clsx(variantStyles[variant], className) + className = clsx(variantStyles[variant], className); - return typeof props.href === 'undefined' ? ( + return typeof props.href === "undefined" ? (