Edits history of script submission #23728 for ' Create Order (shopify)'

  • bunnative
    One script reply has been approved by the moderators
    Ap­pro­ved
    //native
    
    /**
     * Create Order
     * Create an order directly (no checkout). Each line item is either a variant ({ variant_id, quantity }) or a custom item ({ title, price, quantity }; price in currency, default the shop currency). financial_status PAID records a manual payment for the full amount (so the order can be refunded later); other statuses are only recorded. No card is charged. Inventory is not decremented unless inventory_behaviour says so.
     */
    export async function main(
      auth: RT.Shopify,
      line_items: {
        variant_id?: string
        title?: string
        price?: number
        quantity: number
      }[],
      customer_id: string | undefined,
      email: string | undefined,
      financial_status:
        | "PAID"
        | "PENDING"
        | "AUTHORIZED"
        | "PARTIALLY_PAID"
        | "PARTIALLY_REFUNDED"
        | "REFUNDED"
        | "VOIDED"
        | "EXPIRED"
        | undefined,
      shipping_address:
        | {
            firstName?: string
            lastName?: string
            company?: string
            address1?: string
            address2?: string
            city?: string
            provinceCode?: string
            zip?: string
            countryCode?: string
            phone?: string
          }
        | undefined,
      note: string | undefined,
      tags: string[] | undefined,
      currency: string | undefined,
      inventory_behaviour:
        | "BYPASS"
        | "DECREMENT_OBEYING_POLICY"
        | "DECREMENT_IGNORING_POLICY"
        | undefined,
      send_receipt: boolean | undefined,
      test: boolean | undefined
    ) {
      const endpoint = `https://${auth.store_name}.myshopify.com/admin/api/2026-10/graphql.json`
      const call = async (body: { query: string; variables?: object }) => {
        const response = await fetch(endpoint, {
          method: "POST",
          headers: {
            "X-Shopify-Access-Token": auth.token,
            "Content-Type": "application/json",
            Accept: "application/json",
          },
          body: JSON.stringify(body),
        })
        if (!response.ok) {
          throw new Error(`${response.status} ${await response.text()}`)
        }
        const { data, errors } = await response.json()
        if (errors) {
          throw new Error(JSON.stringify(errors))
        }
        return data
      }
      const gid = (value: string, type: string) =>
        value.startsWith("gid://") ? value : `gid://shopify/${type}/${value}`
    
      // Custom-item prices need a currency; default to the shop's.
      const currencyCode =
        currency ||
        (line_items.some((item) => item.price != null)
          ? (await call({ query: `query { shop { currencyCode } }` })).shop
              .currencyCode
          : undefined)
    
      const data = await call({
        query: `mutation ($order: OrderCreateOrderInput!, $options: OrderCreateOptionsInput) { orderCreate(order: $order, options: $options) { order { id } userErrors { field message } } }`,
        variables: {
          order: {
            lineItems: line_items.map((item) => ({
              variantId: item.variant_id
                ? gid(item.variant_id, "ProductVariant")
                : undefined,
              title: item.title || undefined,
              priceSet:
                item.price != null
                  ? { shopMoney: { amount: item.price, currencyCode } }
                  : undefined,
              quantity: item.quantity,
            })),
            customer: customer_id
              ? { toAssociate: { id: gid(customer_id, "Customer") } }
              : undefined,
            email: email || undefined,
            // A bare PAID status has no transaction behind it (nothing to refund); orderMarkAsPaid below records one.
            financialStatus:
              financial_status && financial_status !== "PAID"
                ? financial_status
                : undefined,
            shippingAddress: shipping_address || undefined,
            note: note || undefined,
            tags: tags?.length ? tags : undefined,
            currency: currency || undefined,
            test: test || undefined,
          },
          options: {
            inventoryBehaviour: inventory_behaviour || undefined,
            sendReceipt: send_receipt || undefined,
          },
        },
      })
      if (data.orderCreate.userErrors.length) {
        throw new Error(JSON.stringify(data.orderCreate.userErrors))
      }
      const id = data.orderCreate.order.id
    
      if (financial_status === "PAID") {
        // A new order can be "temporarily unavailable to be modified" for a few seconds (e.g. while inventory is claimed).
        for (let attempt = 1; ; attempt++) {
          const paid = await call({
            query: `mutation ($input: OrderMarkAsPaidInput!) { orderMarkAsPaid(input: $input) { userErrors { field message } } }`,
            variables: { input: { id } },
          })
          const userErrors = paid.orderMarkAsPaid.userErrors
          if (!userErrors.length) break
          if (
            attempt === 10 ||
            !/temporarily unavailable/i.test(userErrors[0].message)
          ) {
            throw new Error(JSON.stringify(userErrors))
          }
          await new Promise((resolve) => setTimeout(resolve, 1000))
        }
      }
    
      return (
        await call({
          query: `query ($id: ID!) { order(id: $id) { id legacyResourceId name email createdAt displayFinancialStatus displayFulfillmentStatus tags note totalPriceSet { shopMoney { amount currencyCode } } totalOutstandingSet { shopMoney { amount currencyCode } } customer { id displayName } lineItems(first: 50) { nodes { id name quantity variant { id } originalUnitPriceSet { shopMoney { amount currencyCode } } } } } }`,
          variables: { id },
        })
      ).order
    }
    

    Submitted by hugo989 3 hours ago