Shopify
A company that sells in a Shopify shop connects the shop to Stabiso. Shopify stays the shop: the storefront, the checkout, the customers' accounts. Stabiso keeps the books: the shop's products arrive as the company's products, and its orders as sales orders of their buyers, invoiced as the goods are sent and paid into the balance the payment provider holds for the company. A drop shipper's suppliers get a purchase order for what they ship, and Shopify's payouts book its fees and chargebacks and are matched on the bank statement; refunds become credit notes paid back out of the provider's balance. The design is ADR-0109.
Connecting a shop
Settings > Shopify (administrators) connects one company to one shop. The shop lets Stabiso in through an app of the company's own, which takes a few minutes to make in Shopify; Stabiso needs nothing from the platform's operator.
- In the shop's admin, open Settings > Apps > Develop apps, then Build apps in Dev Dashboard, and create an app, for example Stabiso.
- In the app's version, give it the Admin API scopes the page lists (read the products and their stock and write the stock, read the customers, orders and fulfilments, read the Shopify Payments payouts). They cover the whole pack, so the app never needs more later. Release the version and install the app on the shop. The orders also need the buyers' names, e-mails and addresses, which Shopify calls protected customer data: an app of the shop's own organization reads them on Shopify's Grow plan and above. When Shopify withholds them the page says so, and Shopify's support can allow them.
- To connect on Shopify's page (below), add the address the page shows
(
https://<your Stabiso>/app/settings/shopify) to the app's allowed redirection URLs. - Copy the app's Client ID and Client secret (the app's Settings) into the page, with the
company and the shop's address (
<name>.myshopify.com, under Settings > Domains).
Then connect it one of two ways:
- Connect: Stabiso asks the shop for an access token with the app's client ID and secret on every round (Shopify's client credentials; a token lives a day). This works when the app and the shop belong to the same Shopify organization.
- Connect on Shopify's page: Shopify asks someone allowed to install apps on the shop to agree, and sends them back to Stabiso with a one-time code, signed with the app's secret. Stabiso checks the signature, trades the code for the app's lasting token and keeps only that. This works for any shop the app is installed on.
A custom app made in the shop's admin before 2026 still connects with its Admin API access token
(shpat_...), under Use a custom app's access token.
Stabiso asks the shop for its name, its currency and whether its prices include the tax, which proves the app. The secret is kept sealed (see security) and never shown again. Connecting again replaces it. A company connected to one shop is not connected to another: disconnect it first, so that the products of two shops never mix.
Products
Every 5 minutes, and on Read now, Stabiso reads the shop's products that changed since the last round (the first round reads them all, at most 500 variants a round; the next goes on from there). Each variant the shop sells (Active, or Unlisted: sold by its link) becomes one product:
| Product | From the Shopify variant |
|---|---|
| The product it is | the company's product whose code is the variant's SKU, when there is one that no other variant took; otherwise a new product |
| Code (a new product) | the SKU when it can be a code (letters, digits, ., _, -, up to 40), else the company's next number (P-00001) |
| Name | the product's title, and the variant's when it has options (Trail shirt - M) |
| Kind, unit, VAT | one-off goods, per piece, at the standard rate; change them on the product |
| List price | the variant's price in the shop's currency from today. Stabiso's list prices are net: when the shop's prices include the tax, the net is worked out at the company's standard rate (CHF 54.05 with 8.1 % is CHF 50.00) |
- Once. A variant becomes a product once per company and shop, also after disconnecting and connecting the same shop again. The database refuses to change or forget which product a variant is sold as.
- Changes. A new name in the shop renames the product; a new price is recorded as a new list price from today (a price is never changed, so a second change on the same day is taken over the next day). A product the shop no longer sells (Draft, Archived) is set inactive, and active again when the shop sells it again. Drafts are never made into products.
- The page lists the shop's variants with the product each is sold as; a row opens the product.
Nothing here posts: a product is master data.
Orders
In the same rounds Stabiso reads the shop's orders that changed (at most 100 a round; the next goes on from there). Each order placed from the connection on is taken into the books; orders placed before it, test orders and orders cancelled before Stabiso read them are not.
| In the books | From the Shopify order |
|---|---|
| Customer | the buyer: a new customer the first time (their name, else their e-mail; their e-mail; the country the goods go to, and the state or province in the US and Canada), a consumer, in the shop's currency, paying at once. Its activation goes through the customer controls like any customer's. Guests are one customer per country, Online shop guests (DE). A buyer is the same customer in every later order |
| Sales order | confirmed at once, dated the day the order was placed, in the shop's currency, with the order's name (#1001) as the customer's reference |
| Its lines | each line of the order: the variant's product where the variant is a product here, else the line's own name; the quantity; the price the shop charged after its discounts, net: when the shop's prices include the tax, without the tax the shop charged on the line. Then the shipping, as a line of its own (to the default revenue) |
| VAT | Stabiso's own, as on every invoice: the rate of the country the goods go to, an export without VAT, the One-Stop Shop |
Sent: invoiced. Each fulfilment (goods the shop sent) is invoiced from the sales order: the quantities it sent, and the shipping with the order's first invoice. The invoice is dated the day Stabiso reads it, its date of supply the day the goods left, and it is submitted like any invoice: posted, or held for the AI reviewer or a second person.
Paid: paid in. Once an invoice is posted and the customer has paid the order in the shop, a
receipt pays the invoice into the payment provider's balance: the account of role
provider_balance (account roles; KMU 1092 Guthaben bei
Zahlungsdienstleistern, IFRS, UK, US and Canada 1080), added from the company's chart the first
time it is needed. The receipt is dated that day and names the order. Shopify pays the balance out
to the bank later, net of its fees (payouts). An order paid later, by bank
transfer for example, is paid in when the shop says it is paid.
The receipt is for the invoice's total. The VAT Stabiso works out can differ from the shop's by a cent of rounding; an invoice more than 0.05 from what the shop charged for the same goods is not paid in: the order shows why (a sale abroad the shop charged Swiss VAT on, for example), and a person records the payment.
An order is done once every line was sent, invoiced and paid (but for goods refunded before they were sent) and its refunds are booked. An order cancelled before anything was sent cancels its sales order; one cancelled after goods were invoiced is credited by its refunds.
The page lists the shop's orders, the newest first, with their customer, what the shop charged, the sales order, how many of its invoices are paid and its state; a row opens the sales order. What keeps an order from being done (a customer whose activation waits, a closed period, an invoice far from the shop's amount) is shown on the order, in plain words, and every round tries it again.
Drop shipping
A drop shipper's suppliers send the goods straight to the customers. Each Shopify product names its vendor; under Drop shipping the page lists the vendors the shop's products name, and a person chooses, for each, which of the company's vendors ships its goods, or We ship them ourselves (the default). Changing it applies to orders taken from then on.
Once an order's sales order is made, its lines of a drop-shipped vendor's goods become a purchase order of the company's vendor who ships them, one per vendor and order:
| Purchase order | From |
|---|---|
| Vendor | the company's vendor named for the shop vendor |
| Lines | each such line of the order: its name and quantity, at the variant's cost per item in Shopify, booked to the purchases of goods (role goods_purchases: PCG 607000 Achats de marchandises, SKR04 6100 Wareneingang; in a chart without it, the cost of goods sold, role cogs: KMU 4200 Handelswarenaufwand, IFRS 5000), with no receipt expected: the goods never pass through the company |
| Currency | the shop's (the cost per item is in it) |
| Delivery address | the customer's, as the order names it |
| Your reference | the order (Shopify #1001) |
It is submitted like any purchase order: the AI reviewer or a second person approves it. The supplier's bill naming it is matched to it against the ordered quantities (purchase orders), which books the cost; a bill above the order within the tolerances goes through, beyond them it waits for approval as any variance. A variant with no cost per item in Shopify keeps the order waiting, with the reason on the order: give it a cost in Shopify and the purchase order follows in the next round. An order cancelled before anything was sent cancels its purchase orders too, unless a bill was matched to one; that waits for a person.
The cost is booked when the supplier's bill arrives, not when the goods leave: at a month's end, goods sent but not yet billed by the supplier are open purchase orders.
Refunds
What the shop gives back on an order is booked as it happened:
- Goods refunded after they were sent become a credit note of their invoice: the refunded quantity of each line as it was invoiced, with the shipping when the refund gave the shipping back. It is submitted like any credit note. Once it is posted, a payment out of the payment provider's balance pays it, dated that day and naming the order: Shopify took the money back from the balance it holds.
- Goods refunded before they were sent (the shop cancels them unsent) are never invoiced: the order needs them no more, and once the rest is sent, invoiced and paid, what is left of its sales order is closed.
A credit note more than 0.05 from what the shop refunded is not paid: the order says why and a person records the payment. So is a refund that gives money back without goods (a gesture of goodwill), or one of goods on more than one invoice at once. An order the shop cancels after goods were sent is refunded the same way. The order list shows each order's credit notes beside its invoices.
Payouts
Shopify Payments pays the balance it holds for the shop out to the company's bank, net of what it keeps. Every round reads the payouts of the last 30 days (from the day the shop was connected) and lists them on the page: when Shopify issued each, what it paid out, its fees and chargebacks, the entry that booked them and whether the bank shows it.
What Shopify kept is booked. Once a payout is on its way to the bank (in transit or paid), one entry dated the day it was issued books what Shopify kept of the balance:
| Line | Account |
|---|---|
| Debit the fees (all of them: on charges, refunds, chargebacks) | bank fees, role bank_fees (KMU 6940 Bankspesen, IFRS 7100) |
| Debit the chargebacks and other adjustments (a credit when a chargeback won gives money back) | losses on receivables, role bad_debt_expense (KMU 3805 Debitorenverluste, IFRS 6600) |
| Credit what it kept | the payment provider's balance, role provider_balance |
It is submitted like any entry: posted, or held for the AI reviewer or a second person. A payout whose amounts do not add up (its fees come to other than Shopify states) is not booked: the page says so and a person books it. A chargeback is booked as a loss with its VAT; whether the VAT on it can be taken back is the accountant's call, as for any write-off. Refunds and reserved funds stay in the balance: a refund is paid out of it by its own payment (refunds).
The bank line is matched by itself. When a bank statement shows the payout, the matcher posts the line to the provider's balance: an incoming line of the payout's exact amount and currency whose text names Shopify, dated from the day the payout was issued up to 7 days later, when exactly one payout fits. Anything else stays for a person to match. Undoing the match frees the payout for another line.
The balance then holds what customers paid that Shopify has not paid out yet. The first payouts
after connecting carry sales from before: their part is for a person to book once, as an opening
balance of the provider's account. Shops without Shopify Payments (another payment provider) have
no payouts to read; their provider's payouts are matched by hand. An app that may not read the
payouts (read_shopify_payments_payouts) gets a note on the page; the products and orders keep
coming.
Problems and disconnecting
- The shop refusing the app (uninstalled, its secret rotated, a permission taken away) sets the connection to Connect again, with the reason on the page. Nothing is read until it is connected again.
- Shopify not answering or asking to slow down is noted on the page; the next round tries again.
- Disconnect stops reading and deletes the secret here. The products stay, and so does which variant each is sold as. Uninstall the app in Shopify to close the door there too.
Security
- The secret (the app's client secret, or the token Shopify gave) is sealed (AES-256-GCM) with a key that is never in the database: it is derived from the database password, which Cloud Run reads from Secret Manager. A backup, an export or a look at the table gives nothing.
- Stabiso only ever calls the shop's own address,
<name>.myshopify.com, checked before anything is sent. - On Shopify's page, the app's secret travels sealed in the signed
stateand comes back only to the person who started; an answer that is not theirs, older than 15 minutes, for another shop or not signed by Shopify with the app's secret is refused. - Connections are never exported and never copied into a test copy, and a test copy refuses to connect: a copy never reads or writes a shop. Nor does a copy get the shop's orders as read (they hold the buyers' names and e-mails); it has their sales orders, invoices and receipts like any.
- Only an administrator connects or disconnects a company, or names who ships a shop vendor's goods; an AI agent never does.
- Errors never carry Shopify's answers, which could echo a customer's data or a token.
Not yet
The next step of the pack: the stock of goods kept in stock sent to the shop. An order edited in the shop after it was taken keeps its sales order as first read; an order with more than 20 lines or 3 fulfilments is named on the page for a person to enter.
API
GET /v1/shopify, POST /v1/shopify/connections (the app's client ID and secret, or a custom
app's token), POST /v1/shopify/authorize and POST /v1/shopify/connections/consent (Shopify's
page), GET /v1/shopify/connections/{shopify_connection_id}/products,
GET /v1/shopify/connections/{shopify_connection_id}/orders,
GET /v1/shopify/connections/{shopify_connection_id}/payouts,
GET and PUT /v1/shopify/connections/{shopify_connection_id}/suppliers (who ships a shop
vendor's goods),
POST /v1/shopify/connections/{shopify_connection_id}/sync and
DELETE /v1/shopify/connections/{shopify_connection_id} (API reference). Every
scheduled job does a round with each shop the API has not read for an hour.