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 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
Use our CocoaPod or perform the following steps.
- Clone or fork our repository.
- Open your project in Xcode.
- Perform File/Add Files to "Your App"... and choose the directory
FsprgEmbeddedStoreMac/FsprgEmbeddedStore
. We suggest to uncheck the optionCopy items into destination group's folder
. - Delete reference to
FsprgEmbeddedStoreMac/FsprgEmbeddedStore/Tests
(in your project) if you don't need FsprgEmbeddedStore unit tests. - Add WebKit.framework and Security.framework to your Targets.
- Read our Integration Guide to learn how to enable your store for FsprgEmbeddedStore requests.
- Create an
AppController
and useFsprgEmbeddedStoreController
. Example1.app and Example2.app are showing how to implement anAppController
in detail. - Open MainMenu.nib.
- Create an instance of
AppController
inside Interface Builder. - Drag WebKit control onto screen and connect it to the storeView Outlet of
AppController
.
@interface AppController : NSObject <FsprgEmbeddedStoreDelegate> {
IBOutlet WebView* storeView;
FsprgEmbeddedStoreController *storeController;
}
- (FsprgEmbeddedStoreController *)storeController;
- (void)setStoreController:(FsprgEmbeddedStoreController *)aStoreController;
- (IBAction)load:(id)sender;
@end
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]];
}
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.
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);
}
}
}
}
- (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;
- (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"]
/*!
* @param aKey type of fulfillment (e.g. license, download)
* @result Specific fulfillment information (FsprgLicense, FsprgFileDownload).
*/
- (id)valueForKey:(NSString *)aKey;
- (NSString *)licenseName;
- (NSString *)licenseEmail;
- (NSString *)licenseCompany;
- (NSString *)firstLicenseCode;
- (NSArray *)licenseCodes;
- (NSDictionary *)licensePropertyList;
- (NSURL *)licenseURL;
- (NSURL *)fileURL;
Example1 app defaults contact fields by accessing MacOS' AddressBook. The order confirmation is a View XIB built inside Interface Builder.
- Set self as delegate on
init
- Set webView to
FsprgEmbeddedStoreController
onawakeFromNib
- Delegate
load:
toloadWithParameters:
ofFsprgEmbeddedStoreController
- Implement
viewWithFrame:forOrder:
by using aNSViewController
(hereOrderViewController
) that uses the View XIB defined inside Interface Builder
@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
- Create class
OrderViewController
by extendingNSViewController
- 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 presents the order confirmation by using HTML, CSS and JavaScript. It uses Matt Gemmell's MGTemplateEngine to render the HTML.
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.
@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.
<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>
The Test application lets you explore FastSpring's parameters and shows you the native order confirmation result (XML plist format).
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.