Chris Stahl Chris Stahl - 4 months ago 33
iOS Question

Stripe Integration iOS eCommerce Parse with JavaScript Cloud Code

Please please help if you know how to integrate these programs. I have spent 3 days trying to get around this problem.
strong text

I am building an eCommerce platform into my app for accepting payment of Credit Cards and Apple Pay. There are two viewControllers included in this process, BagTableViewController and AddCreditCardViewController. There is also javascript for the Parse Cloud Code. I have included all code.

My pods are all up-to-date and have reverted the Parse JavaScript SDK to version 1.5.0 because parse has not updated their libraries.

My current issue occurs when I attempt to Authorize a Credit Card in the AddCreditCardViewController. After entering the test credit card information provided by Stripe, the user then presses the Authorize bar button.

When I press "Authorize", Stripe creates the Tokens and Customers, but it does not charge the customer. Instead, I receive this error in Xcode:

[Bolts] Warning:

BFTask
caught an exception in the continuation block. This behavior is discouraged and will be removed in a future release. Caught Exception: *** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[1]

In my attempts to debug this issue, I located this line as the point where a breakpoint initiates instead of the error.

NSDictionary *params = @{@"chargeCustomer":customerId, @"orderId":weakSelf.order.objectId};


Which makes sense because the charges are not being created on Stripe because the error happens on that line, and the very next line is the PFCloud callFuctionInBackround to charge the customer.

I can find very little information regarding this error, but I believe I am passing the wrong information into the NSDictionary for *params.

Can anyone help me out here? I am totally lost

// BagTableViewController.h
// Created by Chris Stahl on 6/28/16.
// Copyright © 2016 Memory Jar. All rights reserved.

#import <UIKit/UIKit.h>
@interface BagTableViewController : UITableViewController
@end

// BagTableViewController.m
// Created by Chris Stahl on 6/28/16.
// Copyright © 2016 Memory Jar. All rights reserved.

#import "BagTableViewController.h"
#import "OrderItemTableViewCell.h"
#import "Constants.h"
#import "User.h"
#import "Order.h"
#import "OrderItem.h"
#import "Product.h"
#import "UserProfileTableViewController.h"
#import "UserPaymentMethodTableViewController.h"
#import "PaymentMethod.h"
#import "AddCreditCardViewController.h"
#import "SVProgressHUD/SVProgressHUD.h"
#import "PassKit/PassKit.h"

@interface BagTableViewController () <PKPaymentAuthorizationViewControllerDelegate>
@property (nonatomic, weak) IBOutlet UILabel *orderNoLabel;
@property (nonatomic, weak) IBOutlet UILabel *orderDateLabel;
@property (nonatomic, weak) IBOutlet UILabel *totalLabel;
@property (nonatomic, weak) IBOutlet UILabel *totalTextLabel;

@property (nonatomic, weak) IBOutlet UIButton *payWithCCButton;
@property (nonatomic, weak) IBOutlet UIButton *payWithApplePayButton;
@property (nonatomic, strong) Order *order;
@property (nonatomic, weak) NSArray *creditCards;
@property (nonatomic) NSDecimalNumber *amount;
@property (nonatomic, strong) PKPaymentRequest *paymentRequest;
@end

@implementation BagTableViewController

-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if ([User currentUser]) {
[self queryForUnfinishedOrder];
}
}

- (void)viewDidLoad {
[self.refreshControl addTarget:self action:@selector(queryForUnfinishedOrder) forControlEvents:UIControlEventValueChanged];
}

-(void)viewWillDisappear:(BOOL)animated {
if (self.order && self.order.isDirty) {
[self.order saveInBackground];
}
}

-(IBAction)queryForUnfinishedOrder {
self.order = nil; //to get ride of the cache
PFQuery *orderQuery = [Order queryForCustomer:[User currentUser] orderStatus:ORDER_NOT_MADE];
__weak typeof(self) weakSelf = self;
[orderQuery getFirstObjectInBackgroundWithBlock:^(PFObject *order, NSError *error){
if ([weakSelf.refreshControl isRefreshing]) {
[weakSelf.refreshControl endRefreshing];
}
if (!error) {
if (order) {
weakSelf.order = (Order *)order;
weakSelf.orderNoLabel.text = @"";
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[dateFormatter setTimeStyle:NSDateFormatterShortStyle];
weakSelf.orderDateLabel.text = [dateFormatter stringFromDate:[NSDate date]];
weakSelf.totalLabel.text = [self.order friendlyTotal];
[weakSelf updateUI];
} else {
[weakSelf updateUI];
}

} else {
[weakSelf updateUI];
}
}];
}

