Commit 5073c14f by Dima Bart

Add BUYClient extension and supporting files for customer API.

parent 212596d3
//
// BUYClient+Customers.h
// Mobile Buy SDK
//
// Created by Gabriel O'Flaherty-Chan on 2016-04-04.
// Copyright © 2016 Shopify Inc. All rights reserved.
//
#import "BUYClient.h"
@class BUYCustomer;
@class BUYOrder;
@class BUYAccountCredentials;
NSString *const BUYClientCustomerAccessToken = @"X-Shopify-Customer-Access-Token";
/**
* 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
//
// BUYClient+Customers.m
// Mobile Buy SDK
//
// Created by Gabriel O'Flaherty-Chan on 2016-04-04.
// Copyright © 2016 Shopify Inc. All rights reserved.
//
#import "BUYClient+Customers.h"
#import "BUYClient_Internal.h"
#import "NSDateFormatter+BUYAdditions.h"
#import "BUYCustomer.h"
#import "BUYAccountCredentials.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) {
block((json && !error ? [[BUYCustomer alloc] initWithDictionary:json] : nil), error);
}];
}
- (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) {
if (json && error == nil) {
[self createTokenForCustomerWithCredentials:credentials customerJSON:json callback:block];
}
else {
block(nil, nil, error);
}
}];
}
- (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) {
if (json && error == nil) {
BUYAuthenticatedResponse *authenticatedResponse = [BUYAuthenticatedResponse responseFromJSON:json];
self.customerToken = authenticatedResponse.accessToken;
if (customerJSON == nil) {
[self getCustomerWithID:authenticatedResponse.customerID callback:^(BUYCustomer *customer, NSError *error) {
block(customer, self.customerToken, error);
}];
}
else {
block([[BUYCustomer alloc] initWithDictionary:json], self.customerToken, error);
}
}
else {
block(nil, nil, error);
}
}];
}
- (NSURLSessionDataTask *)loginCustomerWithCredentials:(BUYAccountCredentials *)credentials callback:(BUYDataCustomerTokenBlock)block
{
return [self createTokenForCustomerWithCredentials:credentials customerJSON:nil callback:block];
}
- (NSURLSessionDataTask *)recoverPasswordForCustomer:(NSString *)email callback:(BUYDataCheckoutStatusBlock)block
{
NSURLComponents *components = [self URLComponentsForPasswordReset];
return [self postRequestForURL:components.URL object:@{@"email": email} completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
error = error ?: [self extractErrorFromResponse:response json:json];
block(statusCode, error);
}];
}
- (NSURLSessionDataTask *)renewCustomerTokenWithID:(NSString *)customerID callback:(BUYDataTokenBlock)block
{
if (self.customerToken) {
NSURLComponents *components = [self URLComponentsForTokenRenewalWithID:customerID];
return [self putRequestForURL:components.URL body:nil completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSString *accessToken = nil;
if (json && error == nil) {
BUYAuthenticatedResponse *authenticatedResponse = [BUYAuthenticatedResponse responseFromJSON:json];
accessToken = authenticatedResponse.accessToken;
}
error = error ?: [self extractErrorFromResponse:response json:json];
block(accessToken, error);
}];
}
else {
block(nil, [NSError errorWithDomain:kShopifyError 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];
NSData *data = [NSJSONSerialization dataWithJSONObject:credentials.JSONRepresentation options:0 error:nil];
return [self putRequestForURL:components.URL body:data completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
if (json && error == nil) {
BUYAccountCredentialItem *emailItem = [BUYAccountCredentialItem itemWithKey:@"email" value:json[@"customer"][@"email"]];
credentials[@"email"] = emailItem;
[self loginCustomerWithCredentials:credentials callback:block];
}
else {
block(nil, nil, error);
}
}];
}
- (NSURLSessionDataTask *)resetPasswordWithCredentials:(BUYAccountCredentials *)credentials customerID:(NSString *)customerID customerToken:(NSString *)customerToken callback:(BUYDataCustomerTokenBlock)block
{
NSURLComponents *components = [self URLComponentsForCustomerPasswordResetWithCustomerID:customerID customerToken:customerToken];
NSData *data = [NSJSONSerialization dataWithJSONObject:credentials.JSONRepresentation options:0 error:nil];
return [self putRequestForURL:components.URL body:data completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
if (json && error == nil) {
BUYAccountCredentialItem *emailItem = [BUYAccountCredentialItem itemWithKey:@"email" value:json[@"customer"][@"email"]];
credentials[@"email"] = emailItem;
[self loginCustomerWithCredentials:credentials callback:block];
}
else {
block(nil, nil, error);
}
}];
}
- (NSURLSessionDataTask *)getOrdersForCustomerWithCallback:(BUYDataOrdersBlock)block
{
NSURLComponents *components = [self URLComponentsForCustomerOrders];
return [self getRequestForURL:components.URL completionHandler:^(NSDictionary *json, NSURLResponse *response, NSError *error) {
NSArray *ordersJSON = json[@"orders"];
if (!error && ordersJSON) {
NSMutableArray *container = [NSMutableArray new];
for (NSDictionary *orderJSON in ordersJSON) {
[container addObject:[[BUYOrder alloc] initWithDictionary:orderJSON]];
}
block([container copy], error);
} else {
block(nil, error);
}
}];
}
#pragma mark - URL Formatting
- (NSURLComponents *)URLComponentsForCustomers
{
return [self customerURLComponents];
}
- (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 *)customerURLComponents
{
return [self customerURLComponentsAppendingPath:nil];
}
- (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
......@@ -272,6 +272,12 @@ typedef void (^BUYDataGiftCardBlock)(BUYGiftCard *giftCard, NSError *error);
*/
@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
......
//
// BUYClient_Internal.h
// Mobile Buy SDK
//
// Created by Gabriel O'Flaherty-Chan on 2016-04-04.
// Copyright © 2016 Shopify Inc. All rights reserved.
//
#import "BUYClient.h"
#import "BUYSerializable.h"
static NSString *const kShopifyError = @"shopify";
@interface BUYClient (Internal)
- (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 *)getRequestForURL:(NSURL *)url completionHandler:(void (^)(NSDictionary *json, NSURLResponse *response, NSError *error))completionHandler;
- (NSURLSessionDataTask *)requestForURL:(NSURL *)url method:(NSString *)method body:(NSData *)body additionalHeaders:(NSDictionary *)headers completionHandler:(void (^)(NSDictionary *json, NSURLResponse *response, NSError *error))completionHandler;
- (NSURLComponents *)URLComponentsForAPIPath:(NSString *)apiPath appendingPath:(NSString *)appendingPath queryItems:(NSDictionary*)queryItems;
- (NSError *)extractErrorFromResponse:(NSURLResponse *)response json:(NSDictionary *)json;
@end
......@@ -31,3 +31,7 @@
- (NSDictionary *)jsonDictionaryForCheckout;
@end
@interface NSDictionary (BUYSerializable) <BUYSerializable>
@end
\ No newline at end of file
//
// BUYSerializable.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 "BUYSerializable.h"
@implementation NSDictionary (BUYSerializable)
- (NSDictionary *)jsonDictionaryForCheckout {
return self;
}
@end
//
// BUYShopifyErrorCodes.h
// Mobile Buy SDK
//
// Created by Gabriel O'Flaherty-Chan on 2016-03-14.
// Copyright © 2016 Shopify Inc. All rights reserved.
//
#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 */
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