Triển khai vòng đời của đơn đặt hàng

Hướng dẫn này cung cấp thông tin tham khảo kỹ thuật về API và giản đồ tải trọng để gửi thông tin cập nhật đầy đủ về trạng thái đơn đặt hàng, sự kiện thực hiện đơn hàng và các khoản điều chỉnh cho Google bằng cách sử dụng webhook cho phiên bản 2026-04-08 của Giao thức thương mại toàn cầu (UCP).

Trước khi tạo các điểm cuối, hãy đảm bảo rằng bạn đã xem Tổng quan về vòng đời của đơn đặt hàng để biết các khái niệm cấp cao, sự kiện bắt buộc và thông tin chi tiết về điểm cuối webhook.

Xác thực và ký yêu cầu

Những thay đổi chính trong phiên bản 2026-04-08 bao gồm việc giới thiệu các tiêu đề webhook bắt buộc mới và các quy trình ký yêu cầu cụ thể.

Tiêu đề webhook bắt buộc

Các tiêu đề HTTP sau đây là bắt buộc đối với tất cả các yêu cầu webhook:

  • Webhook-Id: Giá trị nhận dạng duy nhất cho sự kiện webhook cụ thể này. Mã nhận dạng này phải khớp với id của sự kiện chính đang được gửi (ví dụ: mã sự kiện thực hiện hoặc mã sự kiện điều chỉnh).
  • Webhook-Timestamp: Dấu thời gian cho biết thời điểm xảy ra sự kiện.

Các tiêu đề này thay thế các trường idcreated_time mà trước đây được dự kiến trong phần dữ liệu thực tế của đơn đặt hàng.

Yêu cầu ký

  1. Tính toán chuỗi đại diện SHA-256 của nội dung yêu cầu thô và đặt tiêu đề Content-Digest.
  2. Chọn một khoá ký trong signing_keys trong hồ sơ UCP.
  3. Tạo cơ sở chữ ký theo RFC 9421.
    • Xem quy cách cho các thành phần đã ký
  4. Đặt tiêu đề UCP-Agent, Signature-InputSignature.
    • UCP-Agent là đường liên kết đến hồ sơ UCP của bạn ở định dạng profile="https://merchant.example.com/.well-known/ucp".
    • Signature-Input là một trường có cấu trúc dạng từ điển mô tả các thành phần có trong chữ ký, cũng như keyid được dùng để ký. này phải khớp với kid của khoá ký mà bạn đã chọn trong signing_keys trong hồ sơ UCP.
    • Tiêu đề Signature chứa cơ sở chữ ký của bạn được ký bằng khoá riêng tư của bạn rồi mã hoá dưới dạng base64.

Hãy xem hướng dẫn ký trên ucp.dev để biết thêm thông tin.

Sự kiện đã tạo đơn đặt hàng

  • Gửi thư: Ngay sau khi đơn đặt hàng được xác nhận (status: processing).

Các thay đổi chính trong phiên bản này:

  • Trường currency hiện là trường bắt buộc ở cấp cao nhất của đối tượng Đơn đặt hàng.
  • Trường type trong mỗi đối tượng trong mảng totals hiện là một chuỗi mở (ví dụ: "tổng phụ", "thuế", "phí", "tổng").
  • Tiêu đề Webhook-Id bắt buộc chứa giá trị nhận dạng duy nhất cho sự kiện webhook. Xin lưu ý rằng bạn vẫn phải có trường id trong tải trọng chứa mã xác nhận đơn đặt hàng.
  • Tiêu đề Webhook-Timestamp bắt buộc cung cấp thời gian tạo, thay thế trường created_time trước đây trong tải trọng.

Ví dụ: Ví dụ này cho thấy một đơn đặt hàng được tạo sau khi người mua hoàn tất quy trình thanh toán.

