1. Top
  2. ブログ一覧
  3. Shopify Admin print actionを使って管理画面に印刷機能を追加する方法
Eコマース

Shopify Admin print actionを使って管理画面に印刷機能を追加する方法

公開日

2024.12.17

Shopify Admin print actionを使って管理画面に印刷機能を追加する方法のサムネイル

Shopifyの運営業務において、商品情報や注文データの印刷が必要になるケースは少なくありません。しかし、標準の管理画面では効率的な印刷機能が限られているため、販売者は手間のかかる操作を強いられることがあります。また、既存のShopifyアプリを使って簡単に印刷できるようにすることもできますが、自社の業務要件に合わせてカスタマイズすることが難しい場合もあります。

こうした課題を解決するために役立つのが Shopify Admin print action です。これを活用することで、管理画面内に印刷ボタンを追加し、販売者が必要なデータを簡単に印刷できるようになります。

Admin print actionとは

Admin print action は、Shopify Admin Extensionsの一種で、管理画面内に印刷機能を追加できるカスタムアクションです。
これにより、以下のような特徴があります。

  • 印刷ワークフローの効率化: 注文や商品データを直接管理画面から印刷可能。
  • 既存UIへの統合: 「その他の操作」メニューやアクションボタンからシームレスに印刷を実行。
  • カスタマイズ性: 自社の業務要件に合わせて印刷テンプレートやデータ内容を自由にカスタマイズ。

Admin print actionの設定手順

以下の手順でAdmin print actionを実装します。

  1. アプリスコープの設定
  2. Admin print actionのextension作成
  3. 印刷ボタンの追加場所の設定
  4. 印刷アクションの実装

今回は、注文詳細ページに印刷ボタンを追加する例を紹介します。

1. アプリスコープの設定

