Updates Custom Integration sample app to use Payment Methods (#1159)
* Updates Custom Integration sample app to use Payment Methods * Update to actually pass payment method ID. * Update Example/Custom Integration/CardExampleViewController.h Co-Authored-By: csabol-stripe <36750494+csabol-stripe@users.noreply.github.com> * Update Stripe/PublicHeaders/STPPaymentMethodCardParams.h Co-Authored-By: csabol-stripe <36750494+csabol-stripe@users.noreply.github.com> * Clean up from review. * Fixes manual confirmation and bug with calling redirect off of the main thread. * Moves Standard Integration to payment intents. * Fix path * Update Example/Custom Integration/CardAutomaticConfirmationViewController.m Co-Authored-By: csabol-stripe <36750494+csabol-stripe@users.noreply.github.com> * Fixes scheme names in run_builds.sh
This commit is contained in:
parent
cc51af51f0
commit
690c3db2f0
|
@ -1,32 +0,0 @@
|
|||
//
|
||||
// BrowseExamplesViewController.h
|
||||
// Custom Integration (ObjC)
|
||||
//
|
||||
// Created by Ben Guo on 2/17/17.
|
||||
// Copyright © 2017 Stripe. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <Stripe/Stripe.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, STPBackendResult) {
|
||||
STPBackendResultSuccess,
|
||||
STPBackendResultFailure,
|
||||
};
|
||||
|
||||
typedef void (^STPSourceSubmissionHandler)(STPBackendResult status, NSError *error);
|
||||
typedef void (^STPPaymentIntentCreationHandler)(STPBackendResult status, NSString *clientSecret, NSError *error);
|
||||
|
||||
|
||||
@protocol ExampleViewControllerDelegate <NSObject>
|
||||
|
||||
- (void)exampleViewController:(UIViewController *)controller didFinishWithMessage:(NSString *)message;
|
||||
- (void)exampleViewController:(UIViewController *)controller didFinishWithError:(NSError *)error;
|
||||
- (void)createBackendChargeWithSource:(NSString *)sourceID completion:(STPSourceSubmissionHandler)completion;
|
||||
- (void)createBackendPaymentIntentWithAmount:(NSNumber *)amount completion:(STPPaymentIntentCreationHandler)completion;
|
||||
|
||||
@end
|
||||
|
||||
@interface BrowseExamplesViewController : UITableViewController
|
||||
|
||||
@end
|
|
@ -1,239 +0,0 @@
|
|||
//
|
||||
// BrowseExamplesViewController.m
|
||||
// Custom Integration (ObjC)
|
||||
//
|
||||
// Created by Ben Guo on 2/17/17.
|
||||
// Copyright © 2017 Stripe. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Stripe/Stripe.h>
|
||||
#import "BrowseExamplesViewController.h"
|
||||
#import "ApplePayExampleViewController.h"
|
||||
#import "CardExampleViewController.h"
|
||||
#import "Constants.h"
|
||||
#import "SofortExampleViewController.h"
|
||||
#import "ThreeDSExampleViewController.h"
|
||||
#import "ThreeDSPaymentIntentExampleViewController.h"
|
||||
|
||||
/**
|
||||
This view controller presents different examples, each of which demonstrates creating a payment using a different payment method.
|
||||
If the example creates a chargeable source or a token, `createBackendChargeWithSource:completion:` will be called to tell our
|
||||
example backend to create the charge request.
|
||||
*/
|
||||
@interface BrowseExamplesViewController () <ExampleViewControllerDelegate>
|
||||
@end
|
||||
|
||||
@implementation BrowseExamplesViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
self.title = @"Examples";
|
||||
self.navigationController.navigationBar.translucent = NO;
|
||||
self.tableView.tableFooterView = [UIView new];
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
return 5;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
UITableViewCell *cell = [UITableViewCell new];
|
||||
switch (indexPath.row) {
|
||||
case 0:
|
||||
cell.textLabel.text = @"Card";
|
||||
break;
|
||||
case 1:
|
||||
cell.textLabel.text = @"Card + 3DS";
|
||||
break;
|
||||
case 2:
|
||||
cell.textLabel.text = @"Apple Pay";
|
||||
break;
|
||||
case 3:
|
||||
cell.textLabel.text = @"Sofort";
|
||||
break;
|
||||
case 4:
|
||||
cell.textLabel.text = @"PaymentIntent: Card + 3DS";
|
||||
break;
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
UIViewController *viewController;
|
||||
switch (indexPath.row) {
|
||||
case 0: {
|
||||
CardExampleViewController *exampleVC = [CardExampleViewController new];
|
||||
exampleVC.delegate = self;
|
||||
viewController = exampleVC;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
ThreeDSExampleViewController *exampleVC = [ThreeDSExampleViewController new];
|
||||
exampleVC.delegate = self;
|
||||
viewController = exampleVC;
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
ApplePayExampleViewController *exampleVC = [ApplePayExampleViewController new];
|
||||
exampleVC.delegate = self;
|
||||
viewController = exampleVC;
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
SofortExampleViewController *exampleVC = [SofortExampleViewController new];
|
||||
exampleVC.delegate = self;
|
||||
viewController = exampleVC;
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
ThreeDSPaymentIntentExampleViewController *exampleVC = [ThreeDSPaymentIntentExampleViewController new];
|
||||
exampleVC.delegate = self;
|
||||
viewController = exampleVC;
|
||||
break;
|
||||
}
|
||||
}
|
||||
[self.navigationController pushViewController:viewController animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - STPBackendCharging
|
||||
|
||||
- (void)createBackendChargeWithSource:(NSString *)sourceID completion:(STPSourceSubmissionHandler)completion {
|
||||
if (!BackendBaseURL) {
|
||||
NSError *error = [NSError errorWithDomain:StripeDomain
|
||||
code:STPInvalidRequestError
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"You must set a backend base URL in Constants.m to create a charge."}];
|
||||
completion(STPBackendResultFailure, error);
|
||||
return;
|
||||
}
|
||||
|
||||
// This passes the token off to our payment backend, which will then actually complete charging the card using your Stripe account's secret key
|
||||
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
|
||||
|
||||
NSString *urlString = [BackendBaseURL stringByAppendingPathComponent:@"create_charge"];
|
||||
NSURL *url = [NSURL URLWithString:urlString];
|
||||
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
|
||||
request.HTTPMethod = @"POST";
|
||||
NSString *postBody = [NSString stringWithFormat:
|
||||
@"source=%@&amount=%@&metadata[charge_request_id]=%@",
|
||||
sourceID,
|
||||
@1099,
|
||||
// example-ios-backend allows passing metadata through to Stripe
|
||||
@"B3E611D1-5FA1-4410-9CEC-00958A5126CB"];
|
||||
NSData *data = [postBody dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request
|
||||
fromData:data
|
||||
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
||||
if (!error && httpResponse.statusCode != 200) {
|
||||
error = [NSError errorWithDomain:StripeDomain
|
||||
code:STPInvalidRequestError
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"There was an error connecting to your payment backend."}];
|
||||
}
|
||||
if (error) {
|
||||
completion(STPBackendResultFailure, error);
|
||||
}
|
||||
else {
|
||||
completion(STPBackendResultSuccess, nil);
|
||||
}
|
||||
}];
|
||||
|
||||
[uploadTask resume];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Ask the example backend to create a PaymentIntent with the specified amount.
|
||||
|
||||
The implementation of this function is not interesting or relevant to using PaymentIntents. The
|
||||
method signature is the most interesting part: you need some way to ask *your* backend to create
|
||||
a PaymentIntent with the correct properties, and then it needs to pass the client secret back.
|
||||
|
||||
@param amount Amount to charge the customer
|
||||
@param completion completion block called with status of backend call & the client secret if successful.
|
||||
*/
|
||||
- (void)createBackendPaymentIntentWithAmount:(NSNumber *)amount completion:(STPPaymentIntentCreationHandler)completion {
|
||||
if (!BackendBaseURL) {
|
||||
NSError *error = [NSError errorWithDomain:StripeDomain
|
||||
code:STPInvalidRequestError
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"You must set a backend base URL in Constants.m to create a charge."}];
|
||||
completion(STPBackendResultFailure, nil, error);
|
||||
return;
|
||||
}
|
||||
|
||||
// This asks the backend to create a PaymentIntent for us, which can then be passed to the Stripe SDK to confirm
|
||||
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
|
||||
|
||||
NSString *urlString = [BackendBaseURL stringByAppendingPathComponent:@"create_intent"];
|
||||
NSURL *url = [NSURL URLWithString:urlString];
|
||||
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
|
||||
request.HTTPMethod = @"POST";
|
||||
NSString *postBody = [NSString stringWithFormat:
|
||||
@"amount=%@&metadata[charge_request_id]=%@",
|
||||
amount,
|
||||
// example-ios-backend allows passing metadata through to Stripe
|
||||
@"B3E611D1-5FA1-4410-9CEC-00958A5126CB"
|
||||
];
|
||||
NSData *data = [postBody dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request
|
||||
fromData:data
|
||||
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
||||
if (!error && httpResponse.statusCode != 200) {
|
||||
error = [NSError errorWithDomain:StripeDomain
|
||||
code:STPInvalidRequestError
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"There was an error connecting to your payment backend."}];
|
||||
}
|
||||
if (error || data == nil) {
|
||||
completion(STPBackendResultFailure, nil, error);
|
||||
}
|
||||
else {
|
||||
NSError *jsonError = nil;
|
||||
id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
|
||||
|
||||
if (json &&
|
||||
[json isKindOfClass:[NSDictionary class]] &&
|
||||
[json[@"secret"] isKindOfClass:[NSString class]]) {
|
||||
completion(STPBackendResultSuccess, json[@"secret"], nil);
|
||||
}
|
||||
else {
|
||||
completion(STPBackendResultFailure, nil, jsonError);
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
[uploadTask resume];
|
||||
}
|
||||
|
||||
#pragma mark - ExampleViewControllerDelegate
|
||||
|
||||
- (void)exampleViewController:(UIViewController *)controller didFinishWithMessage:(NSString *)message {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:message preferredStyle:UIAlertControllerStyleAlert];
|
||||
UIAlertAction *action = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) {
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}];
|
||||
[alertController addAction:action];
|
||||
[controller presentViewController:alertController animated:YES completion:nil];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)exampleViewController:(UIViewController *)controller didFinishWithError:(NSError *)error {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:[error localizedDescription] preferredStyle:UIAlertControllerStyleAlert];
|
||||
UIAlertAction *action = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) {
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}];
|
||||
[alertController addAction:action];
|
||||
[controller presentViewController:alertController animated:YES completion:nil];
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
|
@ -1,105 +0,0 @@
|
|||
//
|
||||
// CardExampleViewController.m
|
||||
// Custom Integration (ObjC)
|
||||
//
|
||||
// Created by Ben Guo on 2/22/17.
|
||||
// Copyright © 2017 Stripe. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Stripe/Stripe.h>
|
||||
#import "CardExampleViewController.h"
|
||||
#import "BrowseExamplesViewController.h"
|
||||
|
||||
/**
|
||||
This example demonstrates creating a payment with a credit/debit card. It creates a token
|
||||
using card information collected with STPPaymentCardTextField, and then sends the token
|
||||
to our example backend to create the charge request.
|
||||
*/
|
||||
@interface CardExampleViewController () <STPPaymentCardTextFieldDelegate, UIScrollViewDelegate>
|
||||
@property (weak, nonatomic) STPPaymentCardTextField *paymentTextField;
|
||||
@property (weak, nonatomic) UIActivityIndicatorView *activityIndicator;
|
||||
@property (weak, nonatomic) UIScrollView *scrollView;
|
||||
@end
|
||||
|
||||
@implementation CardExampleViewController
|
||||
|
||||
- (void)loadView {
|
||||
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectZero];
|
||||
scrollView.delegate = self;
|
||||
scrollView.alwaysBounceVertical = YES;
|
||||
scrollView.backgroundColor = [UIColor whiteColor];
|
||||
self.view = scrollView;
|
||||
self.scrollView = scrollView;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.title = @"Card";
|
||||
self.edgesForExtendedLayout = UIRectEdgeNone;
|
||||
|
||||
UIBarButtonItem *buyButton = [[UIBarButtonItem alloc] initWithTitle:@"Pay" style:UIBarButtonItemStyleDone target:self action:@selector(pay)];
|
||||
buyButton.enabled = NO;
|
||||
self.navigationItem.rightBarButtonItem = buyButton;
|
||||
|
||||
STPPaymentCardTextField *paymentTextField = [[STPPaymentCardTextField alloc] init];
|
||||
paymentTextField.delegate = self;
|
||||
paymentTextField.cursorColor = [UIColor purpleColor];
|
||||
paymentTextField.postalCodeEntryEnabled = YES;
|
||||
self.paymentTextField = paymentTextField;
|
||||
[self.view addSubview:paymentTextField];
|
||||
|
||||
UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
|
||||
activityIndicator.hidesWhenStopped = YES;
|
||||
self.activityIndicator = activityIndicator;
|
||||
[self.view addSubview:activityIndicator];
|
||||
}
|
||||
|
||||
- (void)viewDidLayoutSubviews {
|
||||
[super viewDidLayoutSubviews];
|
||||
CGFloat padding = 15;
|
||||
CGFloat width = CGRectGetWidth(self.view.frame) - (padding*2);
|
||||
CGRect bounds = self.view.bounds;
|
||||
self.paymentTextField.frame = CGRectMake(padding, padding, width, 44);
|
||||
self.activityIndicator.center = CGPointMake(CGRectGetMidX(bounds),
|
||||
CGRectGetMaxY(self.paymentTextField.frame) + padding*2);
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
[self.paymentTextField becomeFirstResponder];
|
||||
}
|
||||
|
||||
- (void)paymentCardTextFieldDidChange:(nonnull STPPaymentCardTextField *)textField {
|
||||
self.navigationItem.rightBarButtonItem.enabled = textField.isValid;
|
||||
}
|
||||
|
||||
- (void)pay {
|
||||
if (![self.paymentTextField isValid]) {
|
||||
return;
|
||||
}
|
||||
if (![Stripe defaultPublishableKey]) {
|
||||
[self.delegate exampleViewController:self didFinishWithMessage:@"Please set a Stripe Publishable Key in Constants.m"];
|
||||
return;
|
||||
}
|
||||
[self.activityIndicator startAnimating];
|
||||
[[STPAPIClient sharedClient] createTokenWithCard:self.paymentTextField.cardParams
|
||||
completion:^(STPToken *token, NSError *error) {
|
||||
if (error) {
|
||||
[self.delegate exampleViewController:self didFinishWithError:error];
|
||||
}
|
||||
[self.delegate createBackendChargeWithSource:token.tokenId completion:^(STPBackendResult result, NSError *error) {
|
||||
if (error) {
|
||||
[self.delegate exampleViewController:self didFinishWithError:error];
|
||||
return;
|
||||
}
|
||||
[self.delegate exampleViewController:self didFinishWithMessage:@"Payment successfully created"];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||
[self.view endEditing:NO];
|
||||
}
|
||||
|
||||
@end
|
|
@ -1,17 +0,0 @@
|
|||
//
|
||||
// Card3DSExampleViewController.h
|
||||
// Custom Integration (ObjC)
|
||||
//
|
||||
// Created by Ben Guo on 2/22/17.
|
||||
// Copyright © 2017 Stripe. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@protocol ExampleViewControllerDelegate;
|
||||
|
||||
@interface ThreeDSExampleViewController : UIViewController
|
||||
|
||||
@property (nonatomic, weak) id<ExampleViewControllerDelegate> delegate;
|
||||
|
||||
@end
|
|
@ -1,180 +0,0 @@
|
|||
//
|
||||
// Card3DSExampleViewController.m
|
||||
// Custom Integration (ObjC)
|
||||
//
|
||||
// Created by Ben Guo on 2/22/17.
|
||||
// Copyright © 2017 Stripe. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Stripe/Stripe.h>
|
||||
#import "ThreeDSExampleViewController.h"
|
||||
#import "BrowseExamplesViewController.h"
|
||||
|
||||
/**
|
||||
This example demonstrates using Sources to accept card payments verified using 3D Secure. First, we
|
||||
create a Source using card information collected with STPPaymentCardTextField. If the card Source
|
||||
indicates that 3D Secure is required, we create a 3D Secure Source and redirect the user to authorize the payment.
|
||||
Otherwise, we send the ID of the card Source to our example backend to create the charge request.
|
||||
|
||||
Because 3D Secure payments require further action from the user, we don't tell our backend to create a charge
|
||||
request after creating a 3D Secure Source. Instead, your backend should listen to the `source.chargeable` webhook
|
||||
event to charge the 3D Secure source. See https://stripe.com/docs/sources#best-practices for more information.
|
||||
|
||||
Note that support for 3D Secure is in preview, and must be activated in the dashboard in order
|
||||
for this example to work. You can request an invite at https://dashboard.stripe.com/account/payments/settings
|
||||
*/
|
||||
@interface ThreeDSExampleViewController () <STPPaymentCardTextFieldDelegate>
|
||||
@property (weak, nonatomic) STPPaymentCardTextField *paymentTextField;
|
||||
@property (weak, nonatomic) UILabel *waitingLabel;
|
||||
@property (weak, nonatomic) UIActivityIndicatorView *activityIndicator;
|
||||
@property (nonatomic) STPRedirectContext *redirectContext;
|
||||
@end
|
||||
|
||||
@implementation ThreeDSExampleViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
self.view.backgroundColor = [UIColor whiteColor];
|
||||
self.title = @"Card + 3DS";
|
||||
self.edgesForExtendedLayout = UIRectEdgeNone;
|
||||
|
||||
STPPaymentCardTextField *paymentTextField = [[STPPaymentCardTextField alloc] init];
|
||||
STPCardParams *cardParams = [STPCardParams new];
|
||||
// Only successful 3D Secure transactions on this test card will succeed.
|
||||
cardParams.number = @"4000000000003063";
|
||||
paymentTextField.cardParams = cardParams;
|
||||
paymentTextField.delegate = self;
|
||||
paymentTextField.cursorColor = [UIColor purpleColor];
|
||||
self.paymentTextField = paymentTextField;
|
||||
[self.view addSubview:paymentTextField];
|
||||
|
||||
UILabel *label = [UILabel new];
|
||||
label.text = @"Waiting for payment authorization";
|
||||
[label sizeToFit];
|
||||
label.textColor = [UIColor grayColor];
|
||||
label.alpha = 0;
|
||||
[self.view addSubview:label];
|
||||
self.waitingLabel = label;
|
||||
|
||||
NSString *title = @"Pay";
|
||||
UIBarButtonItem *payButton = [[UIBarButtonItem alloc] initWithTitle:title style:UIBarButtonItemStyleDone target:self action:@selector(pay)];
|
||||
payButton.enabled = paymentTextField.isValid;
|
||||
self.navigationItem.rightBarButtonItem = payButton;
|
||||
|
||||
UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
|
||||
activityIndicator.hidesWhenStopped = YES;
|
||||
self.activityIndicator = activityIndicator;
|
||||
[self.view addSubview:activityIndicator];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
[self.paymentTextField becomeFirstResponder];
|
||||
}
|
||||
|
||||
- (void)viewDidLayoutSubviews {
|
||||
[super viewDidLayoutSubviews];
|
||||
CGFloat padding = 15;
|
||||
CGFloat width = CGRectGetWidth(self.view.frame) - (padding*2);
|
||||
CGRect bounds = self.view.bounds;
|
||||
self.paymentTextField.frame = CGRectMake(padding, padding, width, 44);
|
||||
self.activityIndicator.center = CGPointMake(CGRectGetMidX(bounds),
|
||||
CGRectGetMaxY(self.paymentTextField.frame) + padding*2);
|
||||
self.waitingLabel.center = CGPointMake(CGRectGetMidX(bounds),
|
||||
CGRectGetMaxY(self.activityIndicator.frame) + padding*2);
|
||||
}
|
||||
|
||||
- (void)updateUIForPaymentInProgress:(BOOL)paymentInProgress {
|
||||
self.navigationController.navigationBar.userInteractionEnabled = !paymentInProgress;
|
||||
self.navigationItem.rightBarButtonItem.enabled = !paymentInProgress;
|
||||
self.paymentTextField.userInteractionEnabled = !paymentInProgress;
|
||||
[UIView animateWithDuration:0.2 animations:^{
|
||||
self.waitingLabel.alpha = paymentInProgress ? 1 : 0;
|
||||
}];
|
||||
if (paymentInProgress) {
|
||||
[self.activityIndicator startAnimating];
|
||||
} else {
|
||||
[self.activityIndicator stopAnimating];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)paymentCardTextFieldDidChange:(nonnull STPPaymentCardTextField *)textField {
|
||||
self.navigationItem.rightBarButtonItem.enabled = textField.isValid;
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated"
|
||||
- (void)pay {
|
||||
if (![self.paymentTextField isValid]) {
|
||||
return;
|
||||
}
|
||||
if (![Stripe defaultPublishableKey]) {
|
||||
[self.delegate exampleViewController:self didFinishWithMessage:@"Please set a Stripe Publishable Key in Constants.m"];
|
||||
return;
|
||||
}
|
||||
[self updateUIForPaymentInProgress:YES];
|
||||
STPAPIClient *stripeClient = [STPAPIClient sharedClient];
|
||||
STPSourceParams *sourceParams = [STPSourceParams cardParamsWithCard:self.paymentTextField.cardParams];
|
||||
[stripeClient createSourceWithParams:sourceParams completion:^(STPSource *source, NSError *error) {
|
||||
if (error) {
|
||||
[self.delegate exampleViewController:self didFinishWithError:error];
|
||||
} else if (source.cardDetails && source.cardDetails.threeDSecure == STPSourceCard3DSecureStatusRequired) {
|
||||
STPSourceParams *threeDSParams = [STPSourceParams threeDSecureParamsWithAmount:1099
|
||||
currency:@"usd"
|
||||
returnURL:@"payments-example://stripe-redirect"
|
||||
card:source.stripeID];
|
||||
[stripeClient createSourceWithParams:threeDSParams completion:^(STPSource * source, NSError *error) {
|
||||
if (error) {
|
||||
[self.delegate exampleViewController:self didFinishWithError:error];
|
||||
} else {
|
||||
// In order to use STPRedirectContext, you'll need to set up
|
||||
// your app delegate to forwards URLs to the Stripe SDK.
|
||||
// See `[Stripe handleStripeURLCallback:]`
|
||||
self.redirectContext = [[STPRedirectContext alloc] initWithSource:source completion:^(NSString *sourceID, NSString *clientSecret, NSError *error) {
|
||||
if (error) {
|
||||
[self.delegate exampleViewController:self didFinishWithError:error];
|
||||
} else {
|
||||
[[STPAPIClient sharedClient] startPollingSourceWithId:sourceID
|
||||
clientSecret:clientSecret
|
||||
timeout:10
|
||||
completion:^(STPSource *source, NSError *error) {
|
||||
[self updateUIForPaymentInProgress:NO];
|
||||
if (error) {
|
||||
[self.delegate exampleViewController:self didFinishWithError:error];
|
||||
} else {
|
||||
switch (source.status) {
|
||||
case STPSourceStatusChargeable:
|
||||
case STPSourceStatusConsumed:
|
||||
[self.delegate exampleViewController:self didFinishWithMessage:@"Payment successfully created"];
|
||||
break;
|
||||
case STPSourceStatusCanceled:
|
||||
[self.delegate exampleViewController:self didFinishWithMessage:@"Payment failed"];
|
||||
break;
|
||||
case STPSourceStatusPending:
|
||||
case STPSourceStatusFailed:
|
||||
case STPSourceStatusUnknown:
|
||||
[self.delegate exampleViewController:self didFinishWithMessage:@"Order received"];
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.redirectContext = nil;
|
||||
}];
|
||||
}
|
||||
}];
|
||||
[self.redirectContext startRedirectFlowFromViewController:self];
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
[self.delegate createBackendChargeWithSource:source.stripeID completion:^(STPBackendResult status, NSError *error) {
|
||||
if (error) {
|
||||
[self.delegate exampleViewController:self didFinishWithError:error];
|
||||
return;
|
||||
}
|
||||
[self.delegate exampleViewController:self didFinishWithMessage:@"Payment successfully created"];
|
||||
}];
|
||||
}
|
||||
}];
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
@end
|
|
@ -1,14 +0,0 @@
|
|||
//
|
||||
// ThreeDSPaymentIntentExampleViewController.h
|
||||
// Custom Integration (ObjC)
|
||||
//
|
||||
// Created by Daniel Jackson on 7/5/18.
|
||||
// Copyright © 2018 Stripe. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "ThreeDSExampleViewController.h"
|
||||
|
||||
@interface ThreeDSPaymentIntentExampleViewController : ThreeDSExampleViewController
|
||||
|
||||
@end
|
|
@ -1,139 +0,0 @@
|
|||
//
|
||||
// ThreeDSPaymentIntentExampleViewController.m
|
||||
// Custom Integration (ObjC)
|
||||
//
|
||||
// Created by Daniel Jackson on 7/5/18.
|
||||
// Copyright © 2018 Stripe. All rights reserved.
|
||||
//
|
||||
|
||||
@import Stripe;
|
||||
|
||||
#import "ThreeDSPaymentIntentExampleViewController.h"
|
||||
#import "BrowseExamplesViewController.h"
|
||||
|
||||
@interface ThreeDSExampleViewController (PrivateMethods)
|
||||
- (void)updateUIForPaymentInProgress:(BOOL)paymentInProgress;
|
||||
|
||||
@property (weak, nonatomic) STPPaymentCardTextField *paymentTextField;
|
||||
@property (nonatomic) STPRedirectContext *redirectContext;
|
||||
@end
|
||||
|
||||
/**
|
||||
This example demonstrates using PaymentIntents to accept card payments verified using 3D Secure.
|
||||
This builds on ThreeDSExampleViewController, which has the same UI but creates a Source instead of using
|
||||
PaymentIntents.
|
||||
|
||||
1. Collect user's card information via `STPPaymentCardTextField`
|
||||
2. Create a `PaymentIntent` on our backend (this can happen concurrently with #1)
|
||||
3. Confirm PaymentIntent using the `STPSourceParams` for the user's card information.
|
||||
4. If the user needs to go through the 3D Secure authentication flow, use `STPRedirectContext` to do so.
|
||||
5. When user returns to the app, or finishes the SafariVC redirect flow, `STPRedirectContext` notifies via callback
|
||||
|
||||
See the documentation at https://stripe.com/docs/payments/dynamic-authentication for more information
|
||||
on using PaymentIntents for dynamic authentication.
|
||||
*/
|
||||
@interface ThreeDSPaymentIntentExampleViewController ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation ThreeDSPaymentIntentExampleViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.title = @"PaymentIntent: Card + 3DS";
|
||||
}
|
||||
|
||||
- (void)pay {
|
||||
if (![self.paymentTextField isValid]) {
|
||||
return;
|
||||
}
|
||||
if (![Stripe defaultPublishableKey]) {
|
||||
[self.delegate exampleViewController:self didFinishWithMessage:@"Please set a Stripe Publishable Key in Constants.m"];
|
||||
return;
|
||||
}
|
||||
[self updateUIForPaymentInProgress:YES];
|
||||
|
||||
// In a more interesting app, you'll probably create your PaymentIntent as soon as you know the
|
||||
// payment amount you wish to collect from your customer. For simplicity, this example does it once they've
|
||||
// pushed the Pay button.
|
||||
// https://stripe.com/docs/payments/dynamic-authentication#create-payment-intent
|
||||
[self.delegate createBackendPaymentIntentWithAmount:@1099 completion:^(STPBackendResult status, NSString *clientSecret, NSError *error) {
|
||||
if (status == STPBackendResultFailure || clientSecret == nil) {
|
||||
[self.delegate exampleViewController:self didFinishWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
STPAPIClient *stripeClient = [STPAPIClient sharedClient];
|
||||
STPPaymentIntentParams *paymentIntentParams = [[STPPaymentIntentParams alloc] initWithClientSecret:clientSecret];
|
||||
paymentIntentParams.sourceParams = [STPSourceParams cardParamsWithCard:self.paymentTextField.cardParams];
|
||||
paymentIntentParams.returnURL = @"payments-example://stripe-redirect";
|
||||
|
||||
[stripeClient confirmPaymentIntentWithParams:paymentIntentParams completion:^(STPPaymentIntent * _Nullable paymentIntent, NSError * _Nullable error) {
|
||||
if (error) {
|
||||
[self.delegate exampleViewController:self didFinishWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
if (paymentIntent.status == STPPaymentIntentStatusRequiresAction) {
|
||||
self.redirectContext = [[STPRedirectContext alloc] initWithPaymentIntent:paymentIntent completion:^(NSString * _Nonnull clientSecret, NSError * _Nullable error) {
|
||||
if (error) {
|
||||
[self.delegate exampleViewController:self didFinishWithError:error];
|
||||
}
|
||||
else {
|
||||
[stripeClient retrievePaymentIntentWithClientSecret:clientSecret completion:^(STPPaymentIntent * _Nullable paymentIntent, NSError * _Nullable error) {
|
||||
if (error) {
|
||||
[self.delegate exampleViewController:self didFinishWithError:error];
|
||||
} else {
|
||||
[self finishWithStatus:paymentIntent.status];
|
||||
}
|
||||
}];
|
||||
}
|
||||
self.redirectContext = nil; // break retain cycle
|
||||
}];
|
||||
|
||||
if (self.redirectContext) {
|
||||
[self.redirectContext startRedirectFlowFromViewController:self];
|
||||
}
|
||||
else {
|
||||
// Could not create STPRedirectContext even though it RequiresSourceAction
|
||||
[self finishWithStatus:paymentIntent.status];
|
||||
}
|
||||
}
|
||||
else {
|
||||
[self finishWithStatus:paymentIntent.status];
|
||||
}
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)finishWithStatus:(STPPaymentIntentStatus)status {
|
||||
switch (status) {
|
||||
// There may have been a problem with the payment method (STPPaymentMethodParams or STPSourceParams)
|
||||
case STPPaymentIntentStatusRequiresPaymentMethod:
|
||||
// did you call `confirmPaymentIntentWithParams:completion`?
|
||||
case STPPaymentIntentStatusRequiresConfirmation:
|
||||
// App should have handled the action, but didn't for some reason
|
||||
case STPPaymentIntentStatusRequiresAction:
|
||||
// The PaymentIntent was canceled (maybe by the backend?)
|
||||
case STPPaymentIntentStatusCanceled:
|
||||
[self.delegate exampleViewController:self didFinishWithMessage:@"Payment failed"];
|
||||
break;
|
||||
|
||||
// Processing. You could detect this case and poll for the final status of the PaymentIntent
|
||||
case STPPaymentIntentStatusProcessing:
|
||||
// Unknown status
|
||||
case STPPaymentIntentStatusUnknown:
|
||||
[self.delegate exampleViewController:self didFinishWithMessage:@"Order received"];
|
||||
break;
|
||||
|
||||
// if captureMethod is manual, backend needs to capture it to receive the funds
|
||||
case STPPaymentIntentStatusRequiresCapture:
|
||||
// succeeded
|
||||
case STPPaymentIntentStatusSucceeded:
|
||||
[self.delegate exampleViewController:self didFinishWithMessage:@"Payment successfully created"];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
|
@ -16,12 +16,12 @@
|
|||
04533F121A68814100C7E52E /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04533F111A68814100C7E52E /* Security.framework */; settings = {ATTRIBUTES = (Required, ); }; };
|
||||
04533F191A688A0A00C7E52E /* Constants.m in Sources */ = {isa = PBXBuildFile; fileRef = 04533F181A688A0A00C7E52E /* Constants.m */; };
|
||||
04D076171A69C11600094431 /* Stripe.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 04533F0C1A68812D00C7E52E /* Stripe.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
366F93B0225FF2A2005CFBF6 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 366F93AF225FF2A2005CFBF6 /* README.md */; };
|
||||
8BBD79C6207FD2F900F85BED /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8BBD79C8207FD2F900F85BED /* Localizable.strings */; };
|
||||
B3BDCADD20EF03010034F7F5 /* ThreeDSPaymentIntentExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B3BDCADB20EF03010034F7F5 /* ThreeDSPaymentIntentExampleViewController.m */; };
|
||||
B3BDCADD20EF03010034F7F5 /* CardAutomaticConfirmationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B3BDCADB20EF03010034F7F5 /* CardAutomaticConfirmationViewController.m */; };
|
||||
C12C50DD1E57B3C800EC6D58 /* BrowseExamplesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C12C50DC1E57B3C800EC6D58 /* BrowseExamplesViewController.m */; };
|
||||
C1CACE861E5DE6C3002D0821 /* CardExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C1CACE851E5DE6C3002D0821 /* CardExampleViewController.m */; };
|
||||
C1CACE861E5DE6C3002D0821 /* CardManualConfirmationExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C1CACE851E5DE6C3002D0821 /* CardManualConfirmationExampleViewController.m */; };
|
||||
C1CACE891E5DF7A9002D0821 /* ApplePayExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C1CACE881E5DF7A9002D0821 /* ApplePayExampleViewController.m */; };
|
||||
C1CACE8F1E5E0F94002D0821 /* ThreeDSExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C1CACE8E1E5E0F94002D0821 /* ThreeDSExampleViewController.m */; };
|
||||
C1CACE941E5E3DF6002D0821 /* SofortExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C1CACE931E5E3DF6002D0821 /* SofortExampleViewController.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
|
@ -40,7 +40,7 @@
|
|||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
04533E871A687F5D00C7E52E /* Custom Integration (ObjC).app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Custom Integration (ObjC).app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
04533E871A687F5D00C7E52E /* Custom Integration (Recommended).app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Custom Integration (Recommended).app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
04533E8B1A687F5D00C7E52E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
04533E8C1A687F5D00C7E52E /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
04533E8E1A687F5D00C7E52E /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
|
||||
|
@ -53,6 +53,7 @@
|
|||
04533F111A68814100C7E52E /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
|
||||
04533F171A688A0A00C7E52E /* Constants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Constants.h; sourceTree = "<group>"; };
|
||||
04533F181A688A0A00C7E52E /* Constants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Constants.m; sourceTree = "<group>"; };
|
||||
366F93AF225FF2A2005CFBF6 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = "Custom Integration/README.md"; sourceTree = "<group>"; };
|
||||
8BBD79C7207FD2F900F85BED /* en */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
8BBD79C9207FD31A00F85BED /* zh-Hans */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
8BBD79CA207FD32100F85BED /* nl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
|
@ -63,17 +64,14 @@
|
|||
8BBD79CF207FD34200F85BED /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
8BBD79D0207FD34A00F85BED /* nb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
8BBD79D1207FD35200F85BED /* es */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
B3BDCADB20EF03010034F7F5 /* ThreeDSPaymentIntentExampleViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreeDSPaymentIntentExampleViewController.m; sourceTree = "<group>"; };
|
||||
B3BDCADC20EF03010034F7F5 /* ThreeDSPaymentIntentExampleViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ThreeDSPaymentIntentExampleViewController.h; sourceTree = "<group>"; };
|
||||
B3BDCADB20EF03010034F7F5 /* CardAutomaticConfirmationViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CardAutomaticConfirmationViewController.m; sourceTree = "<group>"; };
|
||||
B3BDCADC20EF03010034F7F5 /* CardAutomaticConfirmationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CardAutomaticConfirmationViewController.h; sourceTree = "<group>"; };
|
||||
C12C50DB1E57B3C800EC6D58 /* BrowseExamplesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BrowseExamplesViewController.h; sourceTree = "<group>"; };
|
||||
C12C50DC1E57B3C800EC6D58 /* BrowseExamplesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BrowseExamplesViewController.m; sourceTree = "<group>"; };
|
||||
C18A6EE31F1EB7A6005600CC /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = "Custom Integration (ObjC)/README.md"; sourceTree = "<group>"; };
|
||||
C1CACE841E5DE6C3002D0821 /* CardExampleViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CardExampleViewController.h; sourceTree = "<group>"; };
|
||||
C1CACE851E5DE6C3002D0821 /* CardExampleViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CardExampleViewController.m; sourceTree = "<group>"; };
|
||||
C1CACE841E5DE6C3002D0821 /* CardManualConfirmationExampleViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CardManualConfirmationExampleViewController.h; sourceTree = "<group>"; };
|
||||
C1CACE851E5DE6C3002D0821 /* CardManualConfirmationExampleViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CardManualConfirmationExampleViewController.m; sourceTree = "<group>"; };
|
||||
C1CACE871E5DF7A9002D0821 /* ApplePayExampleViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ApplePayExampleViewController.h; sourceTree = "<group>"; };
|
||||
C1CACE881E5DF7A9002D0821 /* ApplePayExampleViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ApplePayExampleViewController.m; sourceTree = "<group>"; };
|
||||
C1CACE8D1E5E0F94002D0821 /* ThreeDSExampleViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ThreeDSExampleViewController.h; sourceTree = "<group>"; };
|
||||
C1CACE8E1E5E0F94002D0821 /* ThreeDSExampleViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreeDSExampleViewController.m; sourceTree = "<group>"; };
|
||||
C1CACE921E5E3DF6002D0821 /* SofortExampleViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SofortExampleViewController.h; sourceTree = "<group>"; };
|
||||
C1CACE931E5E3DF6002D0821 /* SofortExampleViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SofortExampleViewController.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
@ -95,8 +93,8 @@
|
|||
04533E7E1A687F5D00C7E52E = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C18A6EE31F1EB7A6005600CC /* README.md */,
|
||||
04533E891A687F5D00C7E52E /* Custom Integration (ObjC) */,
|
||||
366F93AF225FF2A2005CFBF6 /* README.md */,
|
||||
04533E891A687F5D00C7E52E /* Custom Integration */,
|
||||
04533F0E1A68813100C7E52E /* Frameworks */,
|
||||
04533E881A687F5D00C7E52E /* Products */,
|
||||
);
|
||||
|
@ -105,12 +103,12 @@
|
|||
04533E881A687F5D00C7E52E /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
04533E871A687F5D00C7E52E /* Custom Integration (ObjC).app */,
|
||||
04533E871A687F5D00C7E52E /* Custom Integration (Recommended).app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
04533E891A687F5D00C7E52E /* Custom Integration (ObjC) */ = {
|
||||
04533E891A687F5D00C7E52E /* Custom Integration */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
04533E8E1A687F5D00C7E52E /* AppDelegate.h */,
|
||||
|
@ -119,8 +117,10 @@
|
|||
C1CACE881E5DF7A9002D0821 /* ApplePayExampleViewController.m */,
|
||||
C12C50DB1E57B3C800EC6D58 /* BrowseExamplesViewController.h */,
|
||||
C12C50DC1E57B3C800EC6D58 /* BrowseExamplesViewController.m */,
|
||||
C1CACE841E5DE6C3002D0821 /* CardExampleViewController.h */,
|
||||
C1CACE851E5DE6C3002D0821 /* CardExampleViewController.m */,
|
||||
B3BDCADC20EF03010034F7F5 /* CardAutomaticConfirmationViewController.h */,
|
||||
B3BDCADB20EF03010034F7F5 /* CardAutomaticConfirmationViewController.m */,
|
||||
C1CACE841E5DE6C3002D0821 /* CardManualConfirmationExampleViewController.h */,
|
||||
C1CACE851E5DE6C3002D0821 /* CardManualConfirmationExampleViewController.m */,
|
||||
04533F171A688A0A00C7E52E /* Constants.h */,
|
||||
04533F181A688A0A00C7E52E /* Constants.m */,
|
||||
04533E971A687F5D00C7E52E /* Images.xcassets */,
|
||||
|
@ -130,12 +130,8 @@
|
|||
C1CACE921E5E3DF6002D0821 /* SofortExampleViewController.h */,
|
||||
C1CACE931E5E3DF6002D0821 /* SofortExampleViewController.m */,
|
||||
04533E8A1A687F5D00C7E52E /* Supporting Files */,
|
||||
C1CACE8D1E5E0F94002D0821 /* ThreeDSExampleViewController.h */,
|
||||
C1CACE8E1E5E0F94002D0821 /* ThreeDSExampleViewController.m */,
|
||||
B3BDCADC20EF03010034F7F5 /* ThreeDSPaymentIntentExampleViewController.h */,
|
||||
B3BDCADB20EF03010034F7F5 /* ThreeDSPaymentIntentExampleViewController.m */,
|
||||
);
|
||||
path = "Custom Integration (ObjC)";
|
||||
path = "Custom Integration";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
04533E8A1A687F5D00C7E52E /* Supporting Files */ = {
|
||||
|
@ -160,9 +156,9 @@
|
|||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
04533E861A687F5D00C7E52E /* Custom Integration (ObjC) */ = {
|
||||
04533E861A687F5D00C7E52E /* Custom Integration (Recommended) */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 04533EAA1A687F5E00C7E52E /* Build configuration list for PBXNativeTarget "Custom Integration (ObjC)" */;
|
||||
buildConfigurationList = 04533EAA1A687F5E00C7E52E /* Build configuration list for PBXNativeTarget "Custom Integration (Recommended)" */;
|
||||
buildPhases = (
|
||||
04533E831A687F5D00C7E52E /* Sources */,
|
||||
04533E841A687F5D00C7E52E /* Frameworks */,
|
||||
|
@ -173,9 +169,9 @@
|
|||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "Custom Integration (ObjC)";
|
||||
name = "Custom Integration (Recommended)";
|
||||
productName = "Custom Integration (ObjC)";
|
||||
productReference = 04533E871A687F5D00C7E52E /* Custom Integration (ObjC).app */;
|
||||
productReference = 04533E871A687F5D00C7E52E /* Custom Integration (Recommended).app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
@ -193,7 +189,7 @@
|
|||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 04533E821A687F5D00C7E52E /* Build configuration list for PBXProject "Custom Integration (ObjC)" */;
|
||||
buildConfigurationList = 04533E821A687F5D00C7E52E /* Build configuration list for PBXProject "Custom Integration (Recommended)" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
|
@ -214,7 +210,7 @@
|
|||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
04533E861A687F5D00C7E52E /* Custom Integration (ObjC) */,
|
||||
04533E861A687F5D00C7E52E /* Custom Integration (Recommended) */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
@ -224,6 +220,7 @@
|
|||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
366F93B0225FF2A2005CFBF6 /* README.md in Resources */,
|
||||
04533E981A687F5D00C7E52E /* Images.xcassets in Resources */,
|
||||
8BBD79C6207FD2F900F85BED /* Localizable.strings in Resources */,
|
||||
);
|
||||
|
@ -238,13 +235,12 @@
|
|||
files = (
|
||||
04533F191A688A0A00C7E52E /* Constants.m in Sources */,
|
||||
C1CACE941E5E3DF6002D0821 /* SofortExampleViewController.m in Sources */,
|
||||
C1CACE861E5DE6C3002D0821 /* CardExampleViewController.m in Sources */,
|
||||
C1CACE861E5DE6C3002D0821 /* CardManualConfirmationExampleViewController.m in Sources */,
|
||||
04533EB21A68802E00C7E52E /* ShippingManager.m in Sources */,
|
||||
B3BDCADD20EF03010034F7F5 /* ThreeDSPaymentIntentExampleViewController.m in Sources */,
|
||||
B3BDCADD20EF03010034F7F5 /* CardAutomaticConfirmationViewController.m in Sources */,
|
||||
04533E901A687F5D00C7E52E /* AppDelegate.m in Sources */,
|
||||
C1CACE891E5DF7A9002D0821 /* ApplePayExampleViewController.m in Sources */,
|
||||
04533E8D1A687F5D00C7E52E /* main.m in Sources */,
|
||||
C1CACE8F1E5E0F94002D0821 /* ThreeDSExampleViewController.m in Sources */,
|
||||
C12C50DD1E57B3C800EC6D58 /* BrowseExamplesViewController.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -405,7 +401,7 @@
|
|||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/**",
|
||||
);
|
||||
INFOPLIST_FILE = "Custom Integration (ObjC)/Info.plist";
|
||||
INFOPLIST_FILE = "$(SRCROOT)/Custom Integration/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.stripe.CustomSDKExample;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
@ -429,7 +425,7 @@
|
|||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/**",
|
||||
);
|
||||
INFOPLIST_FILE = "Custom Integration (ObjC)/Info.plist";
|
||||
INFOPLIST_FILE = "$(SRCROOT)/Custom Integration/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.stripe.CustomSDKExample;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
@ -440,7 +436,7 @@
|
|||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
04533E821A687F5D00C7E52E /* Build configuration list for PBXProject "Custom Integration (ObjC)" */ = {
|
||||
04533E821A687F5D00C7E52E /* Build configuration list for PBXProject "Custom Integration (Recommended)" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
04533EA81A687F5E00C7E52E /* Debug */,
|
||||
|
@ -449,7 +445,7 @@
|
|||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
04533EAA1A687F5E00C7E52E /* Build configuration list for PBXNativeTarget "Custom Integration (ObjC)" */ = {
|
||||
04533EAA1A687F5E00C7E52E /* Build configuration list for PBXNativeTarget "Custom Integration (Recommended)" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
04533EAB1A687F5E00C7E52E /* Debug */,
|
|
@ -15,9 +15,9 @@
|
|||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "04533E861A687F5D00C7E52E"
|
||||
BuildableName = "Custom Integration (ObjC).app"
|
||||
BlueprintName = "Custom Integration (ObjC)"
|
||||
ReferencedContainer = "container:Custom Integration (ObjC).xcodeproj">
|
||||
BuildableName = "Custom Integration (Recommended).app"
|
||||
BlueprintName = "Custom Integration (Recommended)"
|
||||
ReferencedContainer = "container:Custom Integration (Recommended).xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
|
@ -43,9 +43,9 @@
|
|||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "04533E861A687F5D00C7E52E"
|
||||
BuildableName = "Custom Integration (ObjC).app"
|
||||
BlueprintName = "Custom Integration (ObjC)"
|
||||
ReferencedContainer = "container:Custom Integration (ObjC).xcodeproj">
|
||||
BuildableName = "Custom Integration (Recommended).app"
|
||||
BlueprintName = "Custom Integration (Recommended)"
|
||||
ReferencedContainer = "container:Custom Integration (Recommended).xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
|
@ -66,9 +66,9 @@
|
|||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "04533E861A687F5D00C7E52E"
|
||||
BuildableName = "Custom Integration (ObjC).app"
|
||||
BlueprintName = "Custom Integration (ObjC)"
|
||||
ReferencedContainer = "container:Custom Integration (ObjC).xcodeproj">
|
||||
BuildableName = "Custom Integration (Recommended).app"
|
||||
BlueprintName = "Custom Integration (Recommended)"
|
||||
ReferencedContainer = "container:Custom Integration (Recommended).xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
|
@ -85,9 +85,9 @@
|
|||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "04533E861A687F5D00C7E52E"
|
||||
BuildableName = "Custom Integration (ObjC).app"
|
||||
BlueprintName = "Custom Integration (ObjC)"
|
||||
ReferencedContainer = "container:Custom Integration (ObjC).xcodeproj">
|
||||
BuildableName = "Custom Integration (Recommended).app"
|
||||
BlueprintName = "Custom Integration (Recommended)"
|
||||
ReferencedContainer = "container:Custom Integration (Recommended).xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// AppDelegate.h
|
||||
// Custom Integration (ObjC)
|
||||
// Custom Integration (Recommended)
|
||||
//
|
||||
// Created by Jack Flintermann on 1/15/15.
|
||||
// Copyright (c) 2015 Stripe. All rights reserved.
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ApplePayExampleViewController.h
|
||||
// Custom Integration (ObjC)
|
||||
// Custom Integration (Recommended)
|
||||
//
|
||||
// Created by Ben Guo on 2/22/17.
|
||||
// Copyright © 2017 Stripe. All rights reserved.
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ApplePayExampleViewController.m
|
||||
// Custom Integration (ObjC)
|
||||
// Custom Integration (Recommended)
|
||||
//
|
||||
// Created by Ben Guo on 2/22/17.
|
||||
// Copyright © 2017 Stripe. All rights reserved.
|
||||
|
@ -120,19 +120,52 @@
|
|||
completion:(void (^)(PKPaymentAuthorizationStatus))completion {
|
||||
[[STPAPIClient sharedClient] createTokenWithPayment:payment
|
||||
completion:^(STPToken *token, NSError *error) {
|
||||
[self.delegate createBackendChargeWithSource:token.tokenId
|
||||
completion:^(STPBackendResult status, NSError *error) {
|
||||
if (status == STPBackendResultSuccess) {
|
||||
self.applePaySucceeded = YES;
|
||||
completion(PKPaymentAuthorizationStatusSuccess);
|
||||
} else {
|
||||
self.applePayError = error;
|
||||
completion(PKPaymentAuthorizationStatusFailure);
|
||||
}
|
||||
}];
|
||||
if (error) {
|
||||
self.applePayError = error;
|
||||
completion(PKPaymentAuthorizationStatusFailure);
|
||||
} else {
|
||||
// We could also send the token.stripeID to our backend to create
|
||||
// a payment method and subsequent payment intent
|
||||
[self _createPaymentMethodForApplePayToken:token completion:completion];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_createPaymentMethodForApplePayToken:(STPToken *)token completion:(void (^)(PKPaymentAuthorizationStatus))completion {
|
||||
STPPaymentMethodCardParams *applePayParams = [[STPPaymentMethodCardParams alloc] init];
|
||||
applePayParams.token = token.stripeID;
|
||||
STPPaymentMethodParams *paymentMethodParams = [STPPaymentMethodParams paramsWithCard:applePayParams
|
||||
billingDetails:nil
|
||||
metadata:nil];
|
||||
|
||||
[[STPAPIClient sharedClient] createPaymentMethodWithParams:paymentMethodParams
|
||||
completion:^(STPPaymentMethod * _Nullable paymentMethod, NSError * _Nullable error) {
|
||||
if (error) {
|
||||
self.applePayError = error;
|
||||
completion(PKPaymentAuthorizationStatusFailure);
|
||||
} else {
|
||||
[self _createAndConfirmPaymentIntentWithPaymentMethod:paymentMethod
|
||||
completion:completion];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_createAndConfirmPaymentIntentWithPaymentMethod:(STPPaymentMethod *)paymentMethod completion:(void (^)(PKPaymentAuthorizationStatus))completion {
|
||||
|
||||
[self.delegate createAndConfirmPaymentIntentWithAmount:@(1000)
|
||||
paymentMethod:paymentMethod.stripeId
|
||||
returnURL:@"payments-example://stripe-redirect"
|
||||
completion:^(STPBackendResult status, STPPaymentIntent *paymentIntent, NSError *error) {
|
||||
if (error) {
|
||||
self.applePayError = error;
|
||||
completion(PKPaymentAuthorizationStatusFailure);
|
||||
} else {
|
||||
self.applePaySucceeded = YES;
|
||||
completion(PKPaymentAuthorizationStatusSuccess);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)paymentAuthorizationViewControllerDidFinish:(PKPaymentAuthorizationViewController *)controller {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self dismissViewControllerAnimated:YES completion:^{
|
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// BrowseExamplesViewController.h
|
||||
// Custom Integration (Recommended)
|
||||
//
|
||||
// Created by Ben Guo on 2/17/17.
|
||||
// Copyright © 2017 Stripe. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <Stripe/Stripe.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, STPBackendResult) {
|
||||
STPBackendResultSuccess,
|
||||
STPBackendResultFailure,
|
||||
};
|
||||
|
||||
typedef void (^STPPaymentIntentCreationHandler)(STPBackendResult status, NSString *clientSecret, NSError *error);
|
||||
typedef void (^STPPaymentIntentCreateAndConfirmHandler)(STPBackendResult status, STPPaymentIntent *paymentIntent, NSError *error);
|
||||
typedef void (^STPRedirectCompletionHandler)(STPPaymentIntent *retrievedIntent, NSError *error);
|
||||
typedef void (^STPConfirmPaymentIntentCompletionHandler)(STPBackendResult status, STPPaymentIntent *paymentIntent, NSError *error);
|
||||
|
||||
|
||||
@protocol ExampleViewControllerDelegate <NSObject>
|
||||
|
||||
- (void)exampleViewController:(UIViewController *)controller didFinishWithMessage:(NSString *)message;
|
||||
- (void)exampleViewController:(UIViewController *)controller didFinishWithError:(NSError *)error;
|
||||
- (void)performRedirectForViewController:(UIViewController *)controller
|
||||
withPaymentIntent:(STPPaymentIntent *)paymentIntent
|
||||
completion:(STPRedirectCompletionHandler)completion;
|
||||
|
||||
- (void)createBackendPaymentIntentWithAmount:(NSNumber *)amount completion:(STPPaymentIntentCreationHandler)completion;
|
||||
- (void)createAndConfirmPaymentIntentWithAmount:(NSNumber *)amount
|
||||
paymentMethod:(NSString *)paymentMethodID
|
||||
returnURL:(NSString *)returnURL
|
||||
completion:(STPPaymentIntentCreateAndConfirmHandler)completion;
|
||||
- (void)confirmPaymentIntent:(STPPaymentIntent *)paymentIntent completion:(STPConfirmPaymentIntentCompletionHandler)completion;
|
||||
|
||||
@end
|
||||
|
||||
@interface BrowseExamplesViewController : UITableViewController
|
||||
|
||||
@end
|
|
@ -0,0 +1,346 @@
|
|||
//
|
||||
// BrowseExamplesViewController.m
|
||||
// Custom Integration (Recommended)
|
||||
//
|
||||
// Created by Ben Guo on 2/17/17.
|
||||
// Copyright © 2017 Stripe. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Stripe/Stripe.h>
|
||||
|
||||
#import "BrowseExamplesViewController.h"
|
||||
|
||||
#import "ApplePayExampleViewController.h"
|
||||
#import "CardAutomaticConfirmationViewController.h"
|
||||
#import "CardManualConfirmationExampleViewController.h"
|
||||
#import "Constants.h"
|
||||
#import "SofortExampleViewController.h"
|
||||
|
||||
/**
|
||||
This view controller presents different examples, each of which demonstrates creating a payment using a different payment method or integration.
|
||||
*/
|
||||
@interface BrowseExamplesViewController () <ExampleViewControllerDelegate>
|
||||
@end
|
||||
|
||||
@implementation BrowseExamplesViewController {
|
||||
STPRedirectContext *_redirectContext;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
self.title = @"Examples";
|
||||
self.navigationController.navigationBar.translucent = NO;
|
||||
self.tableView.tableFooterView = [UIView new];
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
return 4;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
UITableViewCell *cell = [UITableViewCell new];
|
||||
switch (indexPath.row) {
|
||||
case 0:
|
||||
cell.textLabel.text = @"Card";
|
||||
break;
|
||||
case 1:
|
||||
cell.textLabel.text = @"Card w/ Manual Integration";
|
||||
break;
|
||||
case 2:
|
||||
cell.textLabel.text = @"Apple Pay";
|
||||
break;
|
||||
case 3:
|
||||
cell.textLabel.text = @"Sofort (Sources)";
|
||||
break;
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
UIViewController *viewController;
|
||||
switch (indexPath.row) {
|
||||
case 0: {
|
||||
CardAutomaticConfirmationViewController *exampleVC = [CardAutomaticConfirmationViewController new];
|
||||
exampleVC.delegate = self;
|
||||
viewController = exampleVC;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
CardManualConfirmationExampleViewController *exampleVC = [CardManualConfirmationExampleViewController new];
|
||||
exampleVC.delegate = self;
|
||||
viewController = exampleVC;
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
ApplePayExampleViewController *exampleVC = [ApplePayExampleViewController new];
|
||||
exampleVC.delegate = self;
|
||||
viewController = exampleVC;
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
SofortExampleViewController *exampleVC = [SofortExampleViewController new];
|
||||
exampleVC.delegate = self;
|
||||
viewController = exampleVC;
|
||||
break;
|
||||
}
|
||||
}
|
||||
[self.navigationController pushViewController:viewController animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - STPBackendCharging
|
||||
|
||||
- (void)_callOnMainThread:(void (^)(void))block {
|
||||
if ([NSThread isMainThread]) {
|
||||
block();
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
block();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Ask the example backend to create a PaymentIntent with the specified amount.
|
||||
|
||||
The implementation of this function is not interesting or relevant to using PaymentIntents. The
|
||||
method signature is the most interesting part: you need some way to ask *your* backend to create
|
||||
a PaymentIntent with the correct properties, and then it needs to pass the client secret back.
|
||||
|
||||
@param amount Amount to charge the customer
|
||||
@param completion completion block called with status of backend call & the client secret if successful.
|
||||
*/
|
||||
- (void)createBackendPaymentIntentWithAmount:(NSNumber *)amount completion:(STPPaymentIntentCreationHandler)completion {
|
||||
if (!BackendBaseURL) {
|
||||
NSError *error = [NSError errorWithDomain:StripeDomain
|
||||
code:STPInvalidRequestError
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"You must set a backend base URL in Constants.m to create a payment intent."}];
|
||||
[self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, error); }];
|
||||
return;
|
||||
}
|
||||
|
||||
// This asks the backend to create a PaymentIntent for us, which can then be passed to the Stripe SDK to confirm
|
||||
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
|
||||
|
||||
NSString *urlString = [BackendBaseURL stringByAppendingPathComponent:@"create_intent"];
|
||||
NSURL *url = [NSURL URLWithString:urlString];
|
||||
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
|
||||
request.HTTPMethod = @"POST";
|
||||
NSString *postBody = [NSString stringWithFormat:
|
||||
@"amount=%@&metadata[charge_request_id]=%@",
|
||||
amount,
|
||||
// example-ios-backend allows passing metadata through to Stripe
|
||||
@"B3E611D1-5FA1-4410-9CEC-00958A5126CB"
|
||||
];
|
||||
NSData *data = [postBody dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request
|
||||
fromData:data
|
||||
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
||||
if (!error && httpResponse.statusCode != 200) {
|
||||
error = [NSError errorWithDomain:StripeDomain
|
||||
code:STPInvalidRequestError
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"There was an error connecting to your payment backend."}];
|
||||
}
|
||||
if (error || data == nil) {
|
||||
[self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, error); }];
|
||||
}
|
||||
else {
|
||||
NSError *jsonError = nil;
|
||||
id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
|
||||
|
||||
if (json &&
|
||||
[json isKindOfClass:[NSDictionary class]] &&
|
||||
[json[@"secret"] isKindOfClass:[NSString class]]) {
|
||||
[self _callOnMainThread:^{ completion(STPBackendResultSuccess, json[@"secret"], nil); }];
|
||||
}
|
||||
else {
|
||||
[self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, jsonError); }];
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
[uploadTask resume];
|
||||
}
|
||||
|
||||
- (void)createAndConfirmPaymentIntentWithAmount:(NSNumber *)amount
|
||||
paymentMethod:(NSString *)paymentMethodID
|
||||
returnURL:(NSString *)returnURL
|
||||
completion:(STPPaymentIntentCreateAndConfirmHandler)completion {
|
||||
if (!BackendBaseURL) {
|
||||
NSError *error = [NSError errorWithDomain:StripeDomain
|
||||
code:STPInvalidRequestError
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"You must set a backend base URL in Constants.m to create a payment intent."}];
|
||||
[self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, error); }];
|
||||
return;
|
||||
}
|
||||
|
||||
// This passes the token off to our payment backend, which will then actually complete charging the card using your Stripe account's secret key
|
||||
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
|
||||
|
||||
NSString *urlString = [BackendBaseURL stringByAppendingPathComponent:@"capture_payment"];
|
||||
NSURL *url = [NSURL URLWithString:urlString];
|
||||
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
|
||||
request.HTTPMethod = @"POST";
|
||||
NSString *postBody = [NSString stringWithFormat:
|
||||
@"payment_method=%@&amount=%@&return_url=%@",
|
||||
paymentMethodID,
|
||||
@1099,
|
||||
returnURL];
|
||||
NSData *data = [postBody dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request
|
||||
fromData:data
|
||||
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
||||
if (!error && httpResponse.statusCode != 200) {
|
||||
error = [NSError errorWithDomain:StripeDomain
|
||||
code:STPInvalidRequestError
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"There was an error connecting to your payment backend."}];
|
||||
}
|
||||
if (error) {
|
||||
[self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, error); }];
|
||||
}
|
||||
else {
|
||||
NSError *jsonError = nil;
|
||||
id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
|
||||
|
||||
if (json && [json isKindOfClass:[NSDictionary class]]) {
|
||||
STPPaymentIntent *intent = [STPPaymentIntent decodedObjectFromAPIResponse:json];
|
||||
if (intent != nil) {
|
||||
[self _callOnMainThread:^{ completion(STPBackendResultSuccess, intent, nil); }];
|
||||
} else {
|
||||
[self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, [NSError errorWithDomain:StripeDomain
|
||||
code:STPAPIError
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"There was an error parsing your backend response to a payment intent."}]); }];
|
||||
}
|
||||
} else {
|
||||
[self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, jsonError); }];
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
[uploadTask resume];
|
||||
}
|
||||
|
||||
- (void)confirmPaymentIntent:(STPPaymentIntent *)paymentIntent completion:(STPConfirmPaymentIntentCompletionHandler)completion {
|
||||
if (!BackendBaseURL) {
|
||||
NSError *error = [NSError errorWithDomain:StripeDomain
|
||||
code:STPInvalidRequestError
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"You must set a backend base URL in Constants.m to confirm a payment intent."}];
|
||||
[self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, error); }];
|
||||
return;
|
||||
}
|
||||
|
||||
// This asks the backend to create a PaymentIntent for us, which can then be passed to the Stripe SDK to confirm
|
||||
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
|
||||
|
||||
NSString *urlString = [BackendBaseURL stringByAppendingPathComponent:@"confirm_payment"];
|
||||
NSURL *url = [NSURL URLWithString:urlString];
|
||||
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
|
||||
request.HTTPMethod = @"POST";
|
||||
NSString *postBody = [NSString stringWithFormat:@"payment_intent_id=%@", paymentIntent.stripeId];
|
||||
NSData *data = [postBody dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request
|
||||
fromData:data
|
||||
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
||||
if (!error && httpResponse.statusCode != 200) {
|
||||
error = [NSError errorWithDomain:StripeDomain
|
||||
code:STPInvalidRequestError
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"There was an error connecting to your payment backend."}];
|
||||
}
|
||||
if (error || data == nil) {
|
||||
[self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, error); }];
|
||||
}
|
||||
else {
|
||||
NSError *jsonError = nil;
|
||||
id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
|
||||
if (json && [json isKindOfClass:[NSDictionary class]]) {
|
||||
STPPaymentIntent *intent = [STPPaymentIntent decodedObjectFromAPIResponse:json];
|
||||
if (intent != nil) {
|
||||
[self _callOnMainThread:^{ completion(STPBackendResultSuccess, intent, nil); }];
|
||||
} else {
|
||||
[self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, [NSError errorWithDomain:StripeDomain
|
||||
code:STPAPIError
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"There was an error parsing your backend response to a payment intent."}]); }];
|
||||
}
|
||||
} else {
|
||||
[self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, jsonError); }];
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
[uploadTask resume];
|
||||
}
|
||||
|
||||
#pragma mark - ExampleViewControllerDelegate
|
||||
|
||||
- (void)exampleViewController:(UIViewController *)controller didFinishWithMessage:(NSString *)message {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:message preferredStyle:UIAlertControllerStyleAlert];
|
||||
UIAlertAction *action = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) {
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}];
|
||||
[alertController addAction:action];
|
||||
[controller presentViewController:alertController animated:YES completion:nil];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)exampleViewController:(UIViewController *)controller didFinishWithError:(NSError *)error {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:[error localizedDescription] preferredStyle:UIAlertControllerStyleAlert];
|
||||
UIAlertAction *action = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) {
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}];
|
||||
[alertController addAction:action];
|
||||
[controller presentViewController:alertController animated:YES completion:nil];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)performRedirectForViewController:(UIViewController *)controller
|
||||
withPaymentIntent:(STPPaymentIntent *)paymentIntent
|
||||
completion:(STPRedirectCompletionHandler)completion {
|
||||
if (_redirectContext != nil) {
|
||||
[self _callOnMainThread:^{ completion(nil,[NSError errorWithDomain:StripeDomain
|
||||
code:STPInvalidRequestError
|
||||
userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"%@ should not have multiple concurrent redirects.", NSStringFromClass([self class])]}]); }];
|
||||
return;
|
||||
}
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
STPRedirectContext *redirectContext = [[STPRedirectContext alloc] initWithPaymentIntent:paymentIntent completion:^(NSString * _Nonnull clientSecret, NSError * _Nullable error) {
|
||||
|
||||
if (error != nil) {
|
||||
[self _callOnMainThread:^{ completion(nil, error); }];
|
||||
} else {
|
||||
|
||||
[[STPAPIClient sharedClient] retrievePaymentIntentWithClientSecret:clientSecret
|
||||
completion:^(STPPaymentIntent * _Nullable retrievedIntent, NSError * _Nullable error) {
|
||||
[self _callOnMainThread:^{ completion(retrievedIntent, error); }];
|
||||
}];
|
||||
}
|
||||
__typeof(self) strongSelf = weakSelf;
|
||||
if (strongSelf != nil) {
|
||||
strongSelf->_redirectContext = nil;
|
||||
}
|
||||
}];
|
||||
|
||||
if (redirectContext) {
|
||||
_redirectContext = redirectContext;
|
||||
[redirectContext startRedirectFlowFromViewController:controller];
|
||||
} else {
|
||||
[self _callOnMainThread:^{ completion(nil,[NSError errorWithDomain:StripeDomain
|
||||
code:STPInvalidRequestError
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"Internal error creating redirect context."}]); }];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,17 @@
|
|||
//
|
||||
// CardAutomaticConfirmationViewController.h
|
||||
// Custom Integration (Recommended)
|
||||
//
|
||||
// Created by Daniel Jackson on 7/5/18.
|
||||
// Copyright © 2018 Stripe. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@protocol ExampleViewControllerDelegate;
|
||||
|
||||
@interface CardAutomaticConfirmationViewController : UIViewController
|
||||
|
||||
@property (nonatomic, weak) id<ExampleViewControllerDelegate> delegate;
|
||||
|
||||
@end
|
|
@ -0,0 +1,189 @@
|
|||
//
|
||||
// CardAutomaticConfirmationViewController.m
|
||||
// Custom Integration (Recommended)
|
||||
//
|
||||
// Created by Daniel Jackson on 7/5/18.
|
||||
// Copyright © 2018 Stripe. All rights reserved.
|
||||
//
|
||||
|
||||
@import Stripe;
|
||||
|
||||
#import "CardAutomaticConfirmationViewController.h"
|
||||
#import "BrowseExamplesViewController.h"
|
||||
|
||||
/**
|
||||
This example demonstrates using PaymentIntents to accept card payments verified using 3D Secure.
|
||||
|
||||
1. Collect user's card information via `STPPaymentCardTextField`
|
||||
2. Create a `PaymentIntent` on our backend (this can happen concurrently with #1)
|
||||
3. Confirm PaymentIntent using the `STPPaymentMethodParams` for the user's card information.
|
||||
4. If the user needs to go through the 3D Secure authentication flow, use `STPRedirectContext` to do so.
|
||||
5. When user returns to the app, or finishes the SafariVC redirect flow, `STPRedirectContext` notifies via callback
|
||||
|
||||
See the documentation at https://stripe.com/docs/payments/payment-intents/ios for more information
|
||||
on using PaymentIntents for dynamic authentication.
|
||||
*/
|
||||
@interface CardAutomaticConfirmationViewController () <STPPaymentCardTextFieldDelegate>
|
||||
|
||||
@property (weak, nonatomic) STPPaymentCardTextField *paymentTextField;
|
||||
@property (weak, nonatomic) UILabel *waitingLabel;
|
||||
@property (weak, nonatomic) UIActivityIndicatorView *activityIndicator;
|
||||
|
||||
@end
|
||||
|
||||
@implementation CardAutomaticConfirmationViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
self.view.backgroundColor = [UIColor whiteColor];
|
||||
self.title = @"Card";
|
||||
self.edgesForExtendedLayout = UIRectEdgeNone;
|
||||
|
||||
STPPaymentCardTextField *paymentTextField = [[STPPaymentCardTextField alloc] init];
|
||||
STPCardParams *cardParams = [STPCardParams new];
|
||||
// Only successful 3D Secure transactions on this test card will succeed.
|
||||
cardParams.number = @"4000000000003063";
|
||||
paymentTextField.cardParams = cardParams;
|
||||
paymentTextField.delegate = self;
|
||||
paymentTextField.cursorColor = [UIColor purpleColor];
|
||||
self.paymentTextField = paymentTextField;
|
||||
[self.view addSubview:paymentTextField];
|
||||
|
||||
UILabel *label = [UILabel new];
|
||||
label.text = @"Waiting for payment authorization";
|
||||
[label sizeToFit];
|
||||
label.textColor = [UIColor grayColor];
|
||||
label.alpha = 0;
|
||||
[self.view addSubview:label];
|
||||
self.waitingLabel = label;
|
||||
|
||||
NSString *title = @"Pay";
|
||||
UIBarButtonItem *payButton = [[UIBarButtonItem alloc] initWithTitle:title style:UIBarButtonItemStyleDone target:self action:@selector(pay)];
|
||||
payButton.enabled = paymentTextField.isValid;
|
||||
self.navigationItem.rightBarButtonItem = payButton;
|
||||
|
||||
UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
|
||||
activityIndicator.hidesWhenStopped = YES;
|
||||
self.activityIndicator = activityIndicator;
|
||||
[self.view addSubview:activityIndicator];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
[self.paymentTextField becomeFirstResponder];
|
||||
}
|
||||
|
||||
- (void)viewDidLayoutSubviews {
|
||||
[super viewDidLayoutSubviews];
|
||||
CGFloat padding = 15;
|
||||
CGFloat width = CGRectGetWidth(self.view.frame) - (padding*2);
|
||||
CGRect bounds = self.view.bounds;
|
||||
self.paymentTextField.frame = CGRectMake(padding, padding, width, 44);
|
||||
self.activityIndicator.center = CGPointMake(CGRectGetMidX(bounds),
|
||||
CGRectGetMaxY(self.paymentTextField.frame) + padding*2);
|
||||
self.waitingLabel.center = CGPointMake(CGRectGetMidX(bounds),
|
||||
CGRectGetMaxY(self.activityIndicator.frame) + padding*2);
|
||||
}
|
||||
|
||||
- (void)updateUIForPaymentInProgress:(BOOL)paymentInProgress {
|
||||
self.navigationController.navigationBar.userInteractionEnabled = !paymentInProgress;
|
||||
self.navigationItem.rightBarButtonItem.enabled = !paymentInProgress;
|
||||
self.paymentTextField.userInteractionEnabled = !paymentInProgress;
|
||||
[UIView animateWithDuration:0.2 animations:^{
|
||||
self.waitingLabel.alpha = paymentInProgress ? 1 : 0;
|
||||
}];
|
||||
if (paymentInProgress) {
|
||||
[self.activityIndicator startAnimating];
|
||||
} else {
|
||||
[self.activityIndicator stopAnimating];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)paymentCardTextFieldDidChange:(nonnull STPPaymentCardTextField *)textField {
|
||||
self.navigationItem.rightBarButtonItem.enabled = textField.isValid;
|
||||
}
|
||||
|
||||
- (void)pay {
|
||||
if (![self.paymentTextField isValid]) {
|
||||
return;
|
||||
}
|
||||
if (![Stripe defaultPublishableKey]) {
|
||||
[self.delegate exampleViewController:self didFinishWithMessage:@"Please set a Stripe Publishable Key in Constants.m"];
|
||||
return;
|
||||
}
|
||||
[self updateUIForPaymentInProgress:YES];
|
||||
|
||||
// In a more interesting app, you'll probably create your PaymentIntent as soon as you know the
|
||||
// payment amount you wish to collect from your customer. For simplicity, this example does it once they've
|
||||
// pushed the Pay button.
|
||||
// https://stripe.com/docs/payments/dynamic-authentication#create-payment-intent
|
||||
[self.delegate createBackendPaymentIntentWithAmount:@1099 completion:^(STPBackendResult status, NSString *clientSecret, NSError *error) {
|
||||
if (status == STPBackendResultFailure || clientSecret == nil) {
|
||||
[self.delegate exampleViewController:self didFinishWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
STPAPIClient *stripeClient = [STPAPIClient sharedClient];
|
||||
STPPaymentIntentParams *paymentIntentParams = [[STPPaymentIntentParams alloc] initWithClientSecret:clientSecret];
|
||||
|
||||
STPPaymentMethodCardParams *cardParams = [[STPPaymentMethodCardParams alloc] initWithCardSourceParams:self.paymentTextField.cardParams];
|
||||
|
||||
|
||||
paymentIntentParams.paymentMethodParams = [STPPaymentMethodParams paramsWithCard:cardParams
|
||||
billingDetails:nil
|
||||
metadata:nil];
|
||||
paymentIntentParams.returnURL = @"payments-example://stripe-redirect";
|
||||
|
||||
[stripeClient confirmPaymentIntentWithParams:paymentIntentParams completion:^(STPPaymentIntent * _Nullable paymentIntent, NSError * _Nullable error) {
|
||||
if (error) {
|
||||
[self.delegate exampleViewController:self didFinishWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
if (paymentIntent.status == STPPaymentIntentStatusRequiresAction) {
|
||||
[self.delegate performRedirectForViewController:self
|
||||
withPaymentIntent:paymentIntent
|
||||
completion:^(STPPaymentIntent *retrievedIntent, NSError *error) {
|
||||
if (error) {
|
||||
[self.delegate exampleViewController:self didFinishWithError:error];
|
||||
} else {
|
||||
[self finishWithStatus:retrievedIntent.status];
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
[self finishWithStatus:paymentIntent.status];
|
||||
}
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)finishWithStatus:(STPPaymentIntentStatus)status {
|
||||
switch (status) {
|
||||
// There may have been a problem with the payment method (STPPaymentMethodParams or STPSourceParams)
|
||||
case STPPaymentIntentStatusRequiresPaymentMethod:
|
||||
// did you call `confirmPaymentIntentWithParams:completion`?
|
||||
case STPPaymentIntentStatusRequiresConfirmation:
|
||||
// App should have handled the action, but didn't for some reason
|
||||
case STPPaymentIntentStatusRequiresAction:
|
||||
// The PaymentIntent was canceled (maybe by the backend?)
|
||||
case STPPaymentIntentStatusCanceled:
|
||||
[self.delegate exampleViewController:self didFinishWithMessage:@"Payment failed"];
|
||||
break;
|
||||
|
||||
// Processing. You could detect this case and poll for the final status of the PaymentIntent
|
||||
case STPPaymentIntentStatusProcessing:
|
||||
// Unknown status
|
||||
case STPPaymentIntentStatusUnknown:
|
||||
[self.delegate exampleViewController:self didFinishWithMessage:@"Order received"];
|
||||
break;
|
||||
|
||||
// if captureMethod is manual, backend needs to capture it to receive the funds
|
||||
case STPPaymentIntentStatusRequiresCapture:
|
||||
// succeeded
|
||||
case STPPaymentIntentStatusSucceeded:
|
||||
[self.delegate exampleViewController:self didFinishWithMessage:@"Payment successfully created"];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// CardExampleViewController.h
|
||||
// Custom Integration (ObjC)
|
||||
// CardManualConfirmationExampleViewController.h
|
||||
// Custom Integration (Recommended)
|
||||
//
|
||||
// Created by Ben Guo on 2/22/17.
|
||||
// Copyright © 2017 Stripe. All rights reserved.
|
||||
|
@ -10,7 +10,7 @@
|
|||
|
||||
@protocol ExampleViewControllerDelegate;
|
||||
|
||||
@interface CardExampleViewController : UIViewController
|
||||
@interface CardManualConfirmationExampleViewController : UIViewController
|
||||
|
||||
@property (nonatomic, weak) id<ExampleViewControllerDelegate> delegate;
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
//
|
||||
// CardManualConfirmationExampleViewController.m
|
||||
// Custom Integration (Recommended)
|
||||
//
|
||||
// Created by Ben Guo on 2/22/17.
|
||||
// Copyright © 2017 Stripe. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Stripe/Stripe.h>
|
||||
#import "CardManualConfirmationExampleViewController.h"
|
||||
#import "BrowseExamplesViewController.h"
|
||||
|
||||
/**
|
||||
This example demonstrates creating a payment with a credit/debit card using Manual Integration.
|
||||
It creates a Payment Method using card information collected with STPPaymentCardTextField, and
|
||||
then sends the Payment Method ID to our example backend to create and confirm the Payment Intent.
|
||||
*/
|
||||
@interface CardManualConfirmationExampleViewController () <STPPaymentCardTextFieldDelegate, UIScrollViewDelegate>
|
||||
@property (weak, nonatomic) STPPaymentCardTextField *paymentTextField;
|
||||
@property (weak, nonatomic) UIActivityIndicatorView *activityIndicator;
|
||||
@property (weak, nonatomic) UIScrollView *scrollView;
|
||||
@end
|
||||
|
||||
@implementation CardManualConfirmationExampleViewController
|
||||
|
||||
- (void)loadView {
|
||||
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectZero];
|
||||
scrollView.delegate = self;
|
||||
scrollView.alwaysBounceVertical = YES;
|
||||
scrollView.backgroundColor = [UIColor whiteColor];
|
||||
self.view = scrollView;
|
||||
self.scrollView = scrollView;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.title = @"Card - Manual Integration";
|
||||
self.edgesForExtendedLayout = UIRectEdgeNone;
|
||||
|
||||
UIBarButtonItem *buyButton = [[UIBarButtonItem alloc] initWithTitle:@"Pay" style:UIBarButtonItemStyleDone target:self action:@selector(pay)];
|
||||
buyButton.enabled = NO;
|
||||
self.navigationItem.rightBarButtonItem = buyButton;
|
||||
|
||||
STPPaymentCardTextField *paymentTextField = [[STPPaymentCardTextField alloc] init];
|
||||
paymentTextField.delegate = self;
|
||||
paymentTextField.cursorColor = [UIColor purpleColor];
|
||||
paymentTextField.postalCodeEntryEnabled = YES;
|
||||
self.paymentTextField = paymentTextField;
|
||||
[self.view addSubview:paymentTextField];
|
||||
|
||||
UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
|
||||
activityIndicator.hidesWhenStopped = YES;
|
||||
self.activityIndicator = activityIndicator;
|
||||
[self.view addSubview:activityIndicator];
|
||||
}
|
||||
|
||||
- (void)viewDidLayoutSubviews {
|
||||
[super viewDidLayoutSubviews];
|
||||
CGFloat padding = 15;
|
||||
CGFloat width = CGRectGetWidth(self.view.frame) - (padding*2);
|
||||
CGRect bounds = self.view.bounds;
|
||||
self.paymentTextField.frame = CGRectMake(padding, padding, width, 44);
|
||||
self.activityIndicator.center = CGPointMake(CGRectGetMidX(bounds),
|
||||
CGRectGetMaxY(self.paymentTextField.frame) + padding*2);
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
[self.paymentTextField becomeFirstResponder];
|
||||
}
|
||||
|
||||
- (void)paymentCardTextFieldDidChange:(nonnull STPPaymentCardTextField *)textField {
|
||||
self.navigationItem.rightBarButtonItem.enabled = textField.isValid;
|
||||
}
|
||||
|
||||
- (void)pay {
|
||||
if (![self.paymentTextField isValid]) {
|
||||
return;
|
||||
}
|
||||
if (![Stripe defaultPublishableKey]) {
|
||||
[self.delegate exampleViewController:self didFinishWithMessage:@"Please set a Stripe Publishable Key in Constants.m"];
|
||||
return;
|
||||
}
|
||||
[self.activityIndicator startAnimating];
|
||||
STPPaymentMethodCardParams *cardParams = [[STPPaymentMethodCardParams alloc] initWithCardSourceParams:self.paymentTextField.cardParams];
|
||||
|
||||
|
||||
STPPaymentMethodParams *paymentMethodParams = [STPPaymentMethodParams paramsWithCard:cardParams
|
||||
billingDetails:nil
|
||||
metadata:nil];
|
||||
[[STPAPIClient sharedClient] createPaymentMethodWithParams:paymentMethodParams
|
||||
completion:^(STPPaymentMethod * _Nullable paymentMethod, NSError * _Nullable error) {
|
||||
if (error) {
|
||||
[self.delegate exampleViewController:self didFinishWithError:error];
|
||||
} else {
|
||||
[self _createAndConfirmPaymentIntentWithPaymentMethod:paymentMethod];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
- (void)_createAndConfirmPaymentIntentWithPaymentMethod:(STPPaymentMethod *)paymentMethod {
|
||||
|
||||
[self.delegate createAndConfirmPaymentIntentWithAmount:@(100)
|
||||
paymentMethod:paymentMethod.stripeId
|
||||
returnURL:@"payments-example://stripe-redirect"
|
||||
completion:^(STPBackendResult status, STPPaymentIntent *paymentIntent, NSError *error) {
|
||||
if (status == STPBackendResultFailure || error) {
|
||||
[self.delegate exampleViewController:self didFinishWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
if (paymentIntent.status == STPPaymentIntentStatusRequiresAction) {
|
||||
[self _performActionForPaymentIntent:paymentIntent];
|
||||
} else {
|
||||
[self.delegate exampleViewController:self didFinishWithMessage:@"Payment successfully created"];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_performActionForPaymentIntent:(STPPaymentIntent *)paymentIntent {
|
||||
[self.delegate performRedirectForViewController:self
|
||||
withPaymentIntent:paymentIntent
|
||||
completion:^(STPPaymentIntent *retrievedIntent, NSError *error) {
|
||||
if (error) {
|
||||
[self.delegate exampleViewController:self didFinishWithError:error];
|
||||
} else {
|
||||
[self.delegate confirmPaymentIntent:retrievedIntent
|
||||
completion:^(STPBackendResult status, STPPaymentIntent *paymentIntent, NSError *error) {
|
||||
if (status == STPBackendResultFailure || error) {
|
||||
[self.delegate exampleViewController:self didFinishWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
if (paymentIntent.status == STPPaymentIntentStatusRequiresAction) {
|
||||
[self _performActionForPaymentIntent:paymentIntent];
|
||||
} else {
|
||||
[self.delegate exampleViewController:self didFinishWithMessage:@"Payment successfully created"];
|
||||
}
|
||||
}];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||
[self.view endEditing:NO];
|
||||
}
|
||||
|
||||
@end
|
|
@ -1,4 +1,4 @@
|
|||
# Custom Integration (ObjC)
|
||||
# Custom Integration (Recommended)
|
||||
|
||||
This example app demonstrates how to to use `STPAPIClient` to accept various payment methods. This may be a useful reference if you're building your own payment UI and not using `STPPaymentContext`.
|
||||
|
||||
|
@ -9,7 +9,7 @@ For more details on using Sources, see https://stripe.com/docs/mobile/ios/source
|
|||
1. If you haven't already, sign up for a [Stripe account](https://dashboard.stripe.com/register) (it takes seconds). Then go to https://dashboard.stripe.com/account/apikeys.
|
||||
2. Execute `./setup.sh` from the root of the repository to build the necessary dependencies
|
||||
3. Open `./Stripe.xcworkspace` (not `./Stripe.xcodeproj`) with Xcode
|
||||
4. Fill in the `stripePublishableKey` constant in `./Example/Custom Integration (ObjC)/Constants.m` with your test "Publishable key" from Stripe. This key should start with `pk_test`.
|
||||
4. Fill in the `stripePublishableKey` constant in `./Example/Custom Integration (Recommended)/Constants.m` with your test "Publishable key" from Stripe. This key should start with `pk_test`.
|
||||
5. Head to [example-ios-backend](https://github.com/stripe/example-ios-backend/tree/v14.0.0) and click "Deploy to Heroku". Provide your Stripe test "Secret key" as the `STRIPE_TEST_SECRET_KEY` environment variable. This key should start with `sk_test`.
|
||||
6. Fill in the `backendBaseURL` constant in `Constants.m` with the app URL Heroku provides (e.g. "https://my-example-app.herokuapp.com")
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// SofortExampleViewController.h
|
||||
// Custom Integration (ObjC)
|
||||
// Custom Integration (Recommended)
|
||||
//
|
||||
// Created by Ben Guo on 2/22/17.
|
||||
// Copyright © 2017 Stripe. All rights reserved.
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// SofortExampleViewController.m
|
||||
// Custom Integration (ObjC)
|
||||
// Custom Integration (Recommended)
|
||||
//
|
||||
// Created by Ben Guo on 2/22/17.
|
||||
// Copyright © 2017 Stripe. All rights reserved.
|
||||
|
@ -11,6 +11,9 @@
|
|||
#import "BrowseExamplesViewController.h"
|
||||
|
||||
/**
|
||||
SOFORT is not currently supported by PaymentMethods, so integration requires the use of Sources.
|
||||
ref. https://stripe.com/docs/payments/payment-methods#transitioning
|
||||
|
||||
This example demonstrates using Sources to accept payments using SOFORT, a popular payment method in Europe.
|
||||
First, we create a Sofort Source object with our payment details. We then redirect the user to the URL
|
||||
in the Source object to authorize the payment, and start polling the Source so that we can display the
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// main.m
|
||||
// Custom Integration (ObjC)
|
||||
// Custom Integration (Recommended)
|
||||
//
|
||||
// Created by Jack Flintermann on 1/15/15.
|
||||
// Copyright (c) 2015 Stripe. All rights reserved.
|
|
@ -40,7 +40,7 @@
|
|||
042CA4141A685E8D00D778E7 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
042CA4191A685E8D00D778E7 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||
042CA41A1A685E8D00D778E7 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
04823F781A6849200098400B /* Standard Integration (Swift).app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Standard Integration (Swift).app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
04823F781A6849200098400B /* Standard Integration (Sources Only).app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Standard Integration (Sources Only).app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
04BC299F1CD81D3900318357 /* BrowseProductsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrowseProductsViewController.swift; sourceTree = "<group>"; };
|
||||
04D075D91A69B82B00094431 /* Standard Integration (Swift).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Standard Integration (Swift).entitlements"; sourceTree = "<group>"; };
|
||||
04D076191A69C14700094431 /* Stripe.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Stripe.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
@ -109,7 +109,7 @@
|
|||
04823F791A6849200098400B /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
04823F781A6849200098400B /* Standard Integration (Swift).app */,
|
||||
04823F781A6849200098400B /* Standard Integration (Sources Only).app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
|
@ -126,9 +126,9 @@
|
|||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
04823F771A6849200098400B /* Standard Integration (Swift) */ = {
|
||||
04823F771A6849200098400B /* Standard Integration (Sources Only) */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 04823F971A6849200098400B /* Build configuration list for PBXNativeTarget "Standard Integration (Swift)" */;
|
||||
buildConfigurationList = 04823F971A6849200098400B /* Build configuration list for PBXNativeTarget "Standard Integration (Sources Only)" */;
|
||||
buildPhases = (
|
||||
C186AC351ECD0E1C00497DE3 /* Check Dependencies */,
|
||||
04823F741A6849200098400B /* Sources */,
|
||||
|
@ -142,9 +142,9 @@
|
|||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "Standard Integration (Swift)";
|
||||
name = "Standard Integration (Sources Only)";
|
||||
productName = "Stripe iOS Exampe (Simple)";
|
||||
productReference = 04823F781A6849200098400B /* Standard Integration (Swift).app */;
|
||||
productReference = 04823F781A6849200098400B /* Standard Integration (Sources Only).app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
@ -175,7 +175,7 @@
|
|||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 04823F731A6849200098400B /* Build configuration list for PBXProject "Standard Integration (Swift)" */;
|
||||
buildConfigurationList = 04823F731A6849200098400B /* Build configuration list for PBXProject "Standard Integration (Sources Only)" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
|
@ -196,7 +196,7 @@
|
|||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
04823F771A6849200098400B /* Standard Integration (Swift) */,
|
||||
04823F771A6849200098400B /* Standard Integration (Sources Only) */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
@ -422,7 +422,7 @@
|
|||
INFOPLIST_FILE = "Standard Integration (Swift)/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.stripe.SimpleSDKExample;
|
||||
PRODUCT_NAME = "Standard Integration (Swift)";
|
||||
PRODUCT_NAME = "Standard Integration (Sources Only)";
|
||||
PROVISIONING_PROFILE = "";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "";
|
||||
|
@ -451,7 +451,7 @@
|
|||
INFOPLIST_FILE = "Standard Integration (Swift)/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.stripe.SimpleSDKExample;
|
||||
PRODUCT_NAME = "Standard Integration (Swift)";
|
||||
PRODUCT_NAME = "Standard Integration (Sources Only)";
|
||||
PROVISIONING_PROFILE = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "";
|
||||
SWIFT_VERSION = 4.0;
|
||||
|
@ -462,7 +462,7 @@
|
|||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
04823F731A6849200098400B /* Build configuration list for PBXProject "Standard Integration (Swift)" */ = {
|
||||
04823F731A6849200098400B /* Build configuration list for PBXProject "Standard Integration (Sources Only)" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
04823F951A6849200098400B /* Debug */,
|
||||
|
@ -471,7 +471,7 @@
|
|||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
04823F971A6849200098400B /* Build configuration list for PBXNativeTarget "Standard Integration (Swift)" */ = {
|
||||
04823F971A6849200098400B /* Build configuration list for PBXNativeTarget "Standard Integration (Sources Only)" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
04823F981A6849200098400B /* Debug */,
|
|
@ -15,9 +15,9 @@
|
|||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "04823F771A6849200098400B"
|
||||
BuildableName = "Standard Integration (Swift).app"
|
||||
BlueprintName = "Standard Integration (Swift)"
|
||||
ReferencedContainer = "container:Standard Integration (Swift).xcodeproj">
|
||||
BuildableName = "Standard Integration (Sources Only).app"
|
||||
BlueprintName = "Standard Integration (Sources Only)"
|
||||
ReferencedContainer = "container:Standard Integration (Sources Only).xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
|
@ -43,9 +43,9 @@
|
|||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "04823F771A6849200098400B"
|
||||
BuildableName = "Standard Integration (Swift).app"
|
||||
BlueprintName = "Standard Integration (Swift)"
|
||||
ReferencedContainer = "container:Standard Integration (Swift).xcodeproj">
|
||||
BuildableName = "Standard Integration (Sources Only).app"
|
||||
BlueprintName = "Standard Integration (Sources Only)"
|
||||
ReferencedContainer = "container:Standard Integration (Sources Only).xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
|
@ -66,9 +66,9 @@
|
|||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "04823F771A6849200098400B"
|
||||
BuildableName = "Standard Integration (Swift).app"
|
||||
BlueprintName = "Standard Integration (Swift)"
|
||||
ReferencedContainer = "container:Standard Integration (Swift).xcodeproj">
|
||||
BuildableName = "Standard Integration (Sources Only).app"
|
||||
BlueprintName = "Standard Integration (Sources Only)"
|
||||
ReferencedContainer = "container:Standard Integration (Sources Only).xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
|
@ -85,9 +85,9 @@
|
|||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "04823F771A6849200098400B"
|
||||
BuildableName = "Standard Integration (Swift).app"
|
||||
BlueprintName = "Standard Integration (Swift)"
|
||||
ReferencedContainer = "container:Standard Integration (Swift).xcodeproj">
|
||||
BuildableName = "Standard Integration (Sources Only).app"
|
||||
BlueprintName = "Standard Integration (Sources Only)"
|
||||
ReferencedContainer = "container:Standard Integration (Sources Only).xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
|
@ -59,6 +59,8 @@ class CheckoutViewController: UIViewController, STPPaymentContextDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
private var redirectContext: STPRedirectContext?
|
||||
|
||||
init(product: String, price: Int, settings: Settings) {
|
||||
|
||||
let stripePublishableKey = self.stripePublishableKey
|
||||
|
@ -188,14 +190,77 @@ class CheckoutViewController: UIViewController, STPPaymentContextDelegate {
|
|||
self.paymentContext.requestPayment()
|
||||
}
|
||||
|
||||
private func performAction(for paymentIntent: STPPaymentIntent, completion: @escaping STPErrorBlock) {
|
||||
if self.redirectContext != nil {
|
||||
completion(NSError(domain: StripeDomain, code: 123, userInfo: [NSLocalizedDescriptionKey: "Should not have multiple concurrent redirects."]))
|
||||
return
|
||||
}
|
||||
|
||||
if let redirectContext = STPRedirectContext(paymentIntent: paymentIntent, completion: { [weak self] (clientSecret, error) in
|
||||
self?.redirectContext = nil
|
||||
if error != nil {
|
||||
completion(error)
|
||||
} else {
|
||||
STPAPIClient.shared().retrievePaymentIntent(withClientSecret: clientSecret, completion: { (retrievedIntent, retrieveError) in
|
||||
if retrieveError != nil {
|
||||
completion(retrieveError)
|
||||
} else {
|
||||
if let retrievedIntent = retrievedIntent {
|
||||
MyAPIClient.sharedClient.confirmPaymentIntent(retrievedIntent
|
||||
, completion: { (confirmedIntent, confirmError) in
|
||||
if confirmError != nil {
|
||||
completion(confirmError)
|
||||
} else {
|
||||
if let confirmedIntent: STPPaymentIntent = confirmedIntent {
|
||||
if confirmedIntent.status == .requiresAction {
|
||||
self?.performAction(for: confirmedIntent, completion: completion)
|
||||
} else {
|
||||
// success
|
||||
completion(nil)
|
||||
}
|
||||
} else {
|
||||
completion(NSError(domain: StripeDomain, code: 123, userInfo: [NSLocalizedDescriptionKey: "Error parsing confirmed payment intent"]))
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
completion(NSError(domain: StripeDomain, code: 123, userInfo: [NSLocalizedDescriptionKey: "Error retrieving payment intent"]))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}) {
|
||||
self.redirectContext = redirectContext
|
||||
redirectContext.startRedirectFlow(from: self)
|
||||
} else {
|
||||
completion(NSError(domain: StripeDomain, code: 123, userInfo: [NSLocalizedDescriptionKey: "Unable to create redirect context for payment intent."]))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: STPPaymentContextDelegate
|
||||
|
||||
func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: @escaping STPErrorBlock) {
|
||||
MyAPIClient.sharedClient.completeCharge(paymentResult,
|
||||
amount: self.paymentContext.paymentAmount,
|
||||
shippingAddress: self.paymentContext.shippingAddress,
|
||||
shippingMethod: self.paymentContext.selectedShippingMethod,
|
||||
completion: completion)
|
||||
MyAPIClient.sharedClient.createAndConfirmPaymentIntent(paymentResult,
|
||||
amount: self.paymentContext.paymentAmount,
|
||||
returnURL: "payments-example://stripe-redirect",
|
||||
shippingAddress: self.paymentContext.shippingAddress,
|
||||
shippingMethod: self.paymentContext.selectedShippingMethod) { (paymentIntent, error) in
|
||||
if error != nil {
|
||||
completion(error)
|
||||
} else {
|
||||
if let paymentIntent = paymentIntent {
|
||||
if paymentIntent.status == .requiresAction {
|
||||
self.performAction(for: paymentIntent, completion: completion)
|
||||
} else {
|
||||
// successful
|
||||
completion(nil)
|
||||
}
|
||||
} else {
|
||||
let err = NSError(domain: StripeDomain, code: 123, userInfo: [NSLocalizedDescriptionKey: "Unable to parse payment intent from response"])
|
||||
completion(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func paymentContext(_ paymentContext: STPPaymentContext, didFinishWith status: STPPaymentStatus, error: Error?) {
|
||||
|
|
|
@ -2,6 +2,17 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>$(BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>payments-example</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
|
|
|
@ -22,12 +22,56 @@ class MyAPIClient: NSObject, STPEphemeralKeyProvider {
|
|||
}
|
||||
}
|
||||
|
||||
func completeCharge(_ result: STPPaymentResult,
|
||||
amount: Int,
|
||||
shippingAddress: STPAddress?,
|
||||
shippingMethod: PKShippingMethod?,
|
||||
completion: @escaping STPErrorBlock) {
|
||||
let url = self.baseURL.appendingPathComponent("charge")
|
||||
func createAndConfirmPaymentIntent(_ result: STPPaymentResult,
|
||||
amount: Int,
|
||||
returnURL: String,
|
||||
shippingAddress: STPAddress?,
|
||||
shippingMethod: PKShippingMethod?,
|
||||
completion: @escaping STPPaymentIntentCompletionBlock) {
|
||||
let url = self.baseURL.appendingPathComponent("capture_payment")
|
||||
var params: [String: Any] = [
|
||||
"source": result.source.stripeID,
|
||||
"amount": amount,
|
||||
"return_url": returnURL,
|
||||
"metadata": [
|
||||
// example-ios-backend allows passing metadata through to Stripe
|
||||
"payment_request_id": "B3E611D1-5FA1-4410-9CEC-00958A5126CB",
|
||||
],
|
||||
]
|
||||
params["shipping"] = STPAddress.shippingInfoForCharge(with: shippingAddress, shippingMethod: shippingMethod)
|
||||
Alamofire.request(url, method: .post, parameters: params)
|
||||
.validate(statusCode: 200..<300)
|
||||
.responseJSON(completionHandler: { (response) in
|
||||
switch response.result {
|
||||
case .success(let json):
|
||||
completion(STPPaymentIntent.decodedObject(fromAPIResponse: json as? [AnyHashable: Any]), nil)
|
||||
case .failure(let error):
|
||||
completion(nil, error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func confirmPaymentIntent(_ paymentIntent: STPPaymentIntent, completion: @escaping STPPaymentIntentCompletionBlock) {
|
||||
let url = self.baseURL.appendingPathComponent("confirm_payment")
|
||||
let params: [String: Any] = ["payment_intent_id": paymentIntent.stripeId]
|
||||
Alamofire.request(url, method: .post, parameters: params)
|
||||
.validate(statusCode: 200..<300)
|
||||
.responseJSON(completionHandler: { (response) in
|
||||
switch response.result {
|
||||
case .success(let json):
|
||||
completion(STPPaymentIntent.decodedObject(fromAPIResponse: json as? [AnyHashable: Any]), nil)
|
||||
case .failure(let error):
|
||||
completion(nil, error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func completePayment(_ result: STPPaymentResult,
|
||||
amount: Int,
|
||||
shippingAddress: STPAddress?,
|
||||
shippingMethod: PKShippingMethod?,
|
||||
completion: @escaping STPErrorBlock) {
|
||||
let url = self.baseURL.appendingPathComponent("confirm_payment")
|
||||
var params: [String: Any] = [
|
||||
"source": result.source.stripeID,
|
||||
"amount": amount,
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# Standard Integration (Swift)
|
||||
# Standard Integration (Sources Only)
|
||||
|
||||
**Note** STPPaymentContext is not currently the recommended integration with StripeiOS because it only supports Sources. See Custom Integration (Recommended) for examples of how to use payment methods and sources when needed.
|
||||
|
||||
This example app demonstrates how to build a payment flow using our pre-built UI components (`STPPaymentContext`).
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
This example app lets you try out the pre-built UI components we provide.
|
||||
|
||||
You can run it without any initial setup and it's a great place to start if you're evaluating whether you want to use our [Standard Integration (Swift)](/Example/Standard%20Integration%20%28Swift%29/README.md) or build your own [Custom Integration (ObjC)](/Example/Custom%20Integration%20%28ObjC%29/README.md).
|
||||
You can run it without any initial setup and it's a great place to start if you're evaluating whether you want to use our [Standard Integration (Swift)](/Example/Standard%20Integration%20%28Swift%29/README.md) or build your own [Custom Integration (Recommended)](/Example/Custom%20Integration%20%28ObjC%29/README.md).
|
||||
|
||||
1. Open `./Stripe.xcworkspace` (not `./Stripe.xcodeproj`) with Xcode
|
||||
2. Build and run the "UI Examples" scheme
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
location = "group:Example/UI Examples.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Example/Standard Integration (Swift).xcodeproj">
|
||||
location = "group:Example/Standard Integration (Sources Only).xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Example/Custom Integration (ObjC).xcodeproj">
|
||||
location = "group:Example/Custom Integration (Recommended).xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
|
||||
#import "STPFormEncodable.h"
|
||||
|
||||
@class STPCardParams;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
|
@ -17,6 +19,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
*/
|
||||
@interface STPPaymentMethodCardParams : NSObject <STPFormEncodable>
|
||||
|
||||
/**
|
||||
A convenience initializer for creating a payment method from a card source.
|
||||
This should be used to help with migrations to Payment Methods from Sources.
|
||||
*/
|
||||
- (instancetype)initWithCardSourceParams:(STPCardParams *)cardSourceParams;
|
||||
|
||||
/**
|
||||
The card number, as a string without any separators. Ex. @"4242424242424242"
|
||||
*/
|
||||
|
|
|
@ -8,12 +8,27 @@
|
|||
|
||||
#import "STPPaymentMethodCardParams.h"
|
||||
|
||||
#import "STPCardParams.h"
|
||||
|
||||
@implementation STPPaymentMethodCardParams
|
||||
|
||||
@synthesize additionalAPIParameters = _additionalAPIParameters;
|
||||
|
||||
- (instancetype)initWithCardSourceParams:(STPCardParams *)cardSourceParams {
|
||||
self = [self init];
|
||||
if (self) {
|
||||
_number = [cardSourceParams.number copy];
|
||||
_expMonth = cardSourceParams.expMonth;
|
||||
_expYear = cardSourceParams.expYear;
|
||||
_cvc = [cardSourceParams.cvc copy];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - STPFormEncodable
|
||||
|
||||
|
||||
+ (NSString *)rootObjectName {
|
||||
return @"card";
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ info "Executing sample app builds (iPhone 6, iOS 11.x)..."
|
|||
|
||||
xcodebuild build \
|
||||
-workspace "Stripe.xcworkspace" \
|
||||
-scheme "Standard Integration (Swift)" \
|
||||
-scheme "Standard Integration (Sources Only)" \
|
||||
-sdk "iphonesimulator" \
|
||||
-destination "platform=iOS Simulator,name=iPhone 6,OS=11.2" \
|
||||
| xcpretty
|
||||
|
@ -49,7 +49,7 @@ fi
|
|||
|
||||
xcodebuild build \
|
||||
-workspace "Stripe.xcworkspace" \
|
||||
-scheme "Custom Integration (ObjC)" \
|
||||
-scheme "Custom Integration (Recommended)" \
|
||||
-sdk "iphonesimulator" \
|
||||
-destination "platform=iOS Simulator,name=iPhone 6,OS=11.2" \
|
||||
| xcpretty
|
||||
|
|
Loading…
Reference in New Issue