Tiêu đề bắt buộc:

  • Webhook-Id: order_01
  • Webhook-Timestamp: 2026-03-23T19:00:00Z
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.order": [{"version": "2026-04-08"}]
    }
  },
  "id": "order_01",
  "checkout_id": "checkout_01",
  "currency": "USD",
   // Always include all line items, even for single-item checkouts. This ensures any add-ons, gifts, or separate charges are accounted for.
  "line_items": [
    {
      "id": "line_1",
      "item":
        {
          "id": "product_123",
          "title": "Running Shoes",
          "price": 10000
        },
      "quantity": { "total": 1, "fulfilled": 0 },
      "totals": [
        {"type": "subtotal", "display_text": null, "amount": 10000},
        {"type": "total", "display_text": null, "amount": 10000}
      ],
      // The status of a line item must match total and fulfilled quantities (e.g., total 1, fulfilled 0 -> status: processing).
      "status": "processing"
    }
  ],
  "totals": [
    {"type": "subtotal", "display_text": "Subtotal", "amount": 10000}, // Tax-inclusive markets: Set display_text to "Subtotal (including taxes)". Amount must include tax.
    {"type": "fee", "display_text": "Service Fee", "amount": 100},
    {"type": "tax", "display_text": "Tax", "amount": 800}, // Tax-inclusive markets: Omit this entry.
    {"type": "total", "display_text": "Total", "amount": 10900}
  ],
  "fulfillment": {
    "expectations": [
      {
        "id": "exp_1",
        "line_items": [{ "id": "line_1", "quantity": 1 }],
        "method_type": "shipping",
        "destination": {
          "first_name": "Alice",
          "last_name": "Example",
          "street_address": "123 Main St",
          "address_locality": "Austin",
          "address_region": "TX",
          "address_country": "US",
          "postal_code": "78701"
        },
        "description": "Arrives in 2-3 business days",  // Maximum length: 200 characters.
        "fulfillable_on": "now"
      }
    ]
  },
  "permalink_url": "https://merchant.example.com/orders/789"
}

Sự kiện thực hiện

Các sự kiện này được gửi trong mảng fulfillment.events.

Đơn đặt hàng đã được vận chuyển

Khi các mặt hàng trong đơn đặt hàng đã được vận chuyển. Bạn phải điền các trường tracking_numbertracking_url cho các sự kiện đã vận chuyển, vì bạn cần điền các trường này để nhóm các mặt hàng một cách chính xác trên trang "Đơn đặt hàng của tôi".

Đã giao đơn đặt hàng

Khi các mặt hàng trong đơn đặt hàng đã được giao.

Ví dụ (shippeddelivered): Ví dụ này cho thấy thông tin cập nhật về đơn đặt hàng sau khi mặt hàng đã được vận chuyển và giao hàng.

Tiêu đề bắt buộc:

  • Webhook-Id: fulfill_evt_2
  • Webhook-Timestamp: 2026-02-10T14:00:00Z
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.order": [{"version": "2026-04-08"}]
    }
  },
  "id": "order_01",
  "checkout_id": "checkout_01",
  "currency": "USD",
  "line_items": [
    {
      "id": "line_1",
      "item":
        {
          "id": "product_123",
          "title": "Running Shoes",
          "price": 10000
        },
      "quantity": { "total": 1, "fulfilled": 1 },
      "totals": [
        {"type": "subtotal", "amount": 10000},
        {"type": "total", "amount": 10000}
      ],
      "status": "fulfilled"
    }
  ],
  "totals": [
    {"type": "subtotal", "amount": 10000},
    {"type": "total", "amount": 10000}
  ],
  // Updated fulfillment details
  "fulfillment": {
    "events": [
      {
        "id": "fulfill_evt_1",
        "occurred_at": "2026-02-08T10:30:00Z",
        "type": "shipped",
        "line_items": [{ "id": "line_1", "quantity": 1 }],
        "tracking_number": "123456789",
        "tracking_url": "https://fedex.com/track/123456789",
        "carrier": "FedEx",
        "description": "Shipping departed from warehouse"
      },
      {
        "id": "fulfill_evt_2",
        "occurred_at": "2026-02-10T14:00:00Z",
        "type": "delivered",
        "line_items": [{ "id": "line_1", "quantity": 1 }],
        "tracking_number": "123456789",
        "tracking_url": "https://fedex.com/track/123456789",
        "carrier": "FedEx",
        "description": "Package delivered"
      }
    ],
    "expectations": [{ "...": "..." }]
  },
  "permalink_url": "https://merchant.example.com/orders/123"
}

Ví dụ về đơn đặt hàng có nhiều mặt hàng

Các ví dụ sau đây minh hoạ cách cấu trúc thông tin cập nhật cho đơn đặt hàng có nhiều mặt hàng và lô hàng tách biệt. Để biết các quy tắc về cách xác định trạng thái gói hàng, hãy xem bài viết Cách xác định trạng thái gói hàng.

Đơn đặt hàng nhiều mặt hàng, giao hàng trong cùng một gói

Ví dụ này cho thấy thông tin cập nhật về đơn đặt hàng cho một gói duy nhất chứa nhiều mặt hàng. Tất cả các mục hàng đều được nhóm trong một sự kiện shipped duy nhất có cùng số vận đơn.