-(void)updateUI {
BOOL shouldClear = self.order == nil;
if (shouldClear) {
self.orderNoLabel.text = NSLocalizedString(@"Your bag is empty.", @"");
self.orderDateLabel.text = @"";
self.totalLabel.text = @"";
self.totalTextLabel.text = @"";
self.payWithApplePayButton.hidden = YES;
self.payWithCCButton.hidden = YES;
self.payWithApplePayButton.enabled = NO;
self.payWithCCButton.enabled = NO;
} else {
self.totalTextLabel.text = NSLocalizedString(@"Total: ", @"");
self.payWithApplePayButton.hidden = NO;
self.payWithCCButton.hidden = NO;
self.payWithApplePayButton.enabled = YES;
self.payWithCCButton.enabled = YES;
}
[self.tableView reloadData];
}

#pragma Mark --- APPLE PAY PROCESS
-(IBAction)onApplePay:(id)sender{
NSString *merchantId = kAppleMerchatID;
self.paymentRequest = [Stripe paymentRequestWithMerchantIdentifier:merchantId];
if ([Stripe canSubmitPaymentRequest:self.paymentRequest]) {
[self.paymentRequest setRequiredShippingAddressFields:PKAddressFieldPostalAddress];
[self.paymentRequest setRequiredBillingAddressFields:PKAddressFieldPostalAddress];
self.paymentRequest.paymentSummaryItems = [self summaryItemsForShippingMethod:nil];
PKPaymentAuthorizationViewController *auth = [[PKPaymentAuthorizationViewController alloc] initWithPaymentRequest:self.paymentRequest];
auth.delegate = self;
if (auth) {
[self presentViewController:auth animated:YES completion:nil];
} else
[SVProgressHUD showErrorWithStatus:NSLocalizedString(@"Something Wrong", @"Something Wrong")];
} else {
[SVProgressHUD showErrorWithStatus:NSLocalizedString(@"Apple Pay is not enabled. Please enable your Apple Pay or Pay with Credit Card.", @"")];
}
}
-(void)paymentAuthorizationViewController:(nonnull PKPaymentAuthorizationViewController *) controller didAuthorizePayment:(nonnull PKPayment *)payment completion:(nonnull void (^)(PKPaymentAuthorizationStatus))completion{
[self handlePaymentAuthorizationWithPayment:payment completion:nil];
}
-(void)paymentAuthorizationViewControllerDidFinish:(nonnull PKPaymentAuthorizationViewController *)controller {
[self dismissViewControllerAnimated:YES completion:nil];
[self queryForUnfinishedOrder];
}
- (void)handlePaymentAuthorizationWithPayment:(PKPayment *)payment completion:(void (^)(PKPaymentAuthorizationStatus))completion {
[[STPAPIClient sharedClient] createTokenWithPayment:payment
completion:^(STPToken *token, NSError *error) {
if (error) {
completion(PKPaymentAuthorizationStatusFailure);
return;
}
[self createBackendChargeWithToken:token completion:completion];
}];
}
- (void)createBackendChargeWithToken:(STPToken *)token completion:(void (^)(PKPaymentAuthorizationStatus))completion {
[self chargeWithToken:token.tokenId];
}
-(void)chargeWithToken:(NSString *)tokenId {
[self.order saveInBackgroundWithBlock:^(BOOL success, NSError *error){
if (!error) {
__weak typeof(self) weakSelf = self;
NSDictionary *params = @{@"chargeToken":tokenId, @"orderId":weakSelf.order.objectId};
[PFCloud callFunctionInBackground:@"chargeToken" withParameters:params block:^(NSString *message, NSError *error){
if (!error) {
[weakSelf queryForUnfinishedOrder];

}
}];
}
}];

}

