Become a Tax Provider for the Orders API

    Let Stripe users leverage your tax API when they sell products.

    When an app creates an order though the Orders API on your behalf, Stripe checks your shipping and tax settings to dynamically, and in real-time, sets those costs. Tax settings can be set as:

    • included: No additional cost, the default.
    • percentage: A flat additional cost, as a percent of the order total, before shipping.
    • dynamic: Determined on the fly per order.
    • provider: Calculated using a third party tax provider.

    If a user chooses the provider, the Orders API will automatically attempt to contact a third party tax provider with the order details to obtain the tax line items for the order. This guide outlines the steps required to become a provider for tax calculations in the Orders API as well as the specific API you are expected to implement.

    Authenticating users

    Users of the Orders API choose their tax calculation strategy in the Stripe Dashboard. When they pick provider, they will be prompted to specify the URL that their provider uses for tax calculation. To become a provider, you should give your users the ability to obtain this URL from your dashboard, so they can paste it into the Stripe Dashboard.

    The URL must accept HTTP POST requests for /create, /:order_id/paid, and /:order_id/refund. The :order_id parameter will be the ID of the Stripe order. In the examples below, that ID would be or_15iahK2eZvKYlo2CzKGgMVNl. If your URL is https://www.example.com/taxes/, your endpoint should be able to respond to:

    • POST https://www.example.com/taxes/create
    • POST https://www.example.com/taxes/:order_id/paid
    • POST https://www.example.com/taxes/:order_id/refund

    The URL will be used as-is by Stripe, which means that you are welcome to include HTTP basic authentication in the URL string. You could, for example, give the user the following URL to let them authenticate as user foo with password bar: POST https://foo:bar@www.example.com/taxes/. The URL is not considered sensitive by Stripe, so we encourage you to use an unguessable identifier as the user’s password instead of the user’s usual password, should you choose to include authentication in the URL.

    The endpoints you need to implement to become an Orders API tax provider through the reverse API are outlined below:

    Order Creation

    When an order is created, Stripe will issue a POST HTTP request to the specified endpoint with the details of the unpaid, created order. Your endpoint should calculate the tax due based upon the order amount and the customer’s address, both of which are in the received order object. The order creation is not a guarantee that the order will eventually get paid. Your endpoint should therefore calculate the estimated tax due but not commit the transaction until the order has been paid.

    We strongly encourage you to reuse the identifiers already included in the order for any transactions you create internally. Using the same identifiers will make it easier for the Stripe user to reconcile tax line items in your system with their Stripe orders. If your system creates a transaction based on the order, for example, reusing the identifier "or_15iahK2eZvKYlo2CzKGgMVNl" helps the user link the transaction with the order. Similarly, if you create line items based on the SKUs in the order, reusing the SKU identifier "sku_h8UvZvy9JA4QXeuR5Wxt" will make it easier for you to later handle payment notifications and refunds.

    Sample Request

    The structure of the request will look like a fully expanded Order, not all of which will be relevant to calculating taxes for an order. Below is an example of the fields that you should expect to be present and usable for calculating taxes.

    {
      "order": {
        "id": "or_15iahK2eZvKYlo2CzKGgMVNl",
        "created": 1426898562,
        "object": "order",
        "shipping": {
          "address": {
            "line1": "1234 Main street",
            "line2": null,
            "city": "Anytown",
            "state": "CA",
            "postal_code": "123456",
            "country": "US"
          }
        },
        "items": [
          {
            "amount": 3000,
            "currency": "usd",
            "description": "Unisex / M",
    See all 56 lines "object": "order_item", "quantity": 2, "type": "sku", "parent": { "id": "sku_h8UvZvy9JA4QXeuR5Wxt", "object": "sku", "metadata": {}, "product": { "id": "prod_6naDTQsFnjCXUqY9ZEph", "object": "product", "metadata": {}, ... }, ... } } ], "shipping_methods": [ { "currency": "usd", "amount": 0, "description": "Standard", "id": "standard" }, { "currency": "usd", "amount": 1000, "description": "Premium", "id": "two_day" } ], "amount": 3000, "currency": "usd", ... } }

    Your endpoint should be able to handle items in the items array with the following types: - sku: One or more items that the customer is ordering. These items will have both a quantity and amount. - discount: One or more discounts to apply to all sku items. Discounts are prorated across SKUs based on the SKU amounts. For example, a $3 discount on an order with a $5 SKU and a $10 SKU should apply $1 to the first SKU and $2 to the second SKU. You should thus effectively calculate taxes on a $4 SKU and a $8 SKU. - shipping: If there are shipping charges in addition to the shipping options in the shipping_methods array, they will show up in the items array.

    Sample Response

    Your endpoint must return a 200 status code, along with a JSON response. The JSON needs a top-level tax_update property. This property must contain an items array of tax OrderItems to be appended to the order. An example of a valid response is:

    {
      "tax_update": {
        "items": [
          {
            "parent": null,
            "type": "tax",
            "description": "Sales tax",
            "amount": 225,
            "currency": "usd"
          }
        ]
      }
    }

    Notice that the the order in the sample request contains two shipping methods. The customer will be able to choose which of the methods ("Standard" or "Premium") then want for the order. If the shipping costs are taxable, you must return a set of tax line items for them as well. An example of a valid response that includes shipping method taxes is:

    {
      "tax_update": {
        "items": [
          {
            "parent": null,
            "type": "tax",
            "description": "Sales tax",
            "amount": 225,
            "currency": "usd"
          }
        ],
        "shipping_methods": [
          {
            "id": "standard",
            "tax_items": null
          },
          {
            "id": "two_day",
            "tax_items": [
              {
                "parent": "two_day",
                "type": "tax",
                "description": "Shipping taxes",
                "amount": 10,
                "currency": "usd"
              }
            ]
          }
        ]
      }
    }

    The shipping_methods array contains elements that refer to each of the shipping methods in the order through an id field. Each element contains zero or more tax OrderItems similar to what is in the items array. The one important difference is that the parent field is set to the id of the shipping method. For example, the $0.10 sales tax line item has the parent field two_day.

    Order Payment

    When an order has been successfully paid, Stripe will issue an asynchronous POST HTTP request to your endpoint. The order object will have a status field with the value paid. At this point, you should commit the transaction.

    Sample Request

    Just like when an order is created, Stripe will send a full Order object. Below is an example of the fields that you should expect to be present:

    {
      "order": {
        "id": "or_15iahK2eZvKYlo2CzKGgMVNl",
        "created": 1426898562,
        "object": "order",
        "shipping": {
          "address": {
            "line1": "1234 Main street",
            "line2": null,
            "city": "Anytown",
            "state": "CA",
            "postal_code": "123456",
            "country": "US"
          }
        },
        "items": [
          {
            "amount": 3000,
            "currency": "usd",
            "description": "Unisex / M",
    See all 62 lines "object": "order_item", "quantity": 2, "type": "sku", "parent": { "id": "sku_h8UvZvy9JA4QXeuR5Wxt", "object": "sku", "metadata": {}, "product": { "id": "prod_6naDTQsFnjCXUqY9ZEph", "object": "product", "metadata": {}, ... }, ... } }, { "parent": "two_day", "type": "shipping", "description": "Premium", "amount": 1000, "currency": "usd" }, { "parent": null, "type": "tax", "description": "Sales tax", "amount": 225, "currency": "usd" }, { "parent": "two_day", "type": "tax", "description": "Shipping taxes", "amount": 10, "currency": "usd" } ], "amount": 4235, "currency": "usd" } }

    Sample Response

    Your endpoint must return a 200 status code. Since the order has already been paid, there are no additional modifications you can make to the order, so you do not need to include a JSON response body.

    Order Refunds

    If part or all of the order is refunded, Stripe will send you a synchronous HTTP POST request to let you calculate the amount of tax to refund. Since refunds can be incremental, the request will include an array of newly returned items under order_return. Your response should include the refunded tax only for those items. Keep in mind that you should most likely only consider items with type sku, shipping, or discount when calculating the tax to refund even though the array may contain other types of order items. The items array of the order will remain unchanged even as items are returned.

    Sample Request

    Stripe will send a full Order object similar to when an order is paid and created. Below is an example of the fields that you should expect to be present for calculating refunds:

    {
      "order": {
        "id": "or_15iahK2eZvKYlo2CzKGgMVNl",
        "created": 1426898562,
        "object": "order",
        "shipping": {
          "address": {
            "line1": "1234 Main street",
            "line2": null,
            "city": "Anytown",
            "state": "CA",
            "postal_code": "123456",
            "country": "US"
          }
        },
        "items": [
          {
            "amount": 3000,
            "currency": "usd",
            "description": "Unisex / M",
    See all 87 lines "object": "order_item", "quantity": 2, "type": "sku", "parent": { "id": "sku_h8UvZvy9JA4QXeuR5Wxt", "object": "sku", "metadata": {}, "product": { "id": "prod_6naDTQsFnjCXUqY9ZEph", "object": "product", "metadata": {}, ... }, ... } }, { "parent": "two_day", "type": "shipping", "description": "Premium", "amount": 1000, "currency": "usd" }, { "parent": null, "type": "tax", "description": "Sales tax", "amount": 225, "currency": "usd" }, { "parent": "two_day", "type": "tax", "description": "Shipping taxes", "amount": 10, "currency": "usd" } ], "amount": 4255, "currency": "usd" }, "order_return": { "amount": 1500, "items": [ { "amount": 1500, "currency": "usd", "description": "Unisex / M", "object": "order_item", "quantity": 1, "type": "sku", "parent": { "id": "sku_h8UvZvy9JA4QXeuR5Wxt", "object": "sku", "metadata": {}, "product": { "id": "prod_6naDTQsFnjCXUqY9ZEph", "object": "product", "metadata": {}, ... }, ... } } ] } }

    Sample Response

    Your endpoint must return a 200 status code, along with a JSON response. The JSON must contain an items array under the top-level tax_update property consisting of refunded tax OrderItems to be appended to the order. Any amount returned must be positive. The description field must match the description you returned when the order was created to let the merchant reconcile returned tax line items with existing line items. In the example response below, half the sales tax is returned since the customer returned one of the two instances of the SKU in the order:

    {
      "tax_update": {
        "items": [
          {
            "parent": null,
            "type": "tax",
            "description": "Sales tax",
            "amount": 112,
            "currency": "usd"
          }
        ]
      }
    }

    Full returns

    A merchant can decide to return the remainder of an order even if they have previously returned part of it. When calculating taxes, you will need to round the tax amount due to an integer, which can be problematic when a merchant wants to return all taxes. Consider the following example based on the order above:

    • A customer orders two SKUs, each of which costs $15. You apply California state tax of 7.5% to the order to determine that $2.25 of taxes will be due.
    • The customer returns one SKU and $1.125 of taxes should be refunded. You will need to round this to a whole number of cents, either $1.12 or $1.13.
    • The customer returns the remainder of the order. If you only see a return of the remaining SKU, you will almost certainly calculate taxes as you did when the customer returned the first SKU. Now, however, you will either be left with $0.01 of tax remaining on the order or refunding $0.01 more than the original tax amount.

    To avoid this issue, Stripe calculates the amount of tax left on the order before calling your /refund endpoint if the merchant is returning the remainder of the order. The line items will be included in the order_return’s items array and represent the taxes Stripe expects you to refund. You should return the same line items in your response. For example, if the customer returns the remainder of the above order (ignoring shipping taxes), you will receive the following request (some fields omitted for clarity):

    {
      "order": {
        "id": "or_15iahK2eZvKYlo2CzKGgMVNl",
        ...
      },
      "order_return": {
        "amount": 1623,
        "items": [
          {
            "amount": 1500,
            "currency": "usd",
            "description": "Unisex / M",
            "object": "order_item",
            "quantity": 1,
            "parent": {
              "id": "sku_h8UvZvy9JA4QXeuR5Wxt",
              ...
            },
            "type": "sku"
          },
          {
            "parent": null,
            "type": "tax",
            "description": "Sales tax",
            "amount": 123,
            "currency": "usd"
          }
        ]
      }
    }

    Because this request includes tax line items, you should update the order in your system to reflect the return of $1.23 of taxes and return the following response:

    {
      "tax_update": {
        "items": [
          {
            "parent": null,
            "type": "tax",
            "description": "Sales tax",
            "amount": 123,
            "currency": "usd"
          }
        ]
      }
    }

    Refunding shipping taxes

    While an order may have one or more shipping methods when it is created, the customer will pick exactly one option before the order is paid. Any shipping taxes that you return under shipping_methods when the order is created will therefore only show up in the items array when the order is paid is refunded. You can see an example of this in the Order Payment section. If the merchant refunds shipping charges, you should calculate the taxes to return like you would with SKU taxes. For example, you would receive the request below (some fields omitted for clarity) if the merchant refunds the shipping costs:

    {
      "order": {
        "id": "or_15iahK2eZvKYlo2CzKGgMVNl",
        ...
      },
      "order_return": {
        "amount": 1000,
        "items": [
          {
            "parent": "two_day",
            "type": "shipping",
            "description": "Premium",
            "amount": 1000,
            "currency": "usd"
          }
        ]
      }
    }

    You should return the shipping taxes that you calculated when the order was created like any other order item taxes in the items nested under tax_update:

    {
      "tax_update": {
        "items": [
          {
            "parent": "two_day",
            "type": "tax",
            "description": "Shipping taxes",
            "amount": 10,
            "currency": "usd"
          }
        ]
      }
    }

    Indicating an error

    Any non-200 responses when creating or refunding orders will be considered an error, which results in the order creation or refund being rejected. This also means that if your endpoint is broken (e.g., due to a syntax error), orders cannot be created.

    Your endpoint can respond to the Stripe request with error messages that the API will return to the order creation request. Error codes are represented in the usual Stripe format:

    {
      "error": {
        "type": "action_failed",
        "code": "address_verification_failed",
        "message": "The postal code 94110 does not match the state NY."
      }
    }

    Acceptable values for the error code are:

    • taxes_calculation_failed: the taxes could not be computed
    • address_verification_failed: the address could not be verified

    Testing Your Integration

    To test your integration first configure your tax integration in the Stripe Dashboard. The Relay settings page in the Dashboard will let you specify a custom tax provider by picking “Change tax” and selecting “Provider” from the first dropdown. We suggest only sending test request (and thus only using test settings) while you are still developing your endpoint. Be sure to test different shipping addresses, shippable and non-shippable goods, as well as full and partial returns.

    Questions?

    We're always happy to help with code or other questions you might have. Search our documentation, contact support, or connect with our sales team. You can also chat live with other developers in #stripe on freenode.

    Was this page helpful? Yes No

    Send

    Thank you for helping improve Stripe's documentation. If you need help or have any questions, please consider contacting support.

    On this page