Tiêu đề bắt buộc:

  • Webhook-Id: fulfill_evt_1
  • Webhook-Timestamp: 2026-02-08T10:30:00Z
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.order": [{"version": "2026-04-08"}]
    }
  },
  "id": "order_multi_01",
  "checkout_id": "checkout_multi_01",
  "currency": "USD",
  "line_items": [
    {
      "id": "line_1",
      "item": { "id": "product_1", "title": "Item 1", "price": 5000 },
      "quantity": { "total": 1, "fulfilled": 1 },
      "totals": [
        {"type": "subtotal", "display_text": null, "amount": 5000},
        {"type": "total", "display_text": null, "amount": 5000}
      ],
      "status": "fulfilled"
    },
    {
      "id": "line_2",
      "item": { "id": "product_2", "title": "Item 2", "price": 5000 },
      "quantity": { "total": 1, "fulfilled": 1 },
      "totals": [
        {"type": "subtotal", "display_text": null, "amount": 5000},
        {"type": "total", "display_text": null, "amount": 5000}
      ],
      "status": "fulfilled"
    }
  ],
  "totals": [
    {"type": "subtotal", "display_text": "Subtotal", "amount": 10000},
    {"type": "fee", "display_text": "Shipping", "amount": 500},
    {"type": "total", "display_text": "Total", "amount": 10500}
  ],
  "fulfillment": {
    "expectations": [
      {
        "id": "exp_1",
        "line_items": [
          { "id": "line_1", "quantity": 1 },
          { "id": "line_2", "quantity": 1 }
        ],
        "method_type": "shipping",
        "destination": {
          "first_name": "Alice",
          "last_name": "Example",
          "street_address": "123 Main St",
          "address_locality": "Austin",
          "address_region": "TX",
          "address_country": "US",
          "postal_code": "78701"
        },
        "description": "Standard Shipping",
        "fulfillable_on": "now"
      }
    ],
    "events": [
      {
        "id": "fulfill_evt_1",
        "occurred_at": "2026-02-08T10:30:00Z",
        "type": "shipped",
        "line_items": [
          { "id": "line_1", "quantity": 1 },
          { "id": "line_2", "quantity": 1 }
        ],
        "tracking_number": "123456789",
        "tracking_url": "https://fedex.com/track/123456789",
        "carrier": "FedEx",
        "description": "Both items shipped together"
      }
    ]
  },
  "permalink_url": "https://merchant.example.com/orders/456"
}

Đơn đặt hàng nhiều mặt hàng, giao hàng riêng

Ví dụ này cho thấy thông tin cập nhật về đơn đặt hàng đối với các lô hàng tách rời. Có nhiều sự kiện shipped, mỗi sự kiện tham chiếu đến line_items cụ thể trong gói tương ứng đó, với các số theo dõi riêng biệt. Vì mỗi gói hàng thể hiện một cam kết riêng biệt về việc thực hiện đơn hàng (ví dụ: tốc độ và chi phí khác nhau trong quy trình thanh toán nhiều nhóm), nên expectations cũng được chia tách.

Tiêu đề bắt buộc:

  • Webhook-Id: fulfill_evt_2
  • Webhook-Timestamp: 2026-02-09T14:00:00Z
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.order": [{"version": "2026-04-08"}]
    }
  },
  "id": "order_multi_02",
  "checkout_id": "checkout_multi_02",
  "currency": "USD",
  "line_items": [
    {
      "id": "line_1",
      "item": { "id": "product_1", "title": "Item 1", "price": 5000 },
      "quantity": { "total": 1, "fulfilled": 1 },
      "totals": [
        {"type": "subtotal", "display_text": null, "amount": 5000},
        {"type": "total", "display_text": null, "amount": 5000}
      ],
      "status": "fulfilled"
    },
    {
      "id": "line_2",
      "item": { "id": "product_2", "title": "Item 2", "price": 5000 },
      "quantity": { "total": 1, "fulfilled": 1 },
      "totals": [
        {"type": "subtotal", "display_text": null, "amount": 5000},
        {"type": "total", "display_text": null, "amount": 5000}
      ],
      "status": "fulfilled"
    }
  ],
  "totals": [
    {"type": "subtotal", "display_text": "Subtotal", "amount": 10000},
    {"type": "fee", "display_text": "Shipping", "amount": 1500},
    {"type": "total", "display_text": "Total", "amount": 11500}
  ],
  "fulfillment": {
    "expectations": [
      {
        "id": "exp_1",
        "line_items": [{ "id": "line_1", "quantity": 1 }],
        "method_type": "shipping",
        "destination": {
          "first_name": "Alice",
          "last_name": "Example",
          "street_address": "123 Main St",
          "address_locality": "Austin",
          "address_region": "TX",
          "address_country": "US",
          "postal_code": "78701"
        },
        "description": "Standard Shipping",
        "fulfillable_on": "now"
      },
      {
        "id": "exp_2",
        "line_items": [{ "id": "line_2", "quantity": 1 }],
        "method_type": "shipping",
        "destination": {
          "first_name": "Alice",
          "last_name": "Example",
          "street_address": "123 Main St",
          "address_locality": "Austin",
          "address_region": "TX",
          "address_country": "US",
          "postal_code": "78701"
        },
        "description": "Express Shipping",
        "fulfillable_on": "now"
      }
    ],
    "events": [
      {
        "id": "fulfill_evt_1",
        "occurred_at": "2026-02-08T10:30:00Z",
        "type": "shipped",
        "line_items": [{ "id": "line_1", "quantity": 1 }],
        "tracking_number": "PKG1_TRACKING",
        "tracking_url": "https://fedex.com/track/PKG1_TRACKING",
        "carrier": "FedEx",
        "description": "First item shipped in package 1"
      },
      {
        "id": "fulfill_evt_2",
        "occurred_at": "2026-02-09T14:00:00Z",
        "type": "shipped",
        "line_items": [{ "id": "line_2", "quantity": 1 }],
        "tracking_number": "PKG2_TRACKING",
        "tracking_url": "https://fedex.com/track/PKG2_TRACKING",
        "carrier": "FedEx",
        "description": "Second item shipped in package 2"
      }
    ]
  },
  "permalink_url": "https://merchant.example.com/orders/457"
}