#pragma mark - Credit Card Process
-(IBAction)onPayWithCreditCard:(id)sender{
if ([[User currentUser] isShippingAddressCompleted]) {
[self inputCreditCard];
} else {
UserProfileTableViewController *viewController = [self.storyboard instantiateViewControllerWithIdentifier:@"UserProfileTableViewController"];
[self.navigationController pushViewController:viewController animated:YES];
}
}

- (void)inputCreditCard {
AddCreditCardViewController *addCreditCardViewController = (AddCreditCardViewController *)[self.storyboard instantiateViewControllerWithIdentifier:@"AddCreditCardViewController"];
__weak typeof(self) weakSelf = self;
addCreditCardViewController.finishBlock = ^(NSString *customerId){
[weakSelf charge:customerId];
};
[self.navigationController pushViewController:addCreditCardViewController animated:YES];
}

-(void)charge:(NSString *)customerId {
[self.order saveInBackgroundWithBlock:^(BOOL success, NSError *error){
if (!error) {
__weak typeof(self) weakSelf = self;
NSDictionary *params = @{@"chargeCustomer":customerId, @"orderId":weakSelf.order.objectId};
[PFCloud callFunctionInBackground:@"chargeCustomer" withParameters:params block:^(NSString *message, NSError *error){
if (!error) {
[weakSelf queryForUnfinishedOrder];
}
}];
}
}];
}

- (NSArray *)summaryItemsForShippingMethod:(PKShippingMethod *)shippingMethod {
NSMutableArray *purchasedItems = [NSMutableArray arrayWithCapacity:[self.order.items count]];
for (OrderItem *item in self.order.items) {
double total = item.quantity * item.product.unitPrice;
NSDecimalNumber *price = [NSDecimalNumber decimalNumberWithMantissa:total exponent:-2 isNegative:NO];
PKPaymentSummaryItem *purchasedItem = [PKPaymentSummaryItem summaryItemWithLabel:item.product.name amount:price];
[purchasedItems addObject:purchasedItem];
}
return [NSArray arrayWithArray:purchasedItems];
}


-(IBAction)onStepper:(id)sender {
UIStepper *stepper = (UIStepper *)sender;
NSInteger index = stepper.tag - 100;
NSMutableArray *orderItems = [NSMutableArray arrayWithArray:self.order.items];
OrderItem *orderItem = orderItems[index];
orderItem.quantity = (int)stepper.value;
if ((int)stepper.value == 0) {
[orderItems removeObjectAtIndex:index];
} else {
[orderItems replaceObjectAtIndex:index withObject:orderItem];
}
if ([orderItems count] == 0) {
[self showDeleteAlert];
} else {
self.order.items = [orderItems copy];
[self.tableView reloadData];
self.totalLabel.text = [self.order friendlyTotal];
}
}

#pragma mark - Table view data source
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 80.0;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.order.items count];
}

- (OrderItemTableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
OrderItemTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BagItemCell" forIndexPath:indexPath];
if (self.order) [cell configureItem:self.order.items[indexPath.row] tag:indexPath.row];
else [cell configureItem:nil tag:100+indexPath.row];
return cell;
}

-(void)showDeleteAlert {
UIAlertController* alert = [UIAlertController alertControllerWithTitle:NSLocalizedString
(@"Empty Bag",@"")
message:NSLocalizedString(@"Are you sure you want to empty your bag?",@"")
preferredStyle:UIAlertControllerStyleAlert];
__weak typeof(self) weakSelf = self;
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString
(@"Yes",@"") style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[weakSelf.order deleteInBackgroundWithBlock:^(BOOL success, NSError *error){
if (!error) {
[weakSelf queryForUnfinishedOrder];
} }];
}];
UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:NSLocalizedString
(@"cancel",@"") style:UIAlertActionStyleCancel
handler:^(UIAlertAction * action) {}];
[alert addAction:defaultAction];
[alert addAction:cancelAction];
[self presentViewController:alert animated:YES completion:nil];
}
@end

// AddCreditCardViewController.h
// Created by Chris Stahl on 6/28/16.
// Copyright © 2016 Memory Jar. All rights reserved.

