Stripe 集成 iOS 电子商务解析与 JavaScript 云代码
Stripe Integration iOS eCommerce Parse with JavaScript Cloud Code
如果您知道如何集成这些程序,请帮忙。我花了 3 天时间试图解决这个问题。
强文本
我正在我的应用程序中构建一个电子商务平台,用于接受信用卡和 Apple Pay 付款。此过程中包含两个视图控制器,BagTableViewController 和 AddCreditCardViewController。解析云代码也有 javascript。我已经包含了所有代码。
我的 pods 都是最新的并且已经将 Parse JavaScript SDK 恢复到版本 1.5.0 因为 parse 没有更新他们的库。
当我尝试在 AddCreditCardViewController 中授权信用卡时,出现了我当前的问题。输入 Stripe 提供的测试信用卡信息后,用户然后按下授权栏按钮。
当我按下 "Authorize" 时,Stripe 会创建令牌和客户,但不会向客户收费。相反,我在 Xcode:
中收到此错误
[Bolts] 警告:BFTask
在继续块中发现异常。不鼓励这种行为,并将在未来的版本中删除。捕获到异常:*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]:尝试从对象 [1]
中插入 nil 对象
在尝试调试此问题时,我将此行定位为断点启动点而不是错误。
NSDictionary *params = @{@"chargeCustomer":customerId, @"orderId":weakSelf.order.objectId};
这是有道理的,因为错误发生在 Stripe 上,因此不会在 Stripe 上创建费用,而下一行是 PFCloud callFuctionInBackround 向客户收费。
关于此错误,我能找到的信息很少,但我相信我将错误的信息传递到 *params 的 NSDictionary 中。
有人可以帮我吗?我完全迷路了
// 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);
}
});
});
所以,我终于想通了。这令人困惑,但我从 AddCreditCard 和 Bag table 视图控制器向 javascript 发送了错误的变量。
在 AddCreditCardViewController 中,我们为 Apple Pay 或信用卡交易创建条带客户。字典是:
NSDictionary *stripeCustomerDictionary = @{@"token":token.tokenId,
@"email":user.email
};
对应的javascript解析云码为:
return Stripe.Customers.create({
description: 'customer for Memory Jar',
card: request.params.token,
email: request.params.email,
然后在 BagTableViewController 中,ChargeToken (Apple Pay) 的字典是:
NSDictionary *params = @{@"chargeToken":tokenId,
@"amount":[NSNumber numberWithDouble:(weakSelf.order.total*100)],
@"orderId":weakSelf.order.objectId
};
而在 javascript 解析云代码中对 ChargeToken 的相应调用是:
return Stripe.Charges.create({
amount: request.params.amount, // express dollars in cents
currency: 'usd',
card: request.params.chargeToken
对于 ChargeCustomer(信用卡)是:
NSDictionary *params = @{
@"customerId":customerId,
@"amount":[NSNumber numberWithDouble:(weakSelf.order.total*100)],
@"orderId":weakSelf.order.objectId
};
而javascript解析云代码中对应的调用是:
return Stripe.Charges.create({
customer: request.params.customerId,
currency: 'usd',
amount: request.params.amount // express dollars in cents
出现错误是因为我没有对齐字典。我以为 javascript 中的第一个单词是 Objective C 代码中的对应单词,但我错了。
最后一个词是 javascript 在 ObjC 代码中查找的内容。
例如:card: request.params.chargeToken
(在 ChargeToken 中调用)
与 NSDictionary 中的 @"chargeToken":tokenId
对齐。 Javascript 要求您提供 chargeToken 的字典定义,然后将其作为 tokenId 传回。
我希望这对一路走来的人有所帮助。我在这个问题上浪费了大约 8 天的时间。
如果您知道如何集成这些程序,请帮忙。我花了 3 天时间试图解决这个问题。 强文本
我正在我的应用程序中构建一个电子商务平台,用于接受信用卡和 Apple Pay 付款。此过程中包含两个视图控制器,BagTableViewController 和 AddCreditCardViewController。解析云代码也有 javascript。我已经包含了所有代码。
我的 pods 都是最新的并且已经将 Parse JavaScript SDK 恢复到版本 1.5.0 因为 parse 没有更新他们的库。
当我尝试在 AddCreditCardViewController 中授权信用卡时,出现了我当前的问题。输入 Stripe 提供的测试信用卡信息后,用户然后按下授权栏按钮。
当我按下 "Authorize" 时,Stripe 会创建令牌和客户,但不会向客户收费。相反,我在 Xcode:
中收到此错误[Bolts] 警告:BFTask
在继续块中发现异常。不鼓励这种行为,并将在未来的版本中删除。捕获到异常:*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]:尝试从对象 [1]
在尝试调试此问题时,我将此行定位为断点启动点而不是错误。
NSDictionary *params = @{@"chargeCustomer":customerId, @"orderId":weakSelf.order.objectId};
这是有道理的,因为错误发生在 Stripe 上,因此不会在 Stripe 上创建费用,而下一行是 PFCloud callFuctionInBackround 向客户收费。
关于此错误,我能找到的信息很少,但我相信我将错误的信息传递到 *params 的 NSDictionary 中。
有人可以帮我吗?我完全迷路了
// 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);
}
});
});
所以,我终于想通了。这令人困惑,但我从 AddCreditCard 和 Bag table 视图控制器向 javascript 发送了错误的变量。
在 AddCreditCardViewController 中,我们为 Apple Pay 或信用卡交易创建条带客户。字典是:
NSDictionary *stripeCustomerDictionary = @{@"token":token.tokenId,
@"email":user.email
};
对应的javascript解析云码为:
return Stripe.Customers.create({
description: 'customer for Memory Jar',
card: request.params.token,
email: request.params.email,
然后在 BagTableViewController 中,ChargeToken (Apple Pay) 的字典是:
NSDictionary *params = @{@"chargeToken":tokenId,
@"amount":[NSNumber numberWithDouble:(weakSelf.order.total*100)],
@"orderId":weakSelf.order.objectId
};
而在 javascript 解析云代码中对 ChargeToken 的相应调用是:
return Stripe.Charges.create({
amount: request.params.amount, // express dollars in cents
currency: 'usd',
card: request.params.chargeToken
对于 ChargeCustomer(信用卡)是:
NSDictionary *params = @{
@"customerId":customerId,
@"amount":[NSNumber numberWithDouble:(weakSelf.order.total*100)],
@"orderId":weakSelf.order.objectId
};
而javascript解析云代码中对应的调用是:
return Stripe.Charges.create({
customer: request.params.customerId,
currency: 'usd',
amount: request.params.amount // express dollars in cents
出现错误是因为我没有对齐字典。我以为 javascript 中的第一个单词是 Objective C 代码中的对应单词,但我错了。
最后一个词是 javascript 在 ObjC 代码中查找的内容。
例如:card: request.params.chargeToken
(在 ChargeToken 中调用)
与 NSDictionary 中的 @"chargeToken":tokenId
对齐。 Javascript 要求您提供 chargeToken 的字典定义,然后将其作为 tokenId 传回。
我希望这对一路走来的人有所帮助。我在这个问题上浪费了大约 8 天的时间。