Skip to content

NativeSquare/refurbed

Repository files navigation

@nativesquare/refurbed

A Convex component that syncs your Refurbed merchant data into local Convex tables — giving you reactive queries, multi-tenant isolation, and zero Refurbed API code in your app. Orders, offers, market offers, and reference data are all available through a simple class-based client or direct component function calls.

Installation

npm install @nativesquare/refurbed

Quick Start

1. Register the component

Create (or update) your convex/convex.config.ts:

// convex/convex.config.ts
import { defineApp } from "convex/server";
import refurbed from "@nativesquare/refurbed/convex.config.js";

const app = defineApp();
app.use(refurbed);

export default app;

2. Instantiate the client

In any Convex function file, create a Refurbed instance:

import { Refurbed } from "@nativesquare/refurbed";
import { components } from "./_generated/api";

const refurbed = new Refurbed(components.refurbed);

3. Create a connection and start syncing

import { Refurbed } from "@nativesquare/refurbed";
import { components } from "./_generated/api";
import { mutation } from "./_generated/server";
import { v } from "convex/values";

const refurbed = new Refurbed(components.refurbed);

export const setupRefurbed = mutation({
  args: { organizationId: v.string(), apiKey: v.string() },
  returns: v.string(),
  handler: async (ctx, args) => {
    // Creates the connection and enables sync (all entity types on by default)
    return await refurbed.createConnection(ctx, {
      organizationId: args.organizationId,
      apiKey: args.apiKey,
    });
  },
});

Three independent cron jobs run every 5 minutes, syncing orders (with order items), offers, and market offers automatically. Your data stays at most 5 minutes stale under normal operation.

Connection Management

Connections link a Refurbed merchant account to your Convex app. Each connection is scoped by organizationId for multi-tenant isolation.

Create a connection

const connectionId = await refurbed.createConnection(ctx, {
  organizationId: "org-acme",
  apiKey: "rfbd_live_...",
});

Throws if a connection already exists for the given organizationId. All sync toggles are enabled by default.

List connections

import { query } from "./_generated/server";

export const listConnections = query({
  args: {},
  returns: v.any(),
  handler: async (ctx) => {
    return await refurbed.listConnections(ctx);
  },
});

Returns all connections with organizationId, sync toggles, last-sync timestamps, sync cursors, and error info. API keys are never returned.

Update a connection (credential rotation)

await refurbed.updateConnection(ctx, {
  connectionId: "...",
  apiKey: "rfbd_live_new_...",
});

Replaces the stored API key and clears any syncError state, re-enabling sync on the next cron cycle.

Delete a connection

await refurbed.deleteConnection(ctx, {
  connectionId: "...",
});

Removes the connection and cascade-deletes all associated synced data (orders, order items, offers, market offers) for that organization. No other organization's data is affected.

Querying Synced Data

Synced data queries read from local Convex tables — they are reactive Convex queries, so subscribed clients auto-update when data changes. No API calls are made.

Orders

import { query } from "./_generated/server";
import { v } from "convex/values";

export const listOrders = query({
  args: { organizationId: v.string() },
  returns: v.any(),
  handler: async (ctx, args) => {
    return await refurbed.listOrders(ctx, {
      organizationId: args.organizationId,
    });
  },
});

Filter by status and limit results:

const shipped = await refurbed.listOrders(ctx, {
  organizationId: "org-acme",
  status: "shipped",
  limit: 10,
});

Get a single order:

const order = await refurbed.getOrder(ctx, {
  organizationId: "org-acme",
  refurbedId: "12345",
});

Order Items

const items = await refurbed.listOrderItems(ctx, {
  organizationId: "org-acme",
  orderId: "12345",
  limit: 50,
});

Get a single order item:

const item = await refurbed.getOrderItem(ctx, {
  organizationId: "org-acme",
  refurbedId: "67890",
});

Offers

const offers = await refurbed.listOffers(ctx, {
  organizationId: "org-acme",
  status: "ACTIVE",
  limit: 20,
});

const offer = await refurbed.getOffer(ctx, {
  organizationId: "org-acme",
  refurbedId: "42",
});

Market Offers

const marketOffers = await refurbed.listMarketOffers(ctx, {
  organizationId: "org-acme",
  offerId: "42",
  limit: 20,
});

const marketOffer = await refurbed.getMarketOffer(ctx, {
  organizationId: "org-acme",
  refurbedId: "42:de", // compound key: "offerId:marketCode"
});

Pass-Through Reference Data

These methods call the live Refurbed API in real time (no local caching). They require an ActionCtx — use them inside Convex action handlers.

Markets & Currencies

import { action } from "./_generated/server";
import { v } from "convex/values";

export const getMarkets = action({
  args: { organizationId: v.string() },
  returns: v.any(),
  handler: async (ctx, args) => {
    return await refurbed.listMarkets(ctx, {
      organizationId: args.organizationId,
    });
  },
});
const market = await refurbed.getMarket(ctx, {
  organizationId: "org-acme",
  marketId: "de",
});

const currencies = await refurbed.listCurrencies(ctx, {
  organizationId: "org-acme",
});

const currency = await refurbed.getCurrency(ctx, {
  organizationId: "org-acme",
  currencyId: "EUR",
});

Instances, Shipping Profiles & Catalog Templates

const instance = await refurbed.getInstance(ctx, {
  organizationId: "org-acme",
  instanceId: "inst-1",
});

