Commit 3ab827c2 by Brent Gulanowski

Merge branch 'integration/sdk-2.0' into task/generated-models

# Conflicts:
#	Mobile Buy SDK/Mobile Buy SDK Tests/BUYClientTest.m
#	Mobile Buy SDK/Mobile Buy SDK/Data/BUYClient.m
#	Mobile Buy SDK/Mobile Buy SDK/Data/BUYClient_Internal.h
#	Mobile Buy SDK/Mobile Buy SDK/Models/BUYObjectProtocol.h
#	Mobile Buy SDK/Mobile Buy SDK/Models/Transient/BUYAddress.m
#	Mobile Buy SDK/Mobile Buy SDK/Models/Transient/BUYCheckout.h
parents e03e9acd 93460a07
...@@ -31,6 +31,11 @@ ...@@ -31,6 +31,11 @@
#import "BUYTestConstants.h" #import "BUYTestConstants.h"
#import "BUYClientTestBase.h" #import "BUYClientTestBase.h"
#import "NSURLComponents+BUYAdditions.h" #import "NSURLComponents+BUYAdditions.h"
#import "BUYShopifyErrorCodes.h"
#import "BUYAccountCredentials.h"
#import "BUYClient+Customers.h"
NSString * const BUYFakeCustomerToken = @"dsfasdgafdg";
@interface BUYClient () @interface BUYClient ()
...@@ -321,4 +326,121 @@ ...@@ -321,4 +326,121 @@
return [self.client.modelManager insertCartWithJSONDictionary:nil]; return [self.client.modelManager insertCartWithJSONDictionary:nil];
} }
#pragma mark - Customer Tests -
- (void)testCustomerCreationURL
{
BUYAccountCredentialItem *firstName = [BUYAccountCredentialItem itemWithKey:@"first_name" value:@"michael"];
BUYAccountCredentialItem *lastName = [BUYAccountCredentialItem itemWithKey:@"last_name" value:@"scott"];
BUYAccountCredentialItem *email = [BUYAccountCredentialItem itemWithKey:@"email" value:@"fake@example.com"];
BUYAccountCredentialItem *password = [BUYAccountCredentialItem itemWithKey:@"password" value:@"password"];
BUYAccountCredentialItem *passwordConfirmation = [BUYAccountCredentialItem itemWithKey:@"password_confirmation" value:@"password"];
BUYAccountCredentials *credentials = [BUYAccountCredentials credentialsWithItems:@[firstName, lastName, email, password, passwordConfirmation]];
NSURLSessionDataTask *task = [self.client createCustomerWithCredentials:credentials callback:nil];
XCTAssertEqualObjects(task.originalRequest.URL.scheme, @"https");
XCTAssertEqualObjects(task.originalRequest.URL.path, @"/api/customers.json");
XCTAssertEqualObjects(task.originalRequest.HTTPMethod, @"POST");
NSError *error = nil;
NSDictionary *payload = [NSJSONSerialization JSONObjectWithData:task.originalRequest.HTTPBody options:0 error:&error];
XCTAssertNil(error);
NSDictionary *dict = @{@"customer": @{
@"first_name": firstName.value,
@"last_name": lastName.value,
@"email": email.value,
@"password": password.value,
@"password_confirmation": passwordConfirmation.value}};
XCTAssertEqualObjects(payload, dict);
}
- (void)testLoginCustomerURL
{
BUYAccountCredentialItem *email = [BUYAccountCredentialItem itemWithKey:@"email" value:@"fake@example.com"];
BUYAccountCredentialItem *password = [BUYAccountCredentialItem itemWithKey:@"password" value:@"password"];
BUYAccountCredentials *credentials = [BUYAccountCredentials credentialsWithItems:@[email, password]];
NSURLSessionDataTask *task = [self.client loginCustomerWithCredentials:credentials callback:nil];
XCTAssertEqualObjects(task.originalRequest.URL.scheme, @"https");
XCTAssertEqualObjects(task.originalRequest.URL.path, @"/api/customers/customer_token.json");
XCTAssertEqualObjects(task.originalRequest.HTTPMethod, @"POST");
NSError *error = nil;
NSDictionary *payload = [NSJSONSerialization JSONObjectWithData:task.originalRequest.HTTPBody options:0 error:&error];
XCTAssertNil(error);
NSDictionary *dict = @{@"customer": @{@"email": email.value, @"password": password.value}};
XCTAssertEqualObjects(payload, dict);
}
- (void)testGetCustomerURL
{
NSURLSessionDataTask *task = [self.client getCustomerWithID:nil callback:nil];
XCTAssertEqualObjects(task.originalRequest.URL.scheme, @"https");
XCTAssertEqualObjects(task.originalRequest.URL.path, @"/api/customers.json");
XCTAssertEqualObjects(task.originalRequest.HTTPMethod, @"GET");
XCTAssertEqualObjects(self.client.customerToken, task.originalRequest.allHTTPHeaderFields[BUYClientCustomerAccessToken]);
}
- (void)testGetOrdersForCustomerURL
{
NSURLSessionDataTask *task = [self.client getOrdersForCustomerWithCallback:nil];
XCTAssertEqualObjects(task.originalRequest.URL.scheme, @"https");
XCTAssertEqualObjects(task.originalRequest.URL.path, @"/api/customers/orders.json");
XCTAssertEqualObjects(task.originalRequest.HTTPMethod, @"GET");
XCTAssertEqualObjects(self.client.customerToken, task.originalRequest.allHTTPHeaderFields[BUYClientCustomerAccessToken]);
}
- (void)testCustomerRecovery
{
NSString *email = @"fake@example.com";
NSURLSessionDataTask *task = [self.client recoverPasswordForCustomer:email callback:nil];
XCTAssertEqualObjects(task.originalRequest.URL.scheme, @"https");
XCTAssertEqualObjects(task.originalRequest.URL.path, @"/api/customers/recover.json");
XCTAssertEqualObjects(task.originalRequest.HTTPMethod, @"POST");
NSError *error = nil;
NSDictionary *payload = [NSJSONSerialization JSONObjectWithData:task.originalRequest.HTTPBody options:0 error:&error];
XCTAssertNil(error);
NSDictionary *dict = @{@"email": email};
XCTAssertEqualObjects(payload, dict);
}
- (void)testTokenRenewal
{
self.client.customerToken = nil;
NSURLSessionDataTask *task = [self.client renewCustomerTokenWithID:nil callback:^(NSString *token, NSError *error) {}];
XCTAssertNil(task); // task should be nil if no customer token was set on the client
self.client.customerToken = BUYFakeCustomerToken;
task = [self.client renewCustomerTokenWithID:@"1" callback:nil];
XCTAssertEqualObjects(task.originalRequest.URL.scheme, @"https");
XCTAssertEqualObjects(task.originalRequest.URL.path, @"/api/customers/1/customer_token/renew.json");
XCTAssertEqualObjects(task.originalRequest.HTTPMethod, @"PUT");
}
- (void)testCustomerActivation
{
BUYAccountCredentialItem *passwordItem = [BUYAccountCredentialItem itemWithKey:@"password" value:@"12345"];
BUYAccountCredentialItem *passwordConfItem = [BUYAccountCredentialItem itemWithKey:@"password_confirmation" value:@"12345"];
BUYAccountCredentials *credentials = [BUYAccountCredentials credentialsWithItems:@[passwordItem, passwordConfItem]];
NSString *customerID = @"12345";
NSString *customerToken = @"12345";
NSURLSessionDataTask *task = [self.client activateCustomerWithCredentials:credentials customerID:customerID customerToken:customerToken callback:nil];
XCTAssertEqualObjects(task.originalRequest.URL.scheme, @"https");
XCTAssertEqualObjects(task.originalRequest.URL.path, @"/api/customers/12345/activate.json");
XCTAssertEqualObjects(task.originalRequest.HTTPMethod, @"PUT");
}
@end @end
...@@ -30,6 +30,8 @@ ...@@ -30,6 +30,8 @@
extern NSString * const BUYShopDomain_Placeholder; extern NSString * const BUYShopDomain_Placeholder;
extern NSString * const BUYAPIKey_Placeholder; extern NSString * const BUYAPIKey_Placeholder;
extern NSString * const BUYAppId_Placeholder; extern NSString * const BUYAppId_Placeholder;
extern NSString * const BUYChannelId_Placeholder;
extern NSString * const BUYFakeCustomerToken;
@interface BUYClientTestBase : XCTestCase @interface BUYClientTestBase : XCTestCase
...@@ -37,6 +39,8 @@ extern NSString * const BUYAppId_Placeholder; ...@@ -37,6 +39,8 @@ extern NSString * const BUYAppId_Placeholder;
@property (nonatomic, strong) NSString *apiKey; @property (nonatomic, strong) NSString *apiKey;
@property (nonatomic, strong) NSString *appId; @property (nonatomic, strong) NSString *appId;
@property (nonatomic, strong) NSString *merchantId; @property (nonatomic, strong) NSString *merchantId;
@property (nonatomic, strong) NSString *customerEmail;
@property (nonatomic, strong) NSString *customerPassword;
@property (nonatomic, strong) NSString *giftCardCode; @property (nonatomic, strong) NSString *giftCardCode;
@property (nonatomic, strong) NSString *giftCardCode2; @property (nonatomic, strong) NSString *giftCardCode2;
@property (nonatomic, strong) NSString *giftCardCode3; @property (nonatomic, strong) NSString *giftCardCode3;
...@@ -45,7 +49,11 @@ extern NSString * const BUYAppId_Placeholder; ...@@ -45,7 +49,11 @@ extern NSString * const BUYAppId_Placeholder;
@property (nonatomic, strong) NSString *giftCardCodeInvalid; @property (nonatomic, strong) NSString *giftCardCodeInvalid;
@property (nonatomic, strong) NSString *discountCodeValid; @property (nonatomic, strong) NSString *discountCodeValid;
@property (nonatomic, strong) NSString *discountCodeExpired; @property (nonatomic, strong) NSString *discountCodeExpired;
@property (nonatomic, strong) NSArray *productIds; @property (nonatomic, strong) NSArray *productIds;
@property (nonatomic, strong) NSNumber *variantUntrackedId;
@property (nonatomic, strong) NSNumber *variantInventory1Id;
@property (nonatomic, strong) NSNumber *variantSoldOutId;
@property (nonatomic, strong) BUYClient *client; @property (nonatomic, strong) BUYClient *client;
......
...@@ -53,6 +53,9 @@ NSString * const BUYAppId_Placeholder = @"app_id"; ...@@ -53,6 +53,9 @@ NSString * const BUYAppId_Placeholder = @"app_id";
self.appId = environment[kBUYTestAppId] ?: jsonConfig[kBUYTestAppId]; self.appId = environment[kBUYTestAppId] ?: jsonConfig[kBUYTestAppId];
self.merchantId = environment[kBUYTestMerchantId] ?: jsonConfig[kBUYTestMerchantId]; self.merchantId = environment[kBUYTestMerchantId] ?: jsonConfig[kBUYTestMerchantId];
self.customerEmail = environment[kBUYTestEmail] ?: jsonConfig[kBUYTestEmail];
self.customerPassword = environment[kBUYTestPassword] ?: jsonConfig[kBUYTestPassword];
NSDictionary *giftCards = jsonConfig[@"gift_cards"]; NSDictionary *giftCards = jsonConfig[@"gift_cards"];
self.giftCardCode = environment[kBUYTestGiftCardCode11] ?: giftCards[@"valid11"][@"code"]; self.giftCardCode = environment[kBUYTestGiftCardCode11] ?: giftCards[@"valid11"][@"code"];
...@@ -67,6 +70,10 @@ NSString * const BUYAppId_Placeholder = @"app_id"; ...@@ -67,6 +70,10 @@ NSString * const BUYAppId_Placeholder = @"app_id";
NSString *productIdsString = [environment[kBUYTestProductIdsCommaSeparated] stringByReplacingOccurrencesOfString:@" " withString:@""]; NSString *productIdsString = [environment[kBUYTestProductIdsCommaSeparated] stringByReplacingOccurrencesOfString:@" " withString:@""];
self.productIds = [productIdsString componentsSeparatedByString:@","]; self.productIds = [productIdsString componentsSeparatedByString:@","];
} else { } else {
self.variantUntrackedId = jsonConfig[@"variants"][@"variant_untracked_id"];
self.variantInventory1Id = jsonConfig[@"variants"][@"variant_inventory1_id"];
self.variantSoldOutId = jsonConfig[@"variants"][@"variant_soldout_id"];
self.productIds = jsonConfig[@"product_ids"]; self.productIds = jsonConfig[@"product_ids"];
} }
......
//
// BUYClientTest_Customer.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 XCTest;
#import "BUYClientTestBase.h"
#import "BUYClient+Customers.h"
#import "BUYAccountCredentials.h"
#import "BUYError+BUYAdditions.h"
#import <OHHTTPStubs/OHHTTPStubs.h>
#import "OHHTTPStubsResponse+Helpers.h"
// Remove this macro entirely when test shop has customer api enabled
//#define CUSTOMER_API_AVAILABLE
@interface BUYClientTest_Customer : BUYClientTestBase
@end
@implementation BUYClientTest_Customer
- (void)tearDown
{
[super tearDown];
[OHHTTPStubs removeAllStubs];
}
- (void)testCustomerDuplicateEmail
{
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest * _Nonnull request) {
return [self shouldUseMocks];
} withStubResponse:^OHHTTPStubsResponse * _Nonnull(NSURLRequest * _Nonnull request) {
return [OHHTTPStubsResponse responseWithKey:@"testCustomerDuplicateEmail"];
}];
XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
BUYAccountCredentialItem *emailItem = [BUYAccountCredentialItem itemWithKey:@"email" value:self.customerEmail];
BUYAccountCredentialItem *passwordItem = [BUYAccountCredentialItem itemWithKey:@"password" value:self.customerPassword];
BUYAccountCredentialItem *passwordConfItem = [BUYAccountCredentialItem itemWithKey:@"password_confirmation" value:self.customerPassword];
BUYAccountCredentials *credentials = [BUYAccountCredentials credentialsWithItems:@[emailItem, passwordItem, passwordConfItem]];
[self.client createCustomerWithCredentials:credentials callback:^(BUYCustomer *customer, NSString *token, NSError *error) {
XCTAssertNil(customer);
XCTAssertNotNil(error);
NSArray *errors = [BUYError errorsFromSignUpJSON:error.userInfo];
XCTAssertEqual(errors.count, 1);
BUYError *customerError = errors[0];
XCTAssertEqualObjects(customerError.code, @"taken");
XCTAssertEqualObjects(customerError.options[@"rescue_from_duplicate"], @YES);
XCTAssertEqualObjects(customerError.options[@"value"], self.customerEmail);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:10 handler:^(NSError * _Nullable error) {
XCTAssertNil(error);
}];
}
- (void)testCustomerInvalidEmailPassword
{
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest * _Nonnull request) {
return [self shouldUseMocks];
} withStubResponse:^OHHTTPStubsResponse * _Nonnull(NSURLRequest * _Nonnull request) {
return [OHHTTPStubsResponse responseWithKey:@"testCustomerInvalidEmailPassword"];
}];
XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
BUYAccountCredentialItem *emailItem = [BUYAccountCredentialItem itemWithKey:@"email" value:@"a"];
BUYAccountCredentialItem *passwordItem = [BUYAccountCredentialItem itemWithKey:@"password" value:@"b"];
BUYAccountCredentialItem *passwordConfItem = [BUYAccountCredentialItem itemWithKey:@"password_confirmation" value:@"c"];
BUYAccountCredentials *credentials = [BUYAccountCredentials credentialsWithItems:@[emailItem, passwordItem, passwordConfItem]];
[self.client createCustomerWithCredentials:credentials callback:^(BUYCustomer *customer, NSString *token, NSError *error) {
XCTAssertNil(customer);
XCTAssertNotNil(error);
NSArray<BUYError *> *errors = [BUYError errorsFromSignUpJSON:error.userInfo];
XCTAssertEqual(errors.count, 3);
BUYError *emailError = errors[0];
XCTAssertEqualObjects(emailError.code, @"invalid");
BUYError *passwordConfError = errors[1];
XCTAssertEqualObjects(passwordConfError.code, @"confirmation");
XCTAssertEqualObjects(passwordConfError.options[@"attribute"], @"Password");
BUYError *passwordError = errors[2];
XCTAssertEqualObjects(passwordError.code, @"too_short");
XCTAssertEqualObjects(passwordError.options[@"count"], @5);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:10 handler:^(NSError * _Nullable error) {
XCTAssertNil(error);
}];
}
- (void)testCustomerLogin
{
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest * _Nonnull request) {
return [self shouldUseMocks];
} withStubResponse:^OHHTTPStubsResponse * _Nonnull(NSURLRequest * _Nonnull request) {
return [OHHTTPStubsResponse responseWithKey:@"testCustomerLogin"];
}];
XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
BUYAccountCredentialItem *emailItem = [BUYAccountCredentialItem itemWithKey:@"email" value:self.customerEmail];
BUYAccountCredentialItem *passwordItem = [BUYAccountCredentialItem itemWithKey:@"password" value:self.customerPassword];
BUYAccountCredentials *credentials = [BUYAccountCredentials credentialsWithItems:@[emailItem, passwordItem]];
[self.client loginCustomerWithCredentials:credentials callback:^(BUYCustomer *customer, NSString *token, NSError *error) {
XCTAssertNil(error);
XCTAssertNotNil(customer);
XCTAssertEqualObjects(customer.email, self.customerEmail);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:10 handler:^(NSError * _Nullable error) {
XCTAssertNil(error);
}];
}
@end
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
#import "BUYClientTestBase.h" #import "BUYClientTestBase.h"
#import <OHHTTPStubs/OHHTTPStubs.h> #import <OHHTTPStubs/OHHTTPStubs.h>
#import "OHHTTPStubsResponse+Helpers.h" #import "OHHTTPStubsResponse+Helpers.h"
#import "BUYShopifyErrorCodes.h"
@interface BUYClientTest_Storefront : BUYClientTestBase @interface BUYClientTest_Storefront : BUYClientTestBase
@property (nonatomic, strong) BUYCollection *collection; @property (nonatomic, strong) BUYCollection *collection;
......
...@@ -78,7 +78,7 @@ ...@@ -78,7 +78,7 @@
- (void)testJsonDictionaryShouldShowAllProperties - (void)testJsonDictionaryShouldShowAllProperties
{ {
BUYProductVariant *variant = [[BUYProductVariant alloc] initWithModelManager:[BUYModelManager modelManager] JSONDictionary:@{ @"id" : @5 }]; BUYProductVariant *variant = [[BUYProductVariant alloc] initWithModelManager:_modelManager JSONDictionary:@{ @"id" : @5 }];
_lineItem = [[BUYLineItem alloc] initWithVariant:variant]; _lineItem = [[BUYLineItem alloc] initWithVariant:variant];
_lineItem.quantity = [NSDecimalNumber decimalNumberWithString:@"3"]; _lineItem.quantity = [NSDecimalNumber decimalNumberWithString:@"3"];
_lineItem.price = [NSDecimalNumber decimalNumberWithString:@"5.55"]; _lineItem.price = [NSDecimalNumber decimalNumberWithString:@"5.55"];
...@@ -93,7 +93,7 @@ ...@@ -93,7 +93,7 @@
- (void)testUpdatingFromJsonShouldUpdateAllValues - (void)testUpdatingFromJsonShouldUpdateAllValues
{ {
BUYLineItem *lineItem = [[BUYLineItem alloc] initWithModelManager:[BUYModelManager modelManager] JSONDictionary:@{ @"id" : @"5", @"price" : @"5.99", @"quantity" : @5, @"requires_shipping" : @YES, @"title" : @"banana" }]; BUYLineItem *lineItem = [[BUYLineItem alloc] initWithModelManager:_modelManager JSONDictionary:@{ @"id" : @"5", @"price" : @"5.99", @"quantity" : @5, @"requires_shipping" : @YES, @"title" : @"banana" }];
XCTAssertEqualObjects(@"5", lineItem.identifier); XCTAssertEqualObjects(@"5", lineItem.identifier);
XCTAssertEqualObjects([NSDecimalNumber decimalNumberWithString:@"5.99"], lineItem.price); XCTAssertEqualObjects([NSDecimalNumber decimalNumberWithString:@"5.99"], lineItem.price);
XCTAssertEqualObjects([NSDecimalNumber decimalNumberWithString:@"5"], lineItem.quantity); XCTAssertEqualObjects([NSDecimalNumber decimalNumberWithString:@"5"], lineItem.quantity);
......
...@@ -170,7 +170,7 @@ ...@@ -170,7 +170,7 @@
static BUYModelManager *modelManager; static BUYModelManager *modelManager;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:@[[NSBundle bundleForClass:self]]]; NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:@[[NSBundle bundleForClass:self]]];
modelManager = [[BUYModelManager alloc] initWithModel:model]; modelManager = [[BUYModelManager alloc] initWithManagedObjectModel:model];
}); });
return modelManager; return modelManager;
......
...@@ -32,6 +32,8 @@ ...@@ -32,6 +32,8 @@
#define kBUYTestAPIKey @"api_key" #define kBUYTestAPIKey @"api_key"
#define kBUYTestAppId @"app_id" #define kBUYTestAppId @"app_id"
#define kBUYTestMerchantId @"merchant_id" #define kBUYTestMerchantId @"merchant_id"
#define kBUYTestEmail @"customer_email"
#define kBUYTestPassword @"customer_password"
#define kBUYTestGiftCardCode11 @"gift_card_code_11" #define kBUYTestGiftCardCode11 @"gift_card_code_11"
#define kBUYTestGiftCardCode25 @"gift_card_code_25" #define kBUYTestGiftCardCode25 @"gift_card_code_25"
#define kBUYTestGiftCardCode50 @"gift_card_code_50" #define kBUYTestGiftCardCode50 @"gift_card_code_50"
......
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="10171" systemVersion="15E65" minimumToolsVersion="Xcode 7.0">
<entity name="Bird" representedClassName="Bird" syncable="YES">
<attribute name="colour" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="identifier" optional="YES" attributeType="Integer 64" defaultValueString="0" syncable="YES"/>
<relationship name="nests" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Nest" inverseName="bird" inverseEntity="Nest" syncable="YES">
<userInfo>
<entry key="JSONPropertyKey" value="nest_ids"/>
<entry key="key" value="value"/>
</userInfo>
</relationship>
<relationship name="researchers" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Researcher" inverseName="birds" inverseEntity="Researcher" syncable="YES"/>
</entity>
<entity name="Branch" representedClassName="Branch" syncable="YES">
<attribute name="ornaments" optional="YES" attributeType="Transformable" syncable="YES">
<userInfo>
<entry key="attributeValueClass" value="NSArray"/>
<entry key="JSONValueTransformer" value="Array"/>
</userInfo>
</attribute>
<relationship name="leaves" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Leaf" inverseName="branch" inverseEntity="Leaf" syncable="YES"/>
<relationship name="nest" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="Nest" inverseName="branch" inverseEntity="Nest" syncable="YES"/>
<relationship name="root" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Root" inverseName="branches" inverseEntity="Root" syncable="YES"/>
</entity>
<entity name="Forest" syncable="YES">
<relationship name="trees" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Root" inverseName="forest" inverseEntity="Root" syncable="YES"/>
<userInfo>
<entry key="private" value="YES"/>
</userInfo>
</entity>
<entity name="Leaf" representedClassName="Leaf" syncable="YES">
<attribute name="date" optional="YES" attributeType="Date" syncable="YES">
<userInfo>
<entry key="JSONPropertyKey" value="createDate"/>
</userInfo>
</attribute>
<attribute name="tags" optional="YES" attributeType="Transformable" syncable="YES">
<userInfo>
<entry key="attributeValueClass" value="NSSet"/>
<entry key="JSONValueTransformer" value="Set"/>
</userInfo>
</attribute>
<relationship name="branch" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Branch" inverseName="leaves" inverseEntity="Branch" syncable="YES"/>
</entity>
<entity name="Nest" representedClassName="Nest" syncable="YES">
<attribute name="eggCount" optional="YES" attributeType="Integer 16" syncable="YES"/>
<relationship name="bird" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Bird" inverseName="nests" inverseEntity="Bird" syncable="YES">
<userInfo>
<entry key="JSONPropertyKey" value="bird_id"/>
</userInfo>
</relationship>
<relationship name="branch" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Branch" inverseName="nest" inverseEntity="Branch" syncable="YES"/>
</entity>
<entity name="Researcher" representedClassName="Researcher" syncable="YES">
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="birds" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Bird" inverseName="researchers" inverseEntity="Bird" syncable="YES"/>
</entity>
<entity name="Root" representedClassName="Root" syncable="YES">
<attribute name="age" optional="YES" attributeType="Decimal" defaultValueString="0.0" syncable="YES"/>
<attribute name="identifier" optional="YES" attributeType="Integer 64" defaultValueString="0" syncable="YES"/>
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="url" optional="YES" attributeType="Transformable" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="NSURL"/>
<entry key="JSONTransformerName" value="BUYURL"/>
</userInfo>
</attribute>
<relationship name="branches" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Branch" inverseName="root" inverseEntity="Branch" syncable="YES"/>
<relationship name="forest" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Forest" inverseName="trees" inverseEntity="Forest" syncable="YES"/>
</entity>
<elements>
<element name="Bird" positionX="146" positionY="111" width="128" height="103"/>
<element name="Branch" positionX="-288" positionY="-3" width="128" height="105"/>
<element name="Forest" positionX="-718" positionY="57" width="128" height="58"/>
<element name="Leaf" positionX="-72" positionY="-18" width="128" height="90"/>
<element name="Nest" positionX="-72" positionY="126" width="128" height="90"/>
<element name="Root" positionX="-504" positionY="-18" width="128" height="133"/>
<element name="Researcher" positionX="-288" positionY="81" width="128" height="75"/>
</elements>
</model>
\ No newline at end of file
//
// TestModel.h
// Mobile Buy SDK
//
// Created by Brent Gulanowski on 2016-04-27.
// Copyright © 2016 Shopify Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "BUYObjectProtocol.h"
#import "BUYModelManagerProtocol.h"
@class NSManagedObjectModel;
@interface TestModelManager : NSObject<BUYModelManager>
@property (nonatomic) NSManagedObjectModel *model;
@end
@interface TestModel : NSObject<BUYObject>
@property (nonatomic, strong) TestModelManager *modelManager;
@end
@class Leaf, Nest, Researcher, Root;
@interface Bird : TestModel
@property (nonatomic) NSNumber *identifier;
@property (nonatomic) NSString *colour;
@property (nonatomic) NSSet<Researcher *> *researchers;
+ (instancetype)birdWithIdentifier:(NSNumber *)identifier;
@end
@interface Branch : TestModel
@property (nonatomic) NSArray<NSString *> *ornaments;
@property (nonatomic) NSSet<Leaf *> *leaves;
@property (nonatomic) Nest *nest;
@end
@interface Forest : TestModel
@property (nonatomic) NSSet<Root *> *trees;
@end
@interface Leaf : TestModel
@property (nonatomic) NSDate *date;
@property (nonatomic) NSSet<NSString *> *tags;
@end
@interface Nest : TestModel
@property (nonatomic) NSNumber *eggCount;
@property (nonatomic) Bird *bird;
@property (nonatomic) Branch *branch;
@end
@interface Researcher : TestModel
@property (nonatomic) NSString *name;
@property (nonatomic) NSSet<Bird *> *birds;
@end
@interface Root : TestModel
@property (nonatomic) NSNumber *identifier;
@property (nonatomic) NSDecimalNumber *age;
@property (nonatomic) NSString *name;
@property (nonatomic) NSURL *url;
@property (nonatomic) NSSet<Branch *> *branches;
@property (nonatomic) Forest *forest;
@end
//
// TestModel.m
// Mobile Buy SDK
//
// Created by Brent Gulanowski on 2016-04-27.
// Copyright © 2016 Shopify Inc. All rights reserved.
//
#import "TestModel.h"
#import "NSEntityDescription+BUYAdditions.h"
#import <CoreData/CoreData.h>
@implementation TestModelManager
- (instancetype)init
{
self = [super init];
if (self) {
self.model = [NSManagedObjectModel mergedModelFromBundles:@[[NSBundle bundleForClass:[self class]]]];
}
return self;
}
- (NSEntityDescription *)buy_entityWithName:(NSString *)entityName
{
return self.model.entitiesByName[entityName];
}
- (Class)managedModelClassForEntityName:(NSString *)entityName
{
return [[self buy_entityWithName:entityName] buy_managedObjectClass];
}
- (id<BUYObject>)buy_objectWithEntityName:(NSString *)entityName JSONDictionary:(NSDictionary *)JSON
{
return [(id)[[self managedModelClassForEntityName:entityName] alloc] initWithModelManager:self JSONDictionary:JSON];
}
- (NSArray<id<BUYObject>> *)buy_objectsWithEntityName:(NSString *)entityName JSONArray:(NSArray *)JSON {
NSMutableArray *array = [NSMutableArray array];
for (NSDictionary *dict in JSON) {
[array addObject:[self buy_objectWithEntityName:entityName JSONDictionary:dict]];
}
return array;
}
// We don't need these methods for testing
- (id<BUYObject>)buy_objectWithEntityName:(NSString *)entityName identifier:(NSNumber *)identifier {
return [entityName isEqualToString:[Bird entityName]] ? [Bird birdWithIdentifier:identifier] : nil;
}
- (NSArray<id<BUYObject>> *)buy_objectsWithEntityName:(NSString *)entityName identifiers:(NSArray *)identifiers { return nil; }
- (void)buy_refreshCacheForObject:(id<BUYObject>)object {}
- (BOOL)buy_purgeObject:(id<BUYObject>)object error:(NSError *__autoreleasing *)error { return YES; }
- (BOOL)buy_purgeObjectsWithEntityName:(NSString *)entityName matchingPredicate:(NSPredicate *)predicate { return YES; }
@end
#pragma mark -
@implementation TestModel
@synthesize modelManager=_modelManager;
- (instancetype)initWithModelManager:(id<BUYModelManager>)modelManager JSONDictionary:(NSDictionary *)dictionary
{
self = [super init];
if (self) {
self.modelManager = modelManager;
self.JSONDictionary = dictionary;
}
return self;
}
- (NSDictionary *)jsonDictionaryForCheckout
{
return self.JSONDictionary;
}
- (NSDictionary *)JSONDictionary
{
return [self.entity buy_JSONForObject:self];
}
- (void)setJSONDictionary:(NSDictionary *)JSONDictionary
{
[self.entity buy_updateObject:self withJSON:JSONDictionary];
}
- (NSDictionary *)JSONEncodedProperties
{
NSMutableDictionary *properties = [[self.entity buy_JSONEncodedProperties] mutableCopy];
for (NSString *relationshipName in self.entity.relationshipsByName) {
NSRelationshipDescription *relationship = properties[relationshipName];
if (relationship.inverseRelationship.deleteRule == NSCascadeDeleteRule) {
[properties removeObjectForKey:relationshipName];
}
}
return properties;
}
+ (NSPredicate *)fetchPredicateWithJSON:(NSDictionary *)JSONDictionary
{
return nil;
}
+ (NSString *)entityName
{
return NSStringFromClass([self class]);
}
- (NSEntityDescription *)entity
{
return [self.modelManager buy_entityWithName:[[self class] entityName]];
}
+ (BOOL)tracksDirtyProperties
{
return NO;
}
+ (BOOL)isPersistentClass
{
return NO;
}
- (BOOL)isEqual:(id)object
{
return [super isEqual:object] || [object isMemberOfClass:[self class]];
}
@end
#pragma mark - Models
@implementation Bird
+ (instancetype)birdWithIdentifier:(NSNumber *)identifier
{
Bird *bird = [[self alloc] init];
bird.identifier = identifier;
return bird;
}
- (BOOL)isEqual:(Bird *)otherModel
{
return ([super isEqual:otherModel] &&
[self.identifier isEqual:otherModel.identifier] &&
[self.colour isEqualToString:otherModel.colour]);
}
- (NSUInteger)hash
{
NSUInteger hash = self.identifier.hash;
hash = (hash << 5) ^ self.colour.hash;
return hash;
}
@end
@implementation Branch
- (BOOL)isEqual:(Branch *)otherModel
{
return ([super isEqual:otherModel] &&
[self.ornaments isEqual:otherModel.ornaments] &&
[self.leaves isEqual:otherModel.leaves]);
}
- (NSUInteger)hash
{
NSUInteger hash = self.ornaments.hash;
hash = (hash << 5) ^ self.leaves.hash;
return hash;
}
@end
@implementation Forest
- (BOOL)isEqual:(Forest *)object
{
return ([super isEqual:object] &&
[self.trees isEqual:object.trees]);
}
- (NSUInteger)hash
{
return self.trees.hash;
}
@end
@implementation Leaf
- (BOOL)isEqual:(Leaf *)object
{
return ([super isEqual:object] &&
[self.date isEqual:object.date] &&
[self.tags isEqual:object.tags]);
}
- (NSUInteger)hash
{
return (self.date.hash << 5) ^ self.tags.hash;
}
@end
@implementation Nest
- (BOOL)isEqual:(Nest *)object
{
return ([super isEqual:object] &&
[self.eggCount isEqual:object.eggCount] &&
((self.bird == nil && object.bird == nil) || [self.bird isEqual:object.bird]));
}
- (NSUInteger)hash
{
return (self.branch.hash << 5) ^ (7231UL + self.eggCount.unsignedIntegerValue);
}
@end
@implementation Researcher
- (BOOL)isEqual:(Researcher *)object
{
return ([super isEqual:object] &&
[self.name isEqual:object.name]);
}
- (NSUInteger)hash
{
return self.name.hash;
}
@end
@implementation Root
- (BOOL)isEqual:(Root *)object
{
return ([super isEqual:object] &&
[self.identifier isEqual:object.identifier] &&
[self.age isEqual:object.age] &&
[self.name isEqual:object.name] &&
[self.url isEqual:object.url] &&
[self.branches isEqual:object.branches]);
}
- (NSUInteger)hash
{
return self.identifier.hash;
}
@end
...@@ -5,10 +5,17 @@ ...@@ -5,10 +5,17 @@
"channel_id": "", "channel_id": "",
"app_id": "", "app_id": "",
"merchant_id": "", "merchant_id": "",
"customer_email": "",
"customer_password": "",
"product_ids": [ "product_ids": [
"", "",
"" ""
], ],
"variants": {
"variant_untracked_id": "",
"variant_inventory1_id": "",
"variant_soldout_id": ""
},
"collection_id": "", "collection_id": "",
"gift_cards": { "gift_cards": {
"ValidGiftCard11": { "ValidGiftCard11": {
......
...@@ -28,7 +28,26 @@ ...@@ -28,7 +28,26 @@
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<Testables> <Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "90F592EB1B0D5EFE0026B382"
BuildableName = "Mobile Buy SDK Tests.xctest"
BlueprintName = "Mobile Buy SDK Tests"
ReferencedContainer = "container:Mobile Buy SDK.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables> </Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "901930E11BC5B9BC00D1134E"
BuildableName = "Buy.framework"
BlueprintName = "Buy"
ReferencedContainer = "container:Mobile Buy SDK.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions> <AdditionalOptions>
</AdditionalOptions> </AdditionalOptions>
</TestAction> </TestAction>
......
...@@ -31,14 +31,14 @@ ...@@ -31,14 +31,14 @@
+ (NSDateFormatter*)dateFormatterForShippingRates + (NSDateFormatter*)dateFormatterForShippingRates
{ {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZ"]; dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSZ";
return dateFormatter; return dateFormatter;
} }
+ (NSDateFormatter*)dateFormatterForPublications + (NSDateFormatter*)dateFormatterForPublications
{ {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZ"]; dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
return dateFormatter; return dateFormatter;
} }
......
...@@ -67,11 +67,11 @@ static NSString * const BUYDateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ"; ...@@ -67,11 +67,11 @@ static NSString * const BUYDateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
[NSValueTransformer setValueTransformer:[[BUYIdentityTransformer alloc] init] forName:BUYIdentityTransformerName]; [NSValueTransformer setValueTransformer:[[BUYIdentityTransformer alloc] init] forName:BUYIdentityTransformerName];
// attribute type transformers // attribute type transformers
[NSValueTransformer setValueTransformer:[[BUYDateTransformer alloc] init] forName:BUYDateTransformerName];
[NSValueTransformer setValueTransformer:[[BUYDecimalNumberTransformer alloc] init] forName:BUYDecimalNumberTransformerName]; [NSValueTransformer setValueTransformer:[[BUYDecimalNumberTransformer alloc] init] forName:BUYDecimalNumberTransformerName];
// value type transformers // value type transformers
[NSValueTransformer setValueTransformer:[[BUYURLTransformer alloc] init] forName:BUYURLTransformerName]; [NSValueTransformer setValueTransformer:[[BUYURLTransformer alloc] init] forName:BUYURLTransformerName];
[NSValueTransformer setValueTransformer:[BUYDateTransformer dateTransformerWithFormat:BUYDateFormat] forName:BUYDateTransformerName];
}); });
return self.userInfo[BUYJSONValueTransformerUserInfoKey]; return self.userInfo[BUYJSONValueTransformerUserInfoKey];
} }
...@@ -166,21 +166,26 @@ static NSString *JSONValueTransformerNameForAttributeType(NSAttributeType type) ...@@ -166,21 +166,26 @@ static NSString *JSONValueTransformerNameForAttributeType(NSAttributeType type)
@implementation NSRelationshipDescription (BUYAdditions) @implementation NSRelationshipDescription (BUYAdditions)
#pragma mark - Helpers
- (NSString *)encodesIdSuffix - (NSString *)encodesIdSuffix
{ {
return (self.isToMany ? @"_ids" : @"_id"); return (self.isToMany ? @"_ids" : @"_id");
} }
- (BOOL)buy_encodesIdInJSON // array -> (ordered)set
- (id)buy_transformArray:(NSArray<id<BUYObject>> *)array
{ {
return [self.JSONPropertyKey hasSuffix:[self encodesIdSuffix]]; return self.isOrdered ? [NSOrderedSet orderedSetWithArray:array] : [NSSet setWithArray:array];
} }
- (id)buy_transformArray:(NSArray<id<BUYObject>> *)array // (ordered)set -> array
- (NSArray *)buy_arrayForCollection:(id)collection
{ {
return self.isOrdered ? [NSOrderedSet orderedSetWithArray:array] : [NSSet setWithArray:array]; return self.ordered ? [collection array] : [collection allObjects];
} }
// JSON -> model
- (id)buy_objectForJSON:(id)JSON modelManager:(id<BUYModelManager>)modelManager - (id)buy_objectForJSON:(id)JSON modelManager:(id<BUYModelManager>)modelManager
{ {
NSString *entityName = self.destinationEntity.name; NSString *entityName = self.destinationEntity.name;
...@@ -194,7 +199,18 @@ static NSString *JSONValueTransformerNameForAttributeType(NSAttributeType type) ...@@ -194,7 +199,18 @@ static NSString *JSONValueTransformerNameForAttributeType(NSAttributeType type)
} }
} }
- (id)buy_collectionForJSON:(NSArray *)JSON object:(id<BUYObject>)object // model -> JSON
- (id)buy_JSONForObject:(NSObject<BUYObject> *)object
{
id json = nil;
if (!self.inverseRelationship || self.inverseRelationship.allowsInverseEncoding) {
json = [self.destinationEntity buy_JSONForObject:object];
}
return json;
}
// JSON -> (ordered)set (of models)
- (id)buy_collectionForJSON:(NSArray *)JSON modelManager:(id<BUYModelManager>)modelManager
{ {
NSString *entityName = self.destinationEntity.name; NSString *entityName = self.destinationEntity.name;
NSArray<id<BUYObject>> *array; NSArray<id<BUYObject>> *array;
...@@ -203,16 +219,23 @@ static NSString *JSONValueTransformerNameForAttributeType(NSAttributeType type) ...@@ -203,16 +219,23 @@ static NSString *JSONValueTransformerNameForAttributeType(NSAttributeType type)
// Otherwise, let the object context decide how to resolve the objects and update them. // Otherwise, let the object context decide how to resolve the objects and update them.
// If device caching is not provided, this will return nothing. // If device caching is not provided, this will return nothing.
if (self.encodesIdInJSON) { if (self.encodesIdInJSON) {
array = [object.modelManager buy_objectsWithEntityName:entityName identifiers:JSON]; array = [modelManager buy_objectsWithEntityName:entityName identifiers:JSON];
} }
else { else {
array = [object.modelManager buy_objectsWithEntityName:entityName JSONArray:JSON]; array = [modelManager buy_objectsWithEntityName:entityName JSONArray:JSON];
} }
// Transform the array to the correct container type (`NSSet` or `NSOrderedSet`). // Transform the array to the correct container type (`NSSet` or `NSOrderedSet`).
return [self buy_transformArray:array]; return [self buy_transformArray:array];
} }
// (ordered)set (of models) -> JSON
- (NSArray *)buy_JSONForCollection:(id)collection
{
return [self.destinationEntity buy_JSONForArray:[self buy_arrayForCollection:collection]];
}
#pragma mark - Property Additions Overrides
- (id)buy_valueForJSON:(id)JSON object:(id<BUYObject>)object - (id)buy_valueForJSON:(id)JSON object:(id<BUYObject>)object
{ {
...@@ -220,41 +243,20 @@ static NSString *JSONValueTransformerNameForAttributeType(NSAttributeType type) ...@@ -220,41 +243,20 @@ static NSString *JSONValueTransformerNameForAttributeType(NSAttributeType type)
// The logic for decoding JSON is slightly different for to-one and to-many relationships. // The logic for decoding JSON is slightly different for to-one and to-many relationships.
// NOTE: by default, without a caching system, inverse relationships are not supported. // NOTE: by default, without a caching system, inverse relationships are not supported.
if (JSON && ![JSON isEqual:[NSNull null]]) { id value = nil;
if ([JSON buy_isValidObject]) {
if (self.isToMany) { if (self.isToMany) {
return [self buy_collectionForJSON:JSON object:object]; value = [self buy_collectionForJSON:JSON modelManager:object.modelManager];
} }
else { else {
return [self buy_objectForJSON:JSON modelManager:object.modelManager]; value = [self buy_objectForJSON:JSON modelManager:object.modelManager];
} }
} }
else { return value;
return nil;
}
} }
- (id)buy_JSONForValue:(id)value - (id)buy_JSONForValue:(id)value
{ {
return self.toMany ? [self buy_JSONForCollection:value] : [self buy_JSONForObject:value];
}
- (NSArray *)buy_JSONForCollection:(id)collection
{
if (self.manyToMany) {
return nil;
}
NSArray *array = [self buy_arrayForCollection:collection];
return self.encodesIdInJSON ? [array valueForKey:@"identifier"] : [self.destinationEntity buy_JSONForArray:array];
}
- (NSArray *)buy_arrayForCollection:(id)collection
{
return self.ordered ? [collection array] : [collection allObjects];
}
- (id)buy_JSONForObject:(NSObject<BUYObject> *)object
{
// JSON generation for a relationship depends on the rules defined in the model. // JSON generation for a relationship depends on the rules defined in the model.
// The model can explicitly specify using an `id` encoding. // The model can explicitly specify using an `id` encoding.
// Alternately, if the relationship is compatible, encode the entire object. // Alternately, if the relationship is compatible, encode the entire object.
...@@ -263,15 +265,24 @@ static NSString *JSONValueTransformerNameForAttributeType(NSAttributeType type) ...@@ -263,15 +265,24 @@ static NSString *JSONValueTransformerNameForAttributeType(NSAttributeType type)
// 2. the inverse relationship is not an ownership relationship // 2. the inverse relationship is not an ownership relationship
// (this is inferred from the `NSCascadeDeleteRule` used by owning objects) // (this is inferred from the `NSCascadeDeleteRule` used by owning objects)
// 3. the relationship is to a "private" entity (not known to the API) // 3. the relationship is to a "private" entity (not known to the API)
id json = nil;
if (self.encodesIdInJSON) { if (self.encodesIdInJSON) {
return [[object valueForKey:self.name] identifier]; json = [value valueForKey:NSStringFromSelector(@selector(identifier))];
} }
else if (!self.inverseRelationship || self.inverseRelationship.allowsInverseEncoding) { else if (!self.toMany) {
return [self.destinationEntity buy_JSONForObject:object]; json = [self buy_JSONForObject:value];
} }
else { else if (!self.manyToMany) {
return nil; json = [self buy_JSONForCollection:value];
} }
return json;
}
#pragma mark - Properties
- (BOOL)buy_encodesIdInJSON
{
return [self.JSONPropertyKey hasSuffix:[self encodesIdSuffix]];
} }
- (BOOL)buy_isManyToMany - (BOOL)buy_isManyToMany
...@@ -295,7 +306,7 @@ static NSString *JSONValueTransformerNameForAttributeType(NSAttributeType type) ...@@ -295,7 +306,7 @@ static NSString *JSONValueTransformerNameForAttributeType(NSAttributeType type)
return nil; return nil;
} }
- (id)buy_reverseTransformedJSONValue:(id)value - (id)buy_JSONForValue:(id)value
{ {
return nil; return nil;
} }
...@@ -304,8 +315,6 @@ static NSString *JSONValueTransformerNameForAttributeType(NSAttributeType type) ...@@ -304,8 +315,6 @@ static NSString *JSONValueTransformerNameForAttributeType(NSAttributeType type)
#pragma mark - #pragma mark -
// We could be really clever here and use an algorithm for string transformation
// but it's not worth it right now.
@implementation NSObject (BUYValueTransforming) @implementation NSObject (BUYValueTransforming)
+ (NSString *)buy_JSONValueTransformerName + (NSString *)buy_JSONValueTransformerName
......
...@@ -32,6 +32,7 @@ FOUNDATION_EXPORT double BuyVersionNumber; ...@@ -32,6 +32,7 @@ FOUNDATION_EXPORT double BuyVersionNumber;
//! Project version string for Buy. //! Project version string for Buy.
FOUNDATION_EXPORT const unsigned char BuyVersionString[]; FOUNDATION_EXPORT const unsigned char BuyVersionString[];
#import <Buy/BUYAccountCredentials.h>
#import <Buy/BUYAddress.h> #import <Buy/BUYAddress.h>
#import <Buy/BUYCart.h> #import <Buy/BUYCart.h>
#import <Buy/BUYCartLineItem.h> #import <Buy/BUYCartLineItem.h>
...@@ -39,6 +40,7 @@ FOUNDATION_EXPORT const unsigned char BuyVersionString[]; ...@@ -39,6 +40,7 @@ FOUNDATION_EXPORT const unsigned char BuyVersionString[];
#import <Buy/BUYCheckoutAttribute.h> #import <Buy/BUYCheckoutAttribute.h>
#import <Buy/BUYCollection.h> #import <Buy/BUYCollection.h>
#import <Buy/BUYCreditCard.h> #import <Buy/BUYCreditCard.h>
#import <Buy/BUYCustomer.h>
#import <Buy/BUYDiscount.h> #import <Buy/BUYDiscount.h>
#import <Buy/BUYGiftCard.h> #import <Buy/BUYGiftCard.h>
#import <Buy/BUYImageLink.h> #import <Buy/BUYImageLink.h>
...@@ -56,12 +58,15 @@ FOUNDATION_EXPORT const unsigned char BuyVersionString[]; ...@@ -56,12 +58,15 @@ FOUNDATION_EXPORT const unsigned char BuyVersionString[];
#import <Buy/BUYApplePayAdditions.h> #import <Buy/BUYApplePayAdditions.h>
#import <Buy/BUYApplePayHelpers.h> #import <Buy/BUYApplePayHelpers.h>
#import <Buy/BUYClient.h> #import <Buy/BUYClient.h>
#import <Buy/BUYClient+Customers.h>
#import <Buy/BUYError.h> #import <Buy/BUYError.h>
#import <Buy/BUYError+BUYAdditions.h>
#import <Buy/BUYManagedObject.h> #import <Buy/BUYManagedObject.h>
#import <Buy/BUYModelManager.h> #import <Buy/BUYModelManager.h>
#import <Buy/BUYModelManagerProtocol.h> #import <Buy/BUYModelManagerProtocol.h>
#import <Buy/BUYObjectProtocol.h> #import <Buy/BUYObjectProtocol.h>
#import <Buy/BUYObserver.h> #import <Buy/BUYObserver.h>
#import <Buy/BUYShopifyErrorCodes.h>
#import <Buy/BUYPaymentButton.h> #import <Buy/BUYPaymentButton.h>
#import <Buy/BUYProductViewController.h> #import <Buy/BUYProductViewController.h>
......
//
// BUYClient+Customers.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"
@class BUYCustomer;
@class BUYOrder;
@class BUYAccountCredentials;
/**
* Return block containing a BUYCustomer object for an existing customer of the shop
*
* @param customer A BUYCustomer
* @param error An optional NSError
*/
typedef void (^BUYDataCustomerBlock)(BUYCustomer *customer, NSError *error);
/**
* Return block containing a customer auth token
*
* @param customer A BUYCustomer
* @param token An authentication token to retrieve the customer later. Store this token securely on the device.
* @param error An optional NSError
*/
typedef void (^BUYDataCustomerTokenBlock)(BUYCustomer *customer, NSString *token, NSError *error);
/**
* Return block containing a customer auth token
*
* @param token An authentication token to retrieve the customer later. Store this token securely on the device.
* @param error An optional NSError
*/
typedef void (^BUYDataTokenBlock)(NSString *token, NSError *error);
/**
* Return block containing an array of BUYOrders
*
* @param orders An array of BUYOrders
* @param error An optional NSError
*/
typedef void (^BUYDataOrdersBlock)(NSArray <BUYOrder*> *orders, NSError *error);
@interface BUYClient (Customers)
/**
* GET /api/customers/:customer_id
* Gets an existing customer
*
* @param customerID A customer ID retrieved from either customer creation or login
* @param block (BUYCustomer *customer, NSError *error)
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getCustomerWithID:(NSString *)customerID callback:(BUYDataCustomerBlock)block;
/**
* POST /api/customers
* Creates a new customer
* Expects first name, last name, email, password, and password confirmation
*
* @param credentials Credentials object containing items for required fields
* @param block (BUYCustomer *customer, NSString *token, NSError *error)
*
* @return The associated NSURLSessionDataTask
*
* @discussion The customer is automatically logged in using -loginCustomerWithCredentials:callback:
*/
- (NSURLSessionDataTask *)createCustomerWithCredentials:(BUYAccountCredentials *)credentials callback:(BUYDataCustomerTokenBlock)block;
/**
* POST /api/customers/customer_token
* Logs in an existing customer
* Expects email and password
*
* @param credentials Credentials object containing items for required fields
* @param block (BUYCustomer *customer, NSString *token, NSError *error)
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)loginCustomerWithCredentials:(BUYAccountCredentials *)credentials callback:(BUYDataCustomerTokenBlock)block;
/**
* POST /api/customers/recover
* Sends email for password recovery to an existing customer
*
* @param email Email to send the password reset to
* @param block (BUYStatus status, NSError *error)
*
* @return the associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)recoverPasswordForCustomer:(NSString *)email callback:(BUYDataCheckoutStatusBlock)block;
/**
* PUT /api/customers/:customer_id/customer_token/renew
* Renews an existing customer's token
*
* @param customerID ID of customer renewing token
* @param block (NSString *token, NSError *error)
*
* @return the associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)renewCustomerTokenWithID:(NSString *)customerID callback:(BUYDataTokenBlock)block;
/**
* PUT /api/customers/:customer_id/activate
* Activates an unactivated customer
*
* @param credentials Credentials containing a password and password confirmation
* @param customerID ID of customer being activated
* @param customerToken Token contained in activation URL
* @param block (BUYCustomer *customer, NSString *token, NSError *error)
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)activateCustomerWithCredentials:(BUYAccountCredentials *)credentials customerID:(NSString *)customerID customerToken:(NSString *)customerToken callback:(BUYDataCustomerTokenBlock)block;
/**
* PUT /api/customers/:customer_id/reset
* Resets an existing customer's password
*
* @param credentials Credentials containing a password and password confirmation
* @param customerID ID of customer resetting password
* @param customerToken Token contained in reset URL
* @param block (BUYCustomer *customer, NSString *token, NSError *error)
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)resetPasswordWithCredentials:(BUYAccountCredentials *)credentials customerID:(NSString *)customerID customerToken:(NSString *)customerToken callback:(BUYDataCustomerTokenBlock)block;
/**
* GET /api/customers/:customer_id/orders
* Gets orders for a given customer
*
* @param token An auth token retrieved from customer creation or customer login API
* @param block (NSArray <BUYOrder*> *orders, NSError *error)
*
* @return The associated NSURLSessionDataTask
*/
- (NSURLSessionDataTask *)getOrdersForCustomerWithCallback:(BUYDataOrdersBlock)block;
@end
...@@ -80,6 +80,8 @@ typedef NS_ENUM(NSUInteger, BUYCollectionSort) { ...@@ -80,6 +80,8 @@ typedef NS_ENUM(NSUInteger, BUYCollectionSort) {
extern NSString * const BUYVersionString; extern NSString * const BUYVersionString;
extern NSString * const BUYClientCustomerAccessToken;
/** /**
* A BUYStatus is associated with the completion of an enqueued job on Shopify. * A BUYStatus is associated with the completion of an enqueued job on Shopify.
* BUYStatus is equal is HTTP status codes returned from the server * BUYStatus is equal is HTTP status codes returned from the server
...@@ -277,6 +279,12 @@ typedef void (^BUYDataGiftCardBlock)(BUYGiftCard *giftCard, NSError *error); ...@@ -277,6 +279,12 @@ typedef void (^BUYDataGiftCardBlock)(BUYGiftCard *giftCard, NSError *error);
*/ */
@property (nonatomic, strong) NSString *urlScheme; @property (nonatomic, strong) NSString *urlScheme;
/**
* Allows the client to hold onto the customer token
*
* @param token The token received from the create and login callbacks
*/
@property (strong, nonatomic) NSString *customerToken;
#pragma mark - Storefront #pragma mark - Storefront
......
...@@ -2,8 +2,26 @@ ...@@ -2,8 +2,26 @@
// BUYClient_Internal.h // BUYClient_Internal.h
// Mobile Buy SDK // Mobile Buy SDK
// //
// Created by Gabriel O'Flaherty-Chan on 2016-04-04. // Created by Shopify.
// Copyright © 2016 Shopify Inc. All rights reserved. // 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" #import "BUYClient.h"
...@@ -13,7 +31,7 @@ extern NSString *const kShopifyError; ...@@ -13,7 +31,7 @@ extern NSString *const kShopifyError;
@interface BUYClient (Internal) @interface BUYClient (Internal)
- (NSURLSessionDataTask *)postRequestForURL:(NSURL *)url object:(id<BUYSerializable>)object completionHandler:(void (^)(NSDictionary *json, NSURLResponse *response, NSError *error))completionHandler; - (NSURLSessionDataTask *)postRequestForURL:(NSURL *)url object:(id <BUYSerializable>)object completionHandler:(void (^)(NSDictionary *json, NSURLResponse *response, NSError *error))completionHandler;
- (NSURLSessionDataTask *)putRequestForURL:(NSURL *)url body:(NSData *)body completionHandler:(void (^)(NSDictionary *json, NSURLResponse *response, NSError *error))completionHandler; - (NSURLSessionDataTask *)putRequestForURL:(NSURL *)url body:(NSData *)body 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;
......
//
// BUYAccountCredentials.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>
/**
* Intended for storing a collection of credential items representing individual values
*/
@class BUYAccountCredentialItem;
@interface BUYAccountCredentials : NSObject
NS_ASSUME_NONNULL_BEGIN
+ (BUYAccountCredentials *)credentialsWithItems:(NSArray<BUYAccountCredentialItem *> *)items;
+ (BUYAccountCredentials *)credentialsWithItemKeys:(NSArray<NSString *> *)keys;
@property (readonly) NSDictionary *JSONRepresentation;
@property (nonatomic, readonly, getter=isValid) BOOL valid;
- (BUYAccountCredentialItem *)objectForKeyedSubscript:(NSString *)key;
- (void)setObject:(BUYAccountCredentialItem *)obj forKeyedSubscript:(NSString *)key;
@end
/**
* Represents a key and KVC-validatable value
*/
@interface BUYAccountCredentialItem : NSObject
+ (instancetype)itemWithKey:(NSString *)key value:(NSString *)value;
@property (nonatomic, getter=isValid) BOOL valid;
@property (nonatomic, strong) NSString *key;
@property (nonatomic, strong) NSString *value;
NS_ASSUME_NONNULL_END
@end
//
// BUYAccountCredentials.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 "BUYAccountCredentials.h"
@class BUYAccountCredentialItem;
@interface BUYAccountCredentials()
@property (strong, nonatomic) NSMutableDictionary<NSString *, BUYAccountCredentialItem *> *items;
@end
@implementation BUYAccountCredentials
+ (BUYAccountCredentials *)credentialsWithItems:(NSArray<BUYAccountCredentialItem *> *)items
{
BUYAccountCredentials *credentials = [BUYAccountCredentials new];
NSMutableDictionary *keyedItems = [NSMutableDictionary dictionary];
for (BUYAccountCredentialItem *item in items) {
keyedItems[item.key] = item;
}
credentials.items = keyedItems;
return credentials;
}
+ (BUYAccountCredentials *)credentialsWithItemKeys:(NSArray<NSString *> *)keys
{
NSMutableArray *items = [NSMutableArray array];
for (NSString *key in keys) {
BUYAccountCredentialItem *item = [BUYAccountCredentialItem itemWithKey:key value:@""];
[items addObject:item];
}
return [BUYAccountCredentials credentialsWithItems:items];
}
- (NSDictionary *)JSONRepresentation
{
__block NSMutableDictionary *customer = [NSMutableDictionary dictionary];
[self.items enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, BUYAccountCredentialItem * _Nonnull obj, BOOL * _Nonnull stop) {
customer[key] = obj.value;
}];
return @{ @"customer": customer };
}
- (BOOL)validateValue:(inout id _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError
{
return [self.items[inKey] validateValue:ioValue forKey:inKey error:outError];
}
- (BUYAccountCredentialItem *)objectForKeyedSubscript:(NSString *)key
{
return self.items[key];
}
- (void)setObject:(BUYAccountCredentialItem *)obj forKeyedSubscript:(NSString *)key
{
self.items[key] = obj;
}
- (BOOL)validationForKey:(NSString *)key
{
return [self.items[key] isValid];
}
- (BOOL)isValid
{
__block BOOL valid = YES;
[self.items enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, BUYAccountCredentialItem * _Nonnull obj, BOOL * _Nonnull stop) {
valid = valid && [obj isValid];
}];
return valid;
}
@end
@implementation BUYAccountCredentialItem
+ (instancetype)itemWithKey:(NSString *)key value:(NSString *)value
{
BUYAccountCredentialItem *item = [BUYAccountCredentialItem new];
item.key = key;
item.value = value;
return item;
}
- (NSString *)value
{
return _value ?: @"";
}
- (BOOL)validateValue:(inout id _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError
{
self.value = *ioValue;
self.valid = self.value.length > 0;
return [self isValid];
}
@end
...@@ -26,55 +26,14 @@ ...@@ -26,55 +26,14 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
extern NSString * const BUYShopifyError; @interface BUYError : NSObject
/** @property (nonatomic, copy) NSString *key;
* A collection of enums for error codes specific to the SDK
*/
typedef NS_ENUM(NSUInteger, BUYCheckoutError){
/**
* An error occurred retrieving the cart for an existing web checkout with BUYStoreViewController
*/
BUYShopifyError_CartFetchError,
/**
* No shipping rates are available for the selected address
*/
BUYShopifyError_NoShippingMethodsToAddress,
/**
* No product or product ID was provided when loading a product in BUYProductViewController
*/
BUYShopifyError_NoProductSpecified,
/**
* The product ID or IDs provided were invalid for the shop. Check that the product are made visible on the Mobile App channel on /admin.
*/
BUYShopifyError_InvalidProductID,
/**
* No collection ID was provided when loading a collection
*/
BUYShopifyError_NoCollectionIdSpecified,
/**
* No gift card code was provided when applying a gift card to a checkout
*/
BUYShopifyError_NoGiftCardSpecified,
/**
* No credit card was provided when calling `storeCreditCard:completion:`
*/
BUYShopifyError_NoCreditCardSpecified,
/**
* No Apple Pay token was provided when attempting to complete a checkout using Apple Pay
*/
BUYShopifyError_NoApplePayTokenSpecified,
/**
* The checkout is invalid and does not have a checkout token. This generally means the BUYCheckout object
* has not been synced with Shopify via `createCheckout:completion:` before making subsequent calls to update
* or complete the checkout
*/
BUYShopifyError_InvalidCheckoutObject,
};
/** - (instancetype)initWithKey:(NSString *)key json:(NSDictionary *)json;
* BUYError overrides `description` and provides a human-readable dictionary for the error
*/ @property (nonatomic, copy) NSString *code;
@interface BUYError : NSError @property (nonatomic, copy) NSString *message;
@property (nonatomic, copy) NSDictionary<NSString *, NSString *> *options;
@end @end
...@@ -26,13 +26,21 @@ ...@@ -26,13 +26,21 @@
#import "BUYError.h" #import "BUYError.h"
NSString * const BUYShopifyError = @"BUYShopifyError";
@implementation BUYError @implementation BUYError
- (instancetype)initWithKey:(NSString *)key json:(NSDictionary *)json
{
self = [super init];
if (self) {
self.key = key;
[self setValuesForKeysWithDictionary:json];
}
return self;
}
- (NSString *)description - (NSString *)description
{ {
return [NSString stringWithFormat:@"Error code %td: %@", self.code, [self userInfo]]; return [NSString stringWithFormat:@"%@ %@", self.key, self.message];
} }
@end @end
...@@ -40,15 +40,13 @@ ...@@ -40,15 +40,13 @@
*/ */
@property (nonatomic, strong, readonly) NSManagedObjectModel *model; @property (nonatomic, strong, readonly) NSManagedObjectModel *model;
- (instancetype)init NS_UNAVAILABLE;
/** /**
* *
* @param model The Core Data managed object model for your given model. Should be the Buy model. * @param model The Core Data managed object model for your given model. Should be the Buy model.
* *
* @return A new model manager object. * @return A new model manager object.
*/ */
- (instancetype)initWithModel:(NSManagedObjectModel *)model NS_DESIGNATED_INITIALIZER; - (instancetype)initWithManagedObjectModel:(NSManagedObjectModel *)model NS_DESIGNATED_INITIALIZER;
/** /**
* Convenience initializer. Instantiates a model using the -mergedModelFromBundles: method and the Buy.framework as the bundle. * Convenience initializer. Instantiates a model using the -mergedModelFromBundles: method and the Buy.framework as the bundle.
......
...@@ -55,7 +55,12 @@ NSString * const BUYProductTagsTransformerName = @"BUYProductTags"; ...@@ -55,7 +55,12 @@ NSString * const BUYProductTagsTransformerName = @"BUYProductTags";
}); });
} }
- (instancetype)initWithModel:(NSManagedObjectModel *)model - (instancetype)init
{
return [self initWithManagedObjectModel:[NSManagedObjectModel mergedModelFromBundles:@[[NSBundle bundleForClass:[self class]]]]];
}
- (instancetype)initWithManagedObjectModel:(NSManagedObjectModel *)model
{ {
self = [super init]; self = [super init];
if (self) { if (self) {
...@@ -66,7 +71,7 @@ NSString * const BUYProductTagsTransformerName = @"BUYProductTags"; ...@@ -66,7 +71,7 @@ NSString * const BUYProductTagsTransformerName = @"BUYProductTags";
+ (instancetype)modelManager + (instancetype)modelManager
{ {
return [[self alloc] initWithModel:[NSManagedObjectModel mergedModelFromBundles:@[[NSBundle bundleForClass:[BUYObject class]]]]]; return [[self alloc] init];
} }
- (NSEntityDescription *)buy_entityWithName:(NSString *)entityName - (NSEntityDescription *)buy_entityWithName:(NSString *)entityName
......
...@@ -25,9 +25,10 @@ ...@@ -25,9 +25,10 @@
// //
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import <Buy/BUYSerializable.h> #import <Buy/BUYSerializable.h>
@class NSEntityDescription;
@protocol BUYModelManager; @protocol BUYModelManager;
/** /**
......
...@@ -31,3 +31,7 @@ ...@@ -31,3 +31,7 @@
- (NSDictionary *)jsonDictionaryForCheckout; - (NSDictionary *)jsonDictionaryForCheckout;
@end @end
@interface NSDictionary (BUYSerializable) <BUYSerializable>
@end
\ No newline at end of file
// //
// BUYEntityDescriptionAdditionsTests.m // BUYSerializable.m
// Mobile Buy SDK // Mobile Buy SDK
// //
// Created by Shopify. // Created by Shopify.
...@@ -24,34 +24,12 @@ ...@@ -24,34 +24,12 @@
// THE SOFTWARE. // THE SOFTWARE.
// //
#import <XCTest/XCTest.h> #import "BUYSerializable.h"
@interface BUYEntityDescriptionAdditionsTests : XCTestCase @implementation NSDictionary (BUYSerializable)
@end - (NSDictionary *)jsonDictionaryForCheckout {
return self;
@implementation BUYEntityDescriptionAdditionsTests
- (void)setUp {
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
[super tearDown];
}
- (void)testExample {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
- (void)testPerformanceExample {
// This is an example of a performance test case.
[self measureBlock:^{
// Put the code you want to measure the time of here.
}];
} }
@end @end
// //
// _BUYAddress.m // BUYAddress.m
// Mobile Buy SDK // Mobile Buy SDK
// //
// Created by Shopify. // Created by Shopify.
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
} }
return NO; return NO;
} }
- (BOOL)isValidAddressForShippingRates - (BOOL)isValidAddressForShippingRates
{ {
......
// //
// _BUYCheckout.h // BUYCheckout.h
// Mobile Buy SDK // Mobile Buy SDK
// //
// Created by Shopify. // Created by Shopify.
......
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
@property (nonatomic, copy) NSDate *updatedAtDate; @property (nonatomic, copy) NSDate *updatedAtDate;
@property (nonatomic, strong) BUYMaskedCreditCard *creditCard; @property (nonatomic, strong) BUYMaskedCreditCard *creditCard;
@property (nonatomic, strong) BUYOrder *order; @property (nonatomic, strong) BUYOrder *order;
@property (nonatomic, copy) NSString *customerId; @property (nonatomic, copy) NSNumber *customerId;
@property (nonatomic, strong) NSURL *privacyPolicyURL; @property (nonatomic, strong) NSURL *privacyPolicyURL;
@property (nonatomic, strong) NSURL *refundPolicyURL; @property (nonatomic, strong) NSURL *refundPolicyURL;
@property (nonatomic, strong) NSURL *termsOfServiceURL; @property (nonatomic, strong) NSURL *termsOfServiceURL;
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
#import "BUYProduct.h" #import "BUYProduct.h"
#import "BUYProductViewController.h" #import "BUYProductViewController.h"
#import "BUYImageKit.h" #import "BUYImageKit.h"
#import "BUYImageLink.h"
#import "BUYProductView.h" #import "BUYProductView.h"
#import "BUYProductViewFooter.h" #import "BUYProductViewFooter.h"
#import "BUYProductHeaderCell.h" #import "BUYProductHeaderCell.h"
...@@ -44,7 +45,7 @@ ...@@ -44,7 +45,7 @@
#import "BUYVariantSelectionViewController.h" #import "BUYVariantSelectionViewController.h"
#import "BUYError.h" #import "BUYError.h"
#import "BUYShop.h" #import "BUYShop.h"
#import "BUYImageLink.h" #import "BUYShopifyErrorCodes.h"
CGFloat const BUYMaxProductViewWidth = 414.0; // We max out to the width of the iPhone 6+ CGFloat const BUYMaxProductViewWidth = 414.0; // We max out to the width of the iPhone 6+
CGFloat const BUYMaxProductViewHeight = 640.0; CGFloat const BUYMaxProductViewHeight = 640.0;
......
...@@ -28,13 +28,20 @@ ...@@ -28,13 +28,20 @@
* Umbrella header used for Cocoapods * Umbrella header used for Cocoapods
*/ */
#import "BUYAccountCredentials.h"
#import "BUYApplePayAdditions.h"
#import "BUYApplePayHelpers.h"
#import "BUYAddress.h" #import "BUYAddress.h"
#import "BUYCart.h" #import "BUYCart.h"
#import "BUYCartLineItem.h" #import "BUYCartLineItem.h"
#import "BUYCheckout.h" #import "BUYCheckout.h"
#import "BUYCheckoutAttribute.h" #import "BUYCheckoutAttribute.h"
#import "BUYClient+Test.h"
#import "BUYClient.h"
#import "BUYClient+Customers.h"
#import "BUYCollection.h" #import "BUYCollection.h"
#import "BUYCreditCard.h" #import "BUYCreditCard.h"
#import "BUYCustomer.h"
#import "BUYDiscount.h" #import "BUYDiscount.h"
#import "BUYGiftCard.h" #import "BUYGiftCard.h"
#import "BUYImageLink.h" #import "BUYImageLink.h"
...@@ -49,9 +56,6 @@ ...@@ -49,9 +56,6 @@
#import "BUYShop.h" #import "BUYShop.h"
#import "BUYTaxLine.h" #import "BUYTaxLine.h"
#import "BUYApplePayAdditions.h"
#import "BUYApplePayHelpers.h"
#import "BUYClient.h"
#import "BUYError.h" #import "BUYError.h"
#import "BUYManagedObject.h" #import "BUYManagedObject.h"
#import "BUYModelManager.h" #import "BUYModelManager.h"
......
...@@ -25,12 +25,14 @@ ...@@ -25,12 +25,14 @@
// //
#import "BUYApplePayHelpers.h" #import "BUYApplePayHelpers.h"
#import "BUYAddress.h"
#import "BUYApplePayAdditions.h" #import "BUYApplePayAdditions.h"
#import "BUYClient.h" #import "BUYClient.h"
#import "BUYCheckout.h" #import "BUYCheckout.h"
#import "BUYError.h" #import "BUYError.h"
#import "BUYModelManager.h" #import "BUYModelManager.h"
#import "BUYShop.h" #import "BUYShop.h"
#import "BUYShopifyErrorCodes.h"
const NSTimeInterval PollDelay = 0.5; const NSTimeInterval PollDelay = 0.5;
......
// //
// BUYPropertyDescriptionAdditionsTests.m // BUYError+BUYAdditions.h
// Mobile Buy SDK // Mobile Buy SDK
// //
// Created by Shopify. // Created by Shopify.
...@@ -24,34 +24,13 @@ ...@@ -24,34 +24,13 @@
// THE SOFTWARE. // THE SOFTWARE.
// //
#import <XCTest/XCTest.h> #import "BUYError.h"
@interface BUYPropertyDescriptionAdditionsTests : XCTestCase
@interface BUYError (Checkout)
+ (NSArray<BUYError *> *)errorsFromCheckoutJSON:(NSDictionary *)json;
@property (readonly) NSString *quantityRemainingMessage;
@end @end
@implementation BUYPropertyDescriptionAdditionsTests @interface BUYError (Customer)
+ (NSArray<BUYError *> *)errorsFromSignUpJSON:(NSDictionary *)json;
- (void)setUp {
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
[super tearDown];
}
- (void)testExample {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
- (void)testPerformanceExample {
// This is an example of a performance test case.
[self measureBlock:^{
// Put the code you want to measure the time of here.
}];
}
@end @end
//
// BUYError+BUYAdditions.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 "BUYError+BUYAdditions.h"
@implementation BUYError (Checkout)
+ (NSArray<BUYError *> *)errorsFromCheckoutJSON:(NSDictionary *)json
{
NSArray *lineItems = json[@"errors"][@"checkout"][@"line_items"];
NSMutableArray *errors = [NSMutableArray array];
for (NSDictionary<NSString *, NSArray *> *lineItem in lineItems) {
if (lineItem == (id)[NSNull null]) {
[errors addObject:lineItem];
}
else {
for (NSString *key in lineItem.allKeys) {
NSDictionary *reason = [lineItem[key] firstObject];
[errors addObject:[[BUYError alloc] initWithKey:key json:reason]];
};
}
};
return errors;
}
- (NSString *)quantityRemainingMessage
{
NSNumber *remaining = (id)self.options[@"remaining"];
NSString *localizedString;
if ([remaining isEqualToNumber:@0]) {
localizedString = NSLocalizedString(@"Completely sold out", @"String describing a line item with zero stock available");
} else {
localizedString = NSLocalizedString(@"Only %1$@ left in stock, reduce the quantity and try again.", @"String describing an out of stock line item with first parameter representing amount remaining");
}
return [NSString localizedStringWithFormat:localizedString, remaining];
}
@end
@implementation BUYError (Customer)
+ (NSArray<BUYError *> *)errorsFromSignUpJSON:(NSDictionary *)json
{
NSDictionary *reasons = json[@"errors"][@"customer"];
__block NSMutableArray *errors = [NSMutableArray array];
[reasons enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSArray * _Nonnull obj, BOOL * _Nonnull stop) {
for (NSDictionary *reason in obj) {
[errors addObject:[[BUYError alloc] initWithKey:key json:reason]];
}
}];
return errors;
}
@end
//
// BUYShopifyErrorCodes.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.
//
#ifndef BUYShopifyErrorCodes_h
#define BUYShopifyErrorCodes_h
static NSString * const BUYShopifyError = @"BUYShopifyError";
/**
* A collection of enums for error codes specific to the SDK
*/
typedef NS_ENUM(NSUInteger, BUYCheckoutError){
/**
* An error occurred retrieving the cart for an existing web checkout with StoreViewController
*/
BUYShopifyError_CartFetchError,
/**
* No shipping rates are available for the selected address
*/
BUYShopifyError_NoShippingMethodsToAddress,
/**
* No product or product ID was provided when loading a product in BUYProductViewController
*/
BUYShopifyError_NoProductSpecified,
/**
* The product ID or IDs provided were invalid for the shop. Check that the product are made visible on the Mobile App channel on /admin.
*/
BUYShopifyError_InvalidProductID,
/**
* No collection ID was provided when loading a collection
*/
BUYShopifyError_NoCollectionIdSpecified,
/**
* No gift card code was provided when applying a gift card to a checkout
*/
BUYShopifyError_NoGiftCardSpecified,
/**
* No credit card was provided when calling `storeCreditCard:completion:`
*/
BUYShopifyError_NoCreditCardSpecified,
/**
* No Apple Pay token was provided when attempting to complete a checkout using Apple Pay
*/
BUYShopifyError_NoApplePayTokenSpecified,
/**
* The checkout is invalid and does not have a checkout token. This generally means the BUYCheckout object
* has not been synced with Shopify via `createCheckout:completion:` before making subsequent calls to update
* or complete the checkout
*/
BUYShopifyError_InvalidCheckoutObject,
/**
* A customer token has not been configured on the client
*/
BUYShopifyError_InvalidCustomerToken
};
#endif /* BUYShopifyErrorCodes_h */
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
// //
#import "BUYDateTransformer.h" #import "BUYDateTransformer.h"
#import "NSDateFormatter+BUYAdditions.h"
NSString * const BUYDateTransformerName = @"BUYDate"; NSString * const BUYDateTransformerName = @"BUYDate";
...@@ -34,6 +35,13 @@ NSString * const BUYDateTransformerName = @"BUYDate"; ...@@ -34,6 +35,13 @@ NSString * const BUYDateTransformerName = @"BUYDate";
@implementation BUYDateTransformer @implementation BUYDateTransformer
- (instancetype)init
{
NSDateFormatter *formatter = [NSDateFormatter dateFormatterForPublications];
formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
return [self initWithDateFormatter:formatter];
}
- (instancetype)initWithDateFormatter:(NSDateFormatter *)formatter - (instancetype)initWithDateFormatter:(NSDateFormatter *)formatter
{ {
self = [super init]; self = [super init];
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
#import "BUYStoreViewController.h" #import "BUYStoreViewController.h"
#import "BUYError.h" #import "BUYError.h"
#import "BUYOrder.h" #import "BUYOrder.h"
#import "BUYShopifyErrorCodes.h"
@interface BUYStoreViewController () <WKNavigationDelegate, WKScriptMessageHandler> @interface BUYStoreViewController () <WKNavigationDelegate, WKScriptMessageHandler>
@end @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