#import <UIKit/UIKit.h>
@class AddCreditCardViewController;
typedef void (^AddCreditCardViewControllerDidFinish)(NSString *customerId);
@interface AddCreditCardViewController : UIViewController
@property (nonatomic, copy) AddCreditCardViewControllerDidFinish finishBlock;
@end

// AddCreditCardViewController.m
// Created by Chris Stahl on 6/28/16.
// Copyright © 2016 Memory Jar. All rights reserved.

#import "AddCreditCardViewController.h"
#import "Stripe/Stripe.h"
#import "User.h"
#import "PaymentMethod.h"

@interface AddCreditCardViewController ()<STPPaymentCardTextFieldDelegate>
@property (nonatomic, weak) IBOutlet STPPaymentCardTextField *paymentView;
@property (weak, nonatomic) UIActivityIndicatorView *activityIndicator;
@end

@implementation AddCreditCardViewController

- (void)viewDidLoad {
[super viewDidLoad];

self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(onCancel:)];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Authorize", @"") style:UIBarButtonItemStylePlain target:self action:@selector(onAuthorize:)];

UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityIndicator.hidesWhenStopped = YES;
self.activityIndicator = activityIndicator;
[self.view addSubview:activityIndicator];
}
- (void)paymentView:(STPPaymentCardTextField *)paymentView withCard:(STPPaymentCardTextField *)card isValid:(BOOL)valid {
self.navigationItem.rightBarButtonItem.enabled = valid;
}
- (void)paymentCardTextFieldDidChange:(STPPaymentCardTextField *)textField {
self.navigationItem.rightBarButtonItem.enabled = textField.isValid;
}
- (void)onCancel:(id)sender {
[self.navigationController popViewControllerAnimated:YES];
}


#pragma mark - Authorize the payment (get paid)

- (void)onAuthorize:(id)sender {
if (![self.paymentView isValid]) {
return;
}

STPCardParams *card = [[STPCardParams alloc] init];
card.number = self.paymentView.cardParams.number;
card.expMonth = self.paymentView.cardParams.expMonth;
card.expYear = self.paymentView.cardParams.expYear;
card.cvc = self.paymentView.cardParams.cvc;

__weak typeof(self) weakSelf = self;

[[STPAPIClient sharedClient] createTokenWithCard:card
completion:^(STPToken *token, NSError *error) {
if (error) {
} else {
User *user = [User currentUser];
NSDictionary *stripeCustomerDictionary = @{@"tokenId":token.tokenId, @"customerEmail":user.email};


[PFCloud callFunctionInBackground:@"createStripeCustomer" withParameters:stripeCustomerDictionary block:^(NSString *customerId, NSError *error) {

if (!error) {
PaymentMethod *creditCard = [PaymentMethod object];
creditCard.owner = user;
creditCard.stripeCustomerId = customerId;
creditCard.expirationMonth = card.expMonth;
creditCard.expirationYear = card.expYear;
creditCard.type = [creditCard friendlyType:(STPCardBrand)creditCard];
creditCard.lastFourDigit = card.last4;
creditCard.stripeCustomerId = customerId;

[creditCard saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error) {
[weakSelf readyToCharge:customerId];
}
}];

} else {

}
}];
}
}];
}

-(void)readyToCharge:(NSString *)customerId {
self.finishBlock(customerId);
[self.navigationController popViewControllerAnimated:YES];
}
@end




Parse.Cloud.define("sendNotification", function(request, response) {
var query = new Parse.Query(Parse.Installation);
var userObj = new Parse.User({
id: request.params.userId
});
query.equalTo("user", userObj);
Parse.Push.send({
where: query,
data: {
alert: request.params.message
}
}, {
success: function() {
response.success(0);
},
error: function(error) {
response.error('push notification error');
}
});
});


var Stripe = require('stripe');
Stripe.initialize('sk_test_xxx');

var STRIPE_API_BASE_URL = 'api.stripe.com/v1';
Stripe.initialize('sk_test_xxx');

var Mailgun = require('mailgun');
Mailgun.initialize("Memory_Jar", "pubkey-xxx");