const profiles = await refurbed.listShippingProfiles(ctx, {
  organizationId: "org-acme",
});

const templates = await refurbed.getCatalogTemplates(ctx, {
  organizationId: "org-acme",
});

Sync Control

Control which entity types are synced without redeployment.

Update individual toggles

Updates the specified toggles on all connections — omitted toggles remain unchanged.

// Disable order sync on all connections, leave offers and market offers unchanged:
await refurbed.updateSettings(ctx, { syncOrdersEnabled: false });

// Enable all three toggles explicitly:
await refurbed.updateSettings(ctx, {
  syncOrdersEnabled: true,
  syncOffersEnabled: true,
  syncMarketOffersEnabled: true,
});

Bulk enable / disable

await refurbed.enableAllSync(ctx);   // Enable all sync toggles on all connections
await refurbed.disableAllSync(ctx);  // Disable all sync toggles on all connections

Manual sync trigger

import { action } from "./_generated/server";
import { v } from "convex/values";

export const triggerSync = action({
  args: { connectionId: v.string() },
  returns: v.null(),
  handler: async (ctx, args) => {
    return await refurbed.triggerSync(ctx, {
      connectionId: args.connectionId,
    });
  },
});

Triggers an immediate sync for all enabled entity types on the given connection. Requires ActionCtx because it makes HTTP calls to the Refurbed API.

API Reference

Method Context Data Source Description
createConnection(ctx, { organizationId, apiKey }) Mutation Local write Create a new merchant connection
listConnections(ctx) Query Local read List all connections (no API keys)
updateConnection(ctx, { connectionId, apiKey }) Mutation Local write Rotate API key, clear sync errors
deleteConnection(ctx, { connectionId }) Mutation Local write Delete connection + cascade data
listOrders(ctx, { organizationId, status?, limit? }) Query Synced local List synced orders with filters
getOrder(ctx, { organizationId, refurbedId }) Query Synced local Get single order by Refurbed ID
listOrderItems(ctx, { organizationId, orderId, limit? }) Query Synced local List order items for an order
getOrderItem(ctx, { organizationId, refurbedId }) Query Synced local Get single order item
listOffers(ctx, { organizationId, status?, limit? }) Query Synced local List synced offers with filters
getOffer(ctx, { organizationId, refurbedId }) Query Synced local Get single offer
listMarketOffers(ctx, { organizationId, offerId?, limit? }) Query Synced local List synced market offers
getMarketOffer(ctx, { organizationId, refurbedId }) Query Synced local Get single market offer
triggerSync(ctx, { connectionId }) Action Live API Manually trigger data sync
updateSettings(ctx, { syncOrdersEnabled?, syncOffersEnabled?, syncMarketOffersEnabled? }) Mutation Local write Update sync toggles
enableAllSync(ctx) Mutation Local write Enable all sync toggles
disableAllSync(ctx) Mutation Local write Disable all sync toggles
listMarkets(ctx, { organizationId }) Action Live API List all markets
getMarket(ctx, { organizationId, marketId }) Action Live API Get market by code
listCurrencies(ctx, { organizationId }) Action Live API List all currencies
getCurrency(ctx, { organizationId, currencyId }) Action Live API Get currency by code
getInstance(ctx, { organizationId, instanceId }) Action Live API Get instance by ID
listShippingProfiles(ctx, { organizationId }) Action Live API List shipping profiles
getCatalogTemplates(ctx, { organizationId }) Action Live API Get catalog templates

Configuration Reference

Sync toggles are per-connection boolean flags that control which entity types are synced by the cron jobs:

Toggle Default Cron Interval Entities Synced
syncOrdersEnabled true 5 minutes Orders + Order Items (coupled)
syncOffersEnabled true 5 minutes Offers
syncMarketOffersEnabled true 5 minutes Market Offers
  • Toggles can be changed at runtime via updateSettings, enableAllSync, or disableAllSync — no redeployment needed.
  • Each entity type syncs independently — a failure in one does not prevent the others from completing.
  • On auth failure (401/403), the connection is flagged with syncError and sync stops for that entity. Use updateConnection with a new API key to clear the error and resume.

Direct Component Access

For advanced use cases, you can call component functions directly instead of using the Refurbed class:

import { query, mutation, action } from "./_generated/server";
import { components } from "./_generated/api";
import { v } from "convex/values";

// Query — use ctx.runQuery
export const listOrders = query({
  args: { organizationId: v.string() },
  returns: v.any(),
  handler: async (ctx, args) => {
    return await ctx.runQuery(
      components.refurbed.public.listOrders,
      args,
    );
  },
});

// Mutation — use ctx.runMutation
export const createConnection = mutation({
  args: { organizationId: v.string(), apiKey: v.string() },
  returns: v.string(),
  handler: async (ctx, args) => {
    return await ctx.runMutation(
      components.refurbed.public.createConnection,
      args,
    );
  },
});

// Action — use ctx.runAction
export const triggerSync = action({
  args: { connectionId: v.string() },
  returns: v.null(),
  handler: async (ctx, args) => {
    return await ctx.runAction(
      components.refurbed.public.triggerSync,
      args,
    );
  },
});

All public functions are available under components.refurbed.public.*. Use ctx.runQuery for queries, ctx.runMutation for mutations, and ctx.runAction for actions.

License

Apache-2.0

About

Convex component for integrating Refurbed into Convex applications — providing reusable backend logic, APIs, and utilities to simplify Refurbed data synchronization and workflows.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors