Back to Refund & Returns Processing

Developer Handover Pack

Everything needed to build the automation from scratch: current state, full agent specs, data flow, and the build stack.

4 pagesPDF · Technical
FS-DOC-04Technical

Developer Handover Pack

Refund & Returns Processing

[YourCompany.com] · Customer Support Department · Prepared by FullSpec · [Today's Date]

This document is the authoritative technical reference for the Refund and Returns Processing automation. It covers the current-state step map, full agent specifications with IO contracts, the end-to-end data flow, and the recommended build stack. The FullSpec team owns everything described here through to deployment. Your team's responsibilities are limited to supplying confirmed eligibility rules, tool credentials, and approval for edge-case behaviour before build begins. All questions during build should go to support@gofullspec.com.

01Process snapshot

Step
Name
Description
1
Receive and Log Return Request
Agent reads inbound request from email, Zendesk, or store contact form and manually creates or updates a Zendesk ticket with customer and order details. Time cost: 8 min.
2
Locate Order in Shopify
Agent opens Shopify, searches by order number or customer email, and reviews order date, items, and fulfilment status. Time cost: 6 min.
3
Check Return Policy Eligibility
Agent manually compares order date against return window, checks item category against exclusions, and decides eligibility, often referencing a shared Google Doc or relying on memory. BOTTLENECK. Time cost: 7 min.
4
Escalate or Reject Out-of-Policy Requests
If outside policy, agent emails the customer a rejection or flags to a supervisor for discretionary override. Time cost: 10 min.
5
Approve Refund and Notify Customer
Agent sends customer a return approval email via Gmail with instructions, return label link if applicable, and expected refund timeframe. Time cost: 8 min.
6
Process Refund in Shopify
Agent navigates to the Shopify order and manually initiates the refund, selecting correct line items, quantities, and refund amount. Time cost: 5 min.
7
Log Refund in Accounting System
Agent opens Xero and manually records the refund transaction, matching the credit note to the correct customer and invoice. BOTTLENECK. Time cost: 8 min.
8
Update Zendesk Ticket and Close
Agent updates the Zendesk ticket with outcome, adds internal notes, sets status to resolved, and tags the ticket for reporting. Time cost: 5 min.
9
Notify Team of High-Value or Complex Cases
For refunds above a set dollar threshold or with disputed reasons, agent manually posts a summary to Slack to alert support lead or finance team. Time cost: 4 min.
Time cost summary: Total manual time per cycle is 61 minutes per return request. At approximately 120 requests per month (30 per week), this equates to 9 manual hours per week and approximately 39 hours per 30-day period. The automation replaces steps 1, 2, 3, 5, 6, 7, 8, and 9. Step 4 (escalation of disputed or out-of-policy requests) remains with a human decision-maker and is intentionally excluded from automation scope.
Developer Handover PackPage 1 of 4
FS-DOC-04Technical

02Agent specifications

Returns Triage Agent

Listens for new or updated Zendesk tickets carrying a return or refund label. Extracts the order reference and customer email from the ticket body or custom fields, calls the Shopify Orders API to retrieve full order data (date, line items, fulfilment status, total amount), and evaluates eligibility against the configured rule set: return window in days, excluded product categories, and any partial-refund logic. Produces a structured decision object containing an approved or flagged status, a reason code, the calculated refund amount, and the enriched order payload. This decision object is the sole input consumed by the Refund Execution Agent. The agent does not take any action on Zendesk or Shopify; it is read-only except for writing the decision record to the shared workflow state.

Trigger
A new Zendesk ticket is created or updated with a tag matching 'return' or 'refund'. The orchestration layer polls Zendesk every 2 minutes or receives a real-time webhook event from the Zendesk webhook target configured on the account.
Tools
Zendesk (ticket read, tag filter), Shopify (Orders API GET /admin/api/2024-01/orders/{order_id}.json)
Replaces steps
Steps 1, 2, 3 (ticket logging, order lookup, eligibility check)
Estimated build
14 hours. Complexity: Moderate.
// Input
zendesk.ticket.id          : string   // Zendesk ticket ID
zendesk.ticket.tags        : string[] // must include 'return' or 'refund'
zendesk.ticket.description : string   // raw customer message body
zendesk.ticket.custom_fields.order_reference : string // extracted or parsed
zendesk.requester.email    : string   // customer email address

// Output
decision.status            : 'approved' | 'flagged_for_review'
decision.reason_code       : string   // e.g. 'WITHIN_WINDOW', 'EXCLUDED_CATEGORY', 'PARTIAL_ELIGIBLE'
decision.refund_amount_usd : number   // calculated refund in USD
decision.order.id          : string   // Shopify order ID
decision.order.created_at  : ISO8601  // order creation date
decision.order.line_items  : object[] // [{id, title, quantity, price}]
decision.order.total_price : string   // original order total
decision.order.fulfillment_status : string
decision.zendesk_ticket_id : string   // passed forward for downstream steps
decision.customer_email    : string
  • Zendesk plan must be Suite Team or above; the webhook targets feature is required to push ticket events in real time. If the account is on a lower tier, fall back to polling the Zendesk Tickets API (GET /api/v2/tickets.json?status=new) every 2 minutes.
  • Shopify API key must have the read_orders scope. Confirm this scope is present on the private app or custom app credential before build. Do not use the storefront API; it does not expose order details.
  • The eligibility rule set must be finalised in writing before this agent is configured. Required parameters: return_window_days (integer), excluded_category_ids (string array mapped to Shopify product_type or tag values), partial_refund_rules (object), and high_value_threshold_usd (number).
  • Order reference extraction from the ticket body should use a regex pattern matching Shopify's default order name format (#\d{4,6}). Confirm the store's actual order name format before finalising the pattern.
  • If no order reference is found in the ticket body or custom fields, the agent must set decision.status to 'flagged_for_review' with reason_code 'NO_ORDER_REFERENCE' and halt. Do not attempt a customer-email-only Shopify search in production without explicit sign-off, as it can return multiple orders.
  • Dedupe: check the orchestration layer's execution log for the zendesk_ticket_id before processing. If a run for that ticket ID already completed within the last 60 minutes, skip and log 'DUPLICATE_SKIP'.
  • Confirm with the business owner whether Zendesk ticket tags are added manually by agents or automatically by a Zendesk trigger rule. This determines whether the webhook fires reliably or whether a polling fallback is required.
Refund Execution Agent

Receives the approved decision object from the Returns Triage Agent and executes all downstream fulfilment actions in sequence: sends a personalised approval email to the customer via Gmail, calls the Shopify Refunds API to process the refund against the correct order and line items, creates a matching credit note in Xero linked to the customer account and original invoice, evaluates whether the refund amount exceeds the configured high-value threshold and posts a structured Slack alert if so, and finally updates the Zendesk ticket with outcome notes, resolution tags, and solved status. Each action is executed sequentially with a success check before proceeding to the next. If any action fails, the agent logs the failure, halts further execution for that ticket, and posts an error alert to the designated Slack error channel without marking the Zendesk ticket as resolved.

Trigger
The Returns Triage Agent sets decision.status to 'approved' and the refund amount is a positive number. This is an internal workflow handoff, not an external webhook.
Tools
Gmail (send), Shopify (Refunds API POST), Xero (Credit Notes API POST), Slack (chat.postMessage), Zendesk (ticket update PUT)
Replaces steps
Steps 5, 6, 7, 8, 9 (approval email, Shopify refund, Xero credit note, Zendesk close, Slack alert)
Estimated build
18 hours. Complexity: Complex.
// Input (from Returns Triage Agent decision object)
decision.status            : 'approved'
decision.refund_amount_usd : number
decision.reason_code       : string
decision.order.id          : string
decision.order.line_items  : object[]
decision.customer_email    : string
decision.zendesk_ticket_id : string

// Output
gmail.message_id           : string   // sent email message ID for audit
shopify.refund.id          : string   // created refund ID
shopify.refund.status      : 'success' | 'error'
xero.credit_note.id        : string   // CreditNoteID from Xero
xero.credit_note.status    : 'AUTHORISED' | 'error'
slack.alert_posted         : boolean  // true if refund >= high_value_threshold_usd
slack.message_ts           : string   // Slack message timestamp if posted
zendesk.ticket.status      : 'solved'
zendesk.ticket.tags_added  : string[] // e.g. ['refund_processed', 'auto_resolved']

// On approval (internal state written to orchestration log)
execution_log.ticket_id    : string
execution_log.completed_at : ISO8601
execution_log.actions      : string[] // ordered list of completed action names
execution_log.errors       : object[] // [{action, error_message, timestamp}]
  • Gmail must be connected via OAuth 2.0 using the https://www.googleapis.com/auth/gmail.send scope on the service account or delegated user. The sending address must match the support email address customers expect. Confirm this address before build.
  • Shopify Refunds API requires the write_orders scope in addition to read_orders. The POST body must include the order ID, refund line items (line_item_id, quantity, restock: true|false), and the shipping refund amount if applicable. Confirm restock behaviour with the business owner before build.
  • Xero integration requires a connected app with the accounting.transactions scope (write) and accounting.contacts scope (read). The credit note must reference the Xero ContactID matched by customer email. If no ContactID is found, flag for manual creation and halt Xero step only; continue remaining actions.
  • The high-value Slack alert threshold (high_value_threshold_usd) must be confirmed in writing. Until confirmed, use a default of $150. The Slack channel name and channel ID must be provided before build. Post to channel ID, not channel name, to survive channel renames.
  • Gmail approval email must use a template stored in the orchestration layer's credential/template store. The template must be confirmed and signed off before go-live. Required merge fields: customer_first_name, order_id, refund_amount_usd, estimated_processing_days.
  • Zendesk ticket update must use PUT /api/v2/tickets/{ticket_id}.json with status: 'solved', tags appended (not replaced), and an internal comment recording the refund ID, Xero credit note ID, and execution timestamp.
  • If the Xero API returns a rate-limit error (HTTP 429), implement an exponential backoff retry: 5 seconds, 15 seconds, 45 seconds. After three failures, log and alert without blocking the remaining actions (Gmail, Shopify, Slack, Zendesk are not dependent on Xero success).
  • Disputed or out-of-policy tickets (decision.status = 'flagged_for_review') must not enter this agent. The orchestration layer routes them directly to a Zendesk ticket update that assigns the ticket to the Support Lead queue and adds the tag 'pending_manual_review'. No email or refund action is taken.
Developer Handover PackPage 2 of 4
FS-DOC-04Technical

03End-to-end data flow

Full trigger-to-output data flow for Refund and Returns Processing. Field names are exact identifiers used across agents and API calls.
// ── TRIGGER ──────────────────────────────────────────────────────────────────
// Source: Zendesk webhook event OR polling fallback every 2 minutes
INPUT {
  zendesk.ticket.id             // e.g. '98234'
  zendesk.ticket.tags           // must include 'return' or 'refund'
  zendesk.ticket.description    // raw customer message
  zendesk.ticket.custom_fields  // includes order_reference if set
  zendesk.requester.email       // customer email
}

// ── DEDUPE CHECK ─────────────────────────────────────────────────────────────
// Orchestration layer checks execution_log for ticket.id within last 60 minutes
// If found: log 'DUPLICATE_SKIP', halt execution
// If not found: continue

// ── AGENT HANDOFF 1: Returns Triage Agent ────────────────────────────────────
// Step A: Extract order reference from ticket.description using regex #\d{4,6}
//         Fallback: ticket.custom_fields.order_reference
//         If no reference found: set decision.status = 'flagged_for_review'
//                                reason_code = 'NO_ORDER_REFERENCE', halt

// Step B: Shopify Orders API lookup
GET /admin/api/2024-01/orders.json?name={order_reference}&status=any
RESPONSE {
  order.id                      // Shopify internal order ID
  order.name                    // e.g. '#10042'
  order.created_at              // ISO8601 order date
  order.financial_status        // 'paid' | 'partially_refunded' | etc.
  order.fulfillment_status      // 'fulfilled' | 'partial' | null
  order.total_price             // string, e.g. '89.00'
  order.line_items[]            // [{id, title, quantity, price, product_type}]
  order.customer.email          // cross-check against zendesk.requester.email
}

// Step C: Eligibility rule evaluation
//   rule.return_window_days     : integer (e.g. 30)
//   rule.excluded_category_ids  : string[] (Shopify product_type values)
//   rule.partial_refund_rules   : object (item-level override map)
//   rule.high_value_threshold_usd : number (e.g. 150)
//
//   Compute: days_since_order = today - order.created_at (in days)
//   If days_since_order > rule.return_window_days -> reason_code = 'OUTSIDE_WINDOW'
//   If order.line_items[].product_type in excluded_category_ids -> reason_code = 'EXCLUDED_CATEGORY'
//   Else -> reason_code = 'WITHIN_WINDOW', status = 'approved'
//   Calculate: decision.refund_amount_usd from eligible line items

DECISION_OBJECT {
  decision.status               // 'approved' | 'flagged_for_review'
  decision.reason_code          // string
  decision.refund_amount_usd    // number
  decision.order.id             // string
  decision.order.created_at     // ISO8601
  decision.order.line_items     // object[]
  decision.order.total_price    // string
  decision.zendesk_ticket_id    // string
  decision.customer_email       // string
}

// ── ROUTING DECISION ─────────────────────────────────────────────────────────
// IF decision.status == 'flagged_for_review':
//   PUT /api/v2/tickets/{zendesk_ticket_id}.json
//     -> assignee: Support Lead group ID
//     -> tags: append ['pending_manual_review']
//     -> internal comment: reason_code + order.id
//   HALT: no further automated actions

// IF decision.status == 'approved': continue to Refund Execution Agent

// ── AGENT HANDOFF 2: Refund Execution Agent ───────────────────────────────────

// Step D: Send approval email via Gmail
POST https://gmail.googleapis.com/gmail/v1/users/me/messages/send
PAYLOAD {
  to: decision.customer_email
  subject: 'Your return request has been approved'
  body: template.merge({
    customer_first_name,
    order_id: decision.order.id,
    refund_amount_usd: decision.refund_amount_usd,
    estimated_processing_days: 3
  })
}
RESPONSE { gmail.message_id }

// Step E: Issue refund in Shopify
POST /admin/api/2024-01/orders/{order.id}/refunds.json
PAYLOAD {
  refund.note: decision.reason_code,
  refund.refund_line_items: [
    { line_item_id, quantity, restock: true }
  ],
  refund.transactions: [{ amount: decision.refund_amount_usd, kind: 'refund' }]
}
RESPONSE { shopify.refund.id, shopify.refund.status }

// Step F: Create credit note in Xero
// Lookup: GET https://api.xero.com/api.xro/2.0/Contacts?where=EmailAddress=="{customer_email}"
// -> xero.contact.ContactID
// If not found: flag 'XERO_CONTACT_NOT_FOUND', skip Xero, continue
POST https://api.xero.com/api.xro/2.0/CreditNotes
PAYLOAD {
  Type: 'ACCREC',
  Contact: { ContactID: xero.contact.ContactID },
  Date: today_ISO8601,
  LineItems: [
    { Description: 'Refund - ' + decision.order.id,
      Quantity: 1,
      UnitAmount: decision.refund_amount_usd,
      AccountCode: '200' }
  ],
  Status: 'AUTHORISED'
}
RESPONSE { xero.credit_note.id, xero.credit_note.status }

// Step G: Conditional Slack alert
// IF decision.refund_amount_usd >= rule.high_value_threshold_usd:
POST https://slack.com/api/chat.postMessage
PAYLOAD {
  channel: slack.channel_id,
  text: 'High-value refund processed',
  blocks: [
    { type: 'section', text:
      'Order: ' + decision.order.id +
      ' | Amount: $' + decision.refund_amount_usd +
      ' | Customer: ' + decision.customer_email
    }
  ]
}
RESPONSE { slack.message_ts }

// Step H: Resolve and tag Zendesk ticket
PUT /api/v2/tickets/{zendesk_ticket_id}.json
PAYLOAD {
  ticket.status: 'solved',
  ticket.tags: append ['refund_processed', 'auto_resolved'],
  ticket.comment: {
    body: 'Refund processed automatically. ' +
          'Shopify refund ID: ' + shopify.refund.id + '. ' +
          'Xero credit note ID: ' + xero.credit_note.id + '. ' +
          'Executed at: ' + ISO8601_timestamp,
    public: false
  }
}

// ── EXECUTION LOG WRITTEN ─────────────────────────────────────────────────────
execution_log {
  ticket_id, completed_at, actions[], errors[]
}
Developer Handover PackPage 3 of 4
FS-DOC-04Technical

04Recommended build stack

Orchestration layer
A workflow automation platform running one workflow per agent: 'returns-triage-agent' and 'refund-execution-agent'. Both workflows share a single credential store containing all API keys and OAuth tokens, accessed via named credential references (not inline secrets). A shared workflow state object (JSON) is passed between the two workflows as the trigger payload for the Refund Execution Agent.
Webhook configuration
Configure a Zendesk webhook target pointing to the automation platform's inbound webhook URL for the returns-triage-agent workflow. Set the webhook to fire on ticket create and ticket update events where tags include 'return' or 'refund'. Enable the HMAC signature header (X-Zendesk-Webhook-Signature) and validate it on inbound requests. Store the webhook signing secret in the credential store. If the Zendesk plan does not support webhook targets, activate the polling fallback node (GET /api/v2/tickets.json?status=new, every 120 seconds).
Templating approach
Store the Gmail approval email template as a named template in the orchestration layer's template store. The template uses merge fields wrapped in double curly braces: {{customer_first_name}}, {{order_id}}, {{refund_amount_usd}}, {{estimated_processing_days}}. The template body and subject line must be confirmed and signed off by the business owner before the Refund Execution Agent goes live. Store the Slack alert block kit JSON as a second named template in the same store.
Error logging
Write all execution outcomes (success and failure) to a Supabase table named 'refund_automation_log' with columns: id (uuid), ticket_id (text), status (text), actions_completed (jsonb), errors (jsonb), created_at (timestamptz). On any action failure, in addition to writing the error row, post an alert to a dedicated Slack channel named '#automation-errors' using the same Slack connection used by the Refund Execution Agent. The alert must include the ticket_id, the failing action name, the HTTP status code returned, and a timestamp.
Testing approach
Build and test in sandbox environments first for all integrated tools: Shopify Partner sandbox store, Xero demo company, Zendesk sandbox account, Gmail test alias. The Slack error channel and alert channel should be set to a private test channel during the build phase. Run all test cases (approved return, out-of-policy rejection, no order reference, high-value threshold trigger, Xero contact not found, Shopify refund API failure) against sandbox before any credential swap to production. Confirm production credential swap with the FullSpec team at support@gofullspec.com before go-live.
Estimated total build time
Returns Triage Agent: 14 hours. Refund Execution Agent: 18 hours. End-to-end integration testing and QA: 6 hours. Total: 38 hours. This matches the confirmed build effort in the project snapshot. Delivery is structured across 4 weeks as per the delivery schedule.
Before build begins, the following must be confirmed in writing: (1) the complete eligibility rule set including return_window_days, excluded_category_ids, partial_refund_rules, and high_value_threshold_usd; (2) the Shopify API key scopes (read_orders and write_orders); (3) the Xero connected app with accounting.transactions write and accounting.contacts read scopes; (4) the Gmail sending address and OAuth delegated user; (5) the Slack channel IDs for the high-value alert channel and the automation-errors channel; and (6) the Zendesk plan tier to confirm whether webhook targets or polling is required. Send all credentials and confirmations to support@gofullspec.com.
Developer Handover PackPage 4 of 4

More documents for this process

Every document generated for Refund & Returns Processing.

Launch Plan
Operations · Owner
View
ROI and Business Case
Finance · Owner
View
Process Runbook / SOP
Operations · Owner
View
Integration and API Spec
Technical · Developer
View
Test and QA Plan
Quality · Developer
View