Commit be11d176 by Dima Bart

Move BUYClient storefront API into category.

parent 723efc71
...@@ -343,6 +343,10 @@ ...@@ -343,6 +343,10 @@
9A0B0C671CEA703E0037D68F /* BUYClient+Routing.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A0B0C641CEA703E0037D68F /* BUYClient+Routing.h */; }; 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 */; }; 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 */; }; 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 */; }; 9A102D1B1CDD1F960026CC43 /* BUYErrorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A102D1A1CDD1F960026CC43 /* BUYErrorTests.m */; };
9A102D1E1CDD25980026CC43 /* BUYOptionValueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A102D1D1CDD25980026CC43 /* BUYOptionValueTests.m */; }; 9A102D1E1CDD25980026CC43 /* BUYOptionValueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A102D1D1CDD25980026CC43 /* BUYOptionValueTests.m */; };
9A47CEFD1CE39F6000A6D5BA /* BUYCreditCardToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A47CEFB1CE39F5B00A6D5BA /* BUYCreditCardToken.m */; }; 9A47CEFD1CE39F6000A6D5BA /* BUYCreditCardToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A47CEFB1CE39F5B00A6D5BA /* BUYCreditCardToken.m */; };
...@@ -619,6 +623,8 @@ ...@@ -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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 9A47CEF81CE39EC200A6D5BA /* BUYPaymentToken.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BUYPaymentToken.h; sourceTree = "<group>"; };
...@@ -1110,6 +1116,8 @@ ...@@ -1110,6 +1116,8 @@
F7FDA17119C93F6F00AF4E93 /* BUYClient.m */, F7FDA17119C93F6F00AF4E93 /* BUYClient.m */,
9A0B0C641CEA703E0037D68F /* BUYClient+Routing.h */, 9A0B0C641CEA703E0037D68F /* BUYClient+Routing.h */,
9A0B0C651CEA703E0037D68F /* BUYClient+Routing.m */, 9A0B0C651CEA703E0037D68F /* BUYClient+Routing.m */,
9A0B0C6A1CEB4D300037D68F /* BUYClient+Storefront.h */,
9A0B0C6B1CEB4D300037D68F /* BUYClient+Storefront.m */,
8498DCB11CDD1B4A00BD12A8 /* BUYClient+Customers.h */, 8498DCB11CDD1B4A00BD12A8 /* BUYClient+Customers.h */,
8498DCB21CDD1B4A00BD12A8 /* BUYClient+Customers.m */, 8498DCB21CDD1B4A00BD12A8 /* BUYClient+Customers.m */,
); );
...@@ -1154,6 +1162,7 @@ ...@@ -1154,6 +1162,7 @@
9019312F1BC5B9BC00D1134E /* BUYLineItem.h in Headers */, 9019312F1BC5B9BC00D1134E /* BUYLineItem.h in Headers */,
90C856B51BD6B0F300936926 /* Buy.h in Headers */, 90C856B51BD6B0F300936926 /* Buy.h in Headers */,
8498DCAB1CDD1B2600BD12A8 /* BUYShopifyErrorCodes.h in Headers */, 8498DCAB1CDD1B2600BD12A8 /* BUYShopifyErrorCodes.h in Headers */,
9A0B0C6D1CEB4D300037D68F /* BUYClient+Storefront.h in Headers */,
9A47CF071CE3ACE000A6D5BA /* BUYPaymentToken.h in Headers */, 9A47CF071CE3ACE000A6D5BA /* BUYPaymentToken.h in Headers */,
901931351BC5B9BC00D1134E /* BUYDiscount.h in Headers */, 901931351BC5B9BC00D1134E /* BUYDiscount.h in Headers */,
8498DCB71CDD1B5400BD12A8 /* BUYClient+Customers.h in Headers */, 8498DCB71CDD1B5400BD12A8 /* BUYClient+Customers.h in Headers */,
...@@ -1255,6 +1264,7 @@ ...@@ -1255,6 +1264,7 @@
84980F4C1CB7613700CFAB58 /* BUYIdentityTransformer.h in Headers */, 84980F4C1CB7613700CFAB58 /* BUYIdentityTransformer.h in Headers */,
BE9A645B1B503CDC0033E558 /* BUYLineItem.h in Headers */, BE9A645B1B503CDC0033E558 /* BUYLineItem.h in Headers */,
8498DCAA1CDD1B2500BD12A8 /* BUYShopifyErrorCodes.h in Headers */, 8498DCAA1CDD1B2500BD12A8 /* BUYShopifyErrorCodes.h in Headers */,
9A0B0C6C1CEB4D300037D68F /* BUYClient+Storefront.h in Headers */,
9A47CF081CE3ACE100A6D5BA /* BUYPaymentToken.h in Headers */, 9A47CF081CE3ACE100A6D5BA /* BUYPaymentToken.h in Headers */,
BE9A644F1B503CA90033E558 /* BUYDiscount.h in Headers */, BE9A644F1B503CA90033E558 /* BUYDiscount.h in Headers */,
8498DCB41CDD1B5400BD12A8 /* BUYClient+Customers.h in Headers */, 8498DCB41CDD1B5400BD12A8 /* BUYClient+Customers.h in Headers */,
...@@ -1560,6 +1570,7 @@ ...@@ -1560,6 +1570,7 @@
9A47CF061CE3A24600A6D5BA /* BUYApplePayToken.m in Sources */, 9A47CF061CE3A24600A6D5BA /* BUYApplePayToken.m in Sources */,
84D915521CC03F1600D334FB /* BUYModelManager.m in Sources */, 84D915521CC03F1600D334FB /* BUYModelManager.m in Sources */,
84DD129C1CC63FE600A2442D /* _BUYCartLineItem.m in Sources */, 84DD129C1CC63FE600A2442D /* _BUYCartLineItem.m in Sources */,
9A0B0C6F1CEB4D300037D68F /* BUYClient+Storefront.m in Sources */,
841ADE121CB6C942000004B0 /* NSDictionary+BUYAdditions.m in Sources */, 841ADE121CB6C942000004B0 /* NSDictionary+BUYAdditions.m in Sources */,
9019310C1BC5B9BC00D1134E /* BUYGiftCard.m in Sources */, 9019310C1BC5B9BC00D1134E /* BUYGiftCard.m in Sources */,
841ADE161CB6C942000004B0 /* NSException+BUYModelAdditions.m in Sources */, 841ADE161CB6C942000004B0 /* NSException+BUYModelAdditions.m in Sources */,
...@@ -1690,6 +1701,7 @@ ...@@ -1690,6 +1701,7 @@
9A47CF051CE3A24600A6D5BA /* BUYApplePayToken.m in Sources */, 9A47CF051CE3A24600A6D5BA /* BUYApplePayToken.m in Sources */,
84D915511CC03F1600D334FB /* BUYModelManager.m in Sources */, 84D915511CC03F1600D334FB /* BUYModelManager.m in Sources */,
84DD12841CC63FE600A2442D /* _BUYCartLineItem.m in Sources */, 84DD12841CC63FE600A2442D /* _BUYCartLineItem.m in Sources */,
9A0B0C6E1CEB4D300037D68F /* BUYClient+Storefront.m in Sources */,
841ADE111CB6C942000004B0 /* NSDictionary+BUYAdditions.m in Sources */, 841ADE111CB6C942000004B0 /* NSDictionary+BUYAdditions.m in Sources */,
BE9A64581B503CD10033E558 /* BUYGiftCard.m in Sources */, BE9A64581B503CD10033E558 /* BUYGiftCard.m in Sources */,
841ADE151CB6C942000004B0 /* NSException+BUYModelAdditions.m in Sources */, 841ADE151CB6C942000004B0 /* NSException+BUYModelAdditions.m in Sources */,
......
...@@ -65,6 +65,7 @@ FOUNDATION_EXPORT const unsigned char BuyVersionString[]; ...@@ -65,6 +65,7 @@ FOUNDATION_EXPORT const unsigned char BuyVersionString[];
#import <Buy/BUYClient.h> #import <Buy/BUYClient.h>
#import <Buy/BUYClient+Customers.h> #import <Buy/BUYClient+Customers.h>
#import <Buy/BUYClient+Checkout.h> #import <Buy/BUYClient+Checkout.h>
#import <Buy/BUYClient+Storefront.h>
#import <Buy/BUYError.h> #import <Buy/BUYError.h>
#import <Buy/BUYError+BUYAdditions.h> #import <Buy/BUYError+BUYAdditions.h>
#import <Buy/BUYManagedObject.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 ...@@ -148,56 +148,6 @@ typedef void (^BUYDataCheckoutStatusBlock)(BUYStatus status, NSError * _Nullable
typedef void (^BUYDataShippingRatesBlock)(NSArray * _Nullable shippingRates, BUYStatus status, NSError * _Nullable error); 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 * Return block containing a list of BUYProductImage objects and/or an NSError
* *
* @param images An array of BUYProductImage objects * @param images An array of BUYProductImage objects
...@@ -287,90 +237,6 @@ NS_ASSUME_NONNULL_BEGIN ...@@ -287,90 +237,6 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
@property (strong, nonatomic, nullable) NSString *customerToken; @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 #pragma mark - Checkout
/** /**
......
...@@ -63,9 +63,6 @@ NSString * const BUYVersionString = @"1.3"; ...@@ -63,9 +63,6 @@ NSString * const BUYVersionString = @"1.3";
NSString *const kShopifyError = @"shopify"; NSString *const kShopifyError = @"shopify";
static NSString *const kBUYClientPathProductPublications = @"product_listings";
static NSString *const kBUYClientPathCollectionPublications = @"collection_listings";
NSString *const BUYClientCustomerAccessToken = @"X-Shopify-Customer-Access-Token"; NSString *const BUYClientCustomerAccessToken = @"X-Shopify-Customer-Access-Token";
@interface BUYClient () <NSURLSessionDelegate> @interface BUYClient () <NSURLSessionDelegate>
...@@ -108,6 +105,8 @@ NSString *const BUYClientCustomerAccessToken = @"X-Shopify-Customer-Access-Token ...@@ -108,6 +105,8 @@ NSString *const BUYClientCustomerAccessToken = @"X-Shopify-Customer-Access-Token
return self; return self;
} }
#pragma mark - Accessors -
- (NSURLSession *)urlSession - (NSURLSession *)urlSession
{ {
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
...@@ -119,133 +118,11 @@ NSString *const BUYClientCustomerAccessToken = @"X-Shopify-Customer-Access-Token ...@@ -119,133 +118,11 @@ NSString *const BUYClientCustomerAccessToken = @"X-Shopify-Customer-Access-Token
return [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; return [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
} }
#pragma mark - Storefront
- (void)setPageSize:(NSUInteger)pageSize - (void)setPageSize:(NSUInteger)pageSize
{ {
_pageSize = MAX(MIN(pageSize, 250), 1); _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 #pragma mark - Checkout
- (void)handleCheckoutResponse:(NSDictionary *)json error:(NSError *)error block:(BUYDataCheckoutBlock)block - (void)handleCheckoutResponse:(NSDictionary *)json error:(NSError *)error block:(BUYDataCheckoutBlock)block
......
...@@ -36,6 +36,10 @@ ...@@ -36,6 +36,10 @@
#import "BUYCartLineItem.h" #import "BUYCartLineItem.h"
#import "BUYCheckout.h" #import "BUYCheckout.h"
#import "BUYCheckoutAttribute.h" #import "BUYCheckoutAttribute.h"
#import "BUYClient+Test.h"
#import "BUYClient.h"
#import "BUYClient+Customers.h"
#import "BUYClient+Storefront.h"
#import "BUYCollection.h" #import "BUYCollection.h"
#import "BUYCreditCard.h" #import "BUYCreditCard.h"
#import "BUYCustomer.h" #import "BUYCustomer.h"
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
#import "BUYAddress.h" #import "BUYAddress.h"
#import "BUYApplePayAdditions.h" #import "BUYApplePayAdditions.h"
#import "BUYClient.h" #import "BUYClient.h"
#import "BUYClient+Storefront.h"
#import "BUYCheckout.h" #import "BUYCheckout.h"
#import "BUYError.h" #import "BUYError.h"
#import "BUYModelManager.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
//
// BUYViewController.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 AddressBook;
@import PassKit;
@import SafariServices;
#import "BUYApplePayAdditions.h"
#import "BUYCart.h"
#import "BUYClient+Storefront.h"
#import "BUYViewController.h"
#import "BUYApplePayHelpers.h"
#import "BUYDiscount.h"
#import "BUYShop.h"
NSString * BUYSafariCallbackURLNotification = @"kBUYSafariCallbackURLNotification";
NSString * BUYURLKey = @"url";
@interface BUYViewController () <SFSafariViewControllerDelegate>
@property (nonatomic, strong) BUYCheckout *checkout;
@property (nonatomic, strong) BUYApplePayHelpers *applePayHelper;
@property (nonatomic, assign) BOOL isLoadingShop;
@property (nonatomic, assign) PKPaymentAuthorizationStatus paymentAuthorizationStatus;
@end
@implementation BUYViewController
@synthesize client = _client;
- (instancetype)initWithClient:(BUYClient *)client
{
self = [super init];
if (self) {
self.client = client;
}
return self;
}
- (void)setClient:(BUYClient *)client
{
_client = client;
if (&PKPaymentNetworkDiscover != NULL) {
self.supportedNetworks = @[PKPaymentNetworkAmex, PKPaymentNetworkMasterCard, PKPaymentNetworkVisa, PKPaymentNetworkDiscover];
} else {
self.supportedNetworks = @[PKPaymentNetworkAmex, PKPaymentNetworkMasterCard, PKPaymentNetworkVisa];
}
}
- (BUYClient*)client
{
if (_client == nil) {
NSLog(@"`BUYClient` has not been initialized. Please initialize BUYViewController with `initWithClient:` or set a `BUYClient` after Storyboard initialization");
}
return _client;
}
- (void)loadShopWithCallback:(void (^)(BOOL, NSError *))block
{
// fetch shop details for the currency and country codes
self.isLoadingShop = YES;
[self.client getShop:^(BUYShop *shop, NSError *error) {
if (error == nil) {
self.shop = shop;
}
self.isLoadingShop = NO;
if (block) block((error == nil), error);
}];
}
- (BOOL)canShowApplePaySetup
{
PKPassLibrary *passLibrary = [[PKPassLibrary alloc] init];
if (self.allowApplePaySetup == YES &&
// Check that it's running iOS 9.0 or above
[passLibrary respondsToSelector:@selector(canAddPaymentPassWithPrimaryAccountIdentifier:)] &&
// Check if the device can add a payment pass
[PKPaymentAuthorizationViewController canMakePayments] &&
// Check that Apple Pay is enabled for the merchant
[self.merchantId length]) {
return YES;
} else {
return NO;
}
}
- (BOOL)isApplePayAvailable
{
// checks if the client is setup to use Apple Pay
// checks if device hardware is capable of using Apple Pay
// checks if the device has a payment card setup
return (self.merchantId.length &&
[PKPaymentAuthorizationViewController canMakePayments] &&
[PKPaymentAuthorizationViewController canMakePaymentsUsingNetworks:self.supportedNetworks]);
}
- (BOOL)shouldShowApplePayButton {
return self.isApplePayAvailable || [self canShowApplePaySetup];
}
- (BOOL)shouldShowApplePaySetup
{
return self.isApplePayAvailable == NO && [self canShowApplePaySetup];
}
#pragma mark - Checkout Flow Methods
#pragma mark - Step 1 - Creating or updating a Checkout
- (void)startApplePayCheckout:(BUYCheckout *)checkout
{
// Default to the failure state, since cancelling a payment would not update the state and thus appear as a success
self.paymentAuthorizationStatus = PKPaymentAuthorizationStatusFailure;
/**
* To perform an Apple Pay checkout, we need both the BUYShop object, and a BUYCheckout
* We will download both in parallel, and continue with the checkout when they both succeed
*/
dispatch_group_t group = dispatch_group_create();
__block NSError *checkoutError = nil;
// download the shop
if (self.shop == nil) {
dispatch_group_enter(group);
[self loadShopWithCallback:^(BOOL success, NSError *error) {
if (error) {
checkoutError = error;
if ([self.delegate respondsToSelector:@selector(controllerFailedToStartApplePayProcess:)]) {
[self.delegate controllerFailedToStartApplePayProcess:self];
}
}
dispatch_group_leave(group);
}];
}
// create the checkout on Shopify
dispatch_group_enter(group);
[self handleCheckout:checkout completion:^(BUYCheckout *checkout, NSError *error) {
if (error) {
checkoutError = error;
if ([self.delegate respondsToSelector:@selector(controller:failedToCreateCheckout:)]) {
[self.delegate controller:self failedToCreateCheckout:error];
}
}
else {
self.checkout = checkout;
}
dispatch_group_leave(group);
}];
// When we have both the shop and checkout, we can request the payment with Apple Pay
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
if (self.checkout && self.shop) {
if ([self.delegate respondsToSelector:@selector(controllerWillCheckoutViaApplePay:)]) {
[self.delegate controllerWillCheckoutViaApplePay:self];
}
self.applePayHelper = [[BUYApplePayHelpers alloc] initWithClient:self.client checkout:self.checkout shop:self.shop];
[self requestPayment];
}
else {
if ([self.delegate respondsToSelector:@selector(controller:failedToCreateCheckout:)]) {
[self.delegate controller:self failedToCreateCheckout:checkoutError];
}
}
});
}
- (void)startWebCheckout:(BUYCheckout *)checkout
{
[self handleCheckout:checkout completion:^(BUYCheckout *checkout, NSError *error) {
[self postCheckoutCompletion:checkout error:error];
}];
}
- (void)postCheckoutCompletion:(BUYCheckout *)checkout error:(NSError *)error
{
if (error == nil) {
self.checkout = checkout;
if ([self.delegate respondsToSelector:@selector(controllerWillCheckoutViaWeb:)]) {
[self.delegate controllerWillCheckoutViaWeb:self];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveCallbackURLNotification:) name:BUYSafariCallbackURLNotification object:nil];
[self openWebCheckout:checkout];
}
else {
if ([self.delegate respondsToSelector:@selector(controller:failedToCreateCheckout:)]) {
[self.delegate controller:self failedToCreateCheckout:error];
}
}
}
- (void)handleCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)completion
{
if ([checkout.token length] > 0) {
[self.client updateCheckout:checkout completion:completion];
} else {
[self.client createCheckout:checkout completion:completion];
}
}
#pragma mark - Alternative Step 1 - Creating a Checkout using a Cart Token
- (void)startCheckoutWithCartToken:(NSString *)token
{
[self.client createCheckoutWithCartToken:token completion:^(BUYCheckout *checkout, NSError *error) {
self.applePayHelper = [[BUYApplePayHelpers alloc] initWithClient:self.client checkout:checkout];
[self handleCheckoutCompletion:checkout error:error];
}];
}
- (void)handleCheckoutCompletion:(BUYCheckout *)checkout error:(NSError *)error
{
if (checkout && error == nil) {
self.checkout = checkout;
[self requestPayment];
}
else {
if ([self.delegate respondsToSelector:@selector(controller:failedToCreateCheckout:)]) {
[self.delegate controller:self failedToCreateCheckout:error];
}
}
}
#pragma mark - Web Checkout
- (void)openWebCheckout:(BUYCheckout *)checkout
{
if ([SFSafariViewController class]) {
SFSafariViewController *safariViewController = [[SFSafariViewController alloc] initWithURL:checkout.webCheckoutURL];
safariViewController.delegate = self;
[self presentViewController:safariViewController animated:YES completion:nil];
}
else {
[[UIApplication sharedApplication] openURL:checkout.webCheckoutURL];
}
}
#pragma mark - Step 2 - Requesting Payment using Apple Pay
- (void)requestPayment
{
//Step 2 - Request payment from the user by presenting an Apple Pay sheet
if (self.merchantId.length == 0) {
NSLog(@"Merchant ID must be configured to use Apple Pay");
if ([self.delegate respondsToSelector:@selector(controllerFailedToStartApplePayProcess:)]) {
[self.delegate controllerFailedToStartApplePayProcess:self];
}
return;
}
if (self.shop == nil) {
NSLog(@"loadShopWithCallback: must be called before starting an Apple Pay checkout");
if ([self.delegate respondsToSelector:@selector(controllerFailedToStartApplePayProcess:)]) {
[self.delegate controllerFailedToStartApplePayProcess:self];
}
return;
}
PKPaymentRequest *request = [self paymentRequest];
request.paymentSummaryItems = [self.checkout buy_summaryItemsWithShopName:self.shop.name];
PKPaymentAuthorizationViewController *controller = [[PKPaymentAuthorizationViewController alloc] initWithPaymentRequest:request];
if (controller) {
controller.delegate = self;
[self presentViewController:controller animated:YES completion:nil];
}
else {
if ([self.delegate respondsToSelector:@selector(controllerFailedToStartApplePayProcess:)]) {
[self.delegate controllerFailedToStartApplePayProcess:self];
}
}
}
- (void)checkoutCompleted:(BUYCheckout *)checkout status:(BUYStatus)status
{
if ([self.delegate respondsToSelector:@selector(controller:didCompleteCheckout:status:)]) {
[self.delegate controller:self didCompleteCheckout:checkout status:status];
}
}
#pragma mark - PKPaymentAuthorizationViewControllerDelegate Methods
- (void)paymentAuthorizationViewController:(PKPaymentAuthorizationViewController *)controller didAuthorizePayment:(PKPayment *)payment completion:(void (^)(PKPaymentAuthorizationStatus status))completion
{
[self.applePayHelper paymentAuthorizationViewController:controller didAuthorizePayment:payment completion:^(PKPaymentAuthorizationStatus status) {
self.paymentAuthorizationStatus = status;
switch (status) {
case PKPaymentAuthorizationStatusFailure:
if ([self.delegate respondsToSelector:@selector(controller:failedToCompleteCheckout:withError:)]) {
[self.delegate controller:self failedToCompleteCheckout:self.checkout withError:self.applePayHelper.lastError];
}
break;
case PKPaymentAuthorizationStatusInvalidShippingPostalAddress:
if ([self.delegate respondsToSelector:@selector(controller:failedToUpdateCheckout:withError:)]) {
[self.delegate controller:self failedToUpdateCheckout:self.checkout withError:self.applePayHelper.lastError];
}
break;
default: {
if ([self.delegate respondsToSelector:@selector(controller:didCompleteCheckout:status:)]) {
BUYStatus buyStatus = (status == PKPaymentAuthorizationStatusSuccess) ? BUYStatusComplete : BUYStatusFailed;
[self.delegate controller:self didCompleteCheckout:self.checkout status:buyStatus];
}
}
break;
}
completion(status);
}];
}
- (void)paymentAuthorizationViewControllerDidFinish:(PKPaymentAuthorizationViewController *)controller
{
// The checkout is done at this point, it may have succeeded or failed. You are responsible for dealing with failure/success earlier in the steps.
[self dismissViewControllerAnimated:YES completion:^{
// If Apple Pay is dismissed with Cancel we need to clear the reservation time on the products in the checkout
if (self.paymentAuthorizationStatus != PKPaymentAuthorizationStatusSuccess) {
[self.client removeProductReservationsFromCheckout:self.checkout completion:^(BUYCheckout *checkout, NSError *error) {
self.checkout = checkout;
if ([self.delegate respondsToSelector:@selector(controller:didDismissApplePayControllerWithStatus:forCheckout:)]) {
[self.delegate controller:self didDismissApplePayControllerWithStatus:self.paymentAuthorizationStatus forCheckout:self.checkout];
}
}];
} else {
if ([self.delegate respondsToSelector:@selector(controller:didDismissApplePayControllerWithStatus:forCheckout:)]) {
[self.delegate controller:self didDismissApplePayControllerWithStatus:self.paymentAuthorizationStatus forCheckout:self.checkout];
}
}
}];
}
- (void)paymentAuthorizationViewController:(PKPaymentAuthorizationViewController *)controller didSelectShippingMethod:(nonnull PKShippingMethod *)shippingMethod completion:(nonnull void (^)(PKPaymentAuthorizationStatus, NSArray<PKPaymentSummaryItem *> * _Nonnull))completion
{
[self.applePayHelper paymentAuthorizationViewController:controller didSelectShippingMethod:shippingMethod completion:^(PKPaymentAuthorizationStatus status, NSArray<PKPaymentSummaryItem *> * _Nonnull summaryItems) {
if (status == PKPaymentAuthorizationStatusInvalidShippingPostalAddress) {
if ([self.delegate respondsToSelector:@selector(controller:failedToGetShippingRates:withError:)]) {
[self.delegate controller:self failedToGetShippingRates:self.checkout withError:self.applePayHelper.lastError];
}
}
completion(status, summaryItems);
}];
}
-(void)paymentAuthorizationViewController:(PKPaymentAuthorizationViewController *)controller didSelectShippingAddress:(ABRecordRef)address completion:(void (^)(PKPaymentAuthorizationStatus, NSArray<PKShippingMethod *> * _Nonnull, NSArray<PKPaymentSummaryItem *> * _Nonnull))completion
{
[self.applePayHelper paymentAuthorizationViewController:controller didSelectShippingAddress:address completion:^(PKPaymentAuthorizationStatus status, NSArray<PKShippingMethod *> * _Nonnull shippingMethods, NSArray<PKPaymentSummaryItem *> * _Nonnull summaryItems) {
if (status == PKPaymentAuthorizationStatusInvalidShippingPostalAddress) {
if ([self.delegate respondsToSelector:@selector(controller:failedToUpdateCheckout:withError:)]) {
[self.delegate controller:self failedToUpdateCheckout:self.checkout withError:self.applePayHelper.lastError];
}
}
completion(status, shippingMethods, summaryItems);
}];
}
- (void)paymentAuthorizationViewController:(PKPaymentAuthorizationViewController *)controller didSelectShippingContact:(PKContact *)contact completion:(void (^)(PKPaymentAuthorizationStatus, NSArray<PKShippingMethod *> * _Nonnull, NSArray<PKPaymentSummaryItem *> * _Nonnull))completion
{
[self.applePayHelper paymentAuthorizationViewController:controller didSelectShippingContact:contact completion:^(PKPaymentAuthorizationStatus status, NSArray<PKShippingMethod *> * _Nonnull shippingMethods, NSArray<PKPaymentSummaryItem *> * _Nonnull summaryItems) {
if (status == PKPaymentAuthorizationStatusInvalidShippingPostalAddress) {
if ([self.delegate respondsToSelector:@selector(controller:failedToUpdateCheckout:withError:)]) {
[self.delegate controller:self failedToUpdateCheckout:self.checkout withError:self.applePayHelper.lastError];
}
}
completion(status, shippingMethods, summaryItems);
}];
}
#pragma mark - Helpers
- (PKPaymentRequest *)paymentRequest
{
PKPaymentRequest *paymentRequest = [[PKPaymentRequest alloc] init];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
NSString *merchantId = self.client.merchantId ? : self.merchantId;
#pragma GCC diagnostic pop
[paymentRequest setMerchantIdentifier:merchantId];
[paymentRequest setRequiredBillingAddressFields:PKAddressFieldAll];
[paymentRequest setRequiredShippingAddressFields:self.checkout.requiresShipping ? PKAddressFieldAll : PKAddressFieldEmail|PKAddressFieldPhone];
[paymentRequest setSupportedNetworks:self.supportedNetworks];
[paymentRequest setMerchantCapabilities:PKMerchantCapability3DS];
[paymentRequest setCountryCode:self.shop.country];
[paymentRequest setCurrencyCode:self.shop.currency];
return paymentRequest;
}
#pragma mark - Web Checkout delegate methods
- (void)safariViewControllerDidFinish:(SFSafariViewController *)controller;
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:BUYSafariCallbackURLNotification object:nil];
if ([self.delegate respondsToSelector:@selector(controller:didDismissWebCheckout:)]) {
[self.delegate controller:self didDismissWebCheckout:self.checkout];
}
}
- (void)didReceiveCallbackURLNotification:(NSNotification *)notification
{
NSURL *url = notification.userInfo[BUYURLKey];
[self.client getCompletionStatusOfCheckoutURL:url completion:^(BUYStatus status, NSError *error) {
[self checkoutCompleted:_checkout status:status];
[[NSNotificationCenter defaultCenter] removeObserver:self name:BUYSafariCallbackURLNotification object:nil];
}];
if (self.presentedViewController) {
[self dismissViewControllerAnimated:YES completion:nil];
}
}
+ (void)completeCheckoutFromLaunchURL:(NSURL *)url
{
[[NSNotificationCenter defaultCenter] postNotificationName:BUYSafariCallbackURLNotification object:nil userInfo:@{BUYURLKey: url}];
}
@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