Commit ed8451d8 by Dima Bart

Merge pull request #165 from Shopify/feature/refactor-routing

Refactor BUYClient to remove creation and management of routes
parents d956de37 f8ef0de0
//
// BUYClient+RoutingTests.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 <XCTest/XCTest.h>
#import "BUYClient+Routing.h"
@interface BUYClient_RoutingTests : XCTestCase
@property (strong, nonatomic) BUYClient *client;
@end
@implementation BUYClient_RoutingTests
#pragma mark - Setup -
- (void)setUp
{
[super setUp];
self.client = [[BUYClient alloc] initWithShopDomain:@"_DOMAIN_" apiKey:@"_API_KEY_" appId:@"_APP_ID_"];
}
#pragma mark - Test Routes -
- (void)testRoutes
{
NSString *identifier = @"_ID_";
NSString *token = @"_TOKEN_";
NSDictionary *parameters = @{ @"param" : @"value" };
XCTAssertEqualObjects(
[self.client urlForAPI].absoluteString,
@"https://_DOMAIN_/api"
);
XCTAssertEqualObjects(
[self.client urlForApps].absoluteString,
@"https://_DOMAIN_/api/apps/_APP_ID_"
);
XCTAssertEqualObjects(
[self.client urlForShop].absoluteString,
@"https://_DOMAIN_/meta.json"
);
XCTAssertEqualObjects(
[self.client urlForProductListingsWithParameters:parameters].absoluteString,
@"https://_DOMAIN_/api/apps/_APP_ID_/product_listings.json?param=value"
);
XCTAssertEqualObjects(
[self.client urlForCollectionListingsWithParameters:parameters].absoluteString,
@"https://_DOMAIN_/api/apps/_APP_ID_/collection_listings.json?param=value"
);
XCTAssertEqualObjects(
[self.client urlForCheckouts].absoluteString,
@"https://_DOMAIN_/api/checkouts.json"
);
XCTAssertEqualObjects(
[self.client urlForCheckoutsWithToken:token].absoluteString,
@"https://_DOMAIN_/api/checkouts/_TOKEN_.json"
);
XCTAssertEqualObjects(
[self.client urlForCheckoutsProcessingWithToken:token].absoluteString,
@"https://_DOMAIN_/api/checkouts/_TOKEN_/processing.json"
);
XCTAssertEqualObjects(
[self.client urlForCheckoutsCompletionWithToken:token].absoluteString,
@"https://_DOMAIN_/api/checkouts/_TOKEN_/complete.json"
);
XCTAssertEqualObjects(
[self.client urlForCheckoutsShippingRatesWithToken:token parameters:parameters].absoluteString,
@"https://_DOMAIN_/api/checkouts/_TOKEN_/shipping_rates.json?param=value"
);
XCTAssertEqualObjects(
[self.client urlForCheckoutsUsingGiftCard].absoluteString,
@"https://_DOMAIN_/api/checkouts/gift_cards.json"
);
XCTAssertEqualObjects(
[self.client urlForCheckoutsUsingGiftCardWithToken:token].absoluteString,
@"https://_DOMAIN_/api/checkouts/_TOKEN_/gift_cards.json"
);
XCTAssertEqualObjects(
[self.client urlForCheckoutsUsingGiftCard:@999 token:token].absoluteString,
@"https://_DOMAIN_/api/checkouts/_TOKEN_/gift_cards/999.json"
);
XCTAssertEqualObjects(
[self.client urlForCustomers].absoluteString,
@"https://_DOMAIN_/api/customers.json"
);
XCTAssertEqualObjects(
[self.client urlForCustomersOrders].absoluteString,
@"https://_DOMAIN_/api/customers/orders.json"
);
XCTAssertEqualObjects(
[self.client urlForCustomersWithID:identifier].absoluteString,
@"https://_DOMAIN_/api/customers/_ID_.json"
);
XCTAssertEqualObjects(
[self.client urlForCustomersActivationWithID:identifier parameters:parameters].absoluteString,
@"https://_DOMAIN_/api/customers/_ID_/activate.json?param=value"
);
XCTAssertEqualObjects(
[self.client urlForCustomersToken].absoluteString,
@"https://_DOMAIN_/api/customers/customer_token.json"
);
XCTAssertEqualObjects(
[self.client urlForCustomersTokenRenewalWithID:identifier].absoluteString,
@"https://_DOMAIN_/api/customers/_ID_/customer_token/renew.json"
);
XCTAssertEqualObjects(
[self.client urlForCustomersPasswordRecovery].absoluteString,
@"https://_DOMAIN_/api/customers/recover.json"
);
XCTAssertEqualObjects(
[self.client urlForCustomersPasswordResetWithID:identifier parameters:parameters].absoluteString,
@"https://_DOMAIN_/api/customers/_ID_/reset.json?param=value"
);
}
@end
......@@ -34,17 +34,12 @@
#import "BUYShopifyErrorCodes.h"
#import "BUYAccountCredentials.h"
#import "BUYClient+Customers.h"
#import "BUYClient+Internal.h"
#import "BUYApplePayToken.h"
#import "BUYApplePayTestToken.h"
NSString * const BUYFakeCustomerToken = @"dsfasdgafdg";
@interface BUYClient ()
+ (BUYStatus)statusForStatusCode:(NSUInteger)statusCode error:(NSError *)error;
@end
@interface BUYClient_Test : BUYClient
@end
......@@ -169,22 +164,22 @@ NSString * const BUYFakeCustomerToken = @"dsfasdgafdg";
- (void)testStatusCodeConversions
{
BUYStatus status = [BUYClient statusForStatusCode:412 error:nil];
BUYStatus status = [self.client statusForStatusCode:412 error:nil];
XCTAssertEqual(BUYStatusPreconditionFailed, status);
status = [BUYClient statusForStatusCode:404 error:nil];
status = [self.client statusForStatusCode:404 error:nil];
XCTAssertEqual(BUYStatusNotFound, status);
status = [BUYClient statusForStatusCode:0 error:[NSError errorWithDomain:@"" code:-1 userInfo:nil]];
status = [self.client statusForStatusCode:0 error:[NSError errorWithDomain:@"" code:-1 userInfo:nil]];
XCTAssertEqual(BUYStatusFailed, status);
status = [BUYClient statusForStatusCode:424 error:nil];
status = [self.client statusForStatusCode:424 error:nil];
XCTAssertEqual(BUYStatusFailed, status);
status = [BUYClient statusForStatusCode:202 error:nil];
status = [self.client statusForStatusCode:202 error:nil];
XCTAssertEqual(BUYStatusProcessing, status);
status = [BUYClient statusForStatusCode:200 error:nil];
status = [self.client statusForStatusCode:200 error:nil];
XCTAssertEqual(BUYStatusComplete, status);
}
......
......@@ -44,7 +44,7 @@ extern NSString * const BUYFakeCustomerToken;
@property (nonatomic, strong) NSString *giftCardCode2;
@property (nonatomic, strong) NSString *giftCardCode3;
@property (nonatomic, strong) NSString *giftCardCodeExpired;
@property (nonatomic, strong) NSString *giftCardIdExpired;
@property (nonatomic, strong) NSNumber *giftCardIdExpired;
@property (nonatomic, strong) NSString *giftCardCodeInvalid;
@property (nonatomic, strong) NSString *discountCodeValid;
@property (nonatomic, strong) NSString *discountCodeExpired;
......
......@@ -517,7 +517,7 @@
return [OHHTTPStubsResponse responseWithKey:@"testRemovingInvalidGiftCardFromCheckout_2"];
}];
BUYGiftCard *giftCard = [[BUYGiftCard alloc] initWithModelManager:_modelManager JSONDictionary:@{ @"id" : @"000" }];
BUYGiftCard *giftCard = [[BUYGiftCard alloc] initWithModelManager:_modelManager JSONDictionary:@{ @"id" : @(000) }];
XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
[self.client removeGiftCard:giftCard fromCheckout:_checkout completion:^(BUYCheckout *checkout, NSError *error) {
XCTAssertNotNil(error);
......
......@@ -31,7 +31,7 @@
"testChangedShippingAddress_5":{"body":"{\"shipping_rates\":[{\"id\":\"shopify-Standard%20Shipping-8.00\",\"price\":\"8.00\",\"title\":\"Standard Shipping\",\"checkout\":{\"total_tax\":\"0.00\",\"total_price\":\"2238.99\",\"subtotal_price\":\"2230.99\"},\"phone_required\":false,\"delivery_range\":null}]}","code":200,"message":"OK"},
"testChangedShippingAddress_6":{"body":"{\"checkout\":{\"created_at\":\"2016-03-07T10:20:52-05:00\",\"currency\":\"CAD\",\"customer_id\":1137418883,\"email\":\"test@test.com\",\"location_id\":null,\"order_id\":null,\"requires_shipping\":true,\"reservation_time\":300,\"source_name\":\"mobile_app\",\"source_identifier\":\"26915715\",\"source_url\":null,\"taxes_included\":false,\"token\":\"39988ee167f982a5299cc96ad4f692de\",\"updated_at\":\"2016-03-07T10:20:55-05:00\",\"payment_due\":\"2238.99\",\"payment_url\":\"https:\\/\\/elb.deposit.shopifycs.com\\/sessions\",\"reservation_time_left\":300,\"subtotal_price\":\"2230.99\",\"total_price\":\"2238.99\",\"total_tax\":\"0.00\",\"attributes\":[],\"note\":\"\",\"order\":null,\"order_status_url\":null,\"privacy_policy_url\":null,\"refund_policy_url\":null,\"terms_of_service_url\":null,\"user_id\":null,\"web_url\":\"https:\\/\\/checkout.shopify.com\\/9575792\\/checkouts\\/39988ee167f982a5299cc96ad4f692de\",\"tax_lines\":[],\"line_items\":[{\"id\":\"0244bebe02b83abe\",\"product_id\":2096063363,\"variant_id\":6030700419,\"sku\":\"\",\"vendor\":\"McCullough Group\",\"title\":\"Actinian Fur Hat\",\"variant_title\":\"Teal\",\"taxable\":false,\"requires_shipping\":true,\"price\":\"2230.99\",\"compare_at_price\":null,\"line_price\":\"2230.99\",\"properties\":{\"size\":\"large\",\"color\":\"red\"},\"quantity\":1,\"grams\":4000,\"fulfillment_service\":\"manual\",\"applied_discounts\":[]}],\"gift_cards\":[],\"shipping_rate\":{\"id\":\"shopify-Standard%20Shipping-8.00\",\"price\":\"8.00\",\"title\":\"Standard Shipping\"},\"shipping_address\":{\"id\":3479863942,\"first_name\":\"MobileBuy\",\"last_name\":\"TestBot\",\"phone\":\"1-555-555-5555\",\"company\":\"Shopify Inc.\",\"address1\":\"150 Elgin Street\",\"address2\":\"8th Floor\",\"city\":\"Toronto\",\"province\":\"Ontario\",\"province_code\":\"ON\",\"country\":\"Canada\",\"country_code\":\"CA\",\"zip\":\"K1N5T5\"},\"credit_card\":null,\"billing_address\":{\"id\":3479863878,\"first_name\":\"MobileBuy\",\"last_name\":\"TestBot\",\"phone\":\"1-555-555-5555\",\"company\":\"Shopify Inc.\",\"address1\":\"150 Elgin Street\",\"address2\":\"8th Floor\",\"city\":\"Ottawa\",\"province\":\"Ontario\",\"province_code\":\"ON\",\"country\":\"Canada\",\"country_code\":\"CA\",\"zip\":\"K1N5T5\"},\"discount\":null}}","code":200,"message":"OK"},
"testCheckoutFlowUsingCreditCard_0":{"body":"{\"product_listings\":[{\"id\":2626498435,\"product_id\":2096063363,\"channel_id\":26915715,\"created_at\":\"2015-08-19T09:47:37-04:00\",\"updated_at\":\"2015-08-19T09:47:37-04:00\",\"body_html\":\"parsing the driver won't do anything, we need to calculate the online PNG program!\",\"handle\":\"actinian-fur-hat\",\"product_type\":\"enable bricks-and-clicks e-business\",\"title\":\"Actinian Fur Hat\",\"vendor\":\"McCullough Group\",\"published_at\":\"2015-08-19T09:47:37-04:00\",\"published\":true,\"available\":true,\"tags\":\"\",\"images\":[{\"id\":4277333187,\"created_at\":\"2015-08-13T14:12:44-04:00\",\"position\":1,\"updated_at\":\"2015-08-13T14:12:44-04:00\",\"product_id\":2096063363,\"src\":\"https:\\/\\/cdn.shopify.com\\/s\\/files\\/1\\/0957\\/5792\\/products\\/Kraepelin3.gif?v=1439489564\",\"variant_ids\":[]}],\"options\":[{\"id\":2524801731,\"name\":\"Color or something\",\"product_id\":2096063363,\"position\":1}],\"variants\":[{\"id\":6030700419,\"title\":\"Teal\",\"option_values\":[{\"option_id\":2524801731,\"name\":\"Color or something\",\"value\":\"Teal\"}],\"price\":\"2230.99\",\"compare_at_price\":null,\"grams\":4000,\"requires_shipping\":true,\"sku\":\"\",\"taxable\":false,\"position\":1,\"available\":true}]}]}","code":200,"message":"OK"},
"testCheckoutFlowUsingCreditCard_1":{"body":"{\"checkout\":{\"created_at\":\"2016-03-07T10:20:56-05:00\",\"currency\":\"CAD\",\"customer_id\":1137418883,\"email\":\"test@test.com\",\"location_id\":null,\"order_id\":null,\"requires_shipping\":true,\"reservation_time\":300,\"source_name\":\"mobile_app\",\"source_identifier\":\"26915715\",\"source_url\":null,\"taxes_included\":false,\"token\":\"4bd92a3e9ab0b3adf790e0774db6c815\",\"updated_at\":\"2016-03-07T10:20:56-05:00\",\"payment_due\":\"2230.99\",\"payment_url\":\"https:\\/\\/elb.deposit.shopifycs.com\\/sessions\",\"reservation_time_left\":300,\"subtotal_price\":\"2230.99\",\"total_price\":\"2230.99\",\"total_tax\":\"0.00\",\"attributes\":[],\"note\":\"\",\"order\":null,\"order_status_url\":null,\"privacy_policy_url\":null,\"refund_policy_url\":null,\"terms_of_service_url\":null,\"user_id\":null,\"web_url\":\"https:\\/\\/checkout.shopify.com\\/9575792\\/checkouts\\/4bd92a3e9ab0b3adf790e0774db6c815\",\"tax_lines\":[],\"line_items\":[{\"id\":\"82a8a49460b69cf3\",\"product_id\":2096063363,\"variant_id\":6030700419,\"sku\":\"\",\"vendor\":\"McCullough Group\",\"title\":\"Actinian Fur Hat\",\"variant_title\":\"Teal\",\"taxable\":false,\"requires_shipping\":true,\"price\":\"2230.99\",\"compare_at_price\":null,\"line_price\":\"2230.99\",\"properties\":{\"size\":\"large\",\"color\":\"red\"},\"quantity\":1,\"grams\":4000,\"fulfillment_service\":\"manual\",\"applied_discounts\":[]}],\"gift_cards\":[],\"shipping_rate\":null,\"shipping_address\":{\"id\":3479864454,\"first_name\":\"MobileBuy\",\"last_name\":\"TestBot\",\"phone\":\"1-555-555-5555\",\"company\":\"Shopify Inc.\",\"address1\":\"150 Elgin Street\",\"address2\":\"8th Floor\",\"city\":\"Ottawa\",\"province\":\"Ontario\",\"province_code\":\"ON\",\"country\":\"Canada\",\"country_code\":\"CA\",\"zip\":\"K1N5T5\"},\"credit_card\":null,\"billing_address\":{\"id\":3479864390,\"first_name\":\"MobileBuy\",\"last_name\":\"TestBot\",\"phone\":\"1-555-555-5555\",\"company\":\"Shopify Inc.\",\"address1\":\"150 Elgin Street\",\"address2\":\"8th Floor\",\"city\":\"Ottawa\",\"province\":\"Ontario\",\"province_code\":\"ON\",\"country\":\"Canada\",\"country_code\":\"CA\",\"zip\":\"K1N5T5\"},\"discount\":null}}","code":201,"message":"Created"},
"testCheckoutFlowUsingCreditCard_1":{"body":"{\"checkout\":{\"created_at\":\"2016-03-07T10:20:56-05:00\",\"currency\":\"CAD\",\"customer_id\":1137418883,\"email\":\"test@test.com\",\"location_id\":null,\"order_id\":null,\"requires_shipping\":true,\"reservation_time\":300,\"source_name\":\"mobile_app\",\"source_identifier\":\"26915715\",\"source_url\":null,\"taxes_included\":false,\"token\":\"4bd92a3e9ab0b3adf790e0774db6c815\",\"updated_at\":\"2016-03-07T10:20:56-05:00\",\"payment_due\":\"2230.99\",\"payment_url\":\"https:\\/\\/elb.deposit.shopifycs.com\\/sessions\",\"reservation_time_left\":300,\"subtotal_price\":\"2230.99\",\"total_price\":\"2230.99\",\"total_tax\":\"0.00\",\"attributes\":[{\"name\":\"attribute1\",\"value\":\"value1\"},{\"name\":\"attribute2\",\"value\":\"value2\"}],\"note\":\"Order note\",\"order\":null,\"order_status_url\":null,\"privacy_policy_url\":null,\"refund_policy_url\":null,\"terms_of_service_url\":null,\"user_id\":null,\"web_url\":\"https:\\/\\/checkout.shopify.com\\/9575792\\/checkouts\\/4bd92a3e9ab0b3adf790e0774db6c815\",\"tax_lines\":[],\"line_items\":[{\"id\":\"82a8a49460b69cf3\",\"product_id\":2096063363,\"variant_id\":6030700419,\"sku\":\"\",\"vendor\":\"McCullough Group\",\"title\":\"Actinian Fur Hat\",\"variant_title\":\"Teal\",\"taxable\":false,\"requires_shipping\":true,\"price\":\"2230.99\",\"compare_at_price\":null,\"line_price\":\"2230.99\",\"properties\":{\"size\":\"large\",\"color\":\"red\"},\"quantity\":1,\"grams\":4000,\"fulfillment_service\":\"manual\",\"applied_discounts\":[]}],\"gift_cards\":[],\"shipping_rate\":null,\"shipping_address\":{\"id\":3479864454,\"first_name\":\"MobileBuy\",\"last_name\":\"TestBot\",\"phone\":\"1-555-555-5555\",\"company\":\"Shopify Inc.\",\"address1\":\"150 Elgin Street\",\"address2\":\"8th Floor\",\"city\":\"Ottawa\",\"province\":\"Ontario\",\"province_code\":\"ON\",\"country\":\"Canada\",\"country_code\":\"CA\",\"zip\":\"K1N5T5\"},\"credit_card\":null,\"billing_address\":{\"id\":3479864390,\"first_name\":\"MobileBuy\",\"last_name\":\"TestBot\",\"phone\":\"1-555-555-5555\",\"company\":\"Shopify Inc.\",\"address1\":\"150 Elgin Street\",\"address2\":\"8th Floor\",\"city\":\"Ottawa\",\"province\":\"Ontario\",\"province_code\":\"ON\",\"country\":\"Canada\",\"country_code\":\"CA\",\"zip\":\"K1N5T5\"},\"discount\":null}}","code":201,"message":"Created"},
"testCheckoutFlowUsingCreditCard_2":{"body":"{\"shipping_rates\":[{\"id\":\"shopify-Standard%20Shipping-8.00\",\"price\":\"8.00\",\"title\":\"Standard Shipping\",\"checkout\":{\"total_tax\":\"0.00\",\"total_price\":\"2238.99\",\"subtotal_price\":\"2230.99\"},\"phone_required\":false,\"delivery_range\":null}]}","code":200,"message":"OK"},
"testCheckoutFlowUsingCreditCard_3":{"body":"{\"checkout\":{\"created_at\":\"2016-03-07T10:20:56-05:00\",\"currency\":\"CAD\",\"customer_id\":1137418883,\"email\":\"test@test.com\",\"location_id\":null,\"order_id\":null,\"requires_shipping\":true,\"reservation_time\":300,\"source_name\":\"mobile_app\",\"source_identifier\":\"26915715\",\"source_url\":null,\"taxes_included\":false,\"token\":\"4bd92a3e9ab0b3adf790e0774db6c815\",\"updated_at\":\"2016-03-07T10:20:57-05:00\",\"payment_due\":\"2238.99\",\"payment_url\":\"https:\\/\\/elb.deposit.shopifycs.com\\/sessions\",\"reservation_time_left\":300,\"subtotal_price\":\"2230.99\",\"total_price\":\"2238.99\",\"total_tax\":\"0.00\",\"attributes\":[],\"note\":\"\",\"order\":null,\"order_status_url\":null,\"privacy_policy_url\":null,\"refund_policy_url\":null,\"terms_of_service_url\":null,\"user_id\":null,\"web_url\":\"https:\\/\\/checkout.shopify.com\\/9575792\\/checkouts\\/4bd92a3e9ab0b3adf790e0774db6c815\",\"tax_lines\":[],\"line_items\":[{\"id\":\"cfdc9344bacf009e\",\"product_id\":2096063363,\"variant_id\":6030700419,\"sku\":\"\",\"vendor\":\"McCullough Group\",\"title\":\"Actinian Fur Hat\",\"variant_title\":\"Teal\",\"taxable\":false,\"requires_shipping\":true,\"price\":\"2230.99\",\"compare_at_price\":null,\"line_price\":\"2230.99\",\"properties\":{\"size\":\"large\",\"color\":\"red\"},\"quantity\":1,\"grams\":4000,\"fulfillment_service\":\"manual\",\"applied_discounts\":[]}],\"gift_cards\":[],\"shipping_rate\":{\"id\":\"shopify-Standard%20Shipping-8.00\",\"price\":\"8.00\",\"title\":\"Standard Shipping\"},\"shipping_address\":{\"id\":3479864454,\"first_name\":\"MobileBuy\",\"last_name\":\"TestBot\",\"phone\":\"1-555-555-5555\",\"company\":\"Shopify Inc.\",\"address1\":\"150 Elgin Street\",\"address2\":\"8th Floor\",\"city\":\"Ottawa\",\"province\":\"Ontario\",\"province_code\":\"ON\",\"country\":\"Canada\",\"country_code\":\"CA\",\"zip\":\"K1N5T5\"},\"credit_card\":null,\"billing_address\":{\"id\":3479864390,\"first_name\":\"MobileBuy\",\"last_name\":\"TestBot\",\"phone\":\"1-555-555-5555\",\"company\":\"Shopify Inc.\",\"address1\":\"150 Elgin Street\",\"address2\":\"8th Floor\",\"city\":\"Ottawa\",\"province\":\"Ontario\",\"province_code\":\"ON\",\"country\":\"Canada\",\"country_code\":\"CA\",\"zip\":\"K1N5T5\"},\"discount\":null}}","code":200,"message":"OK"},
"testCheckoutFlowUsingCreditCard_4":{"body":"{\"id\":\"west-6e4f578272359fe593c24c473c9d077b\"}","code":200,"message":"OK"},
......
......@@ -124,10 +124,10 @@
8498DCAD1CDD1B2F00BD12A8 /* BUYError+BUYAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 8498DCA81CDD1B1C00BD12A8 /* BUYError+BUYAdditions.m */; };
8498DCAE1CDD1B2F00BD12A8 /* BUYError+BUYAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 8498DCA71CDD1B1C00BD12A8 /* BUYError+BUYAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
8498DCAF1CDD1B2F00BD12A8 /* BUYError+BUYAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 8498DCA81CDD1B1C00BD12A8 /* BUYError+BUYAdditions.m */; };
8498DCB31CDD1B5400BD12A8 /* BUYClient_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 8498DCB01CDD1B4A00BD12A8 /* BUYClient_Internal.h */; };
8498DCB31CDD1B5400BD12A8 /* BUYClient+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 8498DCB01CDD1B4A00BD12A8 /* BUYClient+Internal.h */; };
8498DCB41CDD1B5400BD12A8 /* BUYClient+Customers.h in Headers */ = {isa = PBXBuildFile; fileRef = 8498DCB11CDD1B4A00BD12A8 /* BUYClient+Customers.h */; settings = {ATTRIBUTES = (Public, ); }; };
8498DCB51CDD1B5400BD12A8 /* BUYClient+Customers.m in Sources */ = {isa = PBXBuildFile; fileRef = 8498DCB21CDD1B4A00BD12A8 /* BUYClient+Customers.m */; };
8498DCB61CDD1B5400BD12A8 /* BUYClient_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 8498DCB01CDD1B4A00BD12A8 /* BUYClient_Internal.h */; };
8498DCB61CDD1B5400BD12A8 /* BUYClient+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 8498DCB01CDD1B4A00BD12A8 /* BUYClient+Internal.h */; };
8498DCB71CDD1B5400BD12A8 /* BUYClient+Customers.h in Headers */ = {isa = PBXBuildFile; fileRef = 8498DCB11CDD1B4A00BD12A8 /* BUYClient+Customers.h */; settings = {ATTRIBUTES = (Public, ); }; };
8498DCB81CDD1B5400BD12A8 /* BUYClient+Customers.m in Sources */ = {isa = PBXBuildFile; fileRef = 8498DCB21CDD1B4A00BD12A8 /* BUYClient+Customers.m */; };
8498DCBB1CDD1FA400BD12A8 /* BUYAccountCredentials.h in Headers */ = {isa = PBXBuildFile; fileRef = 8498DCB91CDD1FA400BD12A8 /* BUYAccountCredentials.h */; settings = {ATTRIBUTES = (Public, ); }; };
......@@ -159,10 +159,10 @@
84B0A73B1CE10ED900253EB0 /* BUYWebCheckoutPaymentProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 84B0A72E1CE10ED900253EB0 /* BUYWebCheckoutPaymentProvider.h */; settings = {ATTRIBUTES = (Public, ); }; };
84B0A73C1CE10ED900253EB0 /* BUYWebCheckoutPaymentProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 84B0A72F1CE10ED900253EB0 /* BUYWebCheckoutPaymentProvider.m */; };
84B0A73D1CE10ED900253EB0 /* BUYWebCheckoutPaymentProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 84B0A72F1CE10ED900253EB0 /* BUYWebCheckoutPaymentProvider.m */; };
84B0A7401CE10F8100253EB0 /* BUYClient+Checkout.h in Headers */ = {isa = PBXBuildFile; fileRef = 84B0A73E1CE10F8100253EB0 /* BUYClient+Checkout.h */; settings = {ATTRIBUTES = (Public, ); }; };
84B0A7411CE10F8100253EB0 /* BUYClient+Checkout.h in Headers */ = {isa = PBXBuildFile; fileRef = 84B0A73E1CE10F8100253EB0 /* BUYClient+Checkout.h */; settings = {ATTRIBUTES = (Public, ); }; };
84B0A7421CE10F8100253EB0 /* BUYClient+Checkout.m in Sources */ = {isa = PBXBuildFile; fileRef = 84B0A73F1CE10F8100253EB0 /* BUYClient+Checkout.m */; };
84B0A7431CE10F8100253EB0 /* BUYClient+Checkout.m in Sources */ = {isa = PBXBuildFile; fileRef = 84B0A73F1CE10F8100253EB0 /* BUYClient+Checkout.m */; };
84B0A7401CE10F8100253EB0 /* BUYClient+CheckoutHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 84B0A73E1CE10F8100253EB0 /* BUYClient+CheckoutHelpers.h */; };
84B0A7411CE10F8100253EB0 /* BUYClient+CheckoutHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 84B0A73E1CE10F8100253EB0 /* BUYClient+CheckoutHelpers.h */; };
84B0A7421CE10F8100253EB0 /* BUYClient+CheckoutHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 84B0A73F1CE10F8100253EB0 /* BUYClient+CheckoutHelpers.m */; };
84B0A7431CE10F8100253EB0 /* BUYClient+CheckoutHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 84B0A73F1CE10F8100253EB0 /* BUYClient+CheckoutHelpers.m */; };
84CD7C2D1CC65D5A00B6EE61 /* _BUYCheckoutAttribute.h in Headers */ = {isa = PBXBuildFile; fileRef = 84CD7C2B1CC65D5500B6EE61 /* _BUYCheckoutAttribute.h */; settings = {ATTRIBUTES = (Public, ); }; };
84CD7C2E1CC65D5A00B6EE61 /* _BUYCheckoutAttribute.h in Headers */ = {isa = PBXBuildFile; fileRef = 84CD7C2B1CC65D5500B6EE61 /* _BUYCheckoutAttribute.h */; settings = {ATTRIBUTES = (Public, ); }; };
84CD7C2F1CC65D7B00B6EE61 /* _BUYCheckoutAttribute.m in Sources */ = {isa = PBXBuildFile; fileRef = 84CD7C2C1CC65D5500B6EE61 /* _BUYCheckoutAttribute.m */; };
......@@ -339,6 +339,23 @@
90F593091B0D5F4C0026B382 /* BUYClientTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 90F592FD1B0D5F4C0026B382 /* BUYClientTest.m */; };
90F5930A1B0D5F4C0026B382 /* BUYLineItemTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 90F592FE1B0D5F4C0026B382 /* BUYLineItemTest.m */; };
90F5930B1B0D5F4C0026B382 /* BUYObjectTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 90F592FF1B0D5F4C0026B382 /* BUYObjectTests.m */; };
9A0B0C661CEA703E0037D68F /* 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 */; };
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 */; };
9A0B0C721CEB52B90037D68F /* BUYClient+Checkout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A0B0C701CEB52B90037D68F /* BUYClient+Checkout.h */; settings = {ATTRIBUTES = (Public, ); }; };
9A0B0C731CEB52B90037D68F /* BUYClient+Checkout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A0B0C701CEB52B90037D68F /* BUYClient+Checkout.h */; settings = {ATTRIBUTES = (Public, ); }; };
9A0B0C741CEB52B90037D68F /* BUYClient+Checkout.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A0B0C711CEB52B90037D68F /* BUYClient+Checkout.m */; };
9A0B0C751CEB52B90037D68F /* BUYClient+Checkout.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A0B0C711CEB52B90037D68F /* BUYClient+Checkout.m */; };
9A0B0C781CEB5BBD0037D68F /* BUYAuthenticatedResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A0B0C761CEB5BBD0037D68F /* BUYAuthenticatedResponse.h */; };
9A0B0C791CEB5BBD0037D68F /* BUYAuthenticatedResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A0B0C761CEB5BBD0037D68F /* BUYAuthenticatedResponse.h */; };
9A0B0C7A1CEB5BBD0037D68F /* BUYAuthenticatedResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A0B0C771CEB5BBD0037D68F /* BUYAuthenticatedResponse.m */; };
9A0B0C7B1CEB5BBD0037D68F /* BUYAuthenticatedResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A0B0C771CEB5BBD0037D68F /* BUYAuthenticatedResponse.m */; };
9A0B0C831CEB981C0037D68F /* BUYClient+RoutingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A0B0C821CEB981C0037D68F /* BUYClient+RoutingTests.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 */; };
......@@ -494,7 +511,7 @@
8498DCA71CDD1B1C00BD12A8 /* BUYError+BUYAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BUYError+BUYAdditions.h"; sourceTree = "<group>"; };
8498DCA81CDD1B1C00BD12A8 /* BUYError+BUYAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BUYError+BUYAdditions.m"; sourceTree = "<group>"; };
8498DCA91CDD1B1C00BD12A8 /* BUYShopifyErrorCodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BUYShopifyErrorCodes.h; sourceTree = "<group>"; };
8498DCB01CDD1B4A00BD12A8 /* BUYClient_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BUYClient_Internal.h; sourceTree = "<group>"; };
8498DCB01CDD1B4A00BD12A8 /* BUYClient+Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BUYClient+Internal.h"; sourceTree = "<group>"; };
8498DCB11CDD1B4A00BD12A8 /* BUYClient+Customers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BUYClient+Customers.h"; sourceTree = "<group>"; };
8498DCB21CDD1B4A00BD12A8 /* BUYClient+Customers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BUYClient+Customers.m"; sourceTree = "<group>"; };
8498DCB91CDD1FA400BD12A8 /* BUYAccountCredentials.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BUYAccountCredentials.h; sourceTree = "<group>"; };
......@@ -517,8 +534,8 @@
84B0A72D1CE10ED900253EB0 /* BUYPaymentProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BUYPaymentProvider.h; sourceTree = "<group>"; };
84B0A72E1CE10ED900253EB0 /* BUYWebCheckoutPaymentProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BUYWebCheckoutPaymentProvider.h; sourceTree = "<group>"; };
84B0A72F1CE10ED900253EB0 /* BUYWebCheckoutPaymentProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BUYWebCheckoutPaymentProvider.m; sourceTree = "<group>"; };
84B0A73E1CE10F8100253EB0 /* BUYClient+Checkout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BUYClient+Checkout.h"; sourceTree = "<group>"; };
84B0A73F1CE10F8100253EB0 /* BUYClient+Checkout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BUYClient+Checkout.m"; sourceTree = "<group>"; };
84B0A73E1CE10F8100253EB0 /* BUYClient+CheckoutHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BUYClient+CheckoutHelpers.h"; sourceTree = "<group>"; };
84B0A73F1CE10F8100253EB0 /* BUYClient+CheckoutHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BUYClient+CheckoutHelpers.m"; sourceTree = "<group>"; };
84CD7C2B1CC65D5500B6EE61 /* _BUYCheckoutAttribute.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _BUYCheckoutAttribute.h; sourceTree = "<group>"; };
84CD7C2C1CC65D5500B6EE61 /* _BUYCheckoutAttribute.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _BUYCheckoutAttribute.m; sourceTree = "<group>"; };
84D73BFE1CDD1931000F978A /* _BUYAddress.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _BUYAddress.h; sourceTree = "<group>"; };
......@@ -611,6 +628,15 @@
90F592FE1B0D5F4C0026B382 /* BUYLineItemTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BUYLineItemTest.m; sourceTree = "<group>"; };
90F592FF1B0D5F4C0026B382 /* BUYObjectTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BUYObjectTests.m; sourceTree = "<group>"; };
90F593001B0D5F4C0026B382 /* BUYTestConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BUYTestConstants.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>"; };
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>"; };
9A0B0C701CEB52B90037D68F /* BUYClient+Checkout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BUYClient+Checkout.h"; sourceTree = "<group>"; };
9A0B0C711CEB52B90037D68F /* BUYClient+Checkout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BUYClient+Checkout.m"; sourceTree = "<group>"; };
9A0B0C761CEB5BBD0037D68F /* BUYAuthenticatedResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BUYAuthenticatedResponse.h; sourceTree = "<group>"; };
9A0B0C771CEB5BBD0037D68F /* BUYAuthenticatedResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BUYAuthenticatedResponse.m; sourceTree = "<group>"; };
9A0B0C821CEB981C0037D68F /* BUYClient+RoutingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "BUYClient+RoutingTests.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>"; };
......@@ -862,8 +888,8 @@
84B0A7281CE10ED900253EB0 /* Payment Providers */ = {
isa = PBXGroup;
children = (
84B0A73E1CE10F8100253EB0 /* BUYClient+Checkout.h */,
84B0A73F1CE10F8100253EB0 /* BUYClient+Checkout.m */,
84B0A73E1CE10F8100253EB0 /* BUYClient+CheckoutHelpers.h */,
84B0A73F1CE10F8100253EB0 /* BUYClient+CheckoutHelpers.m */,
84B0A7291CE10ED900253EB0 /* BUYApplePayPaymentProvider.h */,
84B0A72A1CE10ED900253EB0 /* BUYApplePayPaymentProvider.m */,
84B0A72B1CE10ED900253EB0 /* BUYPaymentController.h */,
......@@ -909,6 +935,7 @@
90F592FB1B0D5F4C0026B382 /* BUYCheckoutTest.m */,
8498DCBF1CDD208200BD12A8 /* BUYClientTest_Customer.m */,
90F592FC1B0D5F4C0026B382 /* BUYClientTest_Storefront.m */,
9A0B0C821CEB981C0037D68F /* BUYClient+RoutingTests.m */,
90F592FD1B0D5F4C0026B382 /* BUYClientTest.m */,
BEB9AE7A1BA866D000575F8A /* BUYClientTestBase.h */,
BEB9AE7C1BA8685600575F8A /* BUYClientTestBase.m */,
......@@ -1024,6 +1051,8 @@
F773744419C779C20039681C /* Models */ = {
isa = PBXGroup;
children = (
9A0B0C761CEB5BBD0037D68F /* BUYAuthenticatedResponse.h */,
9A0B0C771CEB5BBD0037D68F /* BUYAuthenticatedResponse.m */,
8498DCB91CDD1FA400BD12A8 /* BUYAccountCredentials.h */,
8498DCBA1CDD1FA400BD12A8 /* BUYAccountCredentials.m */,
F77374AA19C796BD0039681C /* BUYCreditCard.h */,
......@@ -1097,9 +1126,15 @@
F7FDA16F19C93F6100AF4E93 /* Data */ = {
isa = PBXGroup;
children = (
8498DCB01CDD1B4A00BD12A8 /* BUYClient_Internal.h */,
8498DCB01CDD1B4A00BD12A8 /* BUYClient+Internal.h */,
F7FDA17019C93F6F00AF4E93 /* BUYClient.h */,
F7FDA17119C93F6F00AF4E93 /* BUYClient.m */,
9A0B0C641CEA703E0037D68F /* BUYClient+Routing.h */,
9A0B0C651CEA703E0037D68F /* BUYClient+Routing.m */,
9A0B0C6A1CEB4D300037D68F /* BUYClient+Storefront.h */,
9A0B0C6B1CEB4D300037D68F /* BUYClient+Storefront.m */,
9A0B0C701CEB52B90037D68F /* BUYClient+Checkout.h */,
9A0B0C711CEB52B90037D68F /* BUYClient+Checkout.m */,
8498DCB11CDD1B4A00BD12A8 /* BUYClient+Customers.h */,
8498DCB21CDD1B4A00BD12A8 /* BUYClient+Customers.m */,
);
......@@ -1122,11 +1157,13 @@
84DD12C51CC63FEE00A2442D /* _BUYGiftCard.h in Headers */,
84DD12A91CC63FE600A2442D /* _BUYOrder.h in Headers */,
84DD129F1CC63FE600A2442D /* _BUYCustomer.h in Headers */,
9A0B0C671CEA703E0037D68F /* BUYClient+Routing.h in Headers */,
84DD12991CC63FE600A2442D /* _BUYCart.h in Headers */,
84DD129D1CC63FE600A2442D /* _BUYCollection.h in Headers */,
9A0B0C731CEB52B90037D68F /* BUYClient+Checkout.h in Headers */,
84DD12A11CC63FE600A2442D /* _BUYImageLink.h in Headers */,
84DD12A51CC63FE600A2442D /* _BUYOption.h in Headers */,
84B0A7411CE10F8100253EB0 /* BUYClient+Checkout.h in Headers */,
84B0A7411CE10F8100253EB0 /* BUYClient+CheckoutHelpers.h in Headers */,
84D73C041CDD1945000F978A /* _BUYAddress.h in Headers */,
84DD12C31CC63FEE00A2442D /* _BUYDiscount.h in Headers */,
84DD12A71CC63FE600A2442D /* _BUYOptionValue.h in Headers */,
......@@ -1143,6 +1180,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 */,
......@@ -1157,7 +1195,8 @@
9019313E1BC5B9BC00D1134E /* BUYApplePayAdditions.h in Headers */,
84980F531CB7616900CFAB58 /* BUYDecimalNumberTransformer.h in Headers */,
901931421BC5B9BC00D1134E /* BUYMaskedCreditCard.h in Headers */,
8498DCB61CDD1B5400BD12A8 /* BUYClient_Internal.h in Headers */,
8498DCB61CDD1B5400BD12A8 /* BUYClient+Internal.h in Headers */,
8498DCB61CDD1B5400BD12A8 /* BUYClient+Internal.h in Headers */,
841ADE201CB6C942000004B0 /* NSURL+BUYAdditions.h in Headers */,
841ADE241CB6C942000004B0 /* NSURLComponents+BUYAdditions.h in Headers */,
841ADE141CB6C942000004B0 /* NSException+BUYAdditions.h in Headers */,
......@@ -1189,6 +1228,7 @@
9019315E1BC5B9BC00D1134E /* BUYError.h in Headers */,
84980F371CB75C2900CFAB58 /* NSPropertyDescription+BUYAdditions.h in Headers */,
901931611BC5B9BC00D1134E /* BUYClient.h in Headers */,
9A0B0C791CEB5BBD0037D68F /* BUYAuthenticatedResponse.h in Headers */,
849810971CB7E07900CFAB58 /* BUYFlatCollectionTransformer.h in Headers */,
901931641BC5B9BC00D1134E /* BUYCartLineItem.h in Headers */,
8498DCAE1CDD1B2F00BD12A8 /* BUYError+BUYAdditions.h in Headers */,
......@@ -1215,11 +1255,13 @@
84DD12B91CC63FEE00A2442D /* _BUYMaskedCreditCard.h in Headers */,
84DD12B31CC63FEE00A2442D /* _BUYCheckout.h in Headers */,
84DD12BD1CC63FEE00A2442D /* _BUYTaxLine.h in Headers */,
9A0B0C661CEA703E0037D68F /* BUYClient+Routing.h in Headers */,
84DD12B71CC63FEE00A2442D /* _BUYGiftCard.h in Headers */,
84DD12911CC63FE600A2442D /* _BUYOrder.h in Headers */,
9A0B0C721CEB52B90037D68F /* BUYClient+Checkout.h in Headers */,
84DD12811CC63FE600A2442D /* _BUYCart.h in Headers */,
84DD12851CC63FE600A2442D /* _BUYCollection.h in Headers */,
84B0A7401CE10F8100253EB0 /* BUYClient+Checkout.h in Headers */,
84B0A7401CE10F8100253EB0 /* BUYClient+CheckoutHelpers.h in Headers */,
84D73C021CDD1944000F978A /* _BUYAddress.h in Headers */,
84DD12891CC63FE600A2442D /* _BUYImageLink.h in Headers */,
84DD128D1CC63FE600A2442D /* _BUYOption.h in Headers */,
......@@ -1235,6 +1277,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 */,
......@@ -1250,7 +1293,8 @@
84980F521CB7616900CFAB58 /* BUYDecimalNumberTransformer.h in Headers */,
BE5DC3631B71022D00B2BC1E /* BUYMaskedCreditCard.h in Headers */,
90C856B61BD6B0F400936926 /* Buy.h in Headers */,
8498DCB31CDD1B5400BD12A8 /* BUYClient_Internal.h in Headers */,
8498DCB31CDD1B5400BD12A8 /* BUYClient+Internal.h in Headers */,
8498DCB31CDD1B5400BD12A8 /* BUYClient+Internal.h in Headers */,
841ADE0F1CB6C942000004B0 /* NSDictionary+BUYAdditions.h in Headers */,
841ADE1F1CB6C942000004B0 /* NSURL+BUYAdditions.h in Headers */,
841ADE231CB6C942000004B0 /* NSURLComponents+BUYAdditions.h in Headers */,
......@@ -1282,6 +1326,7 @@
BE47340F1B66C4EF00AA721A /* BUYError.h in Headers */,
84980F361CB75C2900CFAB58 /* NSPropertyDescription+BUYAdditions.h in Headers */,
BE9A64471B503C8B0033E558 /* BUYClient.h in Headers */,
9A0B0C781CEB5BBD0037D68F /* BUYAuthenticatedResponse.h in Headers */,
849810961CB7E07900CFAB58 /* BUYFlatCollectionTransformer.h in Headers */,
9003969B1B601DF400226B73 /* BUYCartLineItem.h in Headers */,
8498DCAC1CDD1B2F00BD12A8 /* BUYError+BUYAdditions.h in Headers */,
......@@ -1497,6 +1542,7 @@
84980F611CB7617E00CFAB58 /* BUYDateTransformer.m in Sources */,
901930E81BC5B9BC00D1134E /* BUYImageLink.m in Sources */,
84DD129E1CC63FE600A2442D /* _BUYCollection.m in Sources */,
9A0B0C691CEA703E0037D68F /* BUYClient+Routing.m in Sources */,
901930E91BC5B9BC00D1134E /* BUYProductVariant.m in Sources */,
84D9154E1CC03F1600D334FB /* BUYManagedObject.m in Sources */,
84B0A7331CE10ED900253EB0 /* BUYApplePayPaymentProvider.m in Sources */,
......@@ -1506,6 +1552,7 @@
84DD12AE1CC63FE600A2442D /* _BUYProductVariant.m in Sources */,
84B0A7211CDD261100253EB0 /* BUYSerializable.m in Sources */,
9A47CEFE1CE39F6100A6D5BA /* BUYCreditCardToken.m in Sources */,
9A0B0C7B1CEB5BBD0037D68F /* BUYAuthenticatedResponse.m in Sources */,
901930F21BC5B9BC00D1134E /* BUYOrder.m in Sources */,
849810991CB7E07900CFAB58 /* BUYFlatCollectionTransformer.m in Sources */,
84DD12A41CC63FE600A2442D /* _BUYLineItem.m in Sources */,
......@@ -1518,6 +1565,7 @@
901930FD1BC5B9BC00D1134E /* BUYTaxLine.m in Sources */,
84980F351CB75C2900CFAB58 /* NSEntityDescription+BUYAdditions.m in Sources */,
84D915461CC0359700D334FB /* BUYObserver.m in Sources */,
9A0B0C751CEB52B90037D68F /* BUYClient+Checkout.m in Sources */,
84D73C091CDD194D000F978A /* BUYAddress.m in Sources */,
901931031BC5B9BC00D1134E /* BUYOptionValue.m in Sources */,
84980F551CB7616900CFAB58 /* BUYDecimalNumberTransformer.m in Sources */,
......@@ -1532,6 +1580,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 */,
......@@ -1554,7 +1603,7 @@
841ADE221CB6C942000004B0 /* NSURL+BUYAdditions.m in Sources */,
901931161BC5B9BC00D1134E /* BUYShippingRate.m in Sources */,
841ADE061CB6C942000004B0 /* NSDate+BUYAdditions.m in Sources */,
84B0A7431CE10F8100253EB0 /* BUYClient+Checkout.m in Sources */,
84B0A7431CE10F8100253EB0 /* BUYClient+CheckoutHelpers.m in Sources */,
84B0A7371CE10ED900253EB0 /* BUYPaymentController.m in Sources */,
9019311C1BC5B9BC00D1134E /* BUYOption.m in Sources */,
84D73C081CDD194D000F978A /* _BUYAddress.m in Sources */,
......@@ -1581,6 +1630,7 @@
849110441CCE9F3F00E53B93 /* BUYTransformerTests.m in Sources */,
8498DCCC1CDD208200BD12A8 /* BUYCustomerTests.m in Sources */,
8498DCCE1CDD208200BD12A8 /* BUYOrderTests.m in Sources */,
9A0B0C831CEB981C0037D68F /* BUYClient+RoutingTests.m in Sources */,
849110351CCE70CE00E53B93 /* BUYDictionaryAdditionsTests.m in Sources */,
90F5930A1B0D5F4C0026B382 /* BUYLineItemTest.m in Sources */,
8498DCCA1CDD208200BD12A8 /* BUYCollectionTests.m in Sources */,
......@@ -1626,6 +1676,7 @@
84980F601CB7617E00CFAB58 /* BUYDateTransformer.m in Sources */,
BE9A645A1B503CD90033E558 /* BUYImageLink.m in Sources */,
84DD12861CC63FE600A2442D /* _BUYCollection.m in Sources */,
9A0B0C681CEA703E0037D68F /* BUYClient+Routing.m in Sources */,
BE9A64641B503CFB0033E558 /* BUYProductVariant.m in Sources */,
84D9154D1CC03F1600D334FB /* BUYManagedObject.m in Sources */,
84B0A7321CE10ED900253EB0 /* BUYApplePayPaymentProvider.m in Sources */,
......@@ -1635,6 +1686,7 @@
84DD12961CC63FE600A2442D /* _BUYProductVariant.m in Sources */,
84B0A7201CDD261100253EB0 /* BUYSerializable.m in Sources */,
9A47CEFD1CE39F6000A6D5BA /* BUYCreditCardToken.m in Sources */,
9A0B0C7A1CEB5BBD0037D68F /* BUYAuthenticatedResponse.m in Sources */,
90E83BC51B9F550E00C95A1B /* BUYOrder.m in Sources */,
849810981CB7E07900CFAB58 /* BUYFlatCollectionTransformer.m in Sources */,
84DD128C1CC63FE600A2442D /* _BUYLineItem.m in Sources */,
......@@ -1647,6 +1699,7 @@
BE9A64521B503CB80033E558 /* BUYTaxLine.m in Sources */,
84980F341CB75C2900CFAB58 /* NSEntityDescription+BUYAdditions.m in Sources */,
84D915451CC0359700D334FB /* BUYObserver.m in Sources */,
9A0B0C741CEB52B90037D68F /* BUYClient+Checkout.m in Sources */,
84D73C071CDD194D000F978A /* BUYAddress.m in Sources */,
BE9A64691B503D0C0033E558 /* BUYOptionValue.m in Sources */,
84980F541CB7616900CFAB58 /* BUYDecimalNumberTransformer.m in Sources */,
......@@ -1661,6 +1714,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 */,
......@@ -1683,7 +1737,7 @@
841ADE211CB6C942000004B0 /* NSURL+BUYAdditions.m in Sources */,
BE9A644E1B503CA60033E558 /* BUYShippingRate.m in Sources */,
841ADE051CB6C942000004B0 /* NSDate+BUYAdditions.m in Sources */,
84B0A7421CE10F8100253EB0 /* BUYClient+Checkout.m in Sources */,
84B0A7421CE10F8100253EB0 /* BUYClient+CheckoutHelpers.m in Sources */,
84B0A7361CE10ED900253EB0 /* BUYPaymentController.m in Sources */,
BE9A64601B503CEC0033E558 /* BUYOption.m in Sources */,
84D73C061CDD194D000F978A /* _BUYAddress.m in Sources */,
......
......@@ -65,6 +65,8 @@ 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/BUYClient+Checkout.h>
#import <Buy/BUYError.h>
#import <Buy/BUYError+BUYAdditions.h>
#import <Buy/BUYManagedObject.h>
......
//
// BUYClient+Checkout.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 BUYCheckout;
@class BUYShippingRate;
@class BUYGiftCard;
@class BUYCreditCard;
@protocol BUYPaymentToken;
/**
* Return block containing a BUYCheckout, id<BUYPaymentToken> and/or an NSError
*
* @param checkout The returned BUYCheckout
* @param paymentToken An opaque payment token type that wraps necessary credentials for payment
* @param error Optional NSError
*/
typedef void (^BUYDataCreditCardBlock)(BUYCheckout * _Nullable checkout, id<BUYPaymentToken> _Nullable paymentToken, NSError * _Nullable error);
/**
* Return block containing a BUYCheckout and/or an NSError
*
* @param checkout The returned BUYCheckout
* @param error Optional NSError
*/
typedef void (^BUYDataCheckoutBlock)(BUYCheckout * _Nullable checkout, NSError * _Nullable error);
/**
* Return block containing BUYShippingRate objects, a BUYStatus and/or an NSError
*
* @param shippingRates Array of SHKShippingRates
* @param status A BUYStatus specifying the requested job's completion status
* @param error Optional NSError
*/
typedef void (^BUYDataShippingRatesBlock)(NSArray<BUYShippingRate *> * _Nullable shippingRates, BUYStatus status, NSError * _Nullable error);
/**
* Return block containing a BUYGiftCard
*
* @param giftCard A BUYGiftCard
* @param error An optional NSError
*/
typedef void (^BUYDataGiftCardBlock)(BUYGiftCard * _Nullable giftCard, NSError * _Nullable error);
@interface BUYClient (Checkout)
/**
* Builds a checkout on Shopify. The checkout object is used to prepare an order
*
* @param checkout BUYCheckout to create on Shopify
* @param block (^BUYDataCheckoutBlock)(BUYCheckout *checkout, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)createCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block;
/**
* Builds a checkout on Shopify using a Cart Token from an existing cart on your Shopify store's storefront.
* The BUYCheckout object is used to prepare an order.
*
* @param cartToken Cart Token associated with an existing BUYCheckout on Shopify
* @param block (^BUYDataCheckoutBlock)(BUYCheckout *checkout, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)createCheckoutWithCartToken:(NSString *)cartToken completion:(BUYDataCheckoutBlock)block;
/**
* Applies a gift card code to the checkout.
*
* @param giftCardCode The gift card code to apply on an existing checkout on Shopify. Note: This is not the same as the gift card identifier.
* @param checkout An existing BUYCheckout on Shopify
* @param block (^BUYDataCheckoutBlock)(BUYCheckout *checkout, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)applyGiftCardWithCode:(NSString *)giftCardCode toCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block;
/**
* Removes a gift card from the checkout.
*
* @param giftCardCode The BUYGiftCard identifier to remove on an existing checkout on Shopify.
* @param checkout An existing BUYCheckout on Shopify
* @param block (^BUYDataCheckoutBlock)(BUYCheckout *checkout, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)removeGiftCard:(BUYGiftCard *)giftCard fromCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block;
/**
* Retrieves an updated version of a BUYCheckout from Shopify.
*
* Note: There's no guarantee that the BUYCheckout returned will be the same as the one that is passed in.
* We recommended using the BUYCheckout returned in the block.
*
* @param checkout The BUYCheckout to retrieve (updated) from Shopify
* @param block (^BUYDataCheckoutBlock)(BUYCheckout *checkout, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block;
/**
* Updates a given BUYCheckout on Shopify.
*
* Note: There's no guarantee that the BUYCheckout returned will be the same as the one that is passed in.
* We recommended using the BUYCheckout returned in the block.
*
* Note: A BUYCheckout object with an `orderId` is a completed checkout.
*
* @param checkout The BUYCheckout to updated on Shopify
* @param block (^BUYDataCheckoutBlock)(BUYCheckout *checkout, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)updateCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block;
/**
* Finalizes the BUYCheckout and charges the payment provider (ex: Credit Card, Apple Pay, etc).
* This enqueues a completion job on Shopify and returns immediately.
* You must get the job's status by calling checkCompletionStatusOfCheckout:block
*
* Note: There's no guarantee that the BUYCheckout returned will be the same as the one that is passed in.
* We recommended using the BUYCheckout returned in the block.
*
* @param checkout The BUYCheckout to complete
* @param paymentToken Opaque payment token object. May be nil if the total checkout amount is equal to $0.00
* @param block (^BUYDataCheckoutBlock)(BUYCheckout *checkout, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)completeCheckout:(BUYCheckout *)checkout paymentToken:(_Nullable id<BUYPaymentToken>)paymentToken completion:(BUYDataCheckoutBlock)block;
/**
* Retrieve the status of a BUYCheckout. This checks the status of the current payment processing job for the provided checkout.
* Once the job is complete (status == BUYStatusComplete), you can retrieve the completed order by calling `getCheckout:completion`
*
* @param checkout The BUYCheckout to retrieve completion status for
* @param block (^BUYDataStatusBlock)(BUYCheckout *checkout, BUYStatus status, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getCompletionStatusOfCheckout:(BUYCheckout *)checkout completion:(BUYDataStatusBlock)block;
/**
* Retrieve the status of a checkout given a URL obtained in the UIApplicationDelegate method `application:sourceApplication:annotation`
*
* @param url The URL resource used to open the application
* @param block (^BUYDataStatusBlock)(BUYCheckout *checkout, BUYStatus status, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getCompletionStatusOfCheckoutURL:(NSURL *)url completion:(BUYDataStatusBlock)block;
#pragma mark - Shipping Rates
/**
* Retrieves a list of applicable shipping rates for a given BUYCheckout.
* Add the preferred/selected BUYShippingRate to BUYCheckout, then update BUYCheckout
*
* @param checkout The BUYCheckout to retrieve shipping rates for
* @param block (^BUYDataShippingRatesBlock)(NSArray *shippingRates, BUYStatus status, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getShippingRatesForCheckout:(BUYCheckout *)checkout completion:(BUYDataShippingRatesBlock)block;
#pragma mark - Payment Management
/**
* Prepares a credit card for usage during the checkout process. This sends it to Shopify's secure servers.
* Note: Storing the token does not charge the associated card (credit or otherwise).
* The card will be charged upon finalizing the checkout (`completeCheckout:completion:`)
*
* You MUST call `completeCheckout:completion:` after this call and receiving a `paymentSessionId`.
* The `paymentSessionId` on the `BUYCheckout` object is not persisted on `updateCheckout:completion:` calls.
*
* @param creditCard BUYCreditCard to prepare for usage
* @param checkout The BUYCheckout associated to the purchase.
* The `billingAddress` stored on the BUYCheckout object is optional and recommended and
* used (if provided) to help with fraud checking.
* @param block (^BUYDataCreditCardBlock)(BUYCheckout *checkout, NSString *paymentSessionId, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)storeCreditCard:(BUYCreditCard *)creditCard checkout:(BUYCheckout *)checkout completion:(BUYDataCreditCardBlock)block;
/**
* Convenience method to release all product inventory reservations by setting its
* `reservationTime` to `@0` and calls `updateCheckout:completion`. We recommend creating
* a new BUYCheckout object from a BUYCart for further API calls.
*
* @param checkout The BUYCheckout to expire
* @param block (^BUYDataCheckoutBlock)(BUYCheckout *checkout, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)removeProductReservationsFromCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block;
@end
NS_ASSUME_NONNULL_END
//
// BUYClient+Checkout.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+Checkout.h"
#import "BUYClient+Internal.h"
#import "BUYClient+Routing.h"
#import "BUYAddress.h"
#import "BUYCheckout.h"
#import "BUYGiftCard.h"
#import "BUYShippingRate.h"
#import "BUYCreditCard.h"
#import "BUYCreditCardToken.h"
#import "BUYAssert.h"
#import "BUYPaymentToken.h"
#import "NSDecimalNumber+BUYAdditions.h"
#define BUYAssertCheckout(checkout) BUYAssert([(checkout) hasToken], @"Checkout assertion failed. Checkout must have a valid token associated with it.")
@implementation BUYClient (Checkout)
- (void)handleCheckoutResponse:(NSDictionary *)json error:(NSError *)error block:(BUYDataCheckoutBlock)block
{
BUYCheckout *checkout = nil;
if (!error) {
checkout = [self.modelManager insertCheckoutWithJSONDictionary:json[@"checkout"]];
}
block(checkout, error);
}
- (void)configureCheckout:(BUYCheckout *)checkout
{
checkout.marketingAttribution = @{@"medium": @"iOS", @"source": self.applicationName};
checkout.sourceName = @"mobile_app";
if (self.urlScheme || checkout.webReturnToURL) {
checkout.webReturnToURL = checkout.webReturnToURL ?: [NSURL URLWithString:self.urlScheme];
checkout.webReturnToLabel = checkout.webReturnToLabel ?: [@"Return to " stringByAppendingString:self.applicationName];
}
}
- (NSURLSessionDataTask *)createCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block
{
BUYAssert(checkout, @"Failed to create checkout. Invalid checkout object.");
// Inject channel and marketing attributions
[self configureCheckout:checkout];
NSDictionary *json = [checkout jsonDictionaryForCheckout];
return [self postCheckout:json completion:block];
}
- (NSURLSessionDataTask *)createCheckoutWithCartToken:(NSString *)cartToken completion:(BUYDataCheckoutBlock)block
{
BUYAssert(cartToken, @"Failed to create checkout. Invalid cart token");
BUYCheckout *checkout = [self.modelManager checkoutwithCartToken:cartToken];
[self configureCheckout:checkout];
NSDictionary *json = [checkout jsonDictionaryForCheckout];
return [self postCheckout:json completion:block];
}
- (NSURLSessionDataTask *)postCheckout:(NSDictionary *)checkoutJSON completion:(BUYDataCheckoutBlock)block
{
return [self postRequestForURL:[self urlForCheckouts] object:checkoutJSON completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
[self handleCheckoutResponse:json error:error block:block];
}];
}
- (NSURLSessionDataTask *)applyGiftCardWithCode:(NSString *)giftCardCode toCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block
{
BUYAssertCheckout(checkout);
BUYAssert(giftCardCode.length > 0, @"Failed to apply gift card code. Invalid gift card code.");
BUYGiftCard *giftCard = [self.modelManager giftCardWithCode:giftCardCode];
NSURL *route = [self urlForCheckoutsUsingGiftCardWithToken:checkout.token];
return [self postRequestForURL:route object:giftCard completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
if (json && !error) {
[self updateCheckout:checkout withGiftCardDictionary:json[@"gift_card"] addingGiftCard:YES];
}
block(checkout, error);
}];
}
- (NSURLSessionDataTask *)removeGiftCard:(BUYGiftCard *)giftCard fromCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block
{
BUYAssertCheckout(checkout);
BUYAssert(giftCard.identifier, @"Failed to remove gift card. Gift card must have a valid identifier.");
NSURL *route = [self urlForCheckoutsUsingGiftCard:giftCard.identifier token:checkout.token];
return [self deleteRequestForURL:route completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
if (!error) {
[self updateCheckout:checkout withGiftCardDictionary:json[@"gift_card"] addingGiftCard:NO];
}
block(checkout, error);
}];
}
- (void)updateCheckout:(BUYCheckout *)checkout withGiftCardDictionary:(NSDictionary *)giftCardDictionary addingGiftCard:(BOOL)addingGiftCard
{
if (addingGiftCard) {
BUYGiftCard *giftCard = [self.modelManager insertGiftCardWithJSONDictionary:giftCardDictionary];
[checkout.giftCardsSet addObject:giftCard];
} else {
[checkout removeGiftCardWithIdentifier:giftCardDictionary[@"id"]];
}
checkout.paymentDue = [NSDecimalNumber buy_decimalNumberFromJSON:giftCardDictionary[@"checkout"][@"payment_due"]];
// Marking the checkout as clean. The properties we have updated above we don't need to re-sync with Shopify.
// There's also an issue with gift cards where syncing the gift card JSON won't work since the update endpoint
// doesn't accept the gift card without a gift card code (which we do not have).
[checkout markAsClean];
}
- (NSURLSessionDataTask *)getCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block
{
BUYAssertCheckout(checkout);
NSURL *route = [self urlForCheckoutsWithToken:checkout.token];
return [self getRequestForURL:route completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
[self handleCheckoutResponse:json error:error block:block];
}];
}
- (NSURLSessionDataTask *)updateCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block
{
BUYAssertCheckout(checkout);
NSURL *route = [self urlForCheckoutsWithToken:checkout.token];
return [self patchRequestForURL:route object:checkout completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
[self handleCheckoutResponse:json error:error block:block];
}];
}
- (NSURLSessionDataTask*)completeCheckout:(BUYCheckout *)checkout paymentToken:(id<BUYPaymentToken>)paymentToken completion:(BUYDataCheckoutBlock)block
{
BUYAssertCheckout(checkout);
BOOL isFree = (checkout.paymentDue && checkout.paymentDue.floatValue == 0);
BUYAssert(paymentToken || isFree, @"Failed to complete checkout. Checkout must have a payment token or have a payment value equal to $0.00");
NSURL *route = [self urlForCheckoutsCompletionWithToken:checkout.token];
return [self postRequestForURL:route object:[paymentToken JSONDictionary] completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
[self handleCheckoutResponse:json error:error block:block];
}];
}
- (NSURLSessionDataTask *)getCompletionStatusOfCheckout:(BUYCheckout *)checkout completion:(BUYDataStatusBlock)block
{
BUYAssertCheckout(checkout);
return [self getCompletionStatusOfCheckoutToken:checkout.token completion:block];
}
- (NSURLSessionDataTask *)getCompletionStatusOfCheckoutURL:(NSURL *)url completion:(BUYDataStatusBlock)block
{
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
NSString *token = nil;
for (NSURLQueryItem *item in components.queryItems) {
if ([item.name isEqualToString:@"checkout[token]"]) {
token = item.value;
break;
}
}
BUYAssert(token, @"Failed to get completion status of checkout. Checkout URL must have a valid token associated with it.");
return [self getCompletionStatusOfCheckoutToken:token completion:block];
}
- (NSURLSessionDataTask *)getCompletionStatusOfCheckoutToken:(NSString *)token completion:(BUYDataStatusBlock)block
{
NSURL *route = [self urlForCheckoutsProcessingWithToken:token];
return [self getRequestForURL:route completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
block([self statusForStatusCode:statusCode error:error], error);
}];
}
#pragma mark - Shipping Rates
- (NSURLSessionDataTask *)getShippingRatesForCheckout:(BUYCheckout *)checkout completion:(BUYDataShippingRatesBlock)block
{
BUYAssertCheckout(checkout);
NSURL *route = [self urlForCheckoutsShippingRatesWithToken:checkout.token parameters:@{
@"checkout" : @"",
}];
return [self getRequestForURL:route completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSArray *shippingRates = nil;
if (json && !error) {
shippingRates = [self.modelManager insertShippingRatesWithJSONArray:json[@"shipping_rates"]];
}
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
block(shippingRates, [self statusForStatusCode:statusCode error:error], error);
}];
}
#pragma mark - Payments
- (NSURLSessionDataTask *)storeCreditCard:(BUYCreditCard *)creditCard checkout:(BUYCheckout *)checkout completion:(BUYDataCreditCardBlock)completion
{
BUYAssertCheckout(checkout);
BUYAssert(creditCard, @"Failed to store credit card. No credit card provided.");
NSMutableDictionary *json = [[NSMutableDictionary alloc] init];
json[@"token"] = checkout.token;
json[@"credit_card"] = [creditCard jsonDictionaryForCheckout];
if (checkout.billingAddress) {
json[@"billing_address"] = [checkout.billingAddress jsonDictionaryForCheckout];
}
return [self postRequestForURL:checkout.paymentURL object:@{ @"checkout" : json } completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
id<BUYPaymentToken> token = nil;
if (!error) {
token = [[BUYCreditCardToken alloc] initWithPaymentSessionID:json[@"id"]];
}
completion(checkout, token, error);
}];
}
- (NSURLSessionDataTask *)removeProductReservationsFromCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block
{
BUYAssertCheckout(checkout);
checkout.reservationTime = @0;
return [self updateCheckout:checkout completion:block];
}
@end
......@@ -113,7 +113,7 @@ typedef void (^BUYDataOrdersBlock)(NSArray <BUYOrder*> * _Nullable orders, NSErr
*
* @return the associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)recoverPasswordForCustomer:(NSString *)email callback:(BUYDataCheckoutStatusBlock)block;
- (NSURLSessionDataTask *)recoverPasswordForCustomer:(NSString *)email callback:(BUYDataStatusBlock)block;
/**
* PUT /api/customers/:customer_id/customer_token/renew
......
......@@ -25,43 +25,23 @@
//
#import "BUYClient+Customers.h"
#import "BUYClient_Internal.h"
#import "BUYClient+Internal.h"
#import "BUYClient+Routing.h"
#import "NSDateFormatter+BUYAdditions.h"
#import "BUYCustomer.h"
#import "BUYAccountCredentials.h"
#import "BUYAuthenticatedResponse.h"
#import "BUYOrder.h"
#import "BUYShopifyErrorCodes.h"
@interface BUYAuthenticatedResponse : NSObject
+ (BUYAuthenticatedResponse *)responseFromJSON:(NSDictionary *)json;
@property (nonatomic, copy) NSString *accessToken;
@property (nonatomic, copy) NSDate *expiry;
@property (nonatomic, copy) NSString *customerID;
@end
@implementation BUYAuthenticatedResponse
+ (BUYAuthenticatedResponse *)responseFromJSON:(NSDictionary *)json
{
BUYAuthenticatedResponse *response = [BUYAuthenticatedResponse new];
NSDictionary *access = json[@"customer_access_token"];
response.accessToken = access[@"access_token"];
NSDateFormatter *formatter = [NSDateFormatter dateFormatterForPublications];
response.expiry = [formatter dateFromString:access[@"expires_at"]];
response.customerID = [NSString stringWithFormat:@"%@", access[@"customer_id"]];
return response;
}
@end
@implementation BUYClient (Customers)
#pragma mark - Customer
- (NSURLSessionDataTask *)getCustomerWithID:(NSString *)customerID callback:(BUYDataCustomerBlock)block
{
NSURLComponents *components = [self URLComponentsForCustomerWithID:customerID];
return [self getRequestForURL:components.URL completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSURL *route = [self urlForCustomersWithID:customerID];
return [self getRequestForURL:route completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
BUYCustomer *customer = nil;
if (json && !error) {
customer = [self.modelManager customerWithJSONDictionary:json];
......@@ -72,8 +52,8 @@
- (NSURLSessionDataTask *)createCustomerWithCredentials:(BUYAccountCredentials *)credentials callback:(BUYDataCustomerTokenBlock)block
{
NSURLComponents *components = [self URLComponentsForCustomers];
return [self postRequestForURL:components.URL object:credentials.JSONRepresentation completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSURL *route = [self urlForCustomers];
return [self postRequestForURL:route object:credentials.JSONRepresentation completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
if (json && !error) {
[self createTokenForCustomerWithCredentials:credentials customerJSON:json callback:block];
}
......@@ -85,10 +65,10 @@
- (NSURLSessionDataTask *)createTokenForCustomerWithCredentials:(BUYAccountCredentials *)credentials customerJSON:(NSDictionary *)customerJSON callback:(BUYDataCustomerTokenBlock)block
{
NSURLComponents *components = [self URLComponentsForCustomerLogin];
return [self postRequestForURL:components.URL object:credentials.JSONRepresentation completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSURL *route = [self urlForCustomersToken];
return [self postRequestForURL:route object:credentials.JSONRepresentation completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
if (json && !error) {
BUYAuthenticatedResponse *authenticatedResponse = [BUYAuthenticatedResponse responseFromJSON:json];
BUYAuthenticatedResponse *authenticatedResponse = [BUYAuthenticatedResponse responseWithJSON:json];
self.customerToken = authenticatedResponse.accessToken;
if (!customerJSON) {
......@@ -112,11 +92,10 @@
return [self createTokenForCustomerWithCredentials:credentials customerJSON:nil callback:block];
}
- (NSURLSessionDataTask *)recoverPasswordForCustomer:(NSString *)email callback:(BUYDataCheckoutStatusBlock)block
- (NSURLSessionDataTask *)recoverPasswordForCustomer:(NSString *)email callback:(BUYDataStatusBlock)block
{
NSURLComponents *components = [self URLComponentsForPasswordReset];
return [self postRequestForURL:components.URL object:@{@"email": email} completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSURL *route = [self urlForCustomersPasswordRecovery];
return [self postRequestForURL:route object:@{@"email": email} completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
if (!error) {
......@@ -130,13 +109,13 @@
- (NSURLSessionDataTask *)renewCustomerTokenWithID:(NSString *)customerID callback:(BUYDataTokenBlock)block
{
if (self.customerToken) {
NSURLComponents *components = [self URLComponentsForTokenRenewalWithID:customerID];
NSURL *route = [self urlForCustomersTokenRenewalWithID:customerID];
return [self putRequestForURL:components.URL object:nil completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
return [self putRequestForURL:route object:nil completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSString *accessToken = nil;
if (json && !error) {
BUYAuthenticatedResponse *authenticatedResponse = [BUYAuthenticatedResponse responseFromJSON:json];
BUYAuthenticatedResponse *authenticatedResponse = [BUYAuthenticatedResponse responseWithJSON:json];
accessToken = authenticatedResponse.accessToken;
}
......@@ -148,16 +127,18 @@
}];
}
else {
block(nil, [NSError errorWithDomain:kShopifyError code:BUYShopifyError_InvalidCustomerToken userInfo:nil]);
block(nil, [NSError errorWithDomain:BUYShopifyErrorDomain code:BUYShopifyError_InvalidCustomerToken userInfo:nil]);
return nil;
}
}
- (NSURLSessionDataTask *)activateCustomerWithCredentials:(BUYAccountCredentials *)credentials customerID:(NSString *)customerID customerToken:(NSString *)customerToken callback:(BUYDataCustomerTokenBlock)block
{
NSURLComponents *components = [self URLComponentsForCustomerActivationWithID:customerID customerToken:customerToken];
NSURL *route = [self urlForCustomersActivationWithID:customerID parameters:@{
@"token": customerToken,
}];
return [self putRequestForURL:components.URL object:credentials.JSONRepresentation completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
return [self putRequestForURL:route object:credentials.JSONRepresentation completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSString *email = json[@"customer"][@"email"];
if (email && !error) {
BUYAccountCredentialItem *emailItem = [BUYAccountCredentialItem itemWithEmail:email];
......@@ -171,9 +152,11 @@
- (NSURLSessionDataTask *)resetPasswordWithCredentials:(BUYAccountCredentials *)credentials customerID:(NSString *)customerID customerToken:(NSString *)customerToken callback:(BUYDataCustomerTokenBlock)block
{
NSURLComponents *components = [self URLComponentsForCustomerPasswordResetWithCustomerID:customerID customerToken:customerToken];
NSURL *route = [self urlForCustomersPasswordResetWithID:customerID parameters:@{
@"token": customerToken,
}];
return [self putRequestForURL:components.URL object:credentials.JSONRepresentation completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
return [self putRequestForURL:route object:credentials.JSONRepresentation completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSString *email = json[@"customer"][@"email"];
if (email && !error) {
BUYAccountCredentialItem *emailItem = [BUYAccountCredentialItem itemWithEmail:email];
......@@ -187,8 +170,8 @@
- (NSURLSessionDataTask *)getOrdersForCustomerWithCallback:(BUYDataOrdersBlock)block
{
NSURLComponents *components = [self URLComponentsForCustomerOrders];
return [self getRequestForURL:components.URL completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSURL *route = [self urlForCustomersOrders];
return [self getRequestForURL:route completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
if (json && !error) {
NSArray *orders = [self.modelManager ordersWithJSONDictionary:json];
block(orders, error);
......@@ -198,68 +181,4 @@
}];
}
#pragma mark - URL Formatting
- (NSURLComponents *)URLComponentsForCustomers
{
return [self customerURLComponentsAppendingPath:nil];
}
- (NSURLComponents *)URLComponentsForCustomerWithID:(NSString *)customerID
{
return [self customerURLComponentsAppendingPath:customerID];
}
- (NSURLComponents *)URLComponentsForCustomerLogin
{
return [self customerURLComponentsAppendingPath:@"customer_token"];
}
- (NSURLComponents *)URLComponentsForCustomerActivationWithID:(NSString *)customerID customerToken:(NSString *)customerToken
{
NSDictionary *queryItems = @{ @"token": customerToken };
NSString *path = [NSString stringWithFormat:@"%@/activate", customerID];
return [self customerURLComponentsAppendingPath:path queryItems:queryItems];
}
- (NSURLComponents *)URLComponentsForCustomerPasswordResetWithCustomerID:(NSString *)customerID customerToken:(NSString *)customerToken
{
NSDictionary *queryItems = @{ @"token": customerToken };
NSString *path = [NSString stringWithFormat:@"%@/reset", customerID];
return [self customerURLComponentsAppendingPath:path queryItems:queryItems];
}
- (NSURLComponents *)URLComponentsForPasswordReset
{
return [self customerURLComponentsAppendingPath:@"recover" queryItems:nil];
}
- (NSURLComponents *)URLComponentsForTokenRenewalWithID:(NSString *)customerID
{
NSString *path = [NSString stringWithFormat:@"%@/customer_token/renew", customerID];
return [self customerURLComponentsAppendingPath:path queryItems:nil];
}
- (NSURLComponents *)URLComponentsForCustomerOrders
{
return [self customerURLComponentsAppendingPath:@"orders" queryItems:nil];
}
#pragma mark - Convenience methods
- (NSURLComponents *)customerURLComponentsAppendingPath:(NSString *)path
{
return [self customerURLComponentsAppendingPath:path queryItems:nil];
}
- (NSURLComponents *)customerURLComponentsAppendingPath:(NSString *)path queryItems:(NSDictionary *)queryItems
{
return [self URLComponentsForAPIPath:@"customers" appendingPath:path queryItems:queryItems];
}
- (NSString *)accessTokenFromHeaders:(NSDictionary *)headers
{
return [headers valueForKey:BUYClientCustomerAccessToken];
}
@end
//
// BUYClient_Internal.h
// BUYClient+Internal.h
// Mobile Buy SDK
//
// Created by Shopify.
......@@ -27,16 +27,20 @@
#import "BUYClient.h"
#import "BUYSerializable.h"
extern NSString *const kShopifyError;
static NSString * const BUYShopifyErrorDomain = @"shopify";
static NSString * const BUYClientVersionString = @"1.3";
static NSString * const BUYClientCustomerAccessToken = @"X-Shopify-Customer-Access-Token";
@interface BUYClient (Internal)
- (NSURLSessionDataTask *)postRequestForURL:(NSURL *)url object:(id <BUYSerializable>)object completionHandler:(void (^)(NSDictionary *json, NSURLResponse *response, NSError *error))completionHandler;
- (NSURLSessionDataTask *)putRequestForURL:(NSURL *)url object:(id<BUYSerializable>)object completionHandler:(void (^)(NSDictionary *json, NSURLResponse *response, NSError *error))completionHandler;
- (NSURLSessionDataTask *)getRequestForURL:(NSURL *)url completionHandler:(void (^)(NSDictionary *json, NSURLResponse *response, NSError *error))completionHandler;
- (NSURLSessionDataTask *)getRequestForURL:(NSURL *)url completionHandler:(void (^)(NSDictionary *json, NSURLResponse *response, NSError *error))completionHandler;
- (NSURLSessionDataTask *)deleteRequestForURL:(NSURL *)url completionHandler:(void (^)(NSDictionary *json, NSURLResponse *response, NSError *error))completionHandler;
- (NSURLComponents *)URLComponentsForAPIPath:(NSString *)apiPath appendingPath:(NSString *)appendingPath queryItems:(NSDictionary*)queryItems;
- (NSURLSessionDataTask *)postRequestForURL:(NSURL *)url object:(id <BUYSerializable>)object completionHandler:(void (^)(NSDictionary *json, NSURLResponse *response, NSError *error))completionHandler;
- (NSURLSessionDataTask *)putRequestForURL:(NSURL *)url object:(id<BUYSerializable>)object completionHandler:(void (^)(NSDictionary *json, NSURLResponse *response, NSError *error))completionHandler;
- (NSURLSessionDataTask *)patchRequestForURL:(NSURL *)url object:(id <BUYSerializable>)object completionHandler:(void (^)(NSDictionary *json, NSURLResponse *response, NSError *error))completionHandler;
- (BUYStatus)statusForStatusCode:(NSUInteger)statusCode error:(NSError *)error;
- (NSError *)errorFromJSON:(NSDictionary *)json response:(NSURLResponse *)response;
@end
//
// BUYClient+Routing.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"
@interface BUYClient (Routing)
- (NSURL *)urlForAPI;
- (NSURL *)urlForApps;
- (NSURL *)urlForShop;
- (NSURL *)urlForProductListingsWithParameters:(NSDictionary *)parameters;
- (NSURL *)urlForCollectionListingsWithParameters:(NSDictionary *)parameters;
- (NSURL *)urlForCheckouts;
- (NSURL *)urlForCheckoutsWithToken:(NSString *)token;
- (NSURL *)urlForCheckoutsProcessingWithToken:(NSString *)token;
- (NSURL *)urlForCheckoutsCompletionWithToken:(NSString *)token;
- (NSURL *)urlForCheckoutsShippingRatesWithToken:(NSString *)token parameters:(NSDictionary *)parameters;
- (NSURL *)urlForCheckoutsUsingGiftCard;
- (NSURL *)urlForCheckoutsUsingGiftCardWithToken:(NSString *)token;
- (NSURL *)urlForCheckoutsUsingGiftCard:(NSNumber *)giftCardID token:(NSString *)token;
- (NSURL *)urlForCustomers;
- (NSURL *)urlForCustomersOrders;
- (NSURL *)urlForCustomersWithID:(NSString *)identifier;
- (NSURL *)urlForCustomersActivationWithID:(NSString *)identifier parameters:(NSDictionary *)parameters;
- (NSURL *)urlForCustomersToken;
- (NSURL *)urlForCustomersTokenRenewalWithID:(NSString *)customerID;
- (NSURL *)urlForCustomersPasswordRecovery;
- (NSURL *)urlForCustomersPasswordResetWithID:(NSString *)identifier parameters:(NSDictionary *)parameters;
@end
//
// BUYClient+Routing.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+Routing.h"
#pragma mark - NSURL (Private Routing) -
@implementation NSURL (PrivateRouting)
+ (instancetype)URLWithFormat:(NSString *)format, ...
{
va_list list;
va_start(list, format);
NSString *URLString = [[NSString alloc] initWithFormat:format arguments:list];
va_end(list);
return [[NSURL alloc] initWithString:URLString];
}
- (NSURL *)appendFormat:(NSString *)format, ...
{
va_list list;
va_start(list, format);
NSString *formattedString = [[NSString alloc] initWithFormat:format arguments:list];
va_end(list);
NSURL *trimmedURL = [self removeExtension];
if (formattedString.length > 0) {
return [trimmedURL URLByAppendingPathComponent:formattedString];
}
return trimmedURL;
}
- (NSURL *)appendPath:(NSString *)path
{
return [self appendFormat:path];
}
- (NSURL *)appendIdentifier:(NSNumber *)identifier
{
return [self appendFormat:@"%@", identifier];
}
- (NSURL *)appendParameters:(NSDictionary *)parameters
{
NSURLComponents *components = [NSURLComponents componentsWithURL:self resolvingAgainstBaseURL:NO];
NSMutableArray *queryItems = [NSMutableArray new];
[parameters enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
[queryItems addObject:[NSURLQueryItem queryItemWithName:key value:[NSString stringWithFormat:@"%@", value]]];
}];
[components setQueryItems:queryItems];
return components.URL;
}
- (NSURL *)appendExtension {
return [self URLByAppendingPathExtension:@"json"];
}
- (NSURL *)removeExtension {
return [self URLByDeletingPathExtension];
}
@end
#pragma mark - BUYClient (Routing) -
@implementation BUYClient (Routing)
#pragma mark - API -
- (NSURL *)urlForShopDomain
{
return [NSURL URLWithFormat:@"https://%@", self.shopDomain];
}
- (NSURL *)urlForAPI
{
return [[self urlForShopDomain] appendPath:@"/api"];
}
- (NSURL *)urlForApps
{
return [[self urlForAPI] appendFormat:@"/apps/%@", self.appId];
}
#pragma mark - Storefront -
- (NSURL *)urlForShop
{
return [[[self urlForShopDomain] appendPath:@"/meta"] appendExtension];
}
- (NSURL *)urlForProductListingsWithParameters:(NSDictionary *)parameters
{
return [[[[self urlForApps] appendPath:@"/product_listings"] appendExtension] appendParameters:parameters];
}
- (NSURL *)urlForCollectionListingsWithParameters:(NSDictionary *)parameters
{
return [[[[self urlForApps] appendPath:@"/collection_listings"] appendExtension] appendParameters:parameters];
}
#pragma mark - Checkout -
- (NSURL *)urlForCheckouts
{
return [[[self urlForAPI] appendPath:@"/checkouts"] appendExtension];
}
- (NSURL *)urlForCheckoutsWithToken:(NSString *)token
{
return [self _urlForCheckoutsAction:@"" withToken:token];
}
- (NSURL *)urlForCheckoutsProcessingWithToken:(NSString *)token
{
return [self _urlForCheckoutsAction:@"/processing" withToken:token];
}
- (NSURL *)urlForCheckoutsCompletionWithToken:(NSString *)token
{
return [self _urlForCheckoutsAction:@"/complete" withToken:token];
}
- (NSURL *)urlForCheckoutsShippingRatesWithToken:(NSString *)token parameters:(NSDictionary *)parameters
{
return [[self _urlForCheckoutsAction:@"/shipping_rates" withToken:token] appendParameters:parameters];
}
- (NSURL *)urlForCheckoutsUsingGiftCard
{
return [[[self urlForCheckouts] appendPath:@"/gift_cards"] appendExtension];
}
- (NSURL *)urlForCheckoutsUsingGiftCardWithToken:(NSString *)token
{
return [[[[self urlForCheckouts] appendPath:token] appendPath:@"/gift_cards"] appendExtension];
}
- (NSURL *)urlForCheckoutsUsingGiftCard:(NSNumber *)giftCardID token:(NSString *)token
{
return [[[self urlForCheckoutsUsingGiftCardWithToken:token] appendIdentifier:giftCardID] appendExtension];
}
#pragma mark - Customers -
- (NSURL *)urlForCustomers
{
return [[[self urlForAPI] appendPath:@"/customers"] appendExtension];
}
- (NSURL *)urlForCustomersOrders
{
return [[[self urlForCustomers] appendPath:@"/orders"] appendExtension];
}
- (NSURL *)urlForCustomersWithID:(NSString *)identifier
{
return [[[self urlForCustomers] appendPath:identifier] appendExtension];
}
- (NSURL *)urlForCustomersActivationWithID:(NSString *)identifier parameters:(NSDictionary *)parameters
{
return [[[[self urlForCustomersWithID:identifier] appendPath:@"/activate"] appendParameters:parameters] appendExtension];
}
- (NSURL *)urlForCustomersToken
{
return [[[self urlForCustomers] appendPath:@"/customer_token"] appendExtension];
}
- (NSURL *)urlForCustomersTokenRenewalWithID:(NSString *)customerID
{
return [[[self urlForCustomersWithID:customerID] appendPath:@"/customer_token/renew"] appendExtension];
}
- (NSURL *)urlForCustomersPasswordRecovery
{
return [[[self urlForCustomers] appendPath:@"/recover"] appendExtension];
}
- (NSURL *)urlForCustomersPasswordResetWithID:(NSString *)identifier parameters:(NSDictionary *)parameters
{
return [[[[self urlForCustomersWithID:identifier] appendPath:@"/reset"] appendExtension] appendParameters:parameters];
}
#pragma mark - Utilities -
- (NSURL *)_urlForCheckoutsAction:(NSString *)action withToken:(NSString *)token
{
return [[[[self urlForCheckouts] appendPath:token] appendPath:action] appendExtension];
}
@end
//
// 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;
/**
* The sort order for products in a collection
*/
typedef NS_ENUM(NSUInteger, BUYCollectionSort) {
/**
* Sort products by best selling using the order set in the shop's admin
*/
BUYCollectionSortCollectionDefault,
/**
* Sort products by best selling
*/
BUYCollectionSortBestSelling,
/**
* Sort products by title, ascending
*/
BUYCollectionSortTitleAscending,
/**
* Sort products by title, descending
*/
BUYCollectionSortTitleDescending,
/**
* Sort products by price (first variant), ascending
*/
BUYCollectionSortPriceAscending,
/**
* Sort products by price (first variant), descending
*/
BUYCollectionSortPriceDescending,
/**
* Sort products by creation date, ascending
*/
BUYCollectionSortCreatedAscending,
/**
* Sort products by creation date, descending
*/
BUYCollectionSortCreatedDescending
};
/**
* 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 urlForShop] 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 urlForProductListingsWithParameters:@{
@"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:BUYShopifyErrorDomain 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 urlForProductListingsWithParameters:@{
@"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:BUYShopifyErrorDomain 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 urlForCollectionListingsWithParameters:@{
@"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 urlForProductListingsWithParameters:@{
@"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
......@@ -26,62 +26,8 @@
@import Foundation;
@class BUYAccountCredentials;
@class BUYCart;
@class BUYCheckout;
@class BUYCreditCard;
@class BUYGiftCard;
@class BUYProduct;
@class BUYProductVariant;
@class BUYShop;
@class BUYCollection;
@class BUYOrder;
@class BUYModelManager;
@protocol BUYPaymentToken;
/**
* The sort order for products in a collection
*/
typedef NS_ENUM(NSUInteger, BUYCollectionSort) {
/**
* Sort products by best selling using the order set in the shop's admin
*/
BUYCollectionSortCollectionDefault,
/**
* Sort products by best selling
*/
BUYCollectionSortBestSelling,
/**
* Sort products by title, ascending
*/
BUYCollectionSortTitleAscending,
/**
* Sort products by title, descending
*/
BUYCollectionSortTitleDescending,
/**
* Sort products by price (first variant), ascending
*/
BUYCollectionSortPriceAscending,
/**
* Sort products by price (first variant), descending
*/
BUYCollectionSortPriceDescending,
/**
* Sort products by creation date, ascending
*/
BUYCollectionSortCreatedAscending,
/**
* Sort products by creation date, descending
*/
BUYCollectionSortCreatedDescending
};
extern NSString * _Nonnull const BUYVersionString;
extern NSString * _Nonnull const BUYClientCustomerAccessToken;
/**
* A BUYStatus is associated with the completion of an enqueued job on Shopify.
* BUYStatus is equal is HTTP status codes returned from the server
......@@ -114,104 +60,12 @@ typedef NS_ENUM(NSUInteger, BUYStatus) {
};
/**
* Return block containing a BUYCheckout, id<BUYPaymentToken> and/or an NSError
*
* @param checkout The returned BUYCheckout
* @param paymentToken An opaque payment token type that wraps necessary credentials for payment
* @param error Optional NSError
*/
typedef void (^BUYDataCreditCardBlock)(BUYCheckout * _Nullable checkout, id<BUYPaymentToken> _Nullable paymentToken, NSError * _Nullable error);
/**
* Return block containing a BUYCheckout and/or an NSError
*
* @param checkout The returned BUYCheckout
* @param error Optional NSError
*/
typedef void (^BUYDataCheckoutBlock)(BUYCheckout * _Nullable checkout, NSError * _Nullable error);
/**
* Return block containing a BUYCheckout, a BUYStatus and/or an NSError
*
* @param status A BUYStatus specifying the requested job's completion status
* @param error Optional NSError
*/
typedef void (^BUYDataCheckoutStatusBlock)(BUYStatus status, NSError * _Nullable error);
/**
* Return block containing BUYShippingRate objects, a BUYStatus and/or an NSError
*
* @param shippingRates Array of SHKShippingRates
* @param status A BUYStatus specifying the requested job's completion status
* @param error Optional NSError
*/
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
* @param error An optional NSError
*/
typedef void (^BUYDataImagesListBlock)(NSArray * _Nullable images, NSError * _Nullable error);
/**
* Return block containing a BUYGiftCard
*
* @param giftCard A BUYGiftCard
* @param error An optional NSError
*/
typedef void (^BUYDataGiftCardBlock)(BUYGiftCard * _Nullable giftCard, NSError * _Nullable error);
typedef void (^BUYDataStatusBlock)(BUYStatus status, NSError * _Nullable error);
/**
The BUYDataClient provides all requests needed to perform request on the Shopify Checkout API.
......@@ -287,245 +141,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
/**
* Builds a checkout on Shopify. The checkout object is used to prepare an order
*
* @param checkout BUYCheckout to create on Shopify
* @param block (^BUYDataCheckoutBlock)(BUYCheckout *checkout, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)createCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block;
/**
* Builds a checkout on Shopify using a Cart Token from an existing cart on your Shopify store's storefront.
* The BUYCheckout object is used to prepare an order.
*
* @param cartToken Cart Token associated with an existing BUYCheckout on Shopify
* @param block (^BUYDataCheckoutBlock)(BUYCheckout *checkout, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)createCheckoutWithCartToken:(NSString *)cartToken completion:(BUYDataCheckoutBlock)block;
/**
* Applies a gift card code to the checkout.
*
* @param giftCardCode The gift card code to apply on an existing checkout on Shopify. Note: This is not the same as the gift card identifier.
* @param checkout An existing BUYCheckout on Shopify
* @param block (^BUYDataCheckoutBlock)(BUYCheckout *checkout, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)applyGiftCardWithCode:(NSString *)giftCardCode toCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block;
/**
* Removes a gift card from the checkout.
*
* @param giftCardCode The BUYGiftCard identifier to remove on an existing checkout on Shopify.
* @param checkout An existing BUYCheckout on Shopify
* @param block (^BUYDataCheckoutBlock)(BUYCheckout *checkout, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)removeGiftCard:(BUYGiftCard *)giftCard fromCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block;
/**
* Retrieves an updated version of a BUYCheckout from Shopify.
*
* Note: There's no guarantee that the BUYCheckout returned will be the same as the one that is passed in.
* We recommended using the BUYCheckout returned in the block.
*
* @param checkout The BUYCheckout to retrieve (updated) from Shopify
* @param block (^BUYDataCheckoutBlock)(BUYCheckout *checkout, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block;
/**
* Updates a given BUYCheckout on Shopify.
*
* Note: There's no guarantee that the BUYCheckout returned will be the same as the one that is passed in.
* We recommended using the BUYCheckout returned in the block.
*
* Note: A BUYCheckout object with an `orderId` is a completed checkout.
*
* @param checkout The BUYCheckout to updated on Shopify
* @param block (^BUYDataCheckoutBlock)(BUYCheckout *checkout, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)updateCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block;
/**
* Finalizes the BUYCheckout and charges the payment provider (ex: Credit Card, Apple Pay, etc).
* This enqueues a completion job on Shopify and returns immediately.
* You must get the job's status by calling checkCompletionStatusOfCheckout:block
*
* Note: There's no guarantee that the BUYCheckout returned will be the same as the one that is passed in.
* We recommended using the BUYCheckout returned in the block.
*
* @param checkout The BUYCheckout to complete
* @param paymentToken Opaque payment token object. May be nil if the total checkout amount is equal to $0.00
* @param block (^BUYDataCheckoutBlock)(BUYCheckout *checkout, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)completeCheckout:(BUYCheckout *)checkout paymentToken:(_Nullable id<BUYPaymentToken>)paymentToken completion:(BUYDataCheckoutBlock)block;
/**
* Retrieve the status of a BUYCheckout. This checks the status of the current payment processing job for the provided checkout.
* Once the job is complete (status == BUYStatusComplete), you can retrieve the completed order by calling `getCheckout:completion`
*
* @param checkout The BUYCheckout to retrieve completion status for
* @param block (^BUYDataCheckoutStatusBlock)(BUYCheckout *checkout, BUYStatus status, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getCompletionStatusOfCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutStatusBlock)block;
/**
* Retrieve the status of a checkout given a URL obtained in the UIApplicationDelegate method `application:sourceApplication:annotation`
*
* @param url The URL resource used to open the application
* @param block (^BUYDataCheckoutStatusBlock)(BUYCheckout *checkout, BUYStatus status, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getCompletionStatusOfCheckoutURL:(NSURL *)url completion:(BUYDataCheckoutStatusBlock)block;
#pragma mark - Shipping Rates
/**
* Retrieves a list of applicable shipping rates for a given BUYCheckout.
* Add the preferred/selected BUYShippingRate to BUYCheckout, then update BUYCheckout
*
* @param checkout The BUYCheckout to retrieve shipping rates for
* @param block (^BUYDataShippingRatesBlock)(NSArray *shippingRates, BUYStatus status, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getShippingRatesForCheckout:(BUYCheckout *)checkout completion:(BUYDataShippingRatesBlock)block;
#pragma mark - Payment Management
/**
* Prepares a credit card for usage during the checkout process. This sends it to Shopify's secure servers.
* Note: Storing the token does not charge the associated card (credit or otherwise).
* The card will be charged upon finalizing the checkout (`completeCheckout:completion:`)
*
* You MUST call `completeCheckout:completion:` after this call and receiving a `paymentSessionId`.
* The `paymentSessionId` on the `BUYCheckout` object is not persisted on `updateCheckout:completion:` calls.
*
* @param creditCard BUYCreditCard to prepare for usage
* @param checkout The BUYCheckout associated to the purchase.
* The `billingAddress` stored on the BUYCheckout object is optional and recommended and
* used (if provided) to help with fraud checking.
* @param block (^BUYDataCreditCardBlock)(BUYCheckout *checkout, NSString *paymentSessionId, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)storeCreditCard:(BUYCreditCard *)creditCard checkout:(BUYCheckout *)checkout completion:(BUYDataCreditCardBlock)block;
/**
* Convenience method to release all product inventory reservations by setting its
* `reservationTime` to `@0` and calls `updateCheckout:completion`. We recommend creating
* a new BUYCheckout object from a BUYCart for further API calls.
*
* @param checkout The BUYCheckout to expire
* @param block (^BUYDataCheckoutBlock)(BUYCheckout *checkout, NSError *error);
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)removeProductReservationsFromCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block;
#pragma mark - Deprecated methods
/**
......@@ -535,5 +150,6 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)enableApplePayWithMerchantId:(NSString *)merchantId NS_DEPRECATED_IOS(8_0, 9_0, "Set the merchantId on a `BUYViewController` subclass instead");
@end
NS_ASSUME_NONNULL_END
@end
......@@ -24,48 +24,11 @@
// THE SOFTWARE.
//
#import "BUYClient_Internal.h"
#import "BUYClient+Internal.h"
#import "BUYAssert.h"
#import "BUYAddress.h"
#import "BUYCart.h"
#import "BUYCheckout.h"
#import "BUYCreditCard.h"
#import "BUYCreditCardToken.h"
#import "BUYCollection.h"
#import "BUYError.h"
#import "BUYGiftCard.h"
#import "BUYModelManager.h"
#import "BUYOrder.h"
#import "BUYProduct.h"
#import "BUYPaymentToken.h"
#import "BUYShippingRate.h"
#import "BUYShop.h"
#import "BUYShopifyErrorCodes.h"
#import "NSDecimalNumber+BUYAdditions.h"
#import "NSDictionary+BUYAdditions.h"
#import "NSURLComponents+BUYAdditions.h"
#define kGET @"GET"
#define kPOST @"POST"
#define kPATCH @"PATCH"
#define kPUT @"PUT"
#define kDELETE @"DELETE"
#define kJSONType @"application/json"
#define kMinSuccessfulStatusCode 200
#define kMaxSuccessfulStatusCode 299
#define BUYAssertCheckout(checkout) BUYAssert([(checkout) hasToken], @"Checkout assertion failed. Checkout must have a valid token associated with it.")
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";
static NSString * const BUYClientJSONMimeType = @"application/json";
@interface BUYClient () <NSURLSessionDelegate>
......@@ -107,419 +70,27 @@ NSString *const BUYClientCustomerAccessToken = @"X-Shopify-Customer-Access-Token
return self;
}
#pragma mark - Accessors -
- (NSURLSession *)urlSession
{
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
config.HTTPAdditionalHeaders = @{@"User-Agent": [NSString stringWithFormat:@"Mobile Buy SDK iOS/%@/%@", BUYVersionString, bundleIdentifier]};
config.HTTPAdditionalHeaders = @{@"User-Agent": [NSString stringWithFormat:@"Mobile Buy SDK iOS/%@/%@", BUYClientVersionString, bundleIdentifier]};
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
{
NSURLComponents *shopComponents = [self URLComponentsForShop];
return [self getRequestForURL:shopComponents.URL 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
{
NSURLComponents *components = [self URLComponentsForChannelsAppendingPath:kBUYClientPathProductPublications
queryItems:@{
@"limit" : @(self.pageSize).stringValue,
@"page" : @(page).stringValue,
}];
return [self getRequestForURL:components.URL 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.");
NSURLComponents *components = [self URLComponentsForChannelsAppendingPath:kBUYClientPathProductPublications
queryItems:@{
@"product_ids" : [productIds componentsJoinedByString:@","]
}];
return [self getRequestForURL:components.URL 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
{
NSURLComponents *components = [self URLComponentsForChannelsAppendingPath:kBUYClientPathCollectionPublications
queryItems:@{
@"limit" : @(self.pageSize).stringValue,
@"page" : @(page).stringValue,
}];
return [self getRequestForURL:components.URL 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.");
NSURLComponents *components = [self URLComponentsForChannelsAppendingPath:kBUYClientPathProductPublications
queryItems:@{
@"collection_id" : collectionId.stringValue,
@"limit" : @(self.pageSize).stringValue,
@"page" : @(page).stringValue,
@"sort_by" : [BUYCollection sortOrderParameterForCollectionSort:sortOrder]
}];
return [self getRequestForURL:components.URL 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 - URL Components
- (NSURLComponents *)URLComponentsForChannelsAppendingPath:(NSString *)appendingPath queryItems:(NSDictionary*)queryItems
{
return [self URLComponentsForAPIPath:[NSString stringWithFormat:@"apps/%@", self.appId] appendingPath:appendingPath queryItems:queryItems];
}
- (NSURLComponents *)URLComponentsForCheckoutsAppendingPath:(NSString *)appendingPath checkoutToken:(NSString *)checkoutToken queryItems:(NSDictionary*)queryItems
{
NSString *apiPath = @"checkouts";
if (checkoutToken) {
apiPath = [NSString pathWithComponents:@[apiPath, checkoutToken]];
}
return [self URLComponentsForAPIPath:[apiPath copy] appendingPath:appendingPath queryItems:queryItems];
}
- (NSURLComponents *)URLComponentsForAPIPath:(NSString *)apiPath appendingPath:(NSString *)appendingPath queryItems:(NSDictionary*)queryItems
{
NSMutableArray *pathComponents = [NSMutableArray array];
[pathComponents addObject:@"/api"];
[pathComponents addObject:apiPath];
if (appendingPath) {
[pathComponents addObject:appendingPath];
}
return [self URLComponentsForPathComponents:pathComponents queryItems:queryItems];
}
- (NSURLComponents *)URLComponentsForShop
{
return [self URLComponentsForPathComponents:@[@"/meta"] queryItems:nil];
}
- (NSURLComponents *)URLComponentsForPathComponents:(NSArray*)pathComponents queryItems:(NSDictionary*)queryItems
{
NSURLComponents *components = [[NSURLComponents alloc] init];
components.scheme = @"https";
components.host = self.shopDomain;
components.path = [[NSString pathWithComponents:pathComponents] stringByAppendingPathExtension:@"json"];
[components setQueryItemsWithDictionary:queryItems];
return components;
}
#pragma mark - Checkout
- (void)handleCheckoutResponse:(NSDictionary *)json error:(NSError *)error block:(BUYDataCheckoutBlock)block
{
BUYCheckout *checkout = nil;
if (!error) {
checkout = [self.modelManager insertCheckoutWithJSONDictionary:json[@"checkout"]];
}
block(checkout, error);
}
- (void)configureCheckout:(BUYCheckout *)checkout
{
checkout.marketingAttribution = @{@"medium": @"iOS", @"source": self.applicationName};
checkout.sourceName = @"mobile_app";
if (self.urlScheme || checkout.webReturnToURL) {
checkout.webReturnToURL = checkout.webReturnToURL ?: [NSURL URLWithString:self.urlScheme];
checkout.webReturnToLabel = checkout.webReturnToLabel ?: [@"Return to " stringByAppendingString:self.applicationName];
}
}
- (NSURLSessionDataTask *)createCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block
{
BUYAssert(checkout, @"Failed to create checkout. Invalid checkout object.");
// Inject channel and marketing attributions
[self configureCheckout:checkout];
NSDictionary *json = [checkout jsonDictionaryForCheckout];
return [self postCheckout:json completion:block];
}
- (NSURLSessionDataTask *)createCheckoutWithCartToken:(NSString *)cartToken completion:(BUYDataCheckoutBlock)block
{
BUYAssert(cartToken, @"Failed to create checkout. Invalid cart token");
BUYCheckout *checkout = [self.modelManager checkoutwithCartToken:cartToken];
[self configureCheckout:checkout];
NSDictionary *json = [checkout jsonDictionaryForCheckout];
return [self postCheckout:json completion:block];
}
- (NSURLSessionDataTask *)postCheckout:(NSDictionary *)checkoutJSON completion:(BUYDataCheckoutBlock)block
{
NSURLComponents *components = [self URLComponentsForCheckoutsAppendingPath:nil checkoutToken:nil queryItems:nil];
return [self postRequestForURL:components.URL object:checkoutJSON completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
[self handleCheckoutResponse:json error:error block:block];
}];
}
- (NSURLSessionDataTask *)applyGiftCardWithCode:(NSString *)giftCardCode toCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block
{
BUYAssertCheckout(checkout);
BUYAssert(giftCardCode.length > 0, @"Failed to apply gift card code. Invalid gift card code.");
BUYGiftCard *giftCard = [self.modelManager giftCardWithCode:giftCardCode];
NSURLComponents *components = [self URLComponentsForCheckoutsAppendingPath:@"gift_cards"
checkoutToken:checkout.token
queryItems:nil];
return [self postRequestForURL:components.URL object:giftCard completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
if (json && !error) {
[self updateCheckout:checkout withGiftCardDictionary:json[@"gift_card"] addingGiftCard:YES];
}
block(checkout, error);
}];
}
- (NSURLSessionDataTask *)removeGiftCard:(BUYGiftCard *)giftCard fromCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block
{
BUYAssertCheckout(checkout);
BUYAssert(giftCard.identifier, @"Failed to remove gift card. Gift card must have a valid identifier.");
NSURLComponents *components = [self URLComponentsForCheckoutsAppendingPath:[NSString stringWithFormat:@"gift_cards/%@", giftCard.identifier]
checkoutToken:checkout.token
queryItems:nil];
return [self deleteRequestForURL:components.URL completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
if (!error) {
[self updateCheckout:checkout withGiftCardDictionary:json[@"gift_card"] addingGiftCard:NO];
}
block(checkout, error);
}];
}
- (void)updateCheckout:(BUYCheckout *)checkout withGiftCardDictionary:(NSDictionary *)giftCardDictionary addingGiftCard:(BOOL)addingGiftCard
{
if (addingGiftCard) {
BUYGiftCard *giftCard = [self.modelManager insertGiftCardWithJSONDictionary:giftCardDictionary];
[checkout.giftCardsSet addObject:giftCard];
} else {
[checkout removeGiftCardWithIdentifier:giftCardDictionary[@"id"]];
}
checkout.paymentDue = [NSDecimalNumber buy_decimalNumberFromJSON:giftCardDictionary[@"checkout"][@"payment_due"]];
// Marking the checkout as clean. The properties we have updated above we don't need to re-sync with Shopify.
// There's also an issue with gift cards where syncing the gift card JSON won't work since the update endpoint
// doesn't accept the gift card without a gift card code (which we do not have).
[checkout markAsClean];
}
- (NSURLSessionDataTask *)getCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block
{
BUYAssertCheckout(checkout);
NSURLComponents *components = [self URLComponentsForCheckoutsAppendingPath:nil
checkoutToken:checkout.token
queryItems:nil];
return [self getRequestForURL:components.URL completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
[self handleCheckoutResponse:json error:error block:block];
}];
}
- (NSURLSessionDataTask *)updateCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block
{
BUYAssertCheckout(checkout);
NSURLComponents *components = [self URLComponentsForCheckoutsAppendingPath:nil
checkoutToken:checkout.token
queryItems:nil];
return [self patchRequestForURL:components.URL object:checkout completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
[self handleCheckoutResponse:json error:error block:block];
}];
}
- (NSURLSessionDataTask*)completeCheckout:(BUYCheckout *)checkout paymentToken:(id<BUYPaymentToken>)paymentToken completion:(BUYDataCheckoutBlock)block
{
BUYAssertCheckout(checkout);
BOOL isFree = (checkout.paymentDue && checkout.paymentDue.floatValue == 0);
BUYAssert(paymentToken || isFree, @"Failed to complete checkout. Checkout must have a payment token or have a payment value equal to $0.00");
NSURLComponents *components = [self URLComponentsForCheckoutsAppendingPath:@"complete"
checkoutToken:checkout.token
queryItems:nil];
return [self postRequestForURL:components.URL object:[paymentToken JSONDictionary] completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
[self handleCheckoutResponse:json error:error block:block];
}];
}
- (NSURLSessionDataTask *)getCompletionStatusOfCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutStatusBlock)block
{
BUYAssertCheckout(checkout);
return [self getCompletionStatusOfCheckoutToken:checkout.token completion:block];
}
- (NSURLSessionDataTask *)getCompletionStatusOfCheckoutURL:(NSURL *)url completion:(BUYDataCheckoutStatusBlock)block
{
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
NSString *token = nil;
for (NSURLQueryItem *item in components.queryItems) {
if ([item.name isEqualToString:@"checkout[token]"]) {
token = item.value;
break;
}
}
BUYAssert(token, @"Failed to get completion status of checkout. Checkout URL must have a valid token associated with it.");
return [self getCompletionStatusOfCheckoutToken:token completion:block];
}
- (NSURLSessionDataTask *)getCompletionStatusOfCheckoutToken:(NSString *)token completion:(BUYDataCheckoutStatusBlock)block
{
NSURLComponents *components = [self URLComponentsForCheckoutsAppendingPath:@"processing"
checkoutToken:token
queryItems:nil];
return [self getRequestForURL:components.URL completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
block([BUYClient statusForStatusCode:statusCode error:error], error);
}];
}
#pragma mark - Shipping Rates
- (NSURLSessionDataTask *)getShippingRatesForCheckout:(BUYCheckout *)checkout completion:(BUYDataShippingRatesBlock)block
{
BUYAssertCheckout(checkout);
NSURLComponents *components = [self URLComponentsForCheckoutsAppendingPath:@"shipping_rates" checkoutToken:checkout.token queryItems:@{ @"checkout" : @"" }];
return [self getRequestForURL:components.URL completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSArray *shippingRates = nil;
if (json && !error) {
shippingRates = [self.modelManager insertShippingRatesWithJSONArray:json[@"shipping_rates"]];
}
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
block(shippingRates, [BUYClient statusForStatusCode:statusCode error:error], error);
}];
}
#pragma mark - Payments
- (NSURLSessionDataTask *)storeCreditCard:(BUYCreditCard *)creditCard checkout:(BUYCheckout *)checkout completion:(BUYDataCreditCardBlock)completion
{
BUYAssertCheckout(checkout);
BUYAssert(creditCard, @"Failed to store credit card. No credit card provided.");
NSMutableDictionary *json = [[NSMutableDictionary alloc] init];
json[@"token"] = checkout.token;
json[@"credit_card"] = [creditCard jsonDictionaryForCheckout];
if (checkout.billingAddress) {
json[@"billing_address"] = [checkout.billingAddress jsonDictionaryForCheckout];
}
return [self postRequestForURL:checkout.paymentURL object:@{ @"checkout" : json } completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
id<BUYPaymentToken> token = nil;
if (!error) {
token = [[BUYCreditCardToken alloc] initWithPaymentSessionID:json[@"id"]];
}
completion(checkout, token, error);
}];
}
- (NSURLSessionDataTask *)removeProductReservationsFromCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)block
{
BUYAssertCheckout(checkout);
checkout.reservationTime = @0;
return [self updateCheckout:checkout completion:block];
}
#pragma mark - Error
+ (BUYStatus)statusForStatusCode:(NSUInteger)statusCode error:(NSError *)error
- (BUYStatus)statusForStatusCode:(NSUInteger)statusCode error:(NSError *)error
{
BUYStatus status = BUYStatusUnknown;
if (statusCode == BUYStatusPreconditionFailed) {
......@@ -543,8 +114,8 @@ NSString *const BUYClientCustomerAccessToken = @"X-Shopify-Customer-Access-Token
- (NSError *)errorFromJSON:(NSDictionary *)json response:(NSURLResponse *)response
{
NSInteger statusCode = [((NSHTTPURLResponse *) response) statusCode];
if (statusCode < kMinSuccessfulStatusCode || statusCode > kMaxSuccessfulStatusCode) {
return [[NSError alloc] initWithDomain:kShopifyError code:statusCode userInfo:json];
if ((int)(statusCode / 100.0) != 2) { // If not a 2xx response code
return [[NSError alloc] initWithDomain:BUYShopifyErrorDomain code:statusCode userInfo:json];
}
return nil;
}
......@@ -553,27 +124,27 @@ NSString *const BUYClientCustomerAccessToken = @"X-Shopify-Customer-Access-Token
- (NSURLSessionDataTask *)getRequestForURL:(NSURL *)url completionHandler:(void (^)(NSDictionary *json, NSURLResponse *response, NSError *error))completionHandler
{
return [self requestForURL:url method:kGET object:nil completionHandler:completionHandler];
return [self requestForURL:url method:@"GET" object:nil completionHandler:completionHandler];
}
- (NSURLSessionDataTask *)postRequestForURL:(NSURL *)url object:(id <BUYSerializable>)object completionHandler:(void (^)(NSDictionary *json, NSURLResponse *response, NSError *error))completionHandler
{
return [self requestForURL:url method:kPOST object:object completionHandler:completionHandler];
return [self requestForURL:url method:@"POST" object:object completionHandler:completionHandler];
}
- (NSURLSessionDataTask *)putRequestForURL:(NSURL *)url object:(id<BUYSerializable>)object completionHandler:(void (^)(NSDictionary *json, NSURLResponse *response, NSError *error))completionHandler
{
return [self requestForURL:url method:kPUT object:object completionHandler:completionHandler];
return [self requestForURL:url method:@"PUT" object:object completionHandler:completionHandler];
}
- (NSURLSessionDataTask *)patchRequestForURL:(NSURL *)url object:(id <BUYSerializable>)object completionHandler:(void (^)(NSDictionary *json, NSURLResponse *response, NSError *error))completionHandler
{
return [self requestForURL:url method:kPATCH object:object completionHandler:completionHandler];
return [self requestForURL:url method:@"PATCH" object:object completionHandler:completionHandler];
}
- (NSURLSessionDataTask *)deleteRequestForURL:(NSURL *)url completionHandler:(void (^)(NSDictionary *json, NSURLResponse *response, NSError *error))completionHandler
{
return [self requestForURL:url method:kDELETE object:nil completionHandler:completionHandler];
return [self requestForURL:url method:@"DELETE" object:nil completionHandler:completionHandler];
}
#pragma mark - Generic Requests
......@@ -597,8 +168,8 @@ NSString *const BUYClientCustomerAccessToken = @"X-Shopify-Customer-Access-Token
}
[request addValue:[self authorizationHeader] forHTTPHeaderField:@"Authorization"];
[request addValue:kJSONType forHTTPHeaderField:@"Content-Type"];
[request addValue:kJSONType forHTTPHeaderField:@"Accept"];
[request addValue:BUYClientJSONMimeType forHTTPHeaderField:@"Content-Type"];
[request addValue:BUYClientJSONMimeType forHTTPHeaderField:@"Accept"];
if (self.customerToken) {
[request addValue:self.customerToken forHTTPHeaderField:BUYClientCustomerAccessToken];
......
//
// BUYAuthenticatedResponse.h
// Mobile Buy SDK
//
// Created by Shopify.
// Copyright (c) 2016 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 <Foundation/Foundation.h>
@interface BUYAuthenticatedResponse : NSObject
@property (nonatomic, copy, readonly) NSString *accessToken;
@property (nonatomic, copy, readonly) NSDate *expiry;
@property (nonatomic, copy, readonly) NSString *customerID;
+ (BUYAuthenticatedResponse *)responseWithJSON:(NSDictionary *)json;
@end
//
// BUYAuthenticatedResponse.m
// Mobile Buy SDK
//
// Created by Shopify.
// Copyright (c) 2016 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 "BUYAuthenticatedResponse.h"
#import "NSDateFormatter+BUYAdditions.h"
@implementation BUYAuthenticatedResponse
+ (BUYAuthenticatedResponse *)responseWithJSON:(NSDictionary *)json
{
return [[[self class] alloc] initWithJSON:json];
}
- (instancetype)initWithJSON:(NSDictionary *)json
{
self = [super init];
if (self) {
NSDateFormatter *formatter = [NSDateFormatter dateFormatterForPublications];
NSDictionary *access = json[@"customer_access_token"];
_accessToken = access[@"access_token"];
_expiry = [formatter dateFromString:access[@"expires_at"]];
_customerID = [NSString stringWithFormat:@"%@", access[@"customer_id"]];
}
return self;
}
@end
......@@ -25,7 +25,7 @@
//
#import <Buy/_BUYCollection.h>
#import "BUYClient.h"
#import "BUYClient+Storefront.h"
@interface BUYCollection : _BUYCollection {}
......
......@@ -31,7 +31,7 @@
#import "BUYApplePayHelpers.h"
#import "BUYApplePayAdditions.h"
#import "BUYShop.h"
#import "BUYClient+Checkout.h"
#import "BUYClient+CheckoutHelpers.h"
NSString *const BUYApplePayPaymentProviderId = @"BUYApplePayPaymentProviderId";
......
......@@ -28,7 +28,7 @@
NS_ASSUME_NONNULL_BEGIN
@interface BUYClient (Checkout)
@interface BUYClient (CheckoutHelpers)
- (NSURLSessionDataTask *)handleCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)completion;
......
......@@ -24,9 +24,9 @@
// THE SOFTWARE.
//
#import "BUYClient+Checkout.h"
#import "BUYClient+CheckoutHelpers.h"
@implementation BUYClient (Checkout)
@implementation BUYClient (CheckoutHelpers)
- (NSURLSessionDataTask *)handleCheckout:(BUYCheckout *)checkout completion:(BUYDataCheckoutBlock)completion
{
......
......@@ -26,7 +26,7 @@
#import "BUYWebCheckoutPaymentProvider.h"
#import "BUYCheckout.h"
#import "BUYClient+Checkout.h"
#import "BUYClient+CheckoutHelpers.h"
@import SafariServices;
......
......@@ -36,6 +36,11 @@
#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 "BUYClient+Checkout.h"
#import "BUYCollection.h"
#import "BUYCreditCard.h"
#import "BUYCustomer.h"
......
......@@ -27,7 +27,8 @@
#import "BUYApplePayHelpers.h"
#import "BUYAddress.h"
#import "BUYApplePayAdditions.h"
#import "BUYClient.h"
#import "BUYClient+Checkout.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"
#import "BUYClient+Checkout.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 "BUYClient+Checkout.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