//Create a stripe customer
Parse.Cloud.define("createStripeCustomer", function(request, response) {
Parse.Cloud.useMasterKey();
Parse.Promise.as().then(function() {
return Stripe.Customers.create({
description: 'customer for Memory Jar',
card: request.params.tokenId,
email: request.params.customerEmail,

}).then(null, function(error) {
console.log('Creating customer with stripe failed. Error: ' + error);
return Parse.Promise.error('An error has occurred.');
});

}).then(function(customer) {
response.success(customer.id);

}, function(error) {
response.error('error with customer creation');
});
});



//Charge the customer
Parse.Cloud.define("chargeCustomer", function(request, response) {
Parse.Cloud.useMasterKey();
var order;
var orderNo;
var total;

Parse.Promise.as().then(function() {
var orderQuery = new Parse.Query('Order');
orderQuery.equalTo('objectId', request.params.orderId);
orderQuery.include("customer");
orderQuery.include(["items.product"]);
orderQuery.descending("createdAt");

return orderQuery.first().then(null, function(error) {
return Parse.Promise.error('Sorry, this order doesn\'t exist.');
});

}).then(function(result) {
order = result;

var items = order.get("items");
for (var i = 0; i < items.length; i++) {
var item = items[i];
var unitPrice = item.get("product").get("unitPrice");
var quantity = item.get("quantity");
total += unitPrice * quantity;
}

}).then(function(result) {
var countQuery = new Parse.Query("Order");
return countQuery.count().then(null, function(error) {
return Parse.Promise.error('Something wrong.');
});

}).then(function(result) {
orderNo = result;

}).then(function(order) {
return Stripe.Charges.create({
amount: 10000, //total.toFixed(2)*100, // express dollars in cents
currency: 'usd',
customer: request.params.customerId


}).then(null, function(error) {
console.log('Charging with stripe failed. Error: ' + error);
return Parse.Promise.error('An error has occurred. Your credit card was not charged.');
});

}).then(function(purchase) {
orderNo = 1000000 + orderNo + 1;

order.set('stripePaymentId', purchase.id);
order.set('orderStatus', 1);
order.set('orderNo', orderNo);
return order.save().then(null, function(error) {
return Parse.Promise.error('A critical error has occurred with your order. Please ' + 'contact us at your earliest convinience. ');
});

}).then(function(order) {
var greeting = "Dear ";
// if (request.params.firstName !== "N/A") greeting += request.params.firstName + ",\n\n";
// var orderId = "Order No. " + orderNo + "\n";
var body = greeting + orderId + "We have received your order for the following item(s): \n\n" + request.params.itemDesc + "\n";
var note = "Note: " + request.params.note + "\n\n";
body += "\Total: $" + 1000 + "\n\n"; //total.toFixed(2)
var thankyou = "Contact us if you have any question!\n\n" + "\n Thank you,\n";
body += thankyou;

return Mailgun.sendEmail({
to: request.params.email,
bcc: 'CUSTOMER-EMAIL',
from: 'YOUR-EMAIL',
subject: '',
text: body

}).then(null, function(error) {
return Parse.Promise.error('Your purchase was successful, but we were not able to ' + 'send you an email. Contact us at customer.service@memoryjar.com ' + 'you have any questions.');
});

}).then(function(charge) {
response.success(charge.id);
},
function(error) {
response.error(error);
});
});


