Serhii NezhyhaiShopify developer

Work Custom app · Integration & migration

Back-office integration app

One Gadget app tying a Shopify store to Ukrainian banking, fiscal receipts, a marketplace, a courier and its suppliers.

Client
Informatica
My role
Sole developer · employee
Period
2024 — present
Outcome
Admin UI extensions10
Third-party systems wired6
Shopify models mirrored~40

Problem

The store's operations lived in spreadsheets and manual checks. Payments were reconciled by eye against a bank statement, fiscal receipts were issued by hand, Rozetka marketplace orders were re-keyed, and supplier product imports were copy-paste. Each of those was a daily job and a steady source of quiet errors.

Constraints

A live store with real orders — nothing could run destructively. Every integration is a third-party API with its own auth, rate limits and downtime, several documented only in Ukrainian.

Hard part

Matching Shopify orders to bank payments when the two systems share no identifier and neither side is clean.

Stack

Stack:
  • Gadget.dev
  • TypeScript
  • GraphQL Admin API
  • Admin UI extensions
  • Polaris
  • React

Links

What it connects

The app runs on Gadget and mirrors around forty Shopify models locally, so the integrations work against a consistent copy of the catalogue and the order book rather than hammering the Admin API. On top of that sit the connections the store actually needs:

  • PrivatBank — pulls statement transactions, deduplicates them, and stores them for reconciliation.
  • Checkbox.ua — issues Ukrainian fiscal receipts: shift management, an order-to-receipt transformer, and sell-receipt creation with retry and backoff.
  • Rozetka — OAuth with auto-refreshing tokens, order-status sync, and a rule-driven XML product feed that maps Shopify products onto Rozetka’s category IDs and required parameters.
  • Nova Poshta — creates and cancels shipping documents and writes tracking numbers back onto fulfilments.
  • Suppliers — Brain, Easy and others, plus Google Sheets price lists, behind one interface; product import can generate descriptions and find a fallback image.
  • SMS — templated order messages through a local gateway.

The hard part

Reconciling payments. A Shopify order and a bank transfer have no shared key: the customer pays from whatever account they like, references it however they like, and the amounts don’t always line up because of partial payments and rounding. The app builds candidate matches on amount and date window, flags the orders it can’t cover, and posts the unresolved ones to a Telegram channel for a human to look at. It doesn’t try to be clever — a wrong automatic match is worse than no match — so the job is to shrink the manual pile, not to remove it.

Shape of the code

Everything outbound is a background job with retries, not a request handler, so a supplier API being down for an hour is a delayed sync rather than a failed one. The store team works through ten Admin UI extensions placed where the task is — a bulk “issue receipts” action on the order list, an SMS panel on the order page, a stage-control block for the internal workflow — so the app meets them where they already are instead of in another tab.

What I’d do differently

The local model mirror earns its keep but drifts if a webhook is missed. A periodic reconciliation pass against Shopify would catch that, instead of waiting for someone to notice a wrong number.