# Building an AI Clothes Changer: provider abstraction, async jobs, and a credit system that won't lose money

> Source: <https://dev.to/gxlbfc_d039fe229d0c50aa9e/building-an-ai-clothes-changer-provider-abstraction-async-jobs-and-a-credit-system-that-wont-2cc1>
> Published: 2026-06-17 08:16:53+00:00

I recently launched [Dressora](https://aiclotheschanger.me/), an AI clothes changer that swaps outfits onto a single photo for virtual try-on. The product side is fun, but the parts I actually sweated over were the boring backend bits: orchestrating multiple AI providers, handling long-running generation jobs, and building a credit system that never double-charges or loses money. Here's what I learned.

AI providers change pricing, rate limits, and quality constantly. Hardcoding one is a trap. I put everything behind a small factory:

``` js
const provider = getProvider("evolink");
const task = await provider.createTask({ prompt, aspectRatio });
```

Each provider implements the same interface (`createTask`

, `handleCallback`

, status mapping). Swapping or adding a provider is a new file, not a refactor. When one provider had an outage, switching the default was a one-line env change.

AI generation takes 10s–minutes. Blocking a request is a non-starter. The flow:

`generate()`

— create a DB record, `handleCallback()`

— download the result, re-upload to R2, mark complete, The frontend just polls a lightweight status endpoint. The webhook is the source of truth.

A gotcha: **always re-upload the provider's output to your own storage.** Provider URLs expire. Downloading and pushing to R2 on completion saved me from dead links later.

Money + concurrency + async failures = the scariest combination. The pattern that worked: **freeze → settle / release.**

`freeze(credits)`

— move credits to a "held" state`settle()`

— actually consume them`release()`

— give them back

``` php
freeze  -> hold created, balance reserved
settle  -> hold consumed (success)
release -> hold returned (failure)
```

This way a failed generation never costs the user, and a user can't fire 10 concurrent jobs with credits for one. I also did **FIFO consumption across credit packages** so credits with the nearest expiry get used first — fairer for users and simpler for accounting.

If you want to see the end result, it's live at [aiclotheschanger.me](https://aiclotheschanger.me/). Happy to answer questions about the architecture in the comments.
