Ahmed.Idris
All projects
Enterprise cloud licensing reseller · North AmericaUNDER NDA

Reseller Licensing Ops

An internal platform for a business that resells Google Workspace licences to other companies. Two systems run that business, and each keeps its own record of the same customers.
  • Zoho: the system that holds what was sold, the plan, the price, the billing schedule.
  • Google’s reseller console: the system that holds what is running on the client’s domain today.
  • Where they drift: a subscription on one side and not the other, seat counts that do not match, a new Google product with no plan in Zoho, customer details recorded differently in each.
  • This platform: one record holding both, and an alert the day they stop matching.
Role
Lead product designer & researcher
Team
Two designers, then three from halfway through
Timeline
May 2025 – mid 2026
Status
Live
FIG 01The AUR Monitor, the whole screen. An AUR is an additional user request: a client asking for extra seats on a subscription they already have. Each column is marked with the system it came from, Z for Zoho and R for the reseller console, and a request is approved once its invoice is paid.Recreated for this portfolio. Names, domains and identifiers are invented.
Outcome
11parts of the stack it replaceswas three platformsThe team worked in eleven places.The synthesis board lists them. The team’s own sheets, scripts, AUR panel, public form and notifications panel. Workspace Subscriptions, Google Voice and Google Vault in the reseller console. Zoho Billing, Books and CRM.
4kinds of mismatch in the buildSeats are only one of the four.A subscription in the console with none in Zoho. Seat counts that do not match. A new Google product not yet mapped to a Zoho plan. Customer details recorded differently in each of the two systems.
4notices before a subscription billswas a manual reminderA renewal is seen before it happens.The discovery document sets notices at 45, 30, 14 and 7 days before the renewal date. The delivered screens carry the auto-renewal setting, the next renewal date and a dunning state, so a renewal is read on the page instead of in the console.
None of these are measured outcomes. They are counts, taken from the build, from the client’s discovery document, and from the synthesis board’s inventory of the old stack. Nothing on this project was measured.
The problem behind the brief

The brief was a dashboard over the Google reseller console. The console was not short of information. The problem was that it and Zoho held different records of the same client, and nothing compared them.

Zoho had what the client agreed to pay for. The console had what the client was using. When the two did not match, somebody was using seats that nobody was charging for, and it stayed that way until a person went looking. So the platform does not just show both numbers. It compares them, and every difference it finds becomes a job with a status, a note of how long it has been open, and a way to close it.

My part

I mapped the work as it was being done, step by step, and marked where it broke. I planned how the findings would be put together on a shared online board the whole team worked on. What the operators said sits beside the step that produced it. The opportunities come after. With the operations team I put those in order and decided what to build first. I then drew the account and subscription model, the discrepancy and request flows, and the client-facing request portal. Each one went from wireframe to final design. I ran the testing sessions with operators and reworked the designs from what they showed. I also built the component library and tokens this platform shares with the other internal tools.

The team

Two designers, and three from halfway through. Both were early in their careers. Alongside the design work I was running reviews and pairing on components. I also wrote down rules we had been improvising, so that the three of us stayed consistent across the platform.

Confidentiality

Everything on this page is a recreation. The client is not named; their branding, real identifiers, customer data and internal documents are replaced with fictional equivalents built for this portfolio. Figures appear only where they describe my own process, and anything commercially sensitive is left out.

The reseller is not named, and neither are its clients: every account, domain, e-mail address, telephone number, order id and seat count in these screens is invented. Zoho and the Google reseller console are named because they are off-the-shelf platforms that thousands of resellers run on; naming them says nothing about how this one works.

Discovery

Two consoles, one client

Operators worked across two monitors and moved information between them by hand. Zoho held the commercial commitment: start dates, negotiated pricing, billing schedule. The console held what was running: seats assigned, products provisioned, domain state.

Neither was wrong. They answered different questions and updated at different times, and nothing reconciled the two.

Four teams inside the reseller work from these two systems every day. None of them are the clients. They are the people who bill the accounts, renew them, look after them, and answer for the revenue.

