Trust is earned, not given

A different perspective

2022-08-16 · Projects

Shopify in C#, part 4: orders and draft orders — reading the ledger, sending the invoice

Part 4from the Shopify in C# series · 6 parts in all

Orders are Shopify's ledger, and draft orders are Shopify's answer to "invoice" — an order a merchant (or your API) composes for the customer, which Shopify can then present for payment. Part 4 reads real orders in GraphQL, creates draft orders, and sends the invoice email — both in REST and GraphQL, because integrations this year still speak both.

Reading orders: one query instead of five

// Order with its money, customer, line items and fulfillment state - the
// query that replaces the REST order + its four follow-up calls.
const string orderQuery = @"
query order($id: ID!) {
  order(id: $id) {
    name                          # #1042 - display id, not the numeric id
    createdAt
    displayFinancialStatus        # PAID / PENDING / REFUNDED / PARTIALLY_REFUNDED
    displayFulfillmentStatus      # FULFILLED / UNFULFILLED / PARTIALLY_FULFILLED
    totalPriceSet { shopMoney { amount currencyCode } }
    customer { firstName lastName email }
    lineItems(first: 20) {
      edges { node { title quantity
        originalUnitPriceSet { shopMoney { amount } } } }
    }
    transactions(first: 10) {
      edges { node { kind status amount { amount } gateway } }
    }
    fulfillments(first: 5) { edges { node { trackingInfo { number url company } } } }
  }
}";
// Fetch by the base64 global id ("gid://shopify/Order/450789469") - GraphQL
// ids are opaque strings; never assemble them by hand more than once.

Draft orders: the "invoice" lifecycle

// A draft order is an order you compose: line items (products OR custom
// items), a customer, optional discounts. Then either mark it paid, collect
// payment yourself, or - the invoice path - have Shopify email a pay link.
const string draftCreate = @"
mutation draftOrderCreate($input: DraftOrderInput!) {
  draftOrderCreate(input: $input) {
    draftOrder { id name invoiceUrl }
    userErrors { field message }
  }
}";
var data = await PostGraphAsync(http, draftCreate, new {
    input = new Dictionary<string, object?> {
        ["email"] = "[email protected]",
        ["note"] = "Quote #Q-2041",
        ["lineItems"] = new object[] {
            new Dictionary<string, object?> {
                ["title"] = "Consulting - March workshop",
                ["quantity"] = 1,
                ["price"] = "850.00",       // custom item: no variant needed
            }}});
// draftOrder.draftOrder.invoiceUrl is a pre-built payment page link - the
// "invoice URL" you could also mail yourself and paste into your own system.

const string sendInvoice = @"
mutation draftOrderInvoiceSend($id: ID!) {
  draftOrderInvoiceSend(draftOrderId: $id) {
    draftOrder { id status }     # status: open -> invoice_sent
    userErrors { field message }
  }
}";
await PostGraphAsync(http, sendInvoice, new { id = draftOrderId });
// Same lifecycle in REST (still perfectly serviceable in 2022):
//   POST /admin/api/2022-04/draft_orders.json
//        { "draft_order": { "email": "...", "line_items": [ { "title": "...",
//          "quantity": 1, "price": "850.00" } ] } }
//   POST /admin/api/2022-04/draft_orders/{id}/send_invoice.json
//   POST /admin/api/2022-04/draft_orders/{id}/complete.json   (mark paid)
//   GET  /admin/api/2022-04/orders.json?status=any     (drafts -> orders)
// Convention worth keeping: REST for quick scripts, GraphQL for anything
// with a payload shape you will maintain.

The draft → order transition, handled correctly

When the customer pays the invoice, Shopify converts the draft into a real order and fires draft_orders/update then order webhooks — your integration should key off the order side for fulfillment and the draft side only for quotes and reminders. That webhook stream is the same reliability problem Stripe solved in that series' part 6, with one twist (base64 HMAC) covered in this series' finale.

Next: what "payment intent" means in a Shopify world — transactions, gateways, and refunds you can calculate before you commit.