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:
Cameron 2019-04-16 16:20:38 -07:00 committed by GitHub
parent cc51af51f0
commit 690c3db2f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 1030 additions and 835 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 */,

View File

@ -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>

View File

@ -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.

View File

@ -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.

View File

@ -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:^{

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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")

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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 */,

View File

@ -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>

View File

@ -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?) {

View File

@ -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>

View File

@ -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,

View File

@ -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`).

View File

@ -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

View File

@ -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>

View File

@ -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"
*/

View File

@ -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";
}

View File

@ -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