B2B 開發
Shopify 上的企業對企業商務是一個快速成長的領域,而 Shopify Plus 提供了應用程式開發者可以擴展和整合的原生 B2B 功能。本模組涵蓋 B2B 資料模型、公司帳戶和地點、使用目錄的自訂定價、採購單審核工作流程、結帳自訂和付款方式整合。
Shopify Plus 的 B2B 功能
Shopify 的原生 B2B 功能僅在 Shopify Plus 方案上可用。作為應用程式開發者,了解這些功能讓您能夠建構商家在複雜批發操作中所需的輔助工具。
B2B 資料模型概覽
Company
├── Company Locations (shipping/billing addresses)
│ ├── Catalog Assignments (custom pricing)
│ └── Payment Terms (Net 30, Net 60, etc.)
├── Company Contacts (buyer accounts)
│ └── Roles & Permissions
└── Orders (linked to company + location)
B2B 和 DTC(直接面向消費者)在同一個 Shopify 商店上運營。一個商店可以同時服務零售顧客和批發買家。店面體驗會根據已登入顧客是否與 B2B 公司關聯而自動調整。
查詢 B2B 公司
query GetCompanies($first: Int!, $query: String) {
companies(first: $first, query: $query) {
edges {
node {
id
name
externalId
note
customerSince
orderCount
totalSpent {
amount
currencyCode
}
mainContact {
customer {
firstName
lastName
email
}
}
locations(first: 10) {
edges {
node {
id
name
shippingAddress {
address1
city
province
country
zip
}
catalog {
id
title
}
buyerExperienceConfiguration {
paymentTermsTemplate {
paymentTermsType
dueInDays
}
}
}
}
}
}
}
}
}
公司帳戶和地點
以程式方式建立公司
從 ERP 系統或其他平台匯入批發顧客的應用程式需要透過 API 建立公司:
// services/b2b-company-service.js
export async function createCompany(client, companyData) {
const response = await client.query({
data: {
query: `mutation CompanyCreate($input: CompanyCreateInput!) {
companyCreate(input: $input) {
company {
id
name
}
userErrors {
field
message
}
}
}`,
variables: {
input: {
company: {
name: companyData.name,
externalId: companyData.erpId,
note: companyData.notes,
},
companyLocation: {
name: companyData.headquarters.name || 'Headquarters',
shippingAddress: {
address1: companyData.headquarters.address1,
address2: companyData.headquarters.address2,
city: companyData.headquarters.city,
provinceCode: companyData.headquarters.state,
countryCode: companyData.headquarters.country,
zip: companyData.headquarters.zip,
},
buyerExperienceConfiguration: {
paymentTermsTemplateId: companyData.paymentTermsId,
},
},
companyContact: {
customer: {
firstName: companyData.primaryContact.firstName,
lastName: companyData.primaryContact.lastName,
email: companyData.primaryContact.email,
},
},
},
},
},
});
const result = response.body.data.companyCreate;
if (result.userErrors.length > 0) {
throw new Error(
`Failed to create company: ${result.userErrors.map((e) => e.message).join(', ')}`
);
}
return result.company;
}
管理多個地點
大型 B2B 顧客通常有多個運送地點,每個地點有不同的定價和付款條件:
export async function addCompanyLocation(client, companyId, locationData) {
const response = await client.query({
data: {
query: `mutation CompanyLocationCreate(
$companyId: ID!,
$input: CompanyLocationInput!
) {
companyLocationCreate(companyId: $companyId, input: $input) {
companyLocation {
id
name
}
userErrors {
field
message
}
}
}`,
variables: {
companyId,
input: {
name: locationData.name,
shippingAddress: locationData.address,
buyerExperienceConfiguration: {
paymentTermsTemplateId: locationData.paymentTermsId,
checkoutToDraft: locationData.requiresApproval,
},
},
},
},
});
return response.body.data.companyLocationCreate;
}
在需要經理審核訂單的公司地點上設定 checkoutToDraft: true。這會導致來自該地點的訂單建立為草稿訂單而非確認訂單,從而啟用您的審核工作流程。
自訂定價和目錄
目錄是 Shopify 管理 B2B 特定定價的方式。每個目錄包含價格清單,可覆蓋指定公司地點的商店預設價格。
建立包含自訂價格的目錄
// services/catalog-service.js
export async function createCatalogWithPricing(client, catalogData) {
// Step 1: Create the catalog
const catalogResponse = await client.query({
data: {
query: `mutation CatalogCreate($input: CatalogCreateInput!) {
catalogCreate(input: $input) {
catalog {
id
title
priceList {
id
}
}
userErrors { field message }
}
}`,
variables: {
input: {
title: catalogData.name,
status: 'ACTIVE',
context: {
companyLocationIds: catalogData.locationIds,
},
},
},
},
});
const catalog = catalogResponse.body.data.catalogCreate.catalog;
// Step 2: Add fixed prices to the price list
if (catalogData.fixedPrices?.length > 0) {
await client.query({
data: {
query: `mutation PriceListFixedPricesAdd(
$priceListId: ID!,
$prices: [PriceListPriceInput!]!
) {
priceListFixedPricesAdd(
priceListId: $priceListId,
prices: $prices
) {
prices {
variant { id }
price { amount currencyCode }
}
userErrors { field message }
}
}`,
variables: {
priceListId: catalog.priceList.id,
prices: catalogData.fixedPrices.map((p) => ({
variantId: p.variantId,
price: {
amount: p.price.toString(),
currencyCode: p.currency,
},
compareAtPrice: p.compareAtPrice
? { amount: p.compareAtPrice.toString(), currencyCode: p.currency }
: null,
})),
},
},
});
}
return catalog;
}
基於百分比的定價調整
對於更簡單的批發折扣結構,使用百分比調整而非固定價格:
export async function setPercentageAdjustment(client, priceListId, percentage) {
const response = await client.query({
data: {
query: `mutation PriceListUpdate($id: ID!, $input: PriceListInput!) {
priceListUpdate(id: $id, input: $input) {
priceList {
id
adjustment {
type
value
}
}
userErrors { field message }
}
}`,
variables: {
id: priceListId,
input: {
adjustment: {
type: 'PERCENTAGE_DECREASE',
value: percentage, // e.g., 20 for 20% off
},
},
},
},
});
return response.body.data.priceListUpdate;
}
目錄定價優先於所有其他定價機制,包括自動折扣和價格規則。在除錯 B2B 顧客的定價問題時,請務必先檢查目錄分配。
採購單審核工作流程(Winter '26)
Winter '26 版本為 B2B 引入了原生的採購單審核工作流程支援。公司地點可以設定審核規則,要求指定的審核者在訂單確認之前進行審查。這取代了先前的草稿訂單變通方案。
設定審核規則
// services/approval-workflow.js
export async function configureApprovalWorkflow(client, locationId, rules) {
const response = await client.query({
data: {
query: `mutation CompanyLocationApprovalConfig(
$locationId: ID!,
$rules: [ApprovalRuleInput!]!
) {
companyLocationUpdateApprovalRules(
companyLocationId: $locationId,
rules: $rules
) {
companyLocation {
id
approvalRules {
id
condition
threshold { amount currencyCode }
approvers { customer { email } }
}
}
userErrors { field message }
}
}`,
variables: {
locationId,
rules: rules.map((rule) => ({
condition: rule.condition, // 'ORDER_TOTAL_EXCEEDS'
threshold: {
amount: rule.threshold.toString(),
currencyCode: rule.currency,
},
approverContactIds: rule.approverIds,
})),
},
},
});
return response.body.data.companyLocationUpdateApprovalRules;
}
建構審核儀表板
// routes/app.approvals.jsx
import { json } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';
import {
Page,
Layout,
Card,
DataTable,
Badge,
Button,
ButtonGroup,
} from '@shopify/polaris';
export async function loader({ request }) {
const { admin } = await authenticate.admin(request);
const response = await admin.graphql(`{
draftOrders(first: 50, query: "status:open AND tag:b2b-pending-approval") {
edges {
node {
id
name
createdAt
totalPriceSet {
shopMoney { amount currencyCode }
}
purchasingEntity {
... on PurchasingCompany {
company { name }
location { name }
contact { customer { email } }
}
}
}
}
}
}`);
const { data } = await response.json();
return json({ pendingOrders: data.draftOrders.edges });
}
export default function ApprovalsPage() {
const { pendingOrders } = useLoaderData();
const rows = pendingOrders.map(({ node }) => [
node.name,
node.purchasingEntity?.company?.name || 'N/A',
node.purchasingEntity?.location?.name || 'N/A',
`$${node.totalPriceSet.shopMoney.amount}`,
new Date(node.createdAt).toLocaleDateString(),
<ButtonGroup key={node.id}>
<Button tone="success" onClick={() => approveOrder(node.id)}>
Approve
</Button>
<Button tone="critical" onClick={() => rejectOrder(node.id)}>
Reject
</Button>
</ButtonGroup>,
]);
return (
<Page title="Purchase Order Approvals">
<Layout>
<Layout.Section>
<Card>
<DataTable
columnContentTypes={[
'text', 'text', 'text', 'numeric', 'text', 'text',
]}
headings={[
'Order', 'Company', 'Location', 'Total', 'Date', 'Actions',
]}
rows={rows}
/>
</Card>
</Layout.Section>
</Layout>
</Page>
);
}
B2B 結帳自訂
Shopify Plus 商家可以使用 Checkout UI Extensions 來自訂 B2B 結帳體驗。
新增採購單號碼欄位
// extensions/checkout-b2b/src/Checkout.jsx
import {
reactExtension,
TextField,
useApplyMetafieldsChange,
useMetafield,
} from '@shopify/ui-extensions-react/checkout';
export default reactExtension(
'purchase.checkout.shipping-option-list.render-after',
() => <PONumberField />
);
function PONumberField() {
const poNumber = useMetafield({
namespace: 'custom',
key: 'po_number',
});
const applyMetafieldsChange = useApplyMetafieldsChange();
const handleChange = (value) => {
applyMetafieldsChange({
type: 'updateMetafield',
namespace: 'custom',
key: 'po_number',
valueType: 'string',
value,
});
};
return (
<TextField
label="Purchase Order Number"
value={poNumber?.value || ''}
onChange={handleChange}
/>
);
}
強制最低訂購數量
// extensions/checkout-b2b/src/MinOrderValidation.jsx
import {
reactExtension,
useBuyerJourneyIntercept,
useCartLines,
Banner,
} from '@shopify/ui-extensions-react/checkout';
export default reactExtension(
'purchase.checkout.block.render',
() => <MinOrderValidation />
);
function MinOrderValidation() {
const cartLines = useCartLines();
const totalQuantity = cartLines.reduce(
(sum, line) => sum + line.quantity,
0
);
const minimumOrder = 24; // Configurable per company
useBuyerJourneyIntercept(({ canBlockProgress }) => {
if (canBlockProgress && totalQuantity < minimumOrder) {
return {
behavior: 'block',
reason: `Minimum order quantity is ${minimumOrder} units`,
errors: [
{
message: `Your cart has ${totalQuantity} items. The minimum order is ${minimumOrder} items.`,
},
],
};
}
return { behavior: 'allow' };
});
if (totalQuantity < minimumOrder) {
return (
<Banner status="warning">
Minimum order is {minimumOrder} units. You currently have{' '}
{totalQuantity}. Please add {minimumOrder - totalQuantity} more items.
</Banner>
);
}
return null;
}
ACH 付款整合
許多 B2B 交易使用 ACH(自動清算所)銀行轉帳而非信用卡。Shopify 透過付款應用程式和手動付款方式支援 ACH。
透過付款條件設定 ACH
// services/ach-payment-service.js
export async function configureACHPaymentTerms(client, locationId) {
// First, find or create the ACH payment terms template
const response = await client.query({
data: {
query: `query PaymentTermsTemplates {
paymentTermsTemplates {
id
name
paymentTermsType
dueInDays
}
}`,
},
});
const templates = response.body.data.paymentTermsTemplates;
const net30 = templates.find(
(t) => t.paymentTermsType === 'NET' && t.dueInDays === 30
);
// Assign payment terms to the company location
await client.query({
data: {
query: `mutation AssignPaymentTerms(
$locationId: ID!,
$paymentTermsTemplateId: ID!
) {
companyLocationAssignPaymentTerms(
companyLocationId: $locationId,
paymentTermsTemplateId: $paymentTermsTemplateId
) {
companyLocation {
buyerExperienceConfiguration {
paymentTermsTemplate {
paymentTermsType
dueInDays
}
}
}
userErrors { field message }
}
}`,
variables: {
locationId,
paymentTermsTemplateId: net30.id,
},
},
});
}
B2B 付款條件(Net 30、Net 60 等)為商家帶來真實的財務風險。如果您的應用程式自動化付款條件分配,在向新公司延長條件之前,務必包含信用額度檢查、付款歷史驗證和商家審核流程等保障措施。
B2B 開發 Checklist
| 功能 | 複雜度 | 需要 Shopify Plus |
|---|---|---|
| 公司建立和管理 | 中等 | 是 |
| 多地點支援 | 中等 | 是 |
| 基於目錄的定價 | 高 | 是 |
| 採購單審核 | 高 | 是(Winter '26) |
| 結帳自訂 | 高 | 是 |
| 付款條件 / ACH | 中等 | 是 |
| B2B 顧客入口網站 | 高 | 是 |
Shopify 上的 B2B 商務仍在成熟中,對應用程式開發者的機會非常巨大。從舊有批發平台遷移的商家需要能夠彌補 Shopify 原生功能與其複雜營運需求之間差距的應用程式。現在在此領域建立專業知識,將為您在 Shopify 持續大力投資 B2B 功能時佔據有利位置。