/FsprgEmbeddedStoreMac

Primary LanguageObjective-CMIT LicenseMIT

Introduction

FastSpring offers an innovative e-commerce engine designed to overcome ease of use, customer service, and cost issues that have plagued software e-commerce companies.

FastSpring's embedded store consists of a controller with some integration points and WebKit's web view. It's thin and very flexible and lets you integrate FastSpring the way that fits best for your application.

To get an idea of how it works, the SDK provides two examples and a test application. All source code is released under the MIT license. It is open to contributions and its use is unrestricted. See RELEASE_NOTES.html for the latest changes.

FsprgEmbeddedStore

FsprgEmbeddedStore consists mainly of the FsprgEmbeddedStoreController and its delegate protocol, FsprgEmbeddedStoreDelegate.

The FsprgEmbeddedStoreController controls the connected WebView (WebKit). It provides functionality to load the store, to monitor the page loading progress and to test if the current connection is secure (https).

@interface FsprgEmbeddedStoreController : NSObject
	- (WebView *)webView;
	- (void)setWebView:(WebView *)aWebView;
	- (id <FsprgEmbeddedStoreDelegate>)delegate;
	- (void)setDelegate:(id <FsprgEmbeddedStoreDelegate>)aDelegate;

	- (void)loadWithParameters:(FsprgStoreParameters *)parameters;
	- (void)loadWithContentsOfFile:(NSString *)aPath;
	- (BOOL)isLoading;
	- (double)estimatedLoadingProgress;
	- (BOOL)isSecure;
	- (NSString *)storeHost;
@end

In addition, it has some integration points defined by the FsprgEmbeddedStoreDelegate protocol. It gives notification of the initial load of the store, of subsequent page loads and of order completion. There's also the possibility to define a view to present the order confirmation to the user.

typedef enum {
	FsprgPageFS,
	FsprgPagePayPal,
	FsprgPageUnknown
} FsprgPageType;