Team at the resellerWhat they are trying to doWhat has to be on screen
BillingAccurate invoices, holds, and what a change made mid-month costsHow often they are billed, payment mode, how long a mismatch has been open, and the charge for the days left in the month
Account managementRenewals and expansionContract end dates, how many seats are in use, plan level
Client successAccount health and onboardingActive seats, recent requests for extra seats, flags
Operations leadershipPlatform health and revenueRecurring revenue, how often the sync with Zoho or the reseller console fails, how many seat requests are getting through
Account NameZMain DomainRCustomer TypeZ
Product NameZUsersRPlanR
ZCame from ZohoRCame from the Google reseller console
Every field says where it came fromA column header carries a letter for its source system. When the two numbers differ, the first question is which system said what. The letter answers that in the table, so there is no need to open either system.
FIG 02Two header cells from the queue, at the size they are read.Recreated for this portfolio. The column names are the product’s own.
Process mapping

Eleven flows, and where they break

Before anything was designed, the operation was mapped: eleven end-to-end flows across licensing, sales and client success. Requests for extra seats, subscription lifecycle, suspension, payment tracking, cancellation, releasing a customer, plan changes, seat reductions, creating a plan for a new product, the monthly tax add-on, and the notification checks across them. Each flow has a lane per role, names the system behind every step, and marks where it breaks.

The sum that decided what the product is
01A customer on a flexible plan holds four thousand seats.
02On the second of the month they add five hundred more, and those seats start working immediately.
03Billing runs monthly, on the cycle that has already closed.
04The invoice for those five hundred seats arrives at the start of the following month.
Unbilled
~30days of seats delivered and not billed, for each late addition. An operator found it by reading two screens. No report showed it.

The problems came out of that mapping. Each flow is a run of steps. A step is one action by one person in one system. Suspend the subscription in the reseller console. Void the last invoice in Zoho. We drew the flows on that same shared board, and for every step the operators said what went wrong at that point in the job. It was written against that step, not collected into one list at the end. The four below are theirs, as they were written on the board. Keeping each problem next to the step that caused it is what showed the product had to compare the two systems rather than report on them.

New products and pricing updates require manual syncing, leading to inconsistency or delays.
Licensing operationsRECORDED
Duplicate invoices can occur, and there’s no clear way to track or validate them. This leads to confusion and extra back-and-forth between teams.
Licensing operationsRECORDED
When a subscription is in the dunning state, there is no next renewal date generated. This prevents the creation of a request, causing delays and confusion — especially when urgent actions are needed.
Licensing operationsRECORDED
Both manual suspension and cancel-at-renewal display as ‘Suspended’ in the console. The only way to distinguish them is by checking the subscription history.
Licensing operationsRECORDED

Two details from that mapping changed the product. The first was the way clients are reminded to pay. That already ran to a fixed schedule: reminders at forty-five, thirty, fourteen and seven days before the renewal date, then thirty days’ grace before the subscription is suspended. Those dates came from the business, not from the design, so the platform automates the schedule it found instead of proposing a new one. The second was a single word. In the upstream console, ‘Suspended’ covered two things. A manual suspension, and a cancellation set for the renewal date. The only way to tell them apart was to open the subscription history. That is why every status in this product says which system it came from.

Alerting

Finding out, without looking

Before this platform, a mismatch was found because somebody went looking for it. Two routines did that work: a licensing dashboard read every day, and a weekly email of error checks. Anything that happened between those two routines waited.

Both sat on top of the notification channel that comes with the Google reseller console. Its messages were inconsistent enough that one operator kept a separate sheet to track what it had and had not sent. Some events produced no message at all: when Google suspends an account for passing its storage limit, the reseller is not told. Duplicated seat-addition requests, where the same extra seats are added a second time, were caught only by a comparison that ran monthly, so one could sit open for weeks.

The platform compares the two systems on a schedule and raises what it finds on the record itself. Four kinds were specified for the build: a subscription in the console with none in Zoho, seat counts that do not match, a new Google product not yet mapped to a Zoho plan, and customer details recorded differently in each system. Nobody has to remember to check.

