Commercetools Swift SDK
安装
要求
- iOS 10.0+ / macOS 10.10+ / tvOS 9.0+ / watchOS 2.0+
- Xcode 11.0+
- Swift 5.1+
CocoaPods
CocoaPods 是 Cocoa 项目的依赖管理器。您可以使用以下命令安装它
$ gem install cocoapods
要构建 CommercetoolsSDK 0.7+,需要 CocoaPods 1.2.1+。
要使用 CocoaPods 将 CommercetoolsSDK 集成到您的 Xcode 项目中,请在 Podfile
中指定它
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!
pod 'Commercetools', '~> 0.12'
然后,运行以下命令
$ pod install
开始使用
Commercetools SDK 使用名为 CommercetoolsConfig.plist
的 .plist
配置文件来收集与 commercetools 平台通信所需的所有信息。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>projectKey</key>
<string>Your Project Key</string>
<key>clientId</key>
<string>Your Client ID</string>
<key>clientSecret</key>
<string>Your Client Secret</string>
<key>scope</key>
<string>Your Client Scope</string>
<key>authUrl</key>
<string>Authorization Url/</string>
<key>apiUrl</key>
<string>API Url</string>
<key>machineLearningApiUrl</key>
<string>Machine Learning API</string>
<key>anonymousSession</key>
<true/>
<key>keychainAccessGroupName</key>
<string>AB123CDEF.yourKeychainGroup</string>
<key>shareWatchSession</key>
<true/>
<key>emergencyContactInfo</key>
<string>[email protected]</string>
<key>storeKey</key>
<string>your-global-store-key</string>
</dict>
</plist>
您还可以指定一个包含这些属性的不同的 .plist
文件路径。
您还可以从商务中心 API 客户端 部分下载您的客户端的配置 .plist
文件,在创建时通过下拉菜单选择 iOS 模板。
在从 Commercetools SDK 使用任何方法之前,请确保您已之前设置所需的配置。
import Commercetools
// Default configuration initializer reads from CommercetoolsConfig.plist file from your app bundle
if let configuration = Config() {
// You can also specify custom logging level
// configuration.logLevel = .error
// Or completely disable all log messages from Commercetools SDK
// configuration.loggingEnabled = false
// Finally, you need set your configuration before using the SDK
Commercetools.config = configuration
} else {
// There are some errors in your .plist file, check log messages for more information
}
已验证和匿名使用
Commercetools 服务的端点可以不使用结账(PlainToken
)使用,也可以使用Guest Checkout(AnonymousToken
)或已登录客户(CustomerToken
)使用。根据配置,与 Commercetools 平台的交互将默认使用 PlainToken
或 AnonymousToken
。
如果某个时候您希望让用户登录,可以通过使用 loginUser
方法实现。
let username = "[email protected]"
let password = "password"
Commercetools.loginCustomer(username, password: password, completionHandler: { result in
if let error = result.errors?.first as? CTError, case .accessTokenRetrievalFailed(let reason) = error {
// Handle error, and possibly get some more information from reason.message
}
})
同样,在注销后,所有进一步的交互将继续使用新的匿名用户令牌。
Commercetools.logoutCustomer()
访问令牌和刷新令牌在应用启动之间被保留。为了检查当前是否正在处理已验证或匿名用户,应使用 authState
属性。
if Commercetools.authState == .plainToken {
// Present login form or other logic
}
为了使您的应用支持匿名会话,应在您的配置 .plist
文件中将 anonymousSession
布尔属性设置为 true
。此外,可以覆盖此设置,并调用以提供可选的自定义 anonymous_id
(用于指标和跟踪)。
Commercetools.obtainAnonymousToken(usingSession: true, anonymousId: "some-custom-id", completionHandler: { error in
if error == nil {
// It is possible for token retrieval to fail, e.g custom token ID has already been taken,
// in which case reason.message from the returned CTError instance is set to the anonymousId is already in use.
}
})
当匿名会话通过注册或登录结束,购物车和订单将迁移到客户,并返回 CustomerSignInResult
,提供对客户配置文件和当前活动购物车的访问。对于登录操作,您可以定义如何将线项从当前活动购物车迁移,通过明确指定以下两种 AnonymousCartSignInMode
值之一:.mergeWithExistingCustomerCart
或 .useAsNewActiveCustomerCart
。
店内客户
当使用店铺时,客户可以全局注册,或者具有针对特定店铺的注册。在后一种情况下,为了在店铺中登录客户,需要设置 storeKey
参数。
let username = "[email protected]"
let password = "password"
Commercetools.loginCustomer(username, password: password, storeKey: "store-key", completionHandler: { result in
if let error = result.errors?.first as? CTError {
// Handle error, and possibly get some more information from reason.message
}
})
如果应用只将与特定商店一起工作,建议将全局商店密钥设置在配置文件.plist
中作为一部分。这样,SDK将使用storeKey
对所有后续的针对商店特定端点的请求。
外部OAuth令牌
Commercetools平台和SDK提供了使用外部OAuth令牌的功能。要从您的应用设置令牌,请使用Commercetools.externalToken
属性。设置后,此令牌将用于所有来自SDK的平台请求。为了停止使用外部令牌,只需将该值设置为nil
即可。
使用外部令牌时,需要手动处理已过期和无效的令牌场景。完成处理程序将提供带有额外信息的CTError
,客户端应决定是否需要刷新令牌或无法恢复(例如已删除的帐户)。
有关如何设置和使用Commercetools平台的外部OAuth的更多信息,请参阅此页面。
在应用扩展中使用SDK
如果您的应用有扩展,并且您希望在那些扩展中使用Commercetools SDK,我们建议启用密钥链共享。通过启用密钥链共享,并在配置文件.plist
中设置适当的访问组名称,SDK将把所有令牌保存在共享密钥链中。确保在访问组名称中包含应用ID前缀 / 团队ID。因此,您可以在您的应用和任何扩展中使用相同的授权状态和令牌来访问所有端点。这同样适用于使用密钥链共享的多个开发团队的应用程序。
在watchOS中使用SDK
由于Apple Watch上的密钥链包含与配对的iPhone上的密钥链不同的条目集,因此通过在配置文件.plist
中设置keychainAccessGroupName
无法通过iOS到watchOS设置相同的客户会话。相反,Commercetools SDK使用WatchConnectivity从iPhone传输访问令牌到Apple Watch,在那里它们也在watchOS密钥链中安全存储。您需要执行的唯一步骤是将配置属性shareWatchSession
设置为true
。
用户在Apple Watch上登录的一种常见方式是通过iPhone应用。当从iOS应用接收到访问令牌时,watchOS SDK将发布通知,因此您可以检查新的authState
并据此进行UI更改。
NotificationCenter.default.addObserver(self, selector: #selector(checkAuthState), name: Notification.Name.WatchSynchronization.DidReceiveTokens, object: nil)
func checkAuthState() {
if Commercetools.authState == .customerToken {
// The customer is logged in, present the appropriate screen
} else {
// The customer is not logged in, present the login message if needed
}
}
使用Commercetools端点
使用和管理通过可用端点提供的数据资源对任何可用端点类来说都非常简单。
根据资源的功能,您可以按特定UUID检索,使用更详细的查询选项,还可以执行创建或更新操作。
所有这些功能都由任何特定支持的端点提供的静态方法提供。例如,您可以使用提供的Cart
类创建购物车
var cartDraft = CartDraft(currency: "EUR")
Cart.create(cartDraft, result: { result in
if let cart = result.model, result.isSuccess {
// Do any work with created `Cart` instance, i.e:
if cart.cartState == .active {
// Our cart is active!
}
}
})
如果您需要来自尚未在我们的SDK中实现的端点的资源,您可以轻松地创建表示该端点的类,并符合适当的协议,这些协议处理许多常见情况下的抽象端点实现。
以下列表代表当前支持的抽象端点。对于每个协议,提供了默认扩展,这几乎可以满足您的所有需求
- 创建端点 -
create(object: [String: AnyObject], expansion: [String]?, result: (Result<ResponseType>) -> Void)
- 更新端点 -
update(id: String, version: UInt, actions: [[String: AnyObject]], expansion: [String]?, result: (Result<ResponseType>) -> Void)
- 按键更新端点 -
updateByKey(key: String, version: UInt, actions: [[String: AnyObject]], expansion: [String]?, result: (Result<ResponseType>) -> Void)
- 查询端点 -
query(predicates predicates: [String]?, sort: [String]?, expansion: [String]?, limit: UInt?, offset: UInt?, result: (Result<QueryResponse<ResponseType>>) -> Void)
- 按ID检索资源端点 -
byId(id: String, expansion: [String]?, result: (Result<ResponseType>) -> Void)
- 按键检索资源端点 -
byKey(key: String, expansion: [String]?, result: (Result<ResponseType>) -> Void)
- 删除端点 -
delete(id: String, version: UInt, expansion: [String]?, result: (Result<ResponseType>) -> Void)
当前支持的端点
项目管理设置
为了获取当前Commercetools项目的支持国家、语言和货币,您应使用项目管理设置端点
Project.settings { result in
if let settings = result.model {
// use settings.currencies, settings.countries, settings.languages, etc
}
}
购物车
购物车端点支持所有常见操作
- 检索活动购物车
Cart.active(result: { result in
if let cart = result.model, result.isSuccess {
// Cart successfully retrieved, response contains currently active cart
} else {
// Your user might not have an active cart at the moment
}
})
- 查询购物车
Cart.query(limit: 2, offset: 1, result: { result in
if let response = result.model, let count = response.count,
let results = response.results, result.isSuccess {
// response contains an array of cart instances
}
})
- 创建新购物车
var cartDraft = CartDraft()
cartDraft.currency = "EUR"
Cart.create(cartDraft, result: { result in
if let cart = result.model, let cartState = cart.cartState, result.isSuccess {
// Cart successfully created, and cart contains
}
})
- 更新现有购物车
let draft = LineItemDraft(productVariantSelection: .productVariant(productId: productId, variantId: variantId))
let updateActions = UpdateActions<CartUpdateAction>(version: version, actions: [.addLineItem(lineItemDraft: draft)])
Cart.update(cartId, actions: updateActions, result: { result in
if let cart = result.model, result.isSuccess {
// Cart successfully updated, response contains updated cart
}
})
- 删除现有购物车
let version = 1 // Set the appropriate current version
Cart.delete(cartId, version: version, result: { result in
if let cart = result.model, result.isSuccess {
// Cart successfully deleted
}
})
- 通过UUID检索购物车
Cart.byId("cddddddd-ffff-4b44-b5b0-004e7d4bc2dd", result: { result in
if let cart = result.model, cart.cartState == .active && result.isSuccess {
// response contains cart dictionary
}
})
此外,还有一组用于店内操作的购物车方法。参数与上述方法相同,额外有一个指定商店键的参数(《storeKey: String》)。
分类
使用常规移动作用域,可以按UUID检索并查询分类。
- 查询分类
Category.query(limit: 10, offset: 1, result: { result in
if let response = result.model, let count = response.count,
let categories = response.results, result.isSuccess {
// categories contains an array of category objects
}
})
- 通过UUID检索分类
Category.byId("cddddddd-ffff-4b44-b5b0-004e7d4bc2dd", result: { result in
if let category = result.model, result.isSuccess {
// response contains category objects
}
})
客户
客户端点为您提供了从iOS应用中进行操作的几种可能操作
- 检索用户资料(用户必须登录)
// Optionally set `storeKey` for customers registered in a specific store.
Customer.profile { result in
if let profile = result.model, let firstName = profile.firstName,
let lastName = profile.lastName, result.isSuccess {
// E.g present user profile details
}
}
- 注册新账户(匿名用户由
AuthManager
处理)
var customerDraft = CustomerDraft()
customerDraft.email = "[email protected]"
customerDraft.password = "password"
// Optionally set `storeKey` to sign up the customer in that store.
Commercetools.signUpCustomer(customerDraft, result: { result in
if let customer = result.model?.customer, let version = customer.version, result.isSuccess {
// User has been successfully signed up.
// Now, you'd probably want to present the login form, or simply
// use AuthManager to login user automatically
}
})
- 更新客户账户(用户必须登录)
var options = SetFirstNameOptions()
options.firstName = "newName"
let updateActions = UpdateActions<CustomerUpdateAction>(version: version, actions: [.setFirstName(options: options)])
// Optionally set `storeKey` for customers registered in a specific store.
Customer.update(actions: updateActions, result: { result in
if let customer = result.model, let version = customer.version, result.isSuccess {
// User profile successfully updated
}
})
- 删除客户账户(用户必须登录)
// Optionally set `storeKey` for customers registered in a specific store.
Customer.delete(version: version, result: { result in
if let customer = result.model, result.isSuccess {
// Customer was successfully deleted
}
})
- 更改密码(用户必须登录)
let version = 1 // Set the appropriate current version
// Optionally set `storeKey` for customers registered in a specific store.
Customer.changePassword(currentPassword: "password", newPassword: "newPassword", version: version, result: { result in
if let customer = result.model, result.isSuccess {
// Password has been changed, and now AuthManager has automatically obtained new access token
}
})
- 使用令牌重置密码(匿名用户由
AuthManager
处理)
let token = "" // Usually this token is retrieved from the password reset link, in case your app does support universal links
// Optionally set `storeKey` for customers registered in a specific store.
Customer.resetPassword(token: token, newPassword: "password", result: { result in
if let customer = result.model, let email = customer.email, result.isSuccess {
// Password has been successfully reset, now would be a good time to present the login screen
}
})
- 使用令牌验证电子邮件(用户必须登录)
let token = "" // Usually this token is retrieved from the activation link, in case your app does support universal links
// Optionally set `storeKey` for customers registered in a specific store.
Customer.verifyEmail(token: token, result: { result in
if let customer = result.model, let email = customer.email, result.isSuccess {
// Email has been successfully verified, probably show UIAlertController with this info
}
})
订单
订单端点提供从现有 购物车
创建订单的能力,也可以通过UUID检索订单,并执行订单查询。
配送方式
为了在结账过程中向客户展示物流选项,您需要使用物流方法端点
- 检索购物车的物流方法
ShippingMethod.for(cart: cart) { result in
if let shippingMethods = result.model, result.isSuccess {
// present shipping methods to the customer
}
}
- 检索国家的物流方法
ShippingMethod.for(country: "DE") { result in
if let shippingMethods = result.model, result.isSuccess {
// present shipping methods to the customer
}
}
- 查询物流方法
let predicate = "name=\"DHL\""
ShippingMethod.query(predicates: [predicate], result: { result in
if let response = result.model, let count = response.count,
let results = response.results, result.isSuccess {
// results contains an array of shipping method objects
}
})
- 通过UUID检索物流方法
ShippingMethod.byId("cddddddd-ffff-4b44-b5b0-004e7d4bc2dd", result: { result in
if let shippingMethod = result.model, result.isSuccess {
// response contains product projection object
}
})
产品投影
您iOS应用检索产品数据的最常见的做法是消耗产品投影端点。以下操作目前得到支持
- 搜索产品投影
ProductProjection.search(sort: ["name.en asc"], limit: 10, lang: Locale(identifier: "en"), text: "Michael Kors", result: { result in
if let response = result.model, let total = response.total,
let results = response.results, result.isSuccess {
// results contains an array of product projection objects
}
})
- 产品投影关键词建议
ProductProjection.suggest(lang: Locale(identifier: "en"), searchKeywords: "michael", result: { result in
if let response = result.json, let keywords = response["searchKeywords.en"] as? [[String: AnyObject]],
let firstKeyword = keywords.first?["text"] as? String, result.isSuccess {
// keywords contains an array of suggestions in dictionary representation
}
})
- 查询产品投影
let predicate = "slug(en=\"michael-kors-bag-30T3GTVT7L-lightbrown\")"
ProductProjection.query(predicates: [predicate], sort: ["name.en asc"], limit: 10, offset: 10, result: { result in
if let response = result.model, let count = response.count,
let results = response.results, result.isSuccess {
// results contains an array of product projection objects
}
})
- 通过UUID检索产品投影
ProductProjection.byId("cddddddd-ffff-4b44-b5b0-004e7d4bc2dd", result: { result in
if let product = result.model, result.isSuccess {
// response contains product projection object
}
})
产品类型
使用常规移动范围,可以通过UUID、键和查询产品类型。
- 查询产品类型
ProductType.query(limit: 10, offset: 1, result: { result in
if let response = result.model, let count = response.count,
let productTypes = response.results, result.isSuccess {
// productTypes contains an array of product type objects
}
})
- 通过UUID检索产品类型
ProductType.byId("cddddddd-ffff-4b44-b5b0-004e7d4bc2dd", result: { result in
if let productType = result.model, result.isSuccess {
// response contains product type object
}
})
- 通过键检索产品类型
ProductType.byKey("main", result: { result in
if let productType = result.model, result.isSuccess {
// response contains product type object
}
})
商店
使用 view_stores 范围,可以通过UUID、键和查询商店。
- 查询商店
Store.query(limit: 10, offset: 1, result: { result in
if let response = result.model, let count = response.count,
let stores = response.results, result.isSuccess {
// stores contains an array of store objects
}
})
- 通过UUID检索商店
Store.byId("cddddddd-ffff-4b44-b5b0-004e7d4bc2dd", result: { result in
if let store = result.model, result.isSuccess {
// response contains store object
}
})
- 通过键检索商店
Store.byKey("unionSquare", result: { result in
if let store = result.model, result.isSuccess {
// response contains store object
}
})
消耗机器学习端点
Commercetools提供的 机器学习API,SDK有一套独立的端点。
当前支持的机器学习端点
类似商品
可以使用一种方法来启动搜索请求,另一种方法来检查进度并获取搜索结果,以搜索类似商品。
- 启动类似商品的搜索请求
let request = SimilarProductSearchRequest(limit: 10, similarityMeasures: SimilarityMeasures(name: 1))
SimilarProducts.initiateSearch(request: request) { result in
if let taskToken = result.model {
// use taskToken.taskId to monitor for progress
}
}
- 检查状态,获取结果
SimilarProducts.status(for: taskToken.taskId) { result in
if let taskStatus = result.model, taskStatus == .success {
// task has been completed, use taskStatus.result to get the products from `PagedQueryResult`
}
}
分类推荐
可以使用提供的查询方法搜索特定产品ID的最佳匹配分类。
- 搜索分类推荐
CategoryRecommendations.query(productId: product.id) { result in
if let results = result.model?.results {
// results contains an array of `ProjectCategoryRecommendation`s
}
}
处理结果
为了检查Commercetools服务的任何操作是否成功执行,您应使用问题结果中的isSuccess
或isFailure
属性。对于所有成功操作,都有两个属性可用于消费实际响应。适用于已集成模型的所有端点建议使用model
。该属性已在上述所有示例中使用。作为备选,如果您正在编写自定义端点,并且不希望添加模型属性和映射,则json
属性将为您提供访问来自Commercetools平台的JSON的[字符串:任何](字典表示)的权限。
对于所有失败操作,应使用结果中的errors
属性来呈现或处理特定问题。CTError
实例是枚举,包含七个主要情况,其中每个情况都包含FailureReason
以及根据特定情况进行的一些附加相关值。
测试设置
如果需要实现与Commercetools服务通信的自定义端点,建议也要对该端点进行测试。我们的XCTestCase
扩展提供了如何设置测试配置的示例。对于某些测试,常规移动客户端范围就足够了(大多数情况下包括view_products manage_my_profile manage_my_orders
)。如果你的测试需要设置或配置具有更高权限(范围),你同样可以设置。出于安全考虑,SDK测试会从环境变量中读取此配置。
在测试类中设置辅助端点也非常简单。你可以声明一个符合特定端点协议的私有类
private class Foobar: QueryEndpoint, ByIdEndpoint, CreateEndpoint, UpdateEndpoint, DeleteEndpoint {
static let path = "foobar"
}