@protocol FsprgEmbeddedStoreDelegate <NSObject>
	- (void)didLoadStore:(NSURL *)url;
	- (void)didLoadPage:(NSURL *)url ofType:(FsprgPageType)pageType;
	- (void)didReceiveOrder:(FsprgOrder *)order;
	- (NSView *)viewWithFrame:(NSRect)frame forOrder:(FsprgOrder *)order;
	- (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame;
	- (void)webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame;
@end

How-to embed it

Use our CocoaPod or perform the following steps.

  1. Clone or fork our repository.
  2. Open your project in Xcode.
  3. Perform File/Add Files to "Your App"... and choose the directory FsprgEmbeddedStoreMac/FsprgEmbeddedStore. We suggest to uncheck the option Copy items into destination group's folder.
  4. Delete reference to FsprgEmbeddedStoreMac/FsprgEmbeddedStore/Tests (in your project) if you don't need FsprgEmbeddedStore unit tests.
  5. Add WebKit.framework and Security.framework to your Targets.

How-to use it

  1. Read our Integration Guide to learn how to enable your store for FsprgEmbeddedStore requests.
  2. Create an AppController and use FsprgEmbeddedStoreController. Example1.app and Example2.app are showing how to implement an AppController in detail.
  3. Open MainMenu.nib.
  4. Create an instance of AppController inside Interface Builder.
  5. Drag WebKit control onto screen and connect it to the storeView Outlet of AppController.

Example: AppController.h

@interface AppController : NSObject <FsprgEmbeddedStoreDelegate> {
	IBOutlet WebView* storeView;
	FsprgEmbeddedStoreController *storeController;
}

- (FsprgEmbeddedStoreController *)storeController;
- (void)setStoreController:(FsprgEmbeddedStoreController *)aStoreController;

- (IBAction)load:(id)sender;

@end

How-to provide a link to the web-store

Sometimes users prefer to use a web-store instead of an embedded-store. Use FsprgStoreParameters to build the web-store URL and open it inside the default browser by using NSWorkspace.

- (IBAction)openWebStoreInBrowser:(id)sender
{
	FsprgStoreParameters *parameters = [FsprgStoreParameters parameters];
	[parameters setOrderProcessType:kFsprgOrderProcessDetail];
	[parameters setStoreId:@"your_store" withProductId:@"your_product"];
	[parameters setMode:kFsprgModeTest];
	[[NSWorkspace sharedWorkspace] openURL:[parameters toURL]];
}

FsprgOrder API

The FsprgOrder object represents the order confirmation returned via FsprgEmbeddedStoreDelegate protocol. To spare you plunging through the headers the following sections contain a real-life example and a compressed API documentation of FsprgOrder and its referred classes.

Example

Here's a real-life example to show the most common case of grabbing the serial number from the fulfilled license. Thanks to Greg Scown from SmileOnMyMac for sharing.

- (void)didReceiveOrder:(FsprgOrder *)order
{
   NSEnumerator *e = [[order orderItems] objectEnumerator];
   FsprgOrderItem *item = nil;
   while (item = [e nextObject]) {
       if ([[item productName] hasPrefix:@"MyItemNamePrefix"]) {
           NSString *userName = [[item license] licenseName];
           NSString *serialNumber = [[item license] firstLicenseCode];
           if ([[[item productName] lowercaseString] rangeOfString:@"upgrade"].location != NSNotFound) {
               NSLog(@"Upgrade purchase:\nName: %@\nSerial #: %@", userName, serialNumber);
           } else {
               NSLog(@"Full purchase:\nName: %@\nSerial #: %@",	userName, serialNumber);
           }
       }
   }
}

FsprgOrder.h

- (BOOL)orderIsTest;
- (NSString *)orderReference;
- (NSString *)orderLanguage;
- (NSString *)orderCurrency;
- (NSNumber *)orderTotal;
- (NSNumber *)orderTotalUSD;
- (NSString *)customerFirstName;
- (NSString *)customerLastName;
- (NSString *)customerCompany;
- (NSString *)customerEmail;
- (FsprgOrderItem *)firstOrderItem; // Shortcut for [[self orderItems] objectAtIndex:0].
- (NSArray *)orderItems;

FsprgOrderItem.h

- (NSString *)productName;
- (NSString *)productDisplay;
- (NSNumber *)quantity;
- (NSNumber *)itemTotal;
- (NSNumber *)itemTotalUSD;
- (NSString *)subscriptionReference; // See https://support.fastspring.com/entries/236487-api-subscriptions
- (NSURL *)subscriptionCustomerURL; // This URL can be presented to the customer to manage their subscription.
- (FsprgFulfillment *)fulfillment;
- (FsprgLicense *)license;           // Shortcut for [[self fulfillment] valueForKey:@"license"]
- (FsprgFileDownload *)download;     // Shortcut for [[self fulfillment] valueForKey:@"download"]

FsprgFulfillment.h

/*!
 * @param aKey type of fulfillment (e.g. license, download)
 * @result Specific fulfillment information (FsprgLicense, FsprgFileDownload).
 */
- (id)valueForKey:(NSString *)aKey;

FsprgLicense.h

- (NSString *)licenseName;
- (NSString *)licenseEmail;
- (NSString *)licenseCompany;
- (NSString *)firstLicenseCode;
- (NSArray *)licenseCodes;
- (NSDictionary *)licensePropertyList;
- (NSURL *)licenseURL;

FsprgFileDownload.h

- (NSURL *)fileURL;

Example1.app

Example1 app defaults contact fields by accessing MacOS' AddressBook. The order confirmation is a View XIB built inside Interface Builder.

Example1.app Screenshot

How-to implement the AppController

  • Set self as delegate on init
  • Set webView to FsprgEmbeddedStoreController on awakeFromNib
  • Delegate load: to loadWithParameters: of FsprgEmbeddedStoreController
  • Implement viewWithFrame:forOrder: by using a NSViewController (here OrderViewController) that uses the View XIB defined inside Interface Builder

Extract from AppController.h

@implementation AppController

- (id) init
{
	self = [super init];
	if (self != nil) {
		[self setStoreController:[[[FsprgEmbeddedStoreController alloc] init] autorelease]];
		[[self storeController] setDelegate:self];
	}
	return self;
}

- (void)awakeFromNib
{
	[[self storeController] setWebView:storeView];
	[self load:nil];
}

- (IBAction)load:(id)sender
{
	FsprgStoreParameters *parameters = [FsprgStoreParameters parameters];
	...
	[[self storeController] loadWithParameters:parameters];
}

- (NSView *)viewWithFrame:(NSRect)frame forOrder:(FsprgOrder *)order
{
	OrderViewController *orderViewController = [[OrderViewController alloc] initWithNibName:@"OrderView" bundle:nil];
	[orderViewController setRepresentedObject:order];

	[[orderViewController view] setFrame:frame];
	return [orderViewController view];
}

@end

How-to create the View XIB

  • Create class OrderViewController by extending NSViewController
  • Create View XIB
  • Set File’s Owner class to OrderViewController
  • Assign File’s Owner view Outlet to the main "Custom View"
  • Bind controls (e.g. label) to File’s Owner representedObject (= FSOrder) to present order confirmation data to the user

Example2.app

Example2 presents the order confirmation by using HTML, CSS and JavaScript. It uses Matt Gemmell's MGTemplateEngine to render the HTML.

Example2.app Screenshot

How-To create order HTML view

The AppController looks like the one in Example1. The only difference is the viewWithFrame:forOrder: implementation. It uses WebFrame's loadHTMLString:baseURL: method to load the HTML and present it to the user.

Extract from AppController.h

@implementation AppController

- (NSView *)viewWithFrame:(NSRect)frame forOrder:(FsprgOrder *)order
{
	MGTemplateEngine *engine = [MGTemplateEngine templateEngine];
	[engine setMatcher:[ICUTemplateMatcher matcherWithTemplateEngine:engine]];

	NSString *templatePath = [[NSBundle mainBundle] pathForResource:@"OrderView" ofType:@"html"];
	NSDictionary *variables = [NSDictionary dictionaryWithObject:order forKey:@"order"];
	NSString *htmlString = [engine processTemplateInFileAtPath:templatePath withVariables:variables];

	NSString *templateDirectory = [templatePath substringToIndex:[templatePath length]-[[templatePath lastPathComponent] length]];
	NSURL *baseURL = [NSURL URLWithString:[NSString stringWithFormat:@"file://%@", templateDirectory]];

	WebFrame *webFrame = [[[WebView alloc] initWithFrame:frame] mainFrame];
	[webFrame loadHTMLString:htmlString baseURL:baseURL];

	return [webFrame frameView];
}

@end

As we set FsprgOrder to a variable we can now conveniently access the order information inside the template. The baseURL points to the Resource directory. Thus, we can access CSS files to style the view and JavaScript to add some behavior and nice effects.

OrderView.html

<html>
	<head>
		<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />
		<title>Your Order</title>
		<link rel="stylesheet" type="text/css" href="OrderView.css">
		<script src="jquery-1.4.2.js"></script>
		<script language="javascript">
			$(function() {
				$(".orderItemsTitle").fadeIn(2000);
				$(".orderItem").fadeIn(2000);
			});
		</script>
	</head>
	<body>
		<div class="thankYouNote">Thanks for your order {{ order.customerFirstName }}!</div>
		<div class="orderItemsTitle">Ordered items</div>
		{% for orderItem in order.orderItems %}
		<div class="orderItem">
			<div class="productName">
			{{ orderItem.productName }}
			{% if orderItem.quantity > 1 %} ({{ orderItem.quantity }}) {% /if %}
			</div>
			<div class="licenseKey">Your license key: {{ orderItem.license.firstLicenseCode }}</div>
		</div>
		{% /for %}
	</body>
</html>

Test.app

The Test application lets you explore FastSpring's parameters and shows you the native order confirmation result (XML plist format).

Test.app Settings Screenshot  Test.app Results Screenshot

You can also store that confirmation result as a plist file and load it by using the FsprgEmbeddedStoreController's loadWithContentsOfFile: method. It simplifies the development and testing of the order confirmation view.