//Create Stripe token for charged customer
Parse.Cloud.define("chargeToken", function(request, response) {
Parse.Cloud.useMasterKey();
var order;
var orderNo;
var total;

Parse.Promise.as().then(function() {
var orderQuery = new Parse.Query('Order');
orderQuery.equalTo('objectId', request.params.orderId);
orderQuery.include("customer");
orderQuery.include(["items.product"]);
orderQuery.descending("createdAt");
return orderQuery.first().then(null, function(error) {
return Parse.Promise.error('Sorry, this order doesn\'t exist.');
});
}).then(function(result) {
order = result;
var items = order.get("items");
for (var i = 0; i < items.length; i++) {
var item = items[i];
var unitPrice = item.get("product").get("unitPrice");
var quantity = item.get("quantity");
total += unitPrice * quantity;
}

}).then(function(result) {
var countQuery = new Parse.Query("Order");
return countQuery.count().then(null, function(error) {
return Parse.Promise.error('Something wrong.');
});

}).then(function(result) {
orderNo = result;

}).then(function(order) {
return Stripe.Charges.create({
amount: 10000, //amount: total.toFixed(2)*100, // express dollars in cents
currency: 'usd',
card: request.params.chargeToken,


}).then(null, function(error) {
console.log('Charging with stripe failed. Error: ' + error);
return Parse.Promise.error('An error has occurred. Your credit card was not charged.');
});

}).then(function(purchase) {
orderNo = 1000000 + orderNo + 1;
order.set('orderStatus', 1); // order made
order.set('orderNo', orderNo);
order.set('stripePaymentId', purchase.id);
return order.save().then(null, function(error) {
return Parse.Promise.error('A critical error has occurred with your order. Please ' + 'contact us at your earliest convinience. ');
});

}).then(function(result) {
var greeting = "Dear ";
// if (order.customer.firstName !== "N/A") greeting +=
// order.customer.firstName + ",\n\n";
var orderId = "Order No. " + orderNo + "\n";
var body = greeting + orderId + " We have received your order for the following item(s): \n\n" + request.params.itemDesc + "\n";
body += "\Total: $" + 1000 + "\n\n";
var thankyou = "Contact us if you have any question!\n\n" + "\n Thank you,\n";
body += thankyou;

return Mailgun.sendEmail({
to: 'chris.stahl12@gmail.com', //order.customer.email,
from: 'YOUR-CONTACT-EMAIL',
subject: 'Your order was successful!',
text: body

}).then(null, function(error) {
return Parse.Promise.error('Your purchase was successful, but we were not able to ' + 'send you an email. Contact us if you have any questions.');
});

}).then(function() {
response.success('Success');
}, function(error) {
response.error(error);
});
});


Parse.Cloud.define("StripeUserCards", function(request, response) {
Parse.Cloud.httpRequest({
method: "GET",
url: "https://" + 'sk_test_bSJVNSp6BUre8e6wOzxhHYgQ' + ':@' + STRIPE_API_BASE_URL + "/customers/" + request.params.customer_id + "/cards",
success: function(cards) {
response.success(cards["data"]);
},
error: function(httpResponse) {
response.error('Request failed with response code ' + httpResponse.status);
}
});
});




Answer

So, I finally figured this out. It was confusing but I was sending the wrong variables to the javascript from the AddCreditCard and Bag table view controllers.

Inside the AddCreditCardViewController, we create the stripe customer for either an Apple Pay or Credit Card Transation. The dictionary is:

NSDictionary *stripeCustomerDictionary = @{@"token":token.tokenId,
                                           @"email":user.email 
};

and the corresponding javascript Parse Cloud Code is:

return Stripe.Customers.create({

                            description: 'customer for Memory Jar',
                            card: request.params.token,
                            email: request.params.email,

Then in BagTableViewController the dictionary for ChargeToken (Apple Pay) is:

NSDictionary *params = @{@"chargeToken":tokenId,
                       @"amount":[NSNumber numberWithDouble:(weakSelf.order.total*100)],
                           @"orderId":weakSelf.order.objectId 
};

and the corresponding call for ChargeToken in the javascript Parse Cloud Code is:

return Stripe.Charges.create({
                             amount: request.params.amount,  // express dollars in cents
                             currency: 'usd',
                             card: request.params.chargeToken

for ChargeCustomer (Credit Card) it is:

NSDictionary *params = @{
                       @"customerId":customerId,
                       @"amount":[NSNumber numberWithDouble:(weakSelf.order.total*100)],
                       @"orderId":weakSelf.order.objectId
};

and the corresponding call in the javascript Parse Cloud Code is:

return Stripe.Charges.create({

                        customer: request.params.customerId,
                        currency: 'usd',
                        amount: request.params.amount  // express dollars in cents

The error was being produced because I did not have the dictionary's aligned. I thought the first word in the javascript was the corresponding word in the Objective C code, but I was wrong.

The last word is what javascript looks for in the ObjC code.

Ex: card: request.params.chargeToken (called in ChargeToken)

lines up with @"chargeToken":tokenId in the NSDictionary. Javascript is asking you to provide the dictionary definition for the chargeToken which is then passed back as the tokenId.

I hope this helps someone along the way. I lost about 8 days to this problem.