Ví dụ về sự kiện điều chỉnh

Các ví dụ sau đây minh hoạ cách cấu trúc thông tin cập nhật cho việc hoàn tiền, trả lại và huỷ. Để biết danh sách các sự kiện được hỗ trợ và định nghĩa của các sự kiện đó, hãy xem Sự kiện điều chỉnh trong phần Tổng quan về vòng đời của đơn đặt hàng.

Huỷ đơn đặt hàng và hoàn tiền

Ví dụ này cho thấy một đơn đặt hàng đã bị huỷ và được hoàn tiền ngay sau khi được đặt.

Các thay đổi chính trong phiên bản này trong ví dụ này:

  • Những mục hàng chịu ảnh hưởng của cancellation hiện sử dụng "status": "removed" trong mảng line_items chính.
  • Khi line_items.statusremoved:
    • line_items.quantity.total được đặt thành 0.
    • Số lượng ban đầu được lưu trữ trong trường line_items.quantity.original mới.

Tiêu đề bắt buộc:

  • Webhook-Id: adj_refund_1
  • Webhook-Timestamp: 2026-02-09T11:05:00Z
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.order": [{"version": "2026-04-08"}]
    }
  },
  "id": "order_02",
  "checkout_id": "checkout_02",
  "currency": "USD",
  "line_items": [
    {
      "id": "line_2",
      "item": {
        "id": "product_456",
        "title": "Smart Watch",
        "price": 29900
      },
      "quantity": {
        "total": 0,  // Item removed from order total.
        "original": 1,  // Original quantity before removal.
        "fulfilled": 0 //  Item was not fulfilled before cancellation.
      },
      "totals": [
        {"type": "subtotal", "amount": 29900},
        {"type": "tax", "amount": 2400},
        {"type": "total", "amount": 32300}
      ],
      "status": "removed" // Item is cancelled, returned, or refunded.
    }
  ],
  "totals": [
    {"type": "subtotal", "amount": 29900},
    {"type": "tax", "amount": 2400},
    {"type": "total", "amount": 32300}
  ],
    // Fulfillment expectations should still be present even if cancelled early.
  "fulfillment": {
    "expectations": [
      {
        "id": "exp_1",
        "line_items": [{ "id": "line_2", "quantity": 1 }],
        "method_type": "shipping",
        "destination": {
          "first_name": "Bob",
          "last_name": "Consumer",
          "street_address": "456 Oak Ave",
          "address_locality": "Anytown",
          "address_region": "CA",
          "address_country": "US",
          "postal_code": "90210"
        },
        "description": "Standard Shipping",
        "fulfillable_on": "now"
      }
    ]
    // "events": [] // No fulfillment events occurred before cancellation.
    },
  "adjustments": [
    {
      "id": "adj_cancel_1",
      "type": "cancellation",
      "description": "Customer changed mind",
      "line_items": [{ "id": "line_2", "quantity": -1 }],
      "occurred_at": "2026-02-09T11:00:00Z",
      "status": "completed"
    },
    {
      "id": "adj_refund_1",
      "type": "refund",
      "description": "Refund for cancelled item",
      "line_items": [{ "id": "line_2", "quantity": -1 }],
      "totals": [
        {"type": "subtotal", "display_text": "Subtotal", "amount": -29900}, // Negative amounts indicate money returned to the buyer. Tax-inclusive markets: Set display_text to "Subtotal (including taxes)". Amount must include tax.
        {"type": "tax", "amount": -2400}, // Tax-inclusive markets: Omit this entry.
        {"type": "total", "display_text": "Total", "amount": -32300}
      ],
      "occurred_at": "2026-02-09T11:05:00Z",
      "status": "completed"
    }
  ],
  "permalink_url": "https://merchant.example.com/orders/12345"
}