Solutions
A sync on a schedule, and a button to run one nowThe platform syncs on a set interval. Every subscription and every customer profile also has a manual sync, for the moment before a renewal when an operator needs to be sure of what they are looking at.
Reminders can be switched off for one customerPayment reminders to clients run on the schedule the business already used. Enterprise accounts that do not follow it can be excepted at the customer level, rather than by remembering not to send them.
A confirmation for every completed actionSeat changed, plan changed, subscription cancelled, subscription added, seat request approved, seat request voided, user added, user removed. Each has its own confirmation, so an operator is not left checking whether the change took.
The reframe

A discrepancy is an object

The mismatch is not reported somewhere else. It appears on the subscription it concerns: a yellowish band above the record, naming the two counts that do not match, with the action that resolves it inside the band.

The disputed field is tinted yellow in the record below, so the operator can see which number is in question. The fix is applied from here, so nobody opens the other console by hand.

FIG 03One client’s subscription record, shown with a discrepancy open.Recreated for this portfolio. Identifiers and seat counts are invented.
Information architecture

One client, eight subscriptions

Enterprise accounts run eight or more concurrent products, each with its own plan, seat count, billing schedule, and state of sync with the other system. A flat table of that is unreadable: horizontal scroll, truncated names, and the important number off the right edge of the screen.

Solutions
Account rowThe numbers that decide whether you need to look: domain, admin, monthly revenue, and one overall flag.
Subscription listExpands in place, grouped by product family. Plan, seats assigned against seats licensed, billing schedule, and whether the two agree.
The two systems, side by sideOpening a subscription shows the two systems side by side, each with its own identifiers, counts and state, and the disputed field tinted in both.
FIG 04The first level: every client the reseller has, one row each. The flag is the first column, so a row that needs attention is marked before the name. Every column header carries the mark of the system the value came from. The Actions column stays pinned to the right as the table scrolls sideways.Recreated for this portfolio. Names, addresses, domains and identifiers are invented.
Trade-offs

Key decisions

The first two make the consequence visible before it is committed. The third I got wrong, and testing showed it.

Decision 1

Timing is a required field

Every plan change, seat adjustment and cancellation asks when it takes effect before it can be submitted. It also states what each option costs. Immediate, immediate with a charge for the days left in the month, or queued for the renewal date. Most disputes here were about when a change took effect, not about the change itself.

Decision 2

Readable plan names instead of raw ids

Operators were reading subscription IDs against documentation to work out which product they were looking at. The interface now shows readable plan codes with product badges. The raw identifiers are still there, with a copy button, for anyone debugging.

Rejected direction

Account notes behind a floating button

Notes and history sat behind a floating action button in the corner. Testers read it as a support chat widget and never opened it. That included accounts whose first note said not to change seat counts without approval. Replaced with a counter in the header and a pinned banner for anything critical.

Testing

What testing changed

The prototype was a Figma file that did not work. One person from the client clicked through it on a call while three others watched and interrupted. That still shows whether someone can find what they need, and whether the words on screen mean what we think they mean.

Solutions
When the change takes effectChanging a plan, changing the seat count and cancelling a subscription all read as finished without saying when the change happened. One of the people watching stopped the walkthrough over it. An immediate change raises an invoice, so whoever is doing it has to know which one they picked. All three dialogs now ask. The choice is Immediately or On Next Billing Date, and it cannot be left blank.
Flags on the leftStatus flags sat in the last column, which on a laptop is off the edge of the screen. They are now the first thing in the row, before the account name.
Opening a customer still needs a buttonThe person driving clicked the customer’s name. That is not what opens the profile; a View Details button is. He said he opens whatever is already in front of him before he goes looking. The name is still not a link.
The word trigger does two jobsAutomated triggers meant nothing to him, and he asked whether it meant emails. Two other people on the call worked out between them that it covered the billing reminders. The field in the dialog below is also called Select Trigger. The word was never sorted out.
Change Plan
Select a new plan to change to. (Current plan is Flexible)
New Plan *
Select a plan
Select Trigger *
Select Trigger
Immediately
On Next Billing Date
CancelChange Plan
FIG 05The Change Plan dialog after the session. Both fields are required, so a change cannot be submitted without saying when it takes effect. The same field went onto the seat change and the cancellation dialogs.Recreated for this portfolio. The plan names are invented.
Scope

