B2B 开发
Shopify 上的企业对企业(B2B)商务是一个快速增长的领域,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
| Feature | Complexity | Shopify Plus Required |
|---|---|---|
| Company creation and management | Medium | Yes |
| Multi-location support | Medium | Yes |
| Catalog-based pricing | High | Yes |
| Purchase order approvals | High | Yes (Winter '26) |
| Checkout customization | High | Yes |
| Payment terms / ACH | Medium | Yes |
| B2B customer portal | High | Yes |
Shopify 上的 B2B 商务仍在不断成熟,应用开发者的机会非常大。从传统批发平台迁移的商家需要能够弥合 Shopify 原生功能与其复杂运营需求之间差距的应用。现在在这个领域建立专业知识,将使你在 Shopify 继续大力投资 B2B 功能时处于有利位置。