Trả lại và hoàn tiền cho đơn đặt hàng

Ví dụ này cho thấy một đơn đặt hàng mà mặt hàng đã được vận chuyển, giao hàng, sau đó được trả lại và hoàn tiền.

Các thay đổi chính trong phiên bản này trong ví dụ này:

  • Các mục hàng chịu ảnh hưởng của return hiện sử dụng "status": "removed" trong mảng line_items chính.
  • Khi line_items.statusremoved:
    • line_items.quantity.total được đặt thành 0.
    • Số lượng ban đầu được lưu trữ trong trường line_items.quantity.original mới.
  • Trong adjustments thuộc loại return, trường line_items.quantity trong mức điều chỉnh sử dụng giá trị âm (ví dụ: -1) để cho biết các mặt hàng đang được trả lại.

Tiêu đề bắt buộc:

  • Webhook-Id: adj_refund_2
  • Webhook-Timestamp: 2026-02-10T10:00:00Z
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.order": [{"version": "2026-04-08"}]
    }
  },
  "id": "order_03",
  "checkout_id": "checkout_03",
  "currency": "USD",
  "line_items": [
    {
      "id": "line_3",
      "item": {
        "id": "product_789",
        "title": "Wireless Earbuds",
        "price": 14900
      },
      "quantity": {
        "total": 0,  // Item removed from order total.
        "original": 1,  // Original quantity before removal.
        "fulfilled": 1 // Was fulfilled before return.
      },
      "totals": [
        {"type": "subtotal", "amount": 14900},
        {"type": "tax", "amount": 1200},
        {"type": "total", "amount": 16100}
      ],
      "status": "removed" // Item is cancelled, returned, or refunded.
    }
  ],
  "totals": [
    {"type": "subtotal", "amount": 14900},
    {"type": "tax", "amount": 1200},
    {"type": "total", "amount": 16100}
  ],
  "fulfillment": {
    "events": [
      {
        "id": "fulfill_evt_1",
        "occurred_at": "2026-02-05T09:00:00Z",
        "type": "shipped",
        "line_items": [{ "id": "line_3", "quantity": 1 }],
        "tracking_number": "987654321",
        "tracking_url": "https://fedex.com/track/987654321",
        "carrier": "FedEx",
        "description": "Item shipped"
      },
      {
        "id": "fulfill_evt_2",
        "occurred_at": "2026-02-07T16:00:00Z",
        "type": "delivered",
        "line_items": [{ "id": "line_3", "quantity": 1 }],
        "tracking_number": "987654321",
        "tracking_url": "https://fedex.com/track/987654321",
        "carrier": "FedEx",
        "description": "Item delivered"
      },
      {
        "id": "fulfill_evt_3",
        "occurred_at": "2026-02-09T09:00:00Z",
        "type": "returned",
        "line_items": [{ "id": "line_3", "quantity": 1 }],
        "tracking_number": "987654321",
        "tracking_url": "https://fedex.com/track/987654321",
        "carrier": "FedEx",
        "description": "Item returned"
      }
    ],
    "expectations": [{ "...": "..." }]
  },
  "adjustments": [
    {
      "id": "adj_return_1",
      "type": "return", // a matching fulfillment event is also added to represent return shipping.
      "description": "Item not compatible",
      "line_items": [{ "id": "line_3", "quantity": -1 }],  // Uses a negative value (such as -1) to indicate a return.
      "occurred_at": "2026-02-09T09:00:00Z",
      "status": "completed"
    },
    {
      "id": "adj_refund_2",
      "type": "refund",
      "description": "Refund for returned item",
      "line_items": [{ "id": "line_3", "quantity": -1 }],
      "totals": [
        {"type": "subtotal", "amount": -14900}, // Negative amounts indicate money returned to the buyer.
        {"type": "tax", "amount": -1200},
        {"type": "total", "amount": -16100}
      ],
      "occurred_at": "2026-02-10T10:00:00Z",
      "status": "completed"
    }
  ],
  "permalink_url": "https://merchant.example.com/orders/67890"
}