What was cut, and why

The platform was designed in full and then reduced for cost and time. Four modules were removed, each costed at two weeks of build. There are two design files for this project, one before the reduction and one after.

Removed from scope
Action historyEvery change made in the app, stored as a log, with filters by type and date and a search across them.
Billing historyInvoices, estimates and credit notes pulled from the billing system, with filters matching the ones operators already used there.
Communication logEvery message the platform sends, and every one it has scheduled, with notes against each one.
Saved presetsFilter and column selections kept per module, so a view an operator builds once can be used again.

The customer profile lost most of its second layer with them. Notes at profile and subscription level. Tags by team. Profile states such as an upgrade in progress. The per-subscription cards for billing and seat-request history. The notes work described in the decisions above was designed, tested, and then cut.

What survived is the reconciliation and the alerts. What went is the record of what had happened: the logs, the history, the notes. That is the material a team reads after something goes wrong, and it was cut first because the platform still runs without it.

Guardrails

Preventing the wrong action

A large part of this platform’s job is stopping people doing things that cause a problem later. If a client has an unpaid invoice, nobody can add seats to their account. The billing system calls that state dunning. The buttons stay on the screen, switched off, and hovering over one says why and what to do about it. An operator can tell being blocked from something being broken.

Clients ask for extra seats on a public form. There is no sign-in, so the form works out who they are from what they type: domain name, contact name, telephone number and e-mail. It checks the domain first, and answers in one of four ways.

  • No domain was given.
  • The domain is not one this reseller holds.
  • The domain is known, but the other details do not match the record.
  • This client is handled offline. The request is taken, and the seats are added by hand.

The form has a cart, so one client can ask for several subscriptions at once, each with its own number of seats. They pay with the card already on file. The request is approved as soon as the invoice is paid, and accounts on the trusted list are approved straight away.

Error messages work the same way. Both systems answer with a code. The platform turns each one into a sentence that says what went wrong, and gives the operator something to do.

What came backWhat the operator readsWhat they can do
DOMAIN_HELDThe domain is verified against another reseller account.Open the transfer guide
AUTH_EXPIREDThe Zoho connection has expired. Sign in again.Re-authenticate
UNMAPPED_SKUA new add-on was found that this system doesn’t recognise yet.Assign a category
PAYMENT_FAILEDThe account is suspended for non-payment. Changes are locked.Review billing
Design system

One vocabulary, several tools

The platform is one of several internal tools that share a component library and a token set. A change to a control reaches all of them at once. Four button levels, used strictly: one solid primary per view, outline for secondary work, ghost for utilities, and a danger state reserved for things that cannot be undone.

A rule decides when a banner appears. An alert cannot be dismissed, sits above the thing it concerns, and has to carry a resolution. An announcement can be dismissed, sits at the top of the page, and carries a link at most. Without that rule every message becomes an alert and operators stop reading all of them. The same library holds the confirmations for each completed action, so a message about a mismatch never looks like a message about a saved change.

The internal design system
Retrospect

What I’d do differently

Both of these cost the project time later on.

In hindsight
Prioritise the features together with the developersPutting the research together took longer than planned, and the client was waiting to see something. So the prioritisation we had planned with the development team was cut short, and I decided on my own what went into the first version. That call needed the people who would build it, and an estimate of what each thing would cost to build.
Get the client’s real budget and appetite in writing, earlyWe worked out what the client wanted as we went, so they had no clear picture of what the features we were proposing would cost in money, time or scope. When that became concrete, a large part of what was already designed had to be cut, and what survived was rewired to fit the smaller product. Knowing it at the start would also have given the prioritisation above the time it needed.
Next projectVehicle Operations Platform