注文データを取得するので、アプリスコープに read_orders を追加します。
`shopify.app.toml

[access_scopes]
scopes = "read_orders"

また、センシティブなデータにアクセスするので別途パートナーダッシュボードからアプリのアクセス許可を設定します。

alt text

alt text

alt text

今回は開発ストアでのテストのみなので最初の①項目を回答するだけでOKです。
しかし、名前や電話番号などの顧客データを取得する場合は、read_customers スコープを設定してかつ、審査を通過する必要があります。

alt text

顧客データのアクセスについてはWork with protected customer dataを参照してください。

さらに、60日以上前の注文データを取得する場合は、read_all_orders スコープが必要です。こちらも今回は設定不要です。

[access_scopes]
scopes = "read_orders read_all_orders"

設置後、パートナーダッシュボードからアプリのアクセス許可を設定します。

alt text

alt text

アプリのアクセス許可を設定したら、デプロイして設定を反映させます。

shopify app deploy

デプロイ後、ダッシュボードからスコープが追加されていることを確認します。

alt text

2. Admin print actionのextension作成

Shopify CLIを使用して印刷用のAdmin action extensionを作成します。

shopify app generate extension --template admin_action --name print-order-details --flavor react

3. 印刷ボタンの追加場所の設定

shopify.extension.tomlを編集し、印刷アクションを追加する対象リソースを指定します。注文詳細ページに追加するのでtargetをadmin.order-details.action.render に設定します。

[[extensions.targeting]]
module = "./src/PrintActionExtension.jsx"
target = "admin.order-details.action.render"

4. 印刷アクションの実装

src/PrintActionExtension.jsx で印刷アクションコンポーネントを実装します。

import { useEffect, useState } from "react";
import {
  reactExtension,
  useApi,
  AdminPrintAction,
  BlockStack,
  Checkbox,
  Text,
} from "@shopify/ui-extensions-react/admin";

// The target used here must match the target used in the extension's toml file (./shopify.extension.toml)
const TARGET = "admin.order-details.print-action.render";

export default reactExtension(TARGET, () => <App />);

function App() {
  // The useApi hook provides access to several useful APIs like i18n and data.
  const { i18n, data } = useApi(TARGET);
  const [src, setSrc] = useState(null);

  const [printInvoice, setPrintInvoice] = useState(true);
  const [printPackingSlip, setPrintPackingSlip] = useState(false);

  useEffect(() => {
    const printTypes = [];
    if (printInvoice) {
      printTypes.push("請求書");
    }
    if (printPackingSlip) {
      printTypes.push("梱包伝票");
    }

    if (printTypes.length) {
      const params = new URLSearchParams({
        printType: printTypes.join(","),
        orderId: data.selected[0].id,
      });

      const fullSrc = `/print?${params.toString()}`;
      setSrc(fullSrc);
    } else {
      setSrc(null);
    }
  }, [data.selected, printInvoice, printPackingSlip]);

  return (
    <AdminPrintAction src={src}>
      <BlockStack blockGap="base">
        <Text fontWeight="bold">{i18n.translate("documents")}</Text>
        <Checkbox
          name="請求書"
          checked={printInvoice}
          onChange={(value) => {
            setPrintInvoice(value);
          }}
        >
          請求書
        </Checkbox>
        <Checkbox
          name="梱包伝票"
          checked={printPackingSlip}
          onChange={(value) => {
            setPrintPackingSlip(value);
          }}
        >
          梱包伝票
        </Checkbox>
      </BlockStack>
    </AdminPrintAction>
  );
}

これで、注文詳細ページに「注文詳細を印刷」ボタンが表示されるようになります。

続いて印刷時にAdmin APIを使用して注文データを取得して印刷データを生成する必要があります。src/router/print.jsファイルを作成し処理を実装しましょう。
※開発モードの場合、アプリのエンドポイントがCloudflare tunnelによって毎回変わるため、corsヘッダーを設定してリクエストを全て許可しておくとよいでしょう。

import { authenticate } from "../shopify.server";

export async function loader({ request }) {
  const { cors, admin } = await authenticate.admin(request);
  const url = new URL(request.url);
  const query = url.searchParams;
  const docs = query.get("printType").split(",");
  const orderId = query.get("orderId");
  const response = await admin.graphql(
    `query getOrder($orderId: ID!) {
      order(id: $orderId) {
        name
        createdAt
        totalPriceSet {
          shopMoney {
            amount
          }
        }
      }
    }`,
    {
      variables: {
        orderId: orderId,
      },
    },
  );
  const orderData = await response.json();
  const order = orderData.data.order;
  const pages = docs.map((docType) => orderPage(docType, order));
  const print = printHTML(pages);
  return cors(
    new Response(print, {
      status: 200,
      headers: {
        "Content-type": "text/html",
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
        "Access-Control-Allow-Headers": "Content-Type, Authorization",
      },
    }),
  );
}

function orderPage(docType, order) {
  const price = order.totalPriceSet.shopMoney.amount;
  const name = order.name;
  const createdAt = order.createdAt.split("T")[0];
  const email = "<!--email_off-->[email protected]<!--/email_off-->";
  const orderTemplate = `<main>
      <div>
        <div class="columns">
          <h1>${docType}</h1>
          <div>
            <p style="text-align: right; margin: 0;">
              Order ${name}<br>
              ${createdAt}
            </p>
          </div>
        </div>
        <div class="columns" style="margin-top: 1.5em;">
          <div class="address">
            <strong>From</strong><br>
            Top Quality Copper Ingots<br>
            <p>123 Broadway<br>
              Denver CO, 80220<br>
              United States</p>
            (123) 456-7891<br>
          </div>
        </div>
        <hr>
        <p>Order total: ¥${price}</p>
        <p style="margin-bottom: 0;">If you have any questions, please send an email to ${email}</p>
      </div>
    </main>`;
  return orderTemplate;
}

const title = `<title>My order printer</title>`;

function printHTML(pages) {
  const pageBreak = `<div class="page-break"></div>`;
  const pageBreakStyles = `
  @media not print {
        .page-break {
          width: 100vw;
          height: 40px;
          background-color: lightgray;
        }
      }
      @media print {
        .page-break {
          page-break-after: always;
        }
      }`;

  const joinedPages = pages.join(pageBreak);
  const printTemplate = `<!DOCTYPE html>
  <html lang="en">
  <head>
    <style>
      body,html {
        font-size: 16px;
        line-height: normal;
        background: none;
        margin: 0;
        padding: 0;
        font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
      }
      body {
        font-size: 0.688rem;
        color: #000;
      }
      main {
        padding: 3rem 2rem;
        height: 100vh;
      }
      h1 {
        font-size: 2.5rem;
        margin: 0;
      }
      h2,h3 {
        font-size: 0.75rem;
        font-weight: bold;
      }
      h2,h3,p {
        margin: 1rem 0 0.5rem 0;
      }
      .address p {
        margin: 0;
      }
      b,strong {
        font-weight: bold;
      }
      .columns {
        display: grid;
        grid-auto-columns: minmax(0, 1fr);
        grid-auto-flow: column;
        word-break: break-word;
      }
      hr {
        clear: both;
        overflow: hidden;
        margin: 1.5em 0;
        border-top: 1px solid #000;
        border-bottom: none;
      }
      .header-row+.row {
        margin-top: 0px;
      }
      .header-row {
        display: none !important;
      }
      table,td,th {
        width: auto;
        border-spacing: 0;
        border-collapse: collapse;
        font-size: 1em;
      }
      td,th {
        border-bottom: none;
      }
      table.table-tabular,.table-tabular {
        border: 1px solid #e3e3e3;
        margin: 0 0 0 0;
        width: 100%;
        border-spacing: 0;
        border-collapse: collapse;
      }
      table.table-tabular th,
      table.table-tabular td,
      .table-tabular th,
      .table-tabular td {
        padding: 0.5em;
      }
      table.table-tabular th,.table-tabular th {
        text-align: left;
        border-bottom: 1px solid #e3e3e3;
      }
      table.table-tabular td,.table-tabular td {
        border-bottom: 1px solid #e3e3e3;
      }
      table.table-tabular tfoot td,.table-tabular tfoot td {
        border-bottom-width: 0px;
        border-top: 1px solid black;
        padding-top: 1em;
      }
      .row {
        margin: 0;
      }`
      ${pageBreakStyles}
    </style>
    ${title}
  </head>
  <body>
    ${joinedPages}
  </body>
  </html>
  `;
  return printTemplate;
}

実行確認

Shopify CLIを使用して、開発サーバーを起動し、実装したAdmin print actionを確認します。

shopify app dev

管理画面の注文詳細ページに「注文詳細を印刷」ボタンが表示され、クリックすることで印刷できることを確認します。

alt text

alt text

まとめ

Shopify Admin print actionを活用することで、管理画面から直接データを印刷できるようになり、販売者の業務負担を大幅に軽減できます。 自社の業務要件に合わせて印刷内容をカスタマイズし、運用効率をさらに向上させましょう。

ただし、実装には高度なコーディングの知識が伴うのでShopify app storeのアプリ(例:Quick Order Printer)を活用することも検討してみてください。

参考