Skip to content

Commit

Permalink
[Hotfix] 3D Security fix (#163)
Browse files Browse the repository at this point in the history
* Fixed the 3D Security issue after migrating to WKWebView;
Fixed the HTML viewport displaying incorrectly;

* Captured the response and fixed encoding

* Improved 3D Secure response handling
  • Loading branch information
mpetrenco authored Nov 18, 2019
1 parent 477ba5a commit 07fab70
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 93 deletions.
2 changes: 1 addition & 1 deletion Source/API/JPCheckCard.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#import <Foundation/Foundation.h>
#import "JPTransaction.h"
#import <Foundation/Foundation.h>

/**
* A JPCheckCard object is returned from invoking the Judo SDK 'checkCard' method, and contains the necessary details of the transaction
Expand Down
5 changes: 1 addition & 4 deletions Source/API/JPTransaction.m
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,7 @@ - (NSError *)validateTransaction { //!OCLINT
return [NSError judoReferenceMissingError];
}

if (![self isKindOfClass:JPRegisterCard.class]
&& ![self isKindOfClass:JPCheckCard.class]
&& ![self isKindOfClass:JPSaveCard.class]
&& !self.amount) {
if (![self isKindOfClass:JPRegisterCard.class] && ![self isKindOfClass:JPCheckCard.class] && ![self isKindOfClass:JPSaveCard.class] && !self.amount) {
return [NSError judoAmountMissingError];
}

Expand Down
120 changes: 78 additions & 42 deletions Source/Controller/JudoPayViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ - (void)setupView {
// Layout Constraints
self.keyboardHeightConstraint = [self.paymentButton.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor constant:0];
self.paymentButtonHeightConstraint = [self.paymentButton.heightAnchor constraintEqualToConstant:self.theme.buttonHeight];

NSArray *constraints = @[
self.paymentButtonHeightConstraint,
[self.paymentButton.leftAnchor constraintEqualToAnchor:self.view.safeLeftAnchor],
Expand Down Expand Up @@ -471,15 +471,15 @@ - (void)setupView {

- (void)viewSafeAreaInsetsDidChange {
[super viewSafeAreaInsetsDidChange];

CGFloat bottomInset = 0.0;

if (@available(iOS 11.0, *)) {
bottomInset = self.view.safeAreaInsets.bottom;
} else {
bottomInset = self.bottomLayoutGuide.length;
}

[self.paymentButton setTitleEdgeInsets:UIEdgeInsetsMake(0, 0, bottomInset, 0)];
self.paymentButtonHeightConstraint.constant = self.theme.buttonHeight + bottomInset;
[self.paymentButton setNeedsUpdateConstraints];
Expand Down Expand Up @@ -876,66 +876,102 @@ - (void)judoPayInputDidChangeText:(JPInputField *)input {

#pragma mark - WKNavigation Delegate Methods

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
- (void)handleRedirectForWebView:(WKWebView *)webView
redirectURL:(NSString *)redirectURL
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {

NSString *urlString = navigationAction.request.URL.absoluteString;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSMutableString *javascriptCode = [NSMutableString new];

if ([urlString rangeOfString:@"Parse3DS"].location == NSNotFound) {
decisionHandler(WKNavigationActionPolicyAllow);
return;
}
[javascriptCode appendString:@"const paRes = document.getElementsByName('PaRes')[0].value;"];
[javascriptCode appendString:@"const md = document.getElementsByName('MD')[0].value;"];
[javascriptCode appendString:@"[paRes, md]"];

NSString *bodyString = [[NSString alloc] initWithData:navigationAction.request.HTTPBody encoding:NSUTF8StringEncoding];
if (!bodyString) {
[webView evaluateJavaScript:javascriptCode
completionHandler:^(NSArray *response, NSError *error) {
NSDictionary *responseDictionary = [self mapToDictionaryWithResponse:response];
[self setLoadingViewTitleForTransactionType:self.transactionType];
[self handleACSFormWithResponse:responseDictionary decisionHandler:decisionHandler];
}];
});
}

- (void)handleACSFormWithResponse:(NSDictionary *)response
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {

if (!self.pending3DSReceiptId) {
if (self.completionBlock) {
self.completionBlock(nil, [NSError judo3DSRequestFailedErrorWithUnderlyingError:nil]);
}
decisionHandler(WKNavigationActionPolicyCancel);
return;
}

NSDictionary *results = [bodyString extractURLComponentsQueryItems];
[self.loadingView startAnimating];
self.title = self.theme.authenticationTitle;

[self.transaction threeDSecureWithParameters:response
receiptId:self.pending3DSReceiptId
completion:^(JPResponse *response, NSError *error) {
[self.loadingView stopAnimating];

if (self.completionBlock) {
if (response) {
self.completionBlock(response, nil);
decisionHandler(WKNavigationActionPolicyAllow);

} else {
NSError *judoError = error ? error : NSError.judoResponseParseError;
self.completionBlock(nil, judoError);
decisionHandler(WKNavigationActionPolicyCancel);
}
}
}];
}

if (self.pending3DSReceiptId) {
if (self.transactionType == TransactionTypeRegisterCard) {
self.loadingView.actionLabel.text = self.theme.verifying3DSRegisterCardTitle;
} else {
self.loadingView.actionLabel.text = self.theme.verifying3DSPaymentTitle;
}
- (NSDictionary *)mapToDictionaryWithResponse:(NSArray *)response {

[self.loadingView startAnimating];
self.title = self.theme.authenticationTitle;
[self.transaction threeDSecureWithParameters:results
receiptId:self.pending3DSReceiptId
completion:^(JPResponse *response, NSError *error) {
[self.loadingView stopAnimating];
if (self.completionBlock) {
if (response) {
self.completionBlock(response, nil);
} else {
NSError *judoError = error ? error : [NSError judo3DSRequestFailedErrorWithUnderlyingError:nil];
self.completionBlock(nil, judoError);
}
}
}];
if (response.count != 2)
return nil;

return @{
@"PaRes" : response[0],
@"MD" : [response[1] stringByReplacingOccurrencesOfString:@" " withString:@"+"]
};
}

- (void)setLoadingViewTitleForTransactionType:(TransactionType)transactionType {
if (transactionType == TransactionTypeRegisterCard) {
self.loadingView.actionLabel.text = self.theme.verifying3DSRegisterCardTitle;
} else {
if (self.completionBlock) {
self.completionBlock(nil, [NSError judo3DSRequestFailedErrorWithUnderlyingError:nil]);
}
self.loadingView.actionLabel.text = self.theme.verifying3DSPaymentTitle;
}
}

[UIView animateWithDuration:0.3
animations:^{ self.threeDSWebView.alpha = 0.0f; }
completion:^(BOOL finished) {
[self.threeDSWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"about:blank"]]];
}];
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {

decisionHandler(WKNavigationActionPolicyCancel);
NSString *urlString = navigationAction.request.URL.absoluteString;

if ([urlString rangeOfString:@"Parse3DS"].location == NSNotFound) {
decisionHandler(WKNavigationActionPolicyAllow);
return;
}

[self handleRedirectForWebView:webView redirectURL:urlString decisionHandler:decisionHandler];
return;
}

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {

NSMutableString *scriptContent = [NSMutableString stringWithString:@"const meta = document.createElement('meta');"];
[scriptContent appendString:@"meta.name='viewport';"];
[scriptContent appendString:@"meta.content='width=device-width';"];
[scriptContent appendString:@"const head = document.getElementsByTagName('head')[0];"];
[scriptContent appendString:@"head.appendChild(meta);"];
[scriptContent appendString:@"meta.name"];

[_threeDSWebView evaluateJavaScript:scriptContent completionHandler:nil];

CGFloat alphaVal = 1.0f;
if ([webView.URL.absoluteString isEqualToString:@"about:blank"]) {
alphaVal = 0.0f;
Expand Down
2 changes: 1 addition & 1 deletion Source/Controller/JudoPaymentMethodsViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ - (void)onCardPaymentButtonDidTap {
currentSession:self.judoKitSession
cardDetails:self.viewModel.cardDetails
completion:completion];

viewController.primaryAccountDetails = self.viewModel.primaryAccountDetails;
viewController.theme = self.theme;
[self.navigationController pushViewController:viewController animated:YES];
Expand Down
6 changes: 3 additions & 3 deletions Source/Extensions/NSError+Judo.m
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,9 @@ + (NSError *)judoParameterError {
}

+ (NSError *)judoApplePayConfigurationError {
return [NSError errorWithDomain:JudoErrorDomain
code:JudoErrorInvalidApplePayConfiguration
userInfo:@{NSLocalizedDescriptionKey : @"Invalid Apple Pay configuration"}];
return [NSError errorWithDomain:JudoErrorDomain
code:JudoErrorInvalidApplePayConfiguration
userInfo:@{NSLocalizedDescriptionKey : @"Invalid Apple Pay configuration"}];
}

+ (NSError *)judoInvalidCardNumberError {
Expand Down
Loading

0 comments on commit 07fab70

Please sign in to comment.