Sources/Client/ShopClient.swift
/**
* Copyright 2019, Momentum Ideas, Co. All rights reserved.
* Source and object computer code contained herein is the private intellectual
* property of Momentum Ideas Co., a Delaware Corporation. Use of this
* code in source form requires permission in writing before use or the
* assembly, distribution, or publishing of derivative works, for commercial
* purposes or any other purpose, from a duly authorized officer of Momentum
* Ideas Co.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
import Foundation
import SwiftGRPC
import OpenCannabis
// Callback Types
/// A `MemberID` is an opaque string which references a user's membership to a given retailer, via a
/// specific partner/location scope pair.
public typealias MemberID = String
/// Callback type definition for shop info, where the current shop status (`OPEN`/`CLOSED`) is returned
/// to invoking code. In some cases, a shop may express its open status as `DELIVERY_ONLY` or
/// `PICKUP_ONLY` if it does not accept a certain kind of orders at this time (but usually does).
///
/// - Parameters:
/// - `CallResult`: gRPC call result object, which includes a status code.
/// - `ShopInfo.Response?`: If the call succeeded, a shop info response, otherwise `nil`.
public typealias ShopInfoCallback = (CallResult, ShopInfo.Response?) -> ()
/// Callback type definition for a shop zipcode check, which verifies whether a given zipcode is eligible
/// for delivery service from a given retailer.
///
/// - Parameters:
/// - `CallResult`: gRPC call result object, which includes a status code.
/// - `CheckZipcode.Response?`: If the call succeeded, a check-zipcode response, else `nil`.
public typealias CheckZipcodeCallback = (CallResult, CheckZipcode.Response?) -> ()
/// Callback type definition for a membership check, which usually occurs before order submission or
/// user enrollment. This check is usually conducted against either the user's phone number (in managed/
/// embedded contexts) or their email address (in web contexts).
///
/// - Parameters:
/// - `CallResult`: gRPC call result object, which includes a status code.
/// - `VerifyMember.Response?`: If the call succeeded, a member verification response, else `nil`.
public typealias VerifyMemberCallback = (CallResult, VerifyMember.Response?) -> ()
/// Callback type definition for a member enrollment operation, which creates a new account from scratch for a
/// user who is present physically or digitally at a retail storefront. This end-user (consumer) account can then
/// be used to submit digital orders, check the status of existing digital orders, adjust preferences for texts or
/// emails from the retailer, and more.
///
/// When conducted from an embedded/managed context (i.e. onsite at a retail location), the flow can be cut
/// in half with the first portion consisting of only a phone number/first name pair. The flow then continues via
/// text message while the user presumably waits for an `ONSITE` order to be fulfilled.
///
/// - Parameters:
/// - `CallResult`: gRPC call result object, which includes a status code.
/// - `EnrollMember.Response?`: Member enrollment response if the operation was a success, or,
/// `nil` if no response was received due to some terminal error.
public typealias EnrollMemberCallback = (CallResult, EnrollMember.Response?) -> ()
/// Callback type definition for a status check on an existing order, either via the universal order ID (which
/// addresses an order globally across all systems and scopes), or an order number, which, when paired with
/// a given partner/location scope, can be mapped to an order ID.
///
/// - Parameters:
/// - `CallResult`: gRPC call result object, which includes a status code.
/// - `GetOrder.Response?`: Response, if the order could be found, otherwise `nil`.
public typealias GetOrderCallback = (CallResult, GetOrder.Response?) -> ()
/// Callback type definition for an operation that submits a commercial order, in a retail context, for a given
/// partner/location scope. This interface is inteded to be used from an end-user/consumer account, either
/// from a device they control and own, or from a managed device in a physical retail setting.
///
/// - Parameters:
/// - `CallResult`: gRPC call result object, which includes a status code.
/// - `SubmitOrder.Response?`: Response from the server, or `nil` if an error was returned.
public typealias SubmitOrderCallback = (CallResult, SubmitOrder.Response?) -> ()
/// Enumerates code-level errors in the Shop client.
public enum ShopClientError: Error {
/// No API key could be resolved, or the given API key was invalid.
case invalidApiKey
/// No partner code could be resolved, or the given partner code was invalid.
case invalidPartnerCode
/// No location code could be resolved, or the given location code was invalid.
case invalidLocationCode
/// Failed to resolve a valid device name/UUID/serial number.
case invalidDeviceName
/// An unspecified internal error occurred.
case internalError
}
/// Provides functionality for the Shop API, which supports operations related to pickup or delivery ordering, member
/// verification and enrollment, and basic shop operations.
public final class ShopClient: RemoteService {
/// Name of the Shop API, which is "shop".
let name = "shop"
/// Version of this service.
let version = "v1"
// MARK: Internals
/// Client-wide settings.
internal let settings: Bloombox.Settings
/// Shop service.
internal var svc: ShopService?
/// Library-internal initializer.
///
/// - Parameter settings: Library-level settings to fall-back to.
public init(settings: Bloombox.Settings) {
self.settings = settings
}
/// Shop service.
///
/// - Parameter apiKey: API key to use with the shop service.
/// - Throws: If required information cannot be resolved.
/// - Returns: Instance of the Shop service.
internal func service(_ apiKey: APIKey) throws -> ShopService {
if let s = self.svc {
return s
}
let svc = RPCServiceFactory<ShopService>.factory(
forService: Transport.config.services.shop,
withSettings: self.settings)
try svc.metadata.add(key: "x-api-key", value: apiKey)
self.svc = svc
return svc
}
/// Resolve partner and location context, throwing an error if it cannot be figured out.
///
/// - Parameter partner: Partner code under which we should conduct an operation.
/// - Parameter location: Location code under which we should conduct an operation.
/// - Parameter apiKey: API key under which we should conduct an operation.
/// - Throws: If required information cannot be resolved. See `ShopClientError`.
/// - Returns: Tuple of the `(partner, location, apiKey)` that should be used.
private func resolveContext(_ partner: PartnerCode? = nil,
_ location: LocationCode? = nil,
_ apiKey: APIKey? = nil) throws -> (partner: PartnerCode, location: LocationCode, apiKey: APIKey) {
let partnerCode: PartnerCode? = partner ?? settings.partner
let locationCode: LocationCode? = location ?? settings.location
let apiKey: APIKey? = apiKey ?? settings.apiKey
// must have an API key
guard apiKey != nil else {
throw ShopClientError.invalidApiKey
}
// validate partner and location codes
guard partnerCode != nil, locationCode != nil else {
// throw error: we require a partner or location code from somewhere
if partnerCode == nil {
throw ShopClientError.invalidPartnerCode
}
throw ShopClientError.invalidLocationCode
}
return (partner: partnerCode!, location: locationCode!, apiKey: apiKey!)
}
/// Resolve partner and location context, throwing an error if it cannot be figured out, this time
/// including a device name, where required.
///
/// - Parameter partner: Partner code under which we should conduct an operation.
/// - Parameter location: Location code under which we should conduct an operation.
/// - Parameter deviceName: Name of the device, or serial number of the device, submitting
/// the retail shop operation to the server.
/// - Parameter apiKey: API key under which we should conduct an operation.
/// - Throws: If required information cannot be resolved. See `ShopClientError`.
/// - Returns: Tuple of the `(partner, location, apiKey)` that should be used.
private func resolveContext(_ partner: PartnerCode? = nil,
_ location: LocationCode? = nil,
_ deviceName: DeviceCode? = nil,
_ apiKey: APIKey? = nil) throws -> (
partner: PartnerCode, location: LocationCode, apiKey: APIKey, deviceName: DeviceCode) {
let partnerCode: PartnerCode? = partner ?? settings.partner
let locationCode: LocationCode? = location ?? settings.location
let deviceName: DeviceCode? = deviceName ?? settings.deviceUUID
let apiKey: APIKey? = apiKey ?? settings.apiKey
// must have an API key
guard apiKey != nil else {
throw ShopClientError.invalidApiKey
}
// validate partner and location codes
guard partnerCode != nil, locationCode != nil else {
// throw error: we require a partner or location code from somewhere
if partnerCode == nil {
throw ShopClientError.invalidPartnerCode
}
throw ShopClientError.invalidLocationCode
}
// validate device name
guard deviceName != nil else {
throw ShopClientError.invalidDeviceName
}
return (partner: partnerCode!, location: locationCode!, apiKey: apiKey!, deviceName: deviceName!)
}
//
//
// MARK: - Public API -
//
//
// MARK: Shop Info
/// Retrieve info about a particular storefront, specifically, its open/closed status, hours, and metadata.
///
/// - Parameter partner: Partner code we should check current status for. Uses settings if unspecified.
/// - Parameter location: Location code we should check current status for. Uses settings if unspecified.
/// - Parameter apiKey: API key we should use for this operation. Uses settings if unspecified.
/// - Throws: If any required information is missing and cannot be resolved from settings.
/// - Returns: Shop info response, if one could be resolved.
public func info(partner: PartnerCode? = nil,
location: LocationCode? = nil,
apiKey: APIKey? = nil) throws -> ShopInfo.Response {
let (partnerCode, locationCode, apiKey) = try resolveContext(partner, location, apiKey)
return try self.service(apiKey).shopInfo(ShopInfo.Request.with { builder in
builder.location = LocationKey.with { builder in
builder.code = locationCode
builder.partner = PartnerKey.with { builder in
builder.code = partnerCode
}
}
})
}
/// Retrieve info, asynchronously, about a particular storefront, specifically, its open/closed status, hours, and
/// metadata.
///
/// - Parameter partner: Partner code we should check current status for. Uses settings if unspecified.
/// - Parameter location: Location code we should check current status for. Uses settings if unspecified.
/// - Parameter apiKey: API key we should use for this operation. Uses settings if unspecified.
/// - Parameter callback: Callback to dispatch with the resulting information, or error.
/// - Throws: If any required information is missing and cannot be resolved from settings.
/// - Returns: RPC call operation, which can be observed or used to cancel the call.
@discardableResult
public func info(partner: PartnerCode? = nil,
location: LocationCode? = nil,
apiKey: APIKey? = nil,
callback: @escaping ShopInfoCallback) throws -> ShopInfoCall {
let (partnerCode, locationCode, apiKey) = try resolveContext(partner, location, apiKey)
return try self.service(apiKey).shopInfo(ShopInfo.Request.with { builder in
builder.location = LocationKey.with { builder in
builder.code = locationCode
builder.partner = PartnerKey.with { builder in
builder.code = partnerCode
}
}
}) { (response, callResult) in
callback(callResult, response)
}
}
// MARK: - Check Zipcode
/// Check a zipcode for delivery eligibility, including any order minimum required, if specified by the server.
///
/// - Parameter zipcode: Zip-code to check for delivery support.
/// - Parameter partner: Partner code we should check zip-code status for. Uses settings if unspecified.
/// - Parameter location: Location code we should check zip-code status for. Uses settings if unspecified.
/// - Parameter apiKey: API key we should use for this operation. Uses settings if unspecified.
/// - Throws: If any required information is missing and cannot be resolved from settings.
/// - Returns: Zipcode check response, if one could be resolved.
public func checkZipcode(zipcode: String,
partner: PartnerCode? = nil,
location: LocationCode? = nil,
apiKey: APIKey? = nil) throws -> CheckZipcode.Response {
let (partnerCode, locationCode, apiKey) = try resolveContext(partner, location, apiKey)
return try self.service(apiKey).checkZipcode(CheckZipcode.Request.with { builder in
builder.zipcode = zipcode
builder.location = LocationKey.with { builder in
builder.code = locationCode
builder.partner = PartnerKey.with { builder in
builder.code = partnerCode
}
}
})
}
/// Check a zipcode, asynchronously, for delivery eligibility, including any order minimum required, if specified by
/// the server.
///
/// - Parameter zipcode: Zip-code to check for delivery support.
/// - Parameter partner: Partner code we should check zip-code status for. Uses settings if unspecified.
/// - Parameter location: Location code we should check zip-code status for. Uses settings if unspecified.
/// - Parameter apiKey: API key we should use for this operation. Uses settings if unspecified.
/// - Parameter callback: Callback to dispatch with the resulting information, or error.
/// - Throws: If any required information is missing and cannot be resolved from settings.
/// - Returns: RPC call operation, which can be observed or used to cancel the call.
@discardableResult
public func checkZipcode(zipcode: String,
partner: PartnerCode? = nil,
location: LocationCode? = nil,
apiKey: APIKey? = nil,
callback: @escaping CheckZipcodeCallback) throws -> CheckZipcodeCall {
let (partnerCode, locationCode, apiKey) = try resolveContext(partner, location, apiKey)
return try self.service(apiKey).checkZipcode(CheckZipcode.Request.with { builder in
builder.zipcode = zipcode
builder.location = LocationKey.with { builder in
builder.code = locationCode
builder.partner = PartnerKey.with { builder in
builder.code = partnerCode
}
}
}) { (response, callResult) in
callback(callResult, response)
}
}
// MARK: - Verify Member
/// Verify a member account by their phone number. "Verify" in this context checks that they have a valid account,
/// membership with the partner/location in question, and have no expired documents, like medical recommendations and
/// IDs.
///
/// - Parameter phone: Phone number to verify the user's account with.
/// - Parameter partner: Partner code we should check for an account in. Uses settings if unspecified.
/// - Parameter location: Location code we should check for an account in. Uses settings if unspecified.
/// - Parameter apiKey: API key we should use for this operation. Uses settings if unspecified.
/// - Throws: If any required information is missing and cannot be resolved from settings.
/// - Returns: Verification result, as a synchronous response.
public func verifyMember(phone: PhoneNumber,
partner: PartnerCode? = nil,
location: LocationCode? = nil,
apiKey: APIKey? = nil) throws -> VerifyMember.Response {
let (partnerCode, locationCode, apiKey) = try resolveContext(partner, location, apiKey)
return try self.service(apiKey).verifyMember(VerifyMember.Request.with { builder in
builder.phone = phone
builder.location = LocationKey.with { builder in
builder.code = locationCode
builder.partner = PartnerKey.with { builder in
builder.code = partnerCode
}
}
})
}
/// Verify a member account by their email address. "Verify" in this context checks that they have a valid account,
/// membership with the partner/location in question, and have no expired documents, like medical recommendations and
/// IDs.
///
/// - Parameter email: Email address to verify the user's account with.
/// - Parameter partner: Partner code we should check for an account in. Uses settings if unspecified.
/// - Parameter location: Location code we should check for an account in. Uses settings if unspecified.
/// - Parameter apiKey: API key we should use for this operation. Uses settings if unspecified.
/// - Throws: If any required information is missing and cannot be resolved from settings.
/// - Returns: Verification result, as a synchronous response.
public func verifyMember(email: String,
partner: PartnerCode? = nil,
location: LocationCode? = nil,
apiKey: APIKey? = nil) throws -> VerifyMember.Response {
let (partnerCode, locationCode, apiKey) = try resolveContext(partner, location, apiKey)
let base64EncodedEmail = email.data(using: .utf8)!.base64EncodedString()
return try self.service(apiKey).verifyMember(VerifyMember.Request.with { builder in
builder.emailAddress = base64EncodedEmail.replacingOccurrences(of: "=", with: "")
builder.location = LocationKey.with { builder in
builder.code = locationCode
builder.partner = PartnerKey.with { builder in
builder.code = partnerCode
}
}
})
}
/// Verify a member account, asynchronously, by their phone number. "Verify" in this context checks that they have a
/// valid account, membership with the partner/location in question, and have no expired documents, like medical
/// recommendations and IDs.
///
/// - Parameter phone: Phone number to verify the user's account with.
/// - Parameter partner: Partner code we should check for an account in. Uses settings if unspecified.
/// - Parameter location: Location code we should check for an account in. Uses settings if unspecified.
/// - Parameter apiKey: API key we should use for this operation. Uses settings if unspecified.
/// - Parameter callback: Callback to dispatch with the resulting information, or error.
/// - Throws: If any required information is missing and cannot be resolved from settings.
/// - Returns: RPC call operation, which can be observed or used to cancel the call.
@discardableResult
public func verifyMember(phone: PhoneNumber,
partner: PartnerCode? = nil,
location: LocationCode? = nil,
apiKey: APIKey? = nil,
callback: @escaping VerifyMemberCallback) throws -> VerifyMemberCall {
let (partnerCode, locationCode, apiKey) = try resolveContext(partner, location, apiKey)
return try self.service(apiKey).verifyMember(VerifyMember.Request.with { builder in
builder.phone = phone
builder.location = LocationKey.with { builder in
builder.code = locationCode
builder.partner = PartnerKey.with { builder in
builder.code = partnerCode
}
}
}) { (response, callResult) in
callback(callResult, response)
}
}
/// Verify a member account, asynchronously, by their email address. "Verify" in this context checks that they have a
/// valid account, membership with the partner/location in question, and have no expired documents, like medical
/// recommendations and IDs.
///
/// - Parameter email: Email address to verify the user's account with.
/// - Parameter partner: Partner code we should check for an account in. Uses settings if unspecified.
/// - Parameter location: Location code we should check for an account in. Uses settings if unspecified.
/// - Parameter apiKey: API key we should use for this operation. Uses settings if unspecified.
/// - Parameter callback: Callback to dispatch with the resulting information, or error.
/// - Throws: If any required information is missing and cannot be resolved from settings.
/// - Returns: RPC call operation, which can be observed or used to cancel the call.
@discardableResult
public func verifyMember(email: String,
partner: PartnerCode? = nil,
location: LocationCode? = nil,
apiKey: APIKey? = nil,
callback: @escaping VerifyMemberCallback) throws -> VerifyMemberCall {
let (partnerCode, locationCode, apiKey) = try resolveContext(partner, location, apiKey)
return try self.service(apiKey).verifyMember(VerifyMember.Request.with { builder in
builder.emailAddress = email
builder.location = LocationKey.with { builder in
builder.code = locationCode
builder.partner = PartnerKey.with { builder in
builder.code = partnerCode
}
}
}) { (response, callResult) in
callback(callResult, response)
}
}
// MARK: - Enroll Member
/// Begin an enrollment flow for a new user account. In this flow, the user is only prompted for contact info and
/// a first-name with which to address them. The enrollment proceeds, optionally, at the user's discretion,
/// resumed by a tap on a texted link to their phone.
///
/// If the user opts to proceed with enrollment, they are issued a digital card under their name to use for future
/// checkins at supporting retail locations.
///
/// - Parameter phone: Phone number to begin enrollment with.
/// - Parameter name: Person's name, to address them with.
/// - Parameter source: Source for this enrollment activity.
/// - Parameter channel: Origin channel to assign to this enrollment record.
/// - Parameter preOrder: Indicate that this enrollment is happening before an order.
/// - Parameter partner: Partner code we should use for this enrollment record.
/// - Parameter location: Location code we should use for this enrollment record.
/// - Parameter deviceName: Name of the device that is signing up this user.
/// - Parameter apiKey: API key to use for this operation.
/// - Throws: Errors that occur client-side, see `ShopClientError`.
/// - Returns: Result of the begin-enrollment call, as a synchronous response from the server.
public func beginEnrollment(phone: PhoneNumber,
name: PersonName,
source: EnrollmentSource,
channel: String? = nil,
preOrder: Bool = false,
partner: PartnerCode? = nil,
location: LocationCode? = nil,
deviceName: DeviceCode? = nil,
apiKey: APIKey? = nil) throws -> EnrollMember.Response {
let (partnerCode, locationCode, apiKey) = try resolveContext(partner, location, apiKey)
return try self.service(apiKey).enrollMember(EnrollMember.Request.with { builder in
builder.early = true
builder.preOrder = preOrder
builder.source = source
if let c = channel {
builder.channel = c
}
if let device = deviceName {
builder.deviceID = device
}
builder.person = Person.with { builder in
builder.name = name
}
builder.location = LocationKey.with { builder in
builder.code = locationCode
builder.partner = PartnerKey.with { builder in
builder.code = partnerCode
}
}
})
}
/// Begin an enrollment flow for a new user account. In this flow, the user is only prompted for contact info and
/// a first-name with which to address them. The enrollment proceeds, optionally, at the user's discretion,
/// resumed by a tap on a texted link to their phone.
///
/// If the user opts to proceed with enrollment, they are issued a digital card under their name to use for future
/// checkins at supporting retail locations.
///
/// - Parameter phone: Phone number to begin enrollment with.
/// - Parameter name: Person's name, to address them with.
/// - Parameter source: Source for this enrollment activity.
/// - Parameter channel: Origin channel to assign to this enrollment record.
/// - Parameter preOrder: Indicate that this enrollment is happening before an order.
/// - Parameter partner: Partner code we should use for this enrollment record.
/// - Parameter location: Location code we should use for this enrollment record.
/// - Parameter deviceName: Name of the device that is signing up this user.
/// - Parameter apiKey: API key to use for this operation.
/// - Parameter callback: Callback to dispatch once the operation is complete, or errors.
/// - Throws: Errors that occur client-side, see `ShopClientError`.
/// - Returns: RPC call operation, which can be observed or used to cancel the call.
@discardableResult
public func beginEnrollment(phone: PhoneNumber,
name: PersonName,
source: EnrollmentSource,
channel: String? = nil,
preOrder: Bool = false,
partner: PartnerCode? = nil,
location: LocationCode? = nil,
deviceName: DeviceCode? = nil,
apiKey: APIKey? = nil,
callback: @escaping EnrollMemberCallback) throws -> EnrollMemberCall {
let (partnerCode, locationCode, apiKey) = try resolveContext(partner, location, apiKey)
return try self.service(apiKey).enrollMember(EnrollMember.Request.with { builder in
builder.early = true
builder.preOrder = preOrder
builder.source = source
if let c = channel {
builder.channel = c
}
if let device = deviceName {
builder.deviceID = device
}
builder.person = Person.with { builder in
builder.name = name
}
builder.location = LocationKey.with { builder in
builder.code = locationCode
builder.partner = PartnerKey.with { builder in
builder.code = partnerCode
}
}
}) { (callResult, response) in
}
}
// MARK: - Get Order
/// Retrieve information about a previously-submitted pickup or delivery order. Includes status information and an
/// action log.
///
/// - Parameter id: ID with which to retrieve an order's status.
/// - Parameter isLocal: Flag indicating whether the ID is a local order number, or a global ID.
/// - Parameter partner: Partner code we should check for an order in. Uses settings if unspecified.
/// - Parameter location: Location code we should check for an order in. Uses settings if unspecified.
/// - Parameter apiKey: API key we should use for this operation. Uses settings if unspecified.
/// - Throws: If any required information is missing and cannot be resolved from settings.
/// - Returns: Order result, as a synchronous response.
public func getOrder(id: OrderID,
isLocal: Bool = false,
partner: PartnerCode? = nil,
location: LocationCode? = nil,
apiKey: APIKey? = nil) throws -> GetOrder.Response {
let (partnerCode, locationCode, apiKey) = try resolveContext(partner, location, apiKey)
return try self.service(apiKey).getOrder(GetOrder.Request.with { builder in
if isLocal {
builder.orderNumber = id
} else {
builder.orderID = id
}
builder.location = LocationKey.with { builder in
builder.code = locationCode
builder.partner = PartnerKey.with { builder in
builder.code = partnerCode
}
}
})
}
/// Retrieve information, asynchronously, about a previously-submitted pickup or delivery order. Includes status
/// information and an action log.
///
/// - Parameter id: ID with which to retrieve an order's status.
/// - Parameter isLocal: Flag indicating whether the ID is a local order number, or a global ID.
/// - Parameter partner: Partner code we should check for an order in. Uses settings if unspecified.
/// - Parameter location: Location code we should check for an order in. Uses settings if unspecified.
/// - Parameter apiKey: API key we should use for this operation. Uses settings if unspecified.
/// - Parameter callback: Callback to dispatch with the resulting information, or error.
/// - Throws: If any required information is missing and cannot be resolved from settings.
/// - Returns: RPC call operation, which can be observed or used to cancel the call.
@discardableResult
public func getOrder(id: OrderID,
isLocal: Bool = false,
partner: PartnerCode? = nil,
location: LocationCode? = nil,
apiKey: APIKey? = nil,
callback: @escaping GetOrderCallback) throws -> GetOrderCall {
let (partnerCode, locationCode, apiKey) = try resolveContext(partner, location, apiKey)
return try self.service(apiKey).getOrder(GetOrder.Request.with { builder in
if isLocal {
builder.orderNumber = id
} else {
builder.orderID = id
}
builder.location = LocationKey.with { builder in
builder.code = locationCode
builder.partner = PartnerKey.with { builder in
builder.code = partnerCode
}
}
}) { (response, callResult) in
callback(callResult, response)
}
}
// MARK: - Submit Order
/// Submit a prepared order to the server for consideration for fulfillment. Order submitted via this
/// endpoint are retail-commercial orders for a physically-present or digitally-present customer. The
/// order is directed to the appropriate retail location, and the staff at that location are notified, via
/// email/SMS/Web Dashboard. In some cases, order tickets may also print from on-site receipt
/// printers, via Blomobox's integration with Google Cloud Print.
///
/// In order to submit an order, a user must be enrolled and active, with a membership record
/// present at the retail location at which the order is being submitted. At the time of order
/// submission, the user must not have an expired ID or medical recommendation (if they are a
/// medical patient), otherwise the order is rejected.
///
/// - Parameter order: Digital order specification to submit on behalf of this user.
/// - Parameter partner: Partner code under which to submit this digital order. If left
/// unspecified, settings are consulted.
/// - Parameter location: Location code under which to submit this digital order. If left
/// unspecifeid, settings are consulted.
/// - Parameter deviceName: Name or serial number of the device submitting the order.
/// - Parameter apiKey: API key to use for this operation.
/// - Throws: Client-side errors, if encountered (see `ShopClientError`).
/// - Returns: Result of the order submission call, as a synchronous response.
public func submitOrder(order: Order,
partner: PartnerCode? = nil,
location: LocationCode? = nil,
deviceName: DeviceCode? = nil,
apiKey: APIKey? = nil) throws -> SubmitOrder.Response {
let (partnerCode, locationCode, apiKey, deviceName) = try resolveContext(partner, location, deviceName, apiKey)
return try self.service(apiKey).submitOrder(SubmitOrder.Request.with { builder in
builder.order = order
builder.device = deviceName
builder.location = LocationKey.with { builder in
builder.code = locationCode
builder.partner = PartnerKey.with { builder in
builder.code = partnerCode
}
}
})
}
/// Submit a prepared order to the server for consideration for fulfillment. Order submitted via this
/// endpoint are retail-commercial orders for a physically-present or digitally-present customer. The
/// order is directed to the appropriate retail location, and the staff at that location are notified, via
/// email/SMS/Web Dashboard. In some cases, order tickets may also print from on-site receipt
/// printers, via Blomobox's integration with Google Cloud Print.
///
/// In order to submit an order, a user must be enrolled and active, with a membership record
/// present at the retail location at which the order is being submitted. At the time of order
/// submission, the user must not have an expired ID or medical recommendation (if they are a
/// medical patient), otherwise the order is rejected.
///
/// - Parameter order: Digital order specification to submit on behalf of this user.
/// - Parameter partner: Partner code under which to submit this digital order. If left
/// unspecified, settings are consulted.
/// - Parameter location: Location code under which to submit this digital order. If left
/// unspecifeid, settings are consulted.
/// - Parameter deviceName: Name or serial number of the device submitting the order.
/// - Parameter apiKey: API key to use for this operation.
/// - Throws: Client-side errors, if encountered (see `ShopClientError`).
/// - Returns: RPC call operation, which can be observed or used to cancel the call.
@discardableResult
public func submitOrder(order: Order,
partner: PartnerCode? = nil,
location: LocationCode? = nil,
deviceName: DeviceCode? = nil,
apiKey: APIKey? = nil,
callback: @escaping SubmitOrderCallback) throws -> SubmitOrderCall {
let (partnerCode, locationCode, apiKey, deviceName) = try resolveContext(partner, location, deviceName, apiKey)
return try self.service(apiKey).submitOrder(SubmitOrder.Request.with { builder in
builder.order = order
builder.device = deviceName
builder.location = LocationKey.with { builder in
builder.code = locationCode
builder.partner = PartnerKey.with { builder in
builder.code = partnerCode
}
}
}) { (response, callResult) in
callback(callResult, response)
}
}
}