如何创建 Shopify 应用 - 分步指南
大家好,我打算创建一个 Shopify 应用,用于订阅,特别是根据德国新法规
取消订阅 。因此,我制定了一个分步计划来创建 Shopify 应用。
我只是从其他来源复制了大部分内容,希望对您有所帮助 :)
词汇:
应用程序与 Shopify 平台交互的关键区域有三个:Shopify 管理员、在线商店和结帐。
Shopify 管理员
登录 Shopify 后,商家可以使用Shopify 管理员设置商店、配置设置并管理业务。
Shopify 后台包含商家 Shopify 业务的核心内容,包括订单、产品和客户。商家还可以在 Shopify 后台安装应用程序。
网上商店
网上商店是商家业务的在线平台。商家可以使用网上商店创建网页、发布博客并销售产品。
作为应用程序开发人员,您可以在商家想要销售的所有地方以及客户想要购买的所有地方建立集成。
查看
商家使用安全的Shopify 结账平台,无论在哪里进行在线销售,都可以接受订单并收取款项。顾客将商品添加到购物车后,可以使用 Shopify 结账平台输入配送信息和付款详情,然后再下单。
应用可以与 Shopify 结账功能集成,为商家和顾客提供更多功能。例如,商家可以使用支持购买后优惠的应用,在结账时向顾客展示交叉销售或追加销售优惠。
创建您的第一个 Shopify 应用
步骤1:注册Shopify合作伙伴账户
首先在此处创建合作伙伴帐户:
https://partners.shopify.com/signup
第 2 步:创建测试商店
开发商店是一个免费的 Shopify 帐户,但有一些限制。作为Shopify 合作伙伴,您可以创建无限数量的开发商店。
您可以使用开发商店测试您创建的任何主题或应用程序,或为客户设置 Shopify 商店。设置开发商店并将其转移给客户可为您带来定期佣金。2.1
. 登录您的合作伙伴控制面板。2.2
. 单击商店。2.3
. 单击添加商店。2.4
. 在商店类型部分中,选择开发商店。2.5
. 在登录信息部分,输入您的商店名称和可用于登录的密码。默认情况下,与您的合作伙伴控制面板关联的电子邮件用作用户名,但您可以根据需要更改它。2.6
. 可选:通过选中创建使用开发人员预览版的不可转让商店来启用开发人员预览版。从下拉列表中选择开发人员预览版。2.7
. 在商店地址部分,输入您的地址。2.8
. 可选:在商店用途部分,选择您创建此开发商店的原因
。2.9单击“保存”。
步骤3:安装最新的Node.js
https://nodejs.org/en/download/
步骤 4:安装 Shopify CLI
如果您想在 Windows 10 上原生使用 Shopify CLI,请首先确保已使用RubyInstaller for Windows(2.7 或更高版本)安装了 Ruby+Devkit。
或者,您也可以通过 Windows Subsystem for Linux 使用 Shopify CLI,在这种情况下,您需要安装以下内容:
安装完先决条件后,您可以使用RubyGems.org软件包管理器将 Shopify CLI 安装为 Ruby gem 。在新的终端窗口中,导航到您的主目录并运行以下命令:
gem install shopify-cli
要验证 Shopify CLI 是否正确安装,请运行以下命令:
shopify version
步骤 5:使用 CLI 登录 Shopify
在使用 CLI 创建项目之前,我们需要登录 Shopify。因此,我们在终端中输入:
shopify login
浏览器标签页将会打开,请登录您的合作伙伴账户。成功登录后,您应该会在终端中看到一条消息
登录合作伙伴组织 xxxx 的商店 xxxxxx.myshopify.com
步骤 6:创建新项目
安装 Shopify CLI 后,您就可以创建新项目了。
导航到要创建项目的目录并运行shopify app create node
。此命令会在子目录中搭建一个新的 Node.js 应用,并在合作伙伴控制面板中创建您的应用。
步骤 7:启动本地开发服务器
创建应用程序后,您可以通过导航到项目目录并运行以shopify node serve
启动本地开发服务器来使用它。
Shopify CLI 使用ngrok创建一个隧道,允许使用唯一的 HTTPS URL 访问您的应用程序,这在创建应用程序时是必需的。
步骤 8:在开发商店中安装您的应用
在服务器运行时,打开终端在上一步中打印出来的 URL。打开该 URL 后,系统会提示您在开发商店中安装该应用。
如果你点击此链接,你应该会看到一条包含你的应用及其作用域的消息。点击“安装”,即可继续下一步。
步骤 9:开始构建应用程序
之前,您使用Shopify CLI创建了一个新应用。现在您可以开始构建您的应用了。
在本教程中,你将完成一系列任务,为你的应用添加一些特定的功能。最终的应用会比较简单,但你将学习如何找到资源来自行构建更复杂的功能。
完成本教程后,你将完成以下任务:
- 在您的开发商店中填充产品以测试您的应用
- 使用 Polaris 构建用户界面的雏形
- 设置 GraphQL 查询来检索产品
- 设置 GraphQL 突变来更新产品价格
步骤 10:填充产品
Shopify CLI 可帮助您添加示例数据来测试应用的行为。您可以使用 Shopify CLI 创建产品、客户和草稿订单的记录。
由于您的应用需要与产品数据进行交互,因此首先需要在开发商店中填充产品:
- 打开一个新的终端窗口。
- 导航到您的项目目录。
- 跑步
shopify populate products
步骤 11:添加空状态
现在您可以在 Shopify 中运行您的应用,并在构建前端组件时查看和测试它们。您可以使用Shopify 的 React 组件库和设计系统Polaris来构建用户界面。
使用 Polaris 为您的应用添加空状态。Polaris空状态组件有助于在商家首次将应用添加到 Shopify 后台时,传达应用的价值及其主要操作。
- 在代码编辑器中,导航到您的
pages/index.js
文件。 - 用组件替换文件的内容
EmptyState
:
import { Heading, Page, TextStyle, Layout, EmptyState} from "@shopify/polaris";
const img = 'https://cdn.shopify.com/s/files/1/0757/9955/files/empty-state.svg';
const Index = () => (
<Page>
<Layout>
<EmptyState // Empty state component
heading="Discount your products temporarily"
action={{
content: 'Select products',
onAction: () => this.setState({ open: true }),
}}
image={img}
>
<p>Select products to change their price temporarily.</p>
</EmptyState>
</Layout>
</Page>
);
export default Index;
当您预览嵌入式应用程序时,它会显示空状态。
步骤 12:添加资源选择器
接下来,添加一个资源选择器,以便您可以从应用中选择产品。您可以使用Shopify 的独立原生 JavaScript 库App Bridge向您的应用添加资源选择器。
App BridgeResourcePicker
操作集提供了基于搜索的界面,帮助您查找并选择一个或多个产品,然后将选定的资源返回到您的应用程序。
在您的pages/index.js
文件中,添加一个用于设置资源选择器状态的类。然后,将ResourcePicker
组件添加到组件上的主要操作按钮EmptyState
:
import React from 'react';
import { Heading, Page, TextStyle, Layout, EmptyState} from "@shopify/polaris";
import { ResourcePicker, TitleBar } from '@shopify/app-bridge-react';
const img = 'https://cdn.shopify.com/s/files/1/0757/9955/files/empty-state.svg';
// Sets the state for the resource picker
class Index extends React.Component {
state = { open: false };
render() {
return (
<Page>
<TitleBar
primaryAction={{
content: 'Select products',
onAction: () => this.setState({ open: true }),
}}
/>
<ResourcePicker // Resource picker component
resourceType="Product"
showVariants={false}
open={this.state.open}
onSelection={(resources) => this.handleSelection(resources)}
onCancel={() => this.setState({ open: false })}
/>
<Layout>
<EmptyState
heading="Discount your products temporarily"
action={{
content: 'Select products',
onAction: () => this.setState({ open: true }),
}}
image={img}
>
<p>Select products to change their price temporarily.</p>
</EmptyState>
</Layout>
</Page>
);
}
handleSelection = (resources) => {
this.setState({ open: false });
console.log(resources);
};
}
export default Index;
在您的嵌入式应用程序中,当您单击“选择产品”时,将打开“添加产品”模式。
步骤 13:添加资源列表
现在您已经设置了资源选择器,接下来需要一种检索产品的方法。您可以使用GraphQL Admin API检索产品。最终,您希望将这些产品显示在资源列表中。
要允许您的应用程序使用 GraphQL 查询数据,请创建一个新文件并在该文件中ResourceList.js
包含graphql-tag
和导入。react-apollo
然后,设置一个 GraphQL 查询来getProducts
检索产品及其价格的列表。
-
跑步
npm install store-js
-
在你的项目中的文件夹
components
中创建一个新文件夹,并在该文件夹中创建一个新文件。pages
ResourceList.js
-
将导入添加到您的
ResourceList.js
文件并设置您的 GraphQL 查询以检索产品及其价格:
import React from 'react';
import gql from 'graphql-tag';
import { Query } from 'react-apollo';
import {
Card,
ResourceList,
Stack,
TextStyle,
Thumbnail,
} from '@shopify/polaris';
import store from 'store-js';
import { Redirect } from '@shopify/app-bridge/actions';
import { Context } from '@shopify/app-bridge-react';
// GraphQL query to retrieve products by IDs.
// The price field belongs to the variants object because
// variations of a product can have different prices.
const GET_PRODUCTS_BY_ID = gql`
query getProducts($ids: [ID!]!) {
nodes(ids: $ids) {
... on Product {
title
handle
descriptionHtml
id
images(first: 1) {
edges {
node {
originalSrc
altText
}
}
}
variants(first: 1) {
edges {
node {
price
id
}
}
}
}
}
}
`;
在您的ResourceList.js
文件中,在 GraphQL 查询之后,设置一个名为 的类,ResourceListWithProducts
该类扩展了ResourceList
组件并返回产品和价格。然后,定义您的ResourceList
组件:
class ResourceListWithProducts extends React.Component {
static contextType = Context;
render() {
const app = this.context;
return (
// GraphQL query to retrieve products and their prices
<Query query={GET_PRODUCTS_BY_ID} variables={{ ids: store.get('ids') }}>
{({ data, loading, error }) => {
if (loading) return <div>Loading…</div>;
if (error) return <div>{error.message}</div>;
return (
<Card>
<ResourceList // Defines your resource list component
showHeader
resourceName={{ singular: 'Product', plural: 'Products' }}
items={data.nodes}
renderItem={item => {
const media = (
<Thumbnail
source={
item.images.edges[0]
? item.images.edges[0].node.originalSrc
: ''
}
alt={
item.images.edges[0]
? item.images.edges[0].node.altText
: ''
}
/>
);
const price = item.variants.edges[0].node.price;
return (
<ResourceList.Item
id={item.id}
media={media}
accessibilityLabel={`View details for ${item.title}`}
onClick={() => {
store.set('item', item);
}}
>
<Stack>
<Stack.Item fill>
<h3>
<TextStyle variation="strong">
{item.title}
</TextStyle>
</h3>
</Stack.Item>
<Stack.Item>
<p>${price}</p>
</Stack.Item>
</Stack>
</ResourceList.Item>
);
}}
/>
</Card>
);
}}
</Query>
);
}
}
export default ResourceListWithProducts;
在pages/index.js
文件中,添加导入代码并为应用的空状态定义一个常量。然后,更新控制 空状态布局的
代码,并指定使用新的资源列表 来显示产品:
import React from 'react';
import { Page, Layout, EmptyState} from "@shopify/polaris";
import { ResourcePicker, TitleBar } from '@shopify/app-bridge-react';
import store from 'store-js';
import ResourceListWithProducts from './components/ResourceList';
const img = 'https://cdn.shopify.com/s/files/1/0757/9955/files/empty-state.svg';
class Index extends React.Component {
state = { open: false };
render() {
// A constant that defines your app's empty state
const emptyState = !store.get('ids');
return (
<Page>
<TitleBar
primaryAction={{
content: 'Select products',
onAction: () => this.setState({ open: true }),
}}
/>
<ResourcePicker
resourceType="Product"
showVariants={false}
open={this.state.open}
onSelection={(resources) => this.handleSelection(resources)}
onCancel={() => this.setState({ open: false })}
/>
{emptyState ? ( // Controls the layout of your app's empty state
<Layout>
<EmptyState
heading="Discount your products temporarily"
action={{
content: 'Select products',
onAction: () => this.setState({ open: true }),
}}
image={img}
>
<p>Select products to change their price temporarily.</p>
</EmptyState>
</Layout>
) : (
// Uses the new resource list that retrieves products by IDs
<ResourceListWithProducts />
)}
</Page>
);
}
handleSelection = (resources) => {
const idsFromResources = resources.selection.map((product) => product.id);
this.setState({ open: false });
store.set('ids', idsFromResources);
};
}
export default Index;
现在,当您单击“选择产品”并从“添加产品”模式添加产品时,将显示产品列表。
步骤 14:更新产品价格
您已实现 GraphQL 查询来读取产品数据,并添加了在资源列表中显示检索到的产品的功能。接下来,您将使用 GraphQL 修改产品数据。
设置一个 GraphQL 突变,用于ProductVariantUpdate
更新应用中的产品价格。
- 在您的文件夹中创建一个新
ApplyRandomPrices.js
文件components
。 - 将导入添加到您的
ApplyRandomPrices.js
文件并设置 GraphQL 变异,以允许您的应用更新产品价格:
页面/组件/ApplyRandomPrices.js
import React, { useState } from 'react';
import gql from 'graphql-tag';
import { Mutation } from 'react-apollo';
import { Layout, Button, Banner, Toast, Stack, Frame } from '@shopify/polaris';
import { Context } from '@shopify/app-bridge-react';
// GraphQL mutation that updates the prices of products
const UPDATE_PRICE = gql`
mutation productVariantUpdate($input: ProductVariantInput!) {
productVariantUpdate(input: $input) {
product {
title
}
productVariant {
id
price
}
}
}
`;
-
在您进行变异之后
ApplyRandomPrices.js
,设置一个名为的类,ApplyRandomPrices
该类接受您变异的输入并将随机价格应用于所选产品:页面/组件/ApplyRandomPrices.js
class ApplyRandomPrices extends React.Component {
static contextType = Context;
render() {
return ( // Uses mutation's input to update product prices
<Mutation mutation={UPDATE_PRICE}>
{(handleSubmit, {error, data}) => {
const [hasResults, setHasResults] = useState(false);
const showError = error && (
<Banner status="critical">{error.message}</Banner>
);
const showToast = hasResults && (
<Toast
content="Successfully updated"
onDismiss={() => setHasResults(false)}
/>
);
return (
<Frame>
{showToast}
<Layout.Section>
{showError}
</Layout.Section>
<Layout.Section>
<Stack distribution={"center"}>
<Button
primary
textAlign={"center"}
onClick={() => {
let promise = new Promise((resolve) => resolve());
for (const variantId in this.props.selectedItems) {
const price = Math.random().toPrecision(3) * 10;
const productVariableInput = {
id: this.props.selectedItems[variantId].variants.edges[0].node.id,
price: price,
};
promise = promise.then(() => handleSubmit({ variables: { input: productVariableInput }}));
}
if (promise) {
promise.then(() => this.props.onUpdate().then(() => setHasResults(true)));
}}
}
>
Randomize prices
</Button>
</Stack>
</Layout.Section>
</Frame>
);
}}
</Mutation>
);
}
}
export default ApplyRandomPrices;
```
`
5. Update your `pages/index.js` file to include the following imports:
`
```typescript
import React from 'react';
import gql from 'graphql-tag';
import { Mutation } from 'react-apollo';
import { Page, Layout, EmptyState, Button, Card } from "@shopify/polaris";
import { ResourcePicker, TitleBar } from '@shopify/app-bridge-react';
import store from 'store-js';
import ResourceListWithProducts from './components/ResourceList';
```
`
6. In `ResourceList.js`, add the `ApplyRandomPrices` import. Implement a constructor on your `ResourceListWithProducts` class and update your GraphQL query to enable refetching products by ID. Finally, update your `ResourceList` component:
pages/components/ResourceList.js
`
```typescript
import React from 'react';
import gql from 'graphql-tag';
import { Query } from 'react-apollo';
import {
Card,
ResourceList,
Stack,
TextStyle,
Thumbnail,
} from '@shopify/polaris';
import store from 'store-js';
import { Redirect } from '@shopify/app-bridge/actions';
import { Context } from '@shopify/app-bridge-react';
import ApplyRandomPrices from './ApplyRandomPrices';
// GraphQL query that retrieves products by ID
const GET_PRODUCTS_BY_ID = gql`
query getProducts($ids: [ID!]!) {
nodes(ids: $ids) {
... on Product {
title
handle
descriptionHtml
id
images(first: 1) {
edges {
node {
originalSrc
altText
}
}
}
variants(first: 1) {
edges {
node {
price
id
}
}
}
}
}
}
`;
class ResourceListWithProducts extends React.Component {
static contextType = Context;
// A constructor that defines selected items and nodes
constructor(props) {
super(props);
this.state = {
selectedItems: [],
selectedNodes: {},
};
}
render() {
const app = this.context;
// Returns products by ID
return (
<Query query={GET_PRODUCTS_BY_ID} variables={{ ids: store.get('ids') }}>
{({ data, loading, error, refetch }) => { // Refetches products by ID
if (loading) return <div>Loading…</div>;
if (error) return <div>{error.message}</div>;
const nodesById = {};
data.nodes.forEach(node => nodesById[node.id] = node);
return (
<>
<Card>
<ResourceList
showHeader
resourceName={{ singular: 'Product', plural: 'Products' }}
items={data.nodes}
selectable
selectedItems={this.state.selectedItems}
onSelectionChange={selectedItems => {
const selectedNodes = {};
selectedItems.forEach(item => selectedNodes[item] = nodesById[item]);
return this.setState({
selectedItems: selectedItems,
selectedNodes: selectedNodes,
});
}}
renderItem={item => {
const media = (
<Thumbnail
source={
item.images.edges[0]
? item.images.edges[0].node.originalSrc
: ''
}
alt={
item.images.edges[0]
? item.images.edges[0].node.altText
: ''
}
/>
);
const price = item.variants.edges[0].node.price;
return (
<ResourceList.Item
id={item.id}
media={media}
accessibilityLabel={`View details for ${item.title}`}
verticalAlignment="center"
onClick={() => {
let index = this.state.selectedItems.indexOf(item.id);
const node = nodesById[item.id];
if (index === -1) {
this.state.selectedItems.push(item.id);
this.state.selectedNodes[item.id] = node;
} else {
this.state.selectedItems.splice(index, 1);
delete this.state.selectedNodes[item.id];
}
this.setState({
selectedItems: this.state.selectedItems,
selectedNodes: this.state.selectedNodes,
});
}}
>
<Stack alignment="center">
<Stack.Item fill>
<h3>
<TextStyle variation="strong">
{item.title}
</TextStyle>
</h3>
</Stack.Item>
<Stack.Item>
<p>${price}</p>
</Stack.Item>
</Stack>
</ResourceList.Item>
);
}}
/>
</Card>
<ApplyRandomPrices selectedItems={this.state.selectedNodes} onUpdate={refetch} />
</>
);
}}
</Query>
);
}
}
export default ResourceListWithProducts;
```
`
In your app, you can now update the prices of products.

## Next steps[](https://shopify.dev/apps/getting-started/add-functionality#next-steps)
- Use [webhooks](https://shopify.dev/apps/webhooks) to stay in sync with Shopify or execute code after a specific event occurs in a shop.
- Identify your [app business model](https://shopify.dev/apps/billing/models) and learn how to use the [Billing API](https://shopify.dev/apps/billing) to bill customers with recurring monthly charges or one-time purchases.
- Learn how to use [app extensions](https://shopify.dev/apps/app-extensions) to add features to Shopify admin or POS.
- Explore the [GraphQL Admin API](https://shopify.dev/api/admin/graphql/reference) and [REST Admin API](https://shopify.dev/api/admin/rest/reference) references.