Commit be11d176 by Dima Bart

Move BUYClient storefront API into category.

parent 723efc71
......@@ -343,6 +343,10 @@
9A0B0C671CEA703E0037D68F /* BUYClient+Routing.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A0B0C641CEA703E0037D68F /* BUYClient+Routing.h */; };
9A0B0C681CEA703E0037D68F /* BUYClient+Routing.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A0B0C651CEA703E0037D68F /* BUYClient+Routing.m */; };
9A0B0C691CEA703E0037D68F /* BUYClient+Routing.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A0B0C651CEA703E0037D68F /* BUYClient+Routing.m */; };
9A0B0C6C1CEB4D300037D68F /* BUYClient+Storefront.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A0B0C6A1CEB4D300037D68F /* BUYClient+Storefront.h */; settings = {ATTRIBUTES = (Public, ); }; };
9A0B0C6D1CEB4D300037D68F /* BUYClient+Storefront.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A0B0C6A1CEB4D300037D68F /* BUYClient+Storefront.h */; settings = {ATTRIBUTES = (Public, ); }; };
9A0B0C6E1CEB4D300037D68F /* BUYClient+Storefront.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A0B0C6B1CEB4D300037D68F /* BUYClient+Storefront.m */; };
9A0B0C6F1CEB4D300037D68F /* BUYClient+Storefront.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A0B0C6B1CEB4D300037D68F /* BUYClient+Storefront.m */; };
9A102D1B1CDD1F960026CC43 /* BUYErrorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A102D1A1CDD1F960026CC43 /* BUYErrorTests.m */; };
9A102D1E1CDD25980026CC43 /* BUYOptionValueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A102D1D1CDD25980026CC43 /* BUYOptionValueTests.m */; };
9A47CEFD1CE39F6000A6D5BA /* BUYCreditCardToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A47CEFB1CE39F5B00A6D5BA /* BUYCreditCardToken.m */; };
......@@ -619,6 +623,8 @@
90FC31A71B50371600AFAB51 /* BUYProductViewHeaderBackgroundImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BUYProductViewHeaderBackgroundImageView.m; path = "Product View/BUYProductViewHeaderBackgroundImageView.m"; sourceTree = "<group>"; };
9A0B0C641CEA703E0037D68F /* BUYClient+Routing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BUYClient+Routing.h"; sourceTree = "<group>"; };
9A0B0C651CEA703E0037D68F /* BUYClient+Routing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BUYClient+Routing.m"; sourceTree = "<group>"; };
9A0B0C6A1CEB4D300037D68F /* BUYClient+Storefront.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BUYClient+Storefront.h"; sourceTree = "<group>"; };
9A0B0C6B1CEB4D300037D68F /* BUYClient+Storefront.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BUYClient+Storefront.m"; sourceTree = "<group>"; };
9A102D1A1CDD1F960026CC43 /* BUYErrorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BUYErrorTests.m; sourceTree = "<group>"; };
9A102D1D1CDD25980026CC43 /* BUYOptionValueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BUYOptionValueTests.m; sourceTree = "<group>"; };
9A47CEF81CE39EC200A6D5BA /* BUYPaymentToken.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BUYPaymentToken.h; sourceTree = "<group>"; };
......@@ -1110,6 +1116,8 @@
F7FDA17119C93F6F00AF4E93 /* BUYClient.m */,
9A0B0C641CEA703E0037D68F /* BUYClient+Routing.h */,
9A0B0C651CEA703E0037D68F /* BUYClient+Routing.m */,
9A0B0C6A1CEB4D300037D68F /* BUYClient+Storefront.h */,
9A0B0C6B1CEB4D300037D68F /* BUYClient+Storefront.m */,
8498DCB11CDD1B4A00BD12A8 /* BUYClient+Customers.h */,
8498DCB21CDD1B4A00BD12A8 /* BUYClient+Customers.m */,
);
......@@ -1154,6 +1162,7 @@
9019312F1BC5B9BC00D1134E /* BUYLineItem.h in Headers */,
90C856B51BD6B0F300936926 /* Buy.h in Headers */,
8498DCAB1CDD1B2600BD12A8 /* BUYShopifyErrorCodes.h in Headers */,
9A0B0C6D1CEB4D300037D68F /* BUYClient+Storefront.h in Headers */,
9A47CF071CE3ACE000A6D5BA /* BUYPaymentToken.h in Headers */,
901931351BC5B9BC00D1134E /* BUYDiscount.h in Headers */,
8498DCB71CDD1B5400BD12A8 /* BUYClient+Customers.h in Headers */,
......@@ -1255,6 +1264,7 @@
84980F4C1CB7613700CFAB58 /* BUYIdentityTransformer.h in Headers */,
BE9A645B1B503CDC0033E558 /* BUYLineItem.h in Headers */,
8498DCAA1CDD1B2500BD12A8 /* BUYShopifyErrorCodes.h in Headers */,
9A0B0C6C1CEB4D300037D68F /* BUYClient+Storefront.h in Headers */,
9A47CF081CE3ACE100A6D5BA /* BUYPaymentToken.h in Headers */,
BE9A644F1B503CA90033E558 /* BUYDiscount.h in Headers */,
8498DCB41CDD1B5400BD12A8 /* BUYClient+Customers.h in Headers */,
......@@ -1560,6 +1570,7 @@
9A47CF061CE3A24600A6D5BA /* BUYApplePayToken.m in Sources */,
84D915521CC03F1600D334FB /* BUYModelManager.m in Sources */,
84DD129C1CC63FE600A2442D /* _BUYCartLineItem.m in Sources */,
9A0B0C6F1CEB4D300037D68F /* BUYClient+Storefront.m in Sources */,
841ADE121CB6C942000004B0 /* NSDictionary+BUYAdditions.m in Sources */,
9019310C1BC5B9BC00D1134E /* BUYGiftCard.m in Sources */,
841ADE161CB6C942000004B0 /* NSException+BUYModelAdditions.m in Sources */,
......@@ -1690,6 +1701,7 @@
9A47CF051CE3A24600A6D5BA /* BUYApplePayToken.m in Sources */,
84D915511CC03F1600D334FB /* BUYModelManager.m in Sources */,
84DD12841CC63FE600A2442D /* _BUYCartLineItem.m in Sources */,
9A0B0C6E1CEB4D300037D68F /* BUYClient+Storefront.m in Sources */,
841ADE111CB6C942000004B0 /* NSDictionary+BUYAdditions.m in Sources */,
BE9A64581B503CD10033E558 /* BUYGiftCard.m in Sources */,
841ADE151CB6C942000004B0 /* NSException+BUYModelAdditions.m in Sources */,
......
......@@ -65,6 +65,7 @@ FOUNDATION_EXPORT const unsigned char BuyVersionString[];
#import <Buy/BUYClient.h>
#import <Buy/BUYClient+Customers.h>
#import <Buy/BUYClient+Checkout.h>
#import <Buy/BUYClient+Storefront.h>
#import <Buy/BUYError.h>
#import <Buy/BUYError+BUYAdditions.h>
#import <Buy/BUYManagedObject.h>
......
//
// BUYClient+Storefront.h
// Mobile Buy SDK
//
// Created by Shopify.
// Copyright (c) 2015 Shopify Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import "BUYClient.h"
NS_ASSUME_NONNULL_BEGIN
@class BUYShop;
@class BUYCollection;
@class BUYProduct;
/**
* Return block containing a BUYShop and/or an NSError
*
* @param shop A BUYShop object
* @param error Optional NSError
*/
typedef void (^BUYDataShopBlock)(BUYShop * _Nullable shop, NSError * _Nullable error);
/**
* Return block containing a list of BUYCollection objects and/or an NSError
*
* @param collections An array of BUYCollection objects
* @param error Optional NSError
*/
typedef void (^BUYDataCollectionsBlock)(NSArray<BUYCollection *> * _Nullable collections, NSError * _Nullable error);
/**
* Return block containing a BUYProduct and/or an NSError
*
* @param product A BUYProduct
* @param error Optional NSError
*/
typedef void (^BUYDataProductBlock)(BUYProduct * _Nullable product, NSError * _Nullable error);
/**
* Return block containing a list of BUYProduct objects and/or an NSError
*
* @param products An array of BUYProduct objects
* @param error Optional NSError
*/
typedef void (^BUYDataProductsBlock)(NSArray<BUYProduct *> * _Nullable products, NSError * _Nullable error);
/**
* Return block containing list of collections
*
* @param collections An array of BUYCollection objects
* @param error Optional NSError
*/
typedef void (^BUYDataCollectionsListBlock)(NSArray<BUYCollection *> * _Nullable collections, NSUInteger page, BOOL reachedEnd, NSError * _Nullable error);
/**
* Return block containing a list of BUYProduct objects, the page requested, a boolean to determine whether the end of the list has been reach and/or an optional NSError
*
* @param products An array of BUYProduct objects
* @param page Index of the page requested
* @param reachedEnd Boolean indicating whether additional pages exist
* @param error An optional NSError
*/
typedef void (^BUYDataProductListBlock)(NSArray<BUYProduct *> * _Nullable products, NSUInteger page, BOOL reachedEnd, NSError * _Nullable error);
@interface BUYClient (Storefront)
/**
* Fetches the shop's metadata (from /meta.json).
*
* @param block (^BUYDataShopBlock)(BUYShop *shop, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getShop:(BUYDataShopBlock)block;
/**
* Fetches a single page of products for the shop.
*
* @param page Page to request. Pages start at 1.
* @param block (^BUYDataProductListBlock)(NSArray *products, NSUInteger page, BOOL reachedEnd, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getProductsPage:(NSUInteger)page completion:(BUYDataProductListBlock)block;
/**
* Fetches a single product by the ID of the product.
*
* @param productId Product ID
* @param block (^BUYDataProductBlock)(BUYProduct *product, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getProductById:(NSString *)productId completion:(BUYDataProductBlock)block;
/**
* Fetches a list of product by the ID of each product.
*
* @param productIds An array of `NSString` objects with Product IDs to fetch
* @param block (^BUYDataProductsBlock)(NSArray *products, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getProductsByIds:(NSArray *)productIds completion:(BUYDataProductsBlock)block;
/**
* Fetches the collections on the shop
*
* @param block (^BUYDataCollectionsBlock)(NSArray *collections, NSError *error)
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getCollections:(BUYDataCollectionsBlock)block;
/**
* Fetches collections based off page
*
* @param page Index of the page requested
* @param block (^BUYDataCollectionsBlock)(NSArray *collections, NSError *error)
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getCollectionsPage:(NSUInteger)page completion:(BUYDataCollectionsListBlock)block;
/**
* Fetches the products in the given collection with the collection's
* default sort order set in the shop's admin
*
* @param page Index of the page requested
* @param collectionId The `collectionId` found in the BUYCollection object to fetch the products from
* @param block (NSArray *products, NSUInteger page, BOOL reachedEnd, NSError *error)
*
* @return the associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getProductsPage:(NSUInteger)page inCollection:(NSNumber *)collectionId completion:(BUYDataProductListBlock)block;
/**
* Fetches the products in the given collection with a given sort order
*
* @param page Index of the page requested
* @param collectionId The `collectionId` found in the BUYCollection object to fetch the products from
* @param sortOrder The sort order that overrides the default collection sort order
* @param block (NSArray *products, NSUInteger page, BOOL reachedEnd, NSError *error)
*
* @return the associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getProductsPage:(NSUInteger)page inCollection:(NSNumber *)collectionId sortOrder:(BUYCollectionSort)sortOrder completion:(BUYDataProductListBlock)block;
@end
NS_ASSUME_NONNULL_END
//
// BUYClient+Storefront.m
// Mobile Buy SDK
//
// Created by Shopify.
// Copyright (c) 2015 Shopify Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import "BUYClient+Storefront.h"
#import "BUYClient+Internal.h"
#import "BUYClient+Routing.h"
#import "BUYShop.h"
#import "BUYCollection.h"
#import "BUYProduct.h"
#import "BUYAssert.h"
#import "BUYShopifyErrorCodes.h"
static NSString * const BUYProductsKey = @"product_listings";
static NSString * const BUYCollectionsKey = @"collection_listings";
@implementation BUYClient (Storefront)
#pragma mark - Utilities -
- (BOOL)hasReachedEndOfPage:(NSArray *)lastFetchedObjects
{
return [lastFetchedObjects count] < self.pageSize;
}
#pragma mark - API -
- (NSURLSessionDataTask *)getShop:(BUYDataShopBlock)block
{
return [self getRequestForURL:[self routeForShop] completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
BUYShop *shop = nil;
if (json && !error) {
shop = [self.modelManager insertShopWithJSONDictionary:json];
}
block(shop, error);
}];
}
- (NSURLSessionDataTask *)getProductsPage:(NSUInteger)page completion:(BUYDataProductListBlock)block
{
NSURL *route = [self routeForProductListingsWithParameters:@{
@"limit" : @(self.pageSize),
@"page" : @(page),
}];
return [self getRequestForURL:route completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSArray *products = nil;
if (json && !error) {
products = [self.modelManager insertProductsWithJSONArray:json[BUYProductsKey]];
}
block(products, page, [self hasReachedEndOfPage:products] || error, error);
}];
}
- (NSURLSessionDataTask *)getProductById:(NSString *)productId completion:(BUYDataProductBlock)block;
{
BUYAssert(productId, @"Failed to get product by ID. Product ID must not be nil.");
return [self getProductsByIds:@[productId] completion:^(NSArray *products, NSError *error) {
if (products.count > 0) {
block(products[0], error);
} else {
if (!error) {
error = [NSError errorWithDomain:kShopifyError code:BUYShopifyError_InvalidProductID userInfo:@{ NSLocalizedDescriptionKey : @"Product ID is not valid. Confirm the product ID on your shop's admin and also ensure that the visibility is on for the Mobile App channel." }];
}
block(nil, error);
}
}];
}
- (NSURLSessionDataTask *)getProductsByIds:(NSArray *)productIds completion:(BUYDataProductsBlock)block
{
BUYAssert(productIds, @"Failed to get product by IDs. Product IDs array must not be nil.");
NSURL *route = [self routeForProductListingsWithParameters:@{
@"product_ids" : [productIds componentsJoinedByString:@","],
}];
return [self getRequestForURL:route completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSArray *products = nil;
if (json && !error) {
products = [self.modelManager insertProductsWithJSONArray:json[BUYProductsKey]];
}
if (!error && products.count == 0) {
error = [NSError errorWithDomain:kShopifyError code:BUYShopifyError_InvalidProductID userInfo:@{ NSLocalizedDescriptionKey : @"Product IDs are not valid. Confirm the product IDs on your shop's admin and also ensure that the visibility is on for the Mobile App channel." }];
}
block(products, error);
}];
}
- (NSURLSessionDataTask *)getCollections:(BUYDataCollectionsBlock)block
{
return [self getCollectionsPage:1 completion:^(NSArray<BUYCollection *> *collections, NSUInteger page, BOOL reachedEnd, NSError *error) {
block(collections, error);
}];
}
- (NSURLSessionDataTask *)getCollectionsPage:(NSUInteger)page completion:(BUYDataCollectionsListBlock)block
{
NSURL *route = [self routeForCollectionListingsWithParameters:@{
@"limit" : @(self.pageSize),
@"page" : @(page),
}];
return [self getRequestForURL:route completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSArray *collections = nil;
if (json && !error) {
collections = [self.modelManager buy_objectsWithEntityName:[BUYCollection entityName] JSONArray:json[BUYCollectionsKey]];
}
block(collections, page, [self hasReachedEndOfPage:collections], error);
}];
}
- (NSURLSessionDataTask *)getProductsPage:(NSUInteger)page inCollection:(NSNumber *)collectionId completion:(BUYDataProductListBlock)block
{
return [self getProductsPage:page inCollection:collectionId sortOrder:BUYCollectionSortCollectionDefault completion:block];
}
- (NSURLSessionDataTask *)getProductsPage:(NSUInteger)page inCollection:(NSNumber *)collectionId sortOrder:(BUYCollectionSort)sortOrder completion:(BUYDataProductListBlock)block
{
BUYAssert(collectionId, @"Failed to get products page. Invalid collectionID.");
NSURL *route = [self routeForProductListingsWithParameters:@{
@"collection_id" : collectionId,
@"limit" : @(self.pageSize),
@"page" : @(page),
@"sort_by" : [BUYCollection sortOrderParameterForCollectionSort:sortOrder]
}];
return [self getRequestForURL:route completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSArray *products = nil;
if (json && !error) {
products = [self.modelManager buy_objectsWithEntityName:[BUYProduct entityName] JSONArray:json[BUYProductsKey]];
}
block(products, page, [self hasReachedEndOfPage:products] || error, error);
}];
}
@end
......@@ -148,56 +148,6 @@ typedef void (^BUYDataCheckoutStatusBlock)(BUYStatus status, NSError * _Nullable
typedef void (^BUYDataShippingRatesBlock)(NSArray * _Nullable shippingRates, BUYStatus status, NSError * _Nullable error);
/**
* Return block containing a BUYShop and/or an NSError
*
* @param shop A BUYShop object
* @param error Optional NSError
*/
typedef void (^BUYDataShopBlock)(BUYShop * _Nullable shop, NSError * _Nullable error);
/**
* Return block containing a list of BUYCollection objects and/or an NSError
*
* @param collections An array of BUYCollection objects
* @param error Optional NSError
*/
typedef void (^BUYDataCollectionsBlock)(NSArray<BUYCollection *> * _Nullable collections, NSError * _Nullable error);
/**
* Return block containing a BUYProduct and/or an NSError
*
* @param product A BUYProduct
* @param error Optional NSError
*/
typedef void (^BUYDataProductBlock)(BUYProduct * _Nullable product, NSError * _Nullable error);
/**
* Return block containing a list of BUYProduct objects and/or an NSError
*
* @param products An array of BUYProduct objects
* @param error Optional NSError
*/
typedef void (^BUYDataProductsBlock)(NSArray<BUYProduct *> * _Nullable products, NSError * _Nullable error);
/**
* Return block containing list of collections
*
* @param collections An array of BUYCollection objects
* @param error Optional NSError
*/
typedef void (^BUYDataCollectionsListBlock)(NSArray<BUYCollection *> * _Nullable collections, NSUInteger page, BOOL reachedEnd, NSError * _Nullable error);
/**
* Return block containing a list of BUYProduct objects, the page requested, a boolean to determine whether the end of the list has been reach and/or an optional NSError
*
* @param products An array of BUYProduct objects
* @param page Index of the page requested
* @param reachedEnd Boolean indicating whether additional pages exist
* @param error An optional NSError
*/
typedef void (^BUYDataProductListBlock)(NSArray<BUYProduct *> * _Nullable products, NSUInteger page, BOOL reachedEnd, NSError * _Nullable error);
/**
* Return block containing a list of BUYProductImage objects and/or an NSError
*
* @param images An array of BUYProductImage objects
......@@ -287,90 +237,6 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (strong, nonatomic, nullable) NSString *customerToken;
#pragma mark - Storefront
/**
* Fetches the shop's metadata (from /meta.json).
*
* @param block (^BUYDataShopBlock)(BUYShop *shop, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getShop:(BUYDataShopBlock)block;
/**
* Fetches a single page of products for the shop.
*
* @param page Page to request. Pages start at 1.
* @param block (^BUYDataProductListBlock)(NSArray *products, NSUInteger page, BOOL reachedEnd, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getProductsPage:(NSUInteger)page completion:(BUYDataProductListBlock)block;
/**
* Fetches a single product by the ID of the product.
*
* @param productId Product ID
* @param block (^BUYDataProductBlock)(BUYProduct *product, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getProductById:(NSString *)productId completion:(BUYDataProductBlock)block;
/**
* Fetches a list of product by the ID of each product.
*
* @param productIds An array of `NSString` objects with Product IDs to fetch
* @param block (^BUYDataProductsBlock)(NSArray *products, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getProductsByIds:(NSArray *)productIds completion:(BUYDataProductsBlock)block;
/**
* Fetches the collections on the shop
*
* @param block (^BUYDataCollectionsBlock)(NSArray *collections, NSError *error)
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getCollections:(BUYDataCollectionsBlock)block;
/**
* Fetches collections based off page
*
* @param page Index of the page requested
* @param block (^BUYDataCollectionsBlock)(NSArray *collections, NSError *error)
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getCollectionsPage:(NSUInteger)page completion:(BUYDataCollectionsListBlock)block;
/**
* Fetches the products in the given collection with the collection's
* default sort order set in the shop's admin
*
* @param page Index of the page requested
* @param collectionId The `collectionId` found in the BUYCollection object to fetch the products from
* @param block (NSArray *products, NSUInteger page, BOOL reachedEnd, NSError *error)
*
* @return the associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getProductsPage:(NSUInteger)page inCollection:(NSNumber *)collectionId completion:(BUYDataProductListBlock)block;
/**
* Fetches the products in the given collection with a given sort order
*
* @param page Index of the page requested
* @param collectionId The `collectionId` found in the BUYCollection object to fetch the products from
* @param sortOrder The sort order that overrides the default collection sort order
* @param block (NSArray *products, NSUInteger page, BOOL reachedEnd, NSError *error)
*
* @return the associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getProductsPage:(NSUInteger)page inCollection:(NSNumber *)collectionId sortOrder:(BUYCollectionSort)sortOrder completion:(BUYDataProductListBlock)block;
#pragma mark - Checkout
/**
......
......@@ -63,9 +63,6 @@ NSString * const BUYVersionString = @"1.3";
NSString *const kShopifyError = @"shopify";
static NSString *const kBUYClientPathProductPublications = @"product_listings";
static NSString *const kBUYClientPathCollectionPublications = @"collection_listings";
NSString *const BUYClientCustomerAccessToken = @"X-Shopify-Customer-Access-Token";
@interface BUYClient () <NSURLSessionDelegate>
......@@ -108,6 +105,8 @@ NSString *const BUYClientCustomerAccessToken = @"X-Shopify-Customer-Access-Token
return self;
}
#pragma mark - Accessors -
- (NSURLSession *)urlSession
{
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
......@@ -119,133 +118,11 @@ NSString *const BUYClientCustomerAccessToken = @"X-Shopify-Customer-Access-Token
return [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
}
#pragma mark - Storefront
- (void)setPageSize:(NSUInteger)pageSize
{
_pageSize = MAX(MIN(pageSize, 250), 1);
}
- (BOOL)hasReachedEndOfPage:(NSArray *)lastFetchedObjects
{
return [lastFetchedObjects count] < self.pageSize;
}
- (NSURLSessionDataTask *)getShop:(BUYDataShopBlock)block
{
return [self getRequestForURL:[self routeForShop] completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
BUYShop *shop = nil;
if (json && !error) {
shop = [self.modelManager insertShopWithJSONDictionary:json];
}
block(shop, error);
}];
}
- (NSURLSessionDataTask *)getProductsPage:(NSUInteger)page completion:(BUYDataProductListBlock)block
{
NSURL *route = [self routeForProductListingsWithParameters:@{
@"limit" : @(self.pageSize),
@"page" : @(page),
}];
return [self getRequestForURL:route completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSArray *products = nil;
if (json && !error) {
products = [self.modelManager insertProductsWithJSONArray:json[kBUYClientPathProductPublications]];
}
block(products, page, [self hasReachedEndOfPage:products] || error, error);
}];
}
- (NSURLSessionDataTask *)getProductById:(NSString *)productId completion:(BUYDataProductBlock)block;
{
BUYAssert(productId, @"Failed to get product by ID. Product ID must not be nil.");
return [self getProductsByIds:@[productId] completion:^(NSArray *products, NSError *error) {
if (products.count > 0) {
block(products[0], error);
} else {
if (!error) {
error = [NSError errorWithDomain:kShopifyError code:BUYShopifyError_InvalidProductID userInfo:@{ NSLocalizedDescriptionKey : @"Product ID is not valid. Confirm the product ID on your shop's admin and also ensure that the visibility is on for the Mobile App channel." }];
}
block(nil, error);
}
}];
}
- (NSURLSessionDataTask *)getProductsByIds:(NSArray *)productIds completion:(BUYDataProductsBlock)block
{
BUYAssert(productIds, @"Failed to get product by IDs. Product IDs array must not be nil.");
NSURL *route = [self routeForProductListingsWithParameters:@{
@"product_ids" : [productIds componentsJoinedByString:@","],
}];
return [self getRequestForURL:route completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSArray *products = nil;
if (json && !error) {
products = [self.modelManager insertProductsWithJSONArray:json[kBUYClientPathProductPublications]];
}
if (!error && products.count == 0) {
error = [NSError errorWithDomain:kShopifyError code:BUYShopifyError_InvalidProductID userInfo:@{ NSLocalizedDescriptionKey : @"Product IDs are not valid. Confirm the product IDs on your shop's admin and also ensure that the visibility is on for the Mobile App channel." }];
}
block(products, error);
}];
}
- (NSURLSessionDataTask *)getCollections:(BUYDataCollectionsBlock)block
{
return [self getCollectionsPage:1 completion:^(NSArray<BUYCollection *> *collections, NSUInteger page, BOOL reachedEnd, NSError *error) {
block(collections, error);
}];
}
- (NSURLSessionDataTask *)getCollectionsPage:(NSUInteger)page completion:(BUYDataCollectionsListBlock)block
{
NSURL *route = [self routeForCollectionListingsWithParameters:@{
@"limit" : @(self.pageSize),
@"page" : @(page),
}];
return [self getRequestForURL:route completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSArray *collections = nil;
if (json && !error) {
collections = [self.modelManager buy_objectsWithEntityName:[BUYCollection entityName] JSONArray:json[kBUYClientPathCollectionPublications]];
}
block(collections, page, [self hasReachedEndOfPage:collections], error);
}];
}
- (NSURLSessionDataTask *)getProductsPage:(NSUInteger)page inCollection:(NSNumber *)collectionId completion:(BUYDataProductListBlock)block
{
return [self getProductsPage:page inCollection:collectionId sortOrder:BUYCollectionSortCollectionDefault completion:block];
}
- (NSURLSessionDataTask *)getProductsPage:(NSUInteger)page inCollection:(NSNumber *)collectionId sortOrder:(BUYCollectionSort)sortOrder completion:(BUYDataProductListBlock)block
{
BUYAssert(collectionId, @"Failed to get products page. Invalid collectionID.");
NSURL *route = [self routeForProductListingsWithParameters:@{
@"collection_id" : collectionId,
@"limit" : @(self.pageSize),
@"page" : @(page),
@"sort_by" : [BUYCollection sortOrderParameterForCollectionSort:sortOrder]
}];
return [self getRequestForURL:route completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSArray *products = nil;
if (json && !error) {
products = [self.modelManager buy_objectsWithEntityName:[BUYProduct entityName] JSONArray:json[kBUYClientPathProductPublications]];
}
block(products, page, [self hasReachedEndOfPage:products] || error, error);
}];
}
#pragma mark - Checkout
- (void)handleCheckoutResponse:(NSDictionary *)json error:(NSError *)error block:(BUYDataCheckoutBlock)block
......
......@@ -36,6 +36,10 @@
#import "BUYCartLineItem.h"
#import "BUYCheckout.h"
#import "BUYCheckoutAttribute.h"
#import "BUYClient+Test.h"
#import "BUYClient.h"
#import "BUYClient+Customers.h"
#import "BUYClient+Storefront.h"
#import "BUYCollection.h"
#import "BUYCreditCard.h"
#import "BUYCustomer.h"
......
......@@ -28,6 +28,7 @@
#import "BUYAddress.h"
#import "BUYApplePayAdditions.h"
#import "BUYClient.h"
#import "BUYClient+Storefront.h"
#import "BUYCheckout.h"
#import "BUYError.h"
#import "BUYModelManager.h"
......
//
// BUYStoreController.m
// Mobile Buy SDK
//
// Created by Shopify.
// Copyright (c) 2015 Shopify Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
@import WebKit;
#import "BUYLineItem.h"
#import "BUYProduct.h"
#import "BUYProductVariant.h"
#import "BUYStoreViewController.h"
#import "BUYError.h"
#import "BUYOrder.h"
#import "BUYShopifyErrorCodes.h"
#import "BUYClient+Storefront.h"
@interface BUYStoreViewController () <WKNavigationDelegate, WKScriptMessageHandler>
@end
@implementation BUYStoreViewController {
NSURL *_url;
WKWebView *_webView;
UIBarButtonItem *_backButton;
UIBarButtonItem *_forwardButton;
BOOL _skippingAppCheckout;
NSURLRequest *_checkoutRequest;
}
@dynamic delegate;
- (instancetype)initWithClient:(BUYClient *)client url:(NSURL *)url
{
self = [super initWithClient:client];
if (self) {
_url = url;
}
return self;
}
- (instancetype)initWithClient:(BUYClient *)client
{
self = [super initWithClient:client];
if (self) {
_url = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@", client.shopDomain]];
}
return self;
}
- (void)loadView
{
WKWebViewConfiguration *configuration = [self webViewConfiguration];
_webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration];
_webView.scrollView.decelerationRate = UIScrollViewDecelerationRateNormal;
_webView.navigationDelegate = self;
self.view = _webView;
_backButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"back"] style:UIBarButtonItemStylePlain target:_webView action:@selector(goBack)];
_forwardButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"forward"] style:UIBarButtonItemStylePlain target:_webView action:@selector(goForward)];
UIBarButtonItem *shareButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(shareTapped)];
UIBarButtonItem *flexible = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
self.toolbarItems = @[_backButton, flexible, _forwardButton, flexible, shareButton, flexible, flexible, flexible];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[_webView loadRequest:[NSURLRequest requestWithURL:_url]];
[self updateButtons];
}
#pragma mark - Button Presses
- (void)updateButtons
{
_backButton.enabled = [_webView canGoBack];
_forwardButton.enabled = [_webView canGoForward];
}
- (void)shareTapped
{
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[_webView.URL] applicationActivities:nil];
activityViewController.excludedActivityTypes = @[UIActivityTypeAddToReadingList, UIActivityTypeAssignToContact, UIActivityTypePrint, UIActivityTypeSaveToCameraRoll];
[self presentViewController:activityViewController animated:YES completion:nil];
}
#pragma mark - Web View Navigation Delegate Methods
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
NSString *currentUrlString = [[[navigationAction request] URL] absoluteString];
BOOL checkoutLink = [currentUrlString containsString:@"/checkout"] || [currentUrlString containsString:@"/sessions"];
BOOL thankYouPage = [currentUrlString containsString:@"thank_you"];
if (checkoutLink && thankYouPage == NO) {
if (_skippingAppCheckout) {
decisionHandler(WKNavigationActionPolicyAllow);
}
else {
decisionHandler(WKNavigationActionPolicyCancel);
[self presentCheckoutMethodSelectionMenuWithCheckoutRequest:[navigationAction request]];
}
}
else {
_skippingAppCheckout = NO;
decisionHandler(WKNavigationActionPolicyAllow);
}
}
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
[self updateButtons];
}
- (void)reloadHomePage
{
[_webView loadRequest:[NSURLRequest requestWithURL:_url]];
}
#pragma mark - Checkout Methods
- (void)presentCheckoutMethodSelectionMenuWithCheckoutRequest:(NSURLRequest *)request
{
_checkoutRequest = request;
if ([self.delegate respondsToSelector:@selector(controller:shouldProceedWithCheckoutType:)]) {
[self.delegate controller:self shouldProceedWithCheckoutType:^(BUYCheckoutType type) {
switch (type) {
case BUYCheckoutTypeNormal:
[self checkoutWithNormalCheckout];
break;
case BUYCheckoutTypeApplePay:
[self checkoutWithApplePay];
break;
default:
break;
}
}];
}
else {
[self checkoutWithNormalCheckout];
}
}
- (void)getProduct:(NSString *)productId withVariantId:(NSString *)variantId completion:(void (^)(BUYProductVariant *variant, NSError *error))completion;
{
[self.client getProductById:productId completion:^(BUYProduct *product, NSError *error) {
BUYProductVariant *selectedVariant = nil;
if (error == nil) {
for (BUYProductVariant *variant in product.variants) {
if ([variant.identifier isEqual:@([variantId longLongValue])]) {
selectedVariant = variant;
break;
}
}
}
else {
NSLog(@"Failed to fetch variant: %@", error);
}
completion(selectedVariant, error);
}];
}
#pragma mark - Checkout Selection Delegate Methods
- (void)checkoutWithApplePay
{
[_webView evaluateJavaScript:@"\
var cartRequest = new XMLHttpRequest();\
cartRequest.open(\"GET\", \"/cart.json\", false);\
cartRequest.onreadystatechange = function() {\
if (cartRequest.readyState == 4 && cartRequest.status == 200) {\
window.webkit.messageHandlers.nativeApp.postMessage(JSON.parse(cartRequest.responseText));\
}\
};\
cartRequest.send(null);"
completionHandler:^(id response, NSError *error) {
}];
}
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
NSDictionary *json = message.body;
NSString *cartToken = json[@"token"];
if (cartToken) {
[self startCheckoutWithCartToken:cartToken];
}
else {
NSError *error = [NSError errorWithDomain:BUYShopifyError code:BUYShopifyError_CartFetchError userInfo:nil];
[self.delegate controller:self failedToCreateCheckout:error];
}
}
- (void)checkoutWithNormalCheckout
{
_skippingAppCheckout = YES;
[_webView loadRequest:_checkoutRequest];
}
#pragma mark - Checkout Completion Handling
- (void)checkoutCompleted:(BUYCheckout *)checkout status:(BUYStatus)status
{
if (status == BUYStatusComplete) {
[self.client getCheckout:checkout completion:^(BUYCheckout *updatedCheckout, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (updatedCheckout.order.statusURL) {
[_webView loadRequest:[NSURLRequest requestWithURL:updatedCheckout.order.statusURL]];
}
else {
NSLog(@"Couldn't redirect to thank you page: %@ (url: %@)", updatedCheckout, updatedCheckout.order.statusURL);
}
});
}];
}
else {
NSError *error = [NSError errorWithDomain:BUYShopifyError code:status userInfo:@{@"checkout": checkout}];
[self.delegate controller:self failedToCompleteCheckout:checkout withError:error];
}
}
#pragma mark - Web View Configuration
- (WKWebViewConfiguration *)webViewConfiguration
{
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
configuration.userContentController = [self userContentConfiguration];
configuration.processPool = [self processPool];
return configuration;
}
- (WKProcessPool *)processPool
{
static WKProcessPool *pool = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
pool = [[WKProcessPool alloc] init];
});
return pool;
}
- (WKUserContentController *)userContentConfiguration
{
//Register our native bridge
WKUserContentController *contentController = [WKUserContentController new];
[contentController addScriptMessageHandler:self name:@"nativeApp"];
return contentController;
}
@end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment