App2App integration on ios with server side

From Barion Documentation
Revision as of 14:38, 7 February 2019 by Macika (talk | contribs) (Created page with "__NOTOC__ In this type of integration, you don't need to edit your server-side. In your mobile application, you don't have to implement network communication. We already did...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search


In this type of integration, you don't need to edit your server-side. In your mobile application, you don't have to implement network communication. We already did it for you. If you use the Barion Android library you have to add a few lines of code to your existing codebase. The disadvantage of this is, that you have to notify your own server-side about the payments and your mobile app can contain the POSKey.


Downloads, example

You can download the example project from this.

This project contains the models, and error handling processes. so this description won't talk about it.

flow chart

center|800px

Server-side developments

The server-side must be capable of the following call types (you can also see them on the flow chart above): :

You can use any languages and technology. In the case of PHP you can use our library, which you can download from Barion's GitHub page. The Barion System using JSON format in the requests and responses.

Integration steps

0. step - Server-side developments

0.1. step - Server-side requests

For the server-side requests, you can use the examples directory in our PHP library on Barion's GitHub page].

In this example we assume the following:

  • For the payment initiator request, we will send the list of products with a POST request under the Items key
  • For the payment's status, we will use a GET request.

Add server-side endpoints to your mobile application:

   
   // Parameters.h file
   @interface Parameters : NSObject

   + (NSString*)startBarionPaymentUrl;
   + (NSString*)getBarionPaymentStateUrl;
   + (NSString*)redirectUrl;

   @end

   // Parameters.m file
   @implementation Parameters

   + (NSString*)startBarionPaymentUrl
   {
      return @"http://your-domain.com/generate_barion_payment.php";
   }

   + (NSString*)getBarionPaymentStateUrl
   {
      return @"http://your-domain.com/get_barion_payment_state.php?PaymentId=";
   }

   + (NSString*)redirectUrl
   {
      NSString* url;
      if ([[[UIDevice currentDevice] systemVersion] floatValue] < 9) {
         url = @"your-domain://";
      } else {
         url = @"https://your-domain.com/";
      }
      return url;
   }

   @end

0.2. step - Setup your server-side for Universal links

In order to the Barion mobile application to redirect the user to the integrator mobile application, you have to put one file into your website's root directory. Your website must support HTTPS protocol. Simply put a file into your website's root directory. Name it to apple-app-site-association. Don't write any extension to the name. The file’s content should be something like this:

   {
      "applinks":{
         "apps":[],
         "details":[
            {
               "appID":"[your ios team id].[your application's bundle id]",
               "paths":[
                  "*"
               ]
            }
         ]
      }
   }

There is a real example on our website.

You can read more about Universal link in Apple's documentation The Universal Link doesn't work in simulator!

1. step - Setup your iOS application for Universal links

Open the XCode. Select your project and the target. Select the Capabilities tab and look for the Associated Domains section! You need to add the domains that you have written in the 0.2 step: center|800px As a result, this will generate a [ProjectName].entitlements file.

2. step - Setup your application to handle your own scheme

Switch to the Info tab, and open the URL Types section! Add your own url scheme as you can see on this image. center|800px

3. step - Create classes for network communication

Here you can see a possible solution:

3.1. step - Network base class

   // BaseConnectionHandler.h
   #import <Foundation/Foundation.h>

   @protocol BaseConnectionHandlerDelegate <NSObject>
      - (void)networkError;
   @end

   @interface BaseConnectionHandler : NSObject <NSURLConnectionDataDelegate, NSURLConnectionDelegate>
   {
       NSURLConnection* _connection;
       NSMutableData* _data;
       float _timeout;
   }

   - (void)cancel;
   - (void)startGETWithUrlString:(NSString*)urlString;
   - (void)startPOSTWithUrlString:(NSString*)urlString jsonString:(NSString*)jsonString;
   - (void)requestComplete:(NSDictionary*)result;
   - (void)error:(NSArray*)barionErrors;

   @end

   // BaseConnectionHandler.m
   #import "AppDelegate.h"
   #import "BaseConnectionHandler.h"
   #import "BarionError.h"

   @implementation BaseConnectionHandler

   static int _activityIndicatorRequest = 0;

   - (id)init
   {
      self = [super init];
      if(self)
      {
         _timeout = 30.0;
         _data = [[NSMutableData alloc] init];
         _activityIndicatorRequest++;
         [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
      }
      return self;
   }

   - (void)dealloc
   {
      [self cancel];
      if(_activityIndicatorRequest > 1) {
         _activityIndicatorRequest--;
      } else {
         _activityIndicatorRequest = 0;
         [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
      }
   }

   - (void)cancel
   {
      if(_connection)
      {
         [_connection cancel];
         _connection = nil;
      }
   }

   - (void)startGETWithUrlString:(NSString*)urlString
   {
      NSURL* url = [NSURL URLWithString: urlString];
      NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL: url];
      [request setTimeoutInterval:_timeout];
      _connection = [[NSURLConnection alloc] initWithRequest: request delegate:self];
   }

   - (void)startPOSTWithUrlString:(NSString*)urlString jsonString:(NSString*)jsonString
   {
      NSURL* url = [[NSURL alloc] initWithString: urlString];
      NSMutableURLRequest* urlRequest = [[NSMutableURLRequest alloc] initWithURL:url];
      NSData* requestData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
      [urlRequest setValue: [NSString stringWithFormat:@"%lu", (unsigned long)requestData.length] 
         forHTTPHeaderField:@"Content-Length"];
      [urlRequest setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"];
      [urlRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
      [urlRequest setHTTPMethod: @"POST"];
      [urlRequest setHTTPBody: requestData];
      [urlRequest setTimeoutInterval:_timeout];
      _connection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
   }

   - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
   {
      [_data appendData: data];
   }

   - (void)connectionDidFinishLoading:(NSURLConnection *)connection
   {
      NSError* jsonParsingError;
      NSDictionary* jsonResult = [NSJSONSerialization JSONObjectWithData: _data options: 0 error: &jsonParsingError];
      if(jsonParsingError != nil) {
         [self error: nil];
      } else {
         NSArray* jsonErrors = [jsonResult objectForKey: @"Errors"];
         if(jsonErrors.count != 0)
         {
            NSMutableArray* errors = [NSMutableArray new];
            for(int i = 0; i < jsonErrors.count; ++i)
            {
               NSDictionary* errorDict = [jsonErrors objectAtIndex: i];
               BarionError* error = [[BarionError alloc] initWithErrorCode: [errorDict objectForKey: @"ErrorCode"] 
                  title: [errorDict objectForKey: @"Title"] description: [errorDict objectForKey:@"Description"]];
               [errors addObject: error];
            }
            [self error: errors];
         } else {
            [self requestComplete: jsonResult];
         }
      }
   }

   - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
   {
      [self cancel];
      [self error: nil];
   }

   - (void)requestComplete:(NSDictionary*)result
   {
   }

   - (void)error:(NSArray*)barionErrors
   {
   }

   - (void)setTimeout:(float)timeout {
      _timeout = timeout;
   }

   @end

3.2. step - Payment initiator network class

   // StartPaymentConnectionHandler.h
   #import "BaseConnectionHandler.h"

   @protocol StartPaymentConnectionHandlerDelegate <BaseConnectionHandlerDelegate>
      - (void)paymentStartSuccessed:(NSDictionary*)result;
      - (void)paymentStartFailed:(NSArray*)errors;
   @end

   @interface StartPaymentConnectionHandler : BaseConnectionHandler
   {
      id<StartPaymentConnectionHandlerDelegate> _delegate;
   }

   - (id)initWithDelegate:(id<StartPaymentConnectionHandlerDelegate>)delegate products:(NSArray*)products;

   @end

   // StartPaymentConnectionHandler.m
   #import "StartPaymentConnectionHandler.h"
   #import "Product.h"
   #import "Parameters.h"

   @implementation StartPaymentConnectionHandler

   - (id)initWithDelegate:(id<StartPaymentConnectionHandlerDelegate>)delegate products:(NSArray*)products;
   {
      self = [super init];
      if(self)
      {
         _delegate = delegate;
        
         NSMutableArray* productsArray = [NSMutableArray new];
         for(int i = 0; i < products.count; ++i)
         {
            Product* p = [products objectAtIndex: i];
            NSMutableDictionary* dict = [NSMutableDictionary new];
            [dict setObject: p.name forKey: @"Name"];
            [dict setObject: p.productDescription forKey: @"Description"];
            [dict setObject: [NSString stringWithFormat: @"%f", p.quantity] forKey: @"Quantity"];
            [dict setObject: p.unit forKey: @"Unit"];
            [dict setObject: [NSString stringWithFormat: @"%f", p.price] forKey: @"UnitPrice"];
            [dict setObject: [NSString stringWithFormat: @"%f", p.price] forKey: @"ItemTotal"];
            [dict setObject: p.SKU forKey: @"SKU"];
            [productsArray addObject: dict];
         }
        
         NSMutableDictionary* containerDict = [NSMutableDictionary new];
         [containerDict setObject: productsArray forKey: @"Products"];
         [containerDict setObject: [Parameters redirectUrl] forKey: @"RedirectUrl"];
        
         NSString* jsonString = [[NSString alloc] initWithData: [NSJSONSerialization dataWithJSONObject: containerDict 
            options:0 error:nil] encoding: NSUTF8StringEncoding];
        
         [self startPOSTWithUrlString: [Parameters startBarionPaymentUrl] jsonString: jsonString];
      }
      return self;
   }

   - (void)requestComplete:(NSDictionary *)result
   {
      [_delegate paymentStartSuccessed: result];
   }

   - (void)error:(NSArray *)barionErrors
   {
      if(barionErrors == nil)
      {
         [_delegate networkError];
      } else {
         [_delegate paymentStartFailed: barionErrors];
      }
   }

   @end

3.3. step - Payment's status network class

   // GetPaymentStateConnectionHandler.h
   #import "BaseConnectionHandler.h"

   @protocol GetPaymentStateConnectionHandlerDelegate <BaseConnectionHandlerDelegate>
      - (void)getPaymentStateSuccessed:(NSDictionary*)response;
      - (void)getPaymentStateFailed:(NSArray*)barionErrors;
   @end

   @interface GetPaymentStateConnectionHandler : BaseConnectionHandler
   {
      id<GetPaymentStateConnectionHandlerDelegate> _delegate;
   }
   
   - (id)initWithDelegate:(id<GetPaymentStateConnectionHandlerDelegate>)delegate paymentId:(NSString*)paymentId;

   @end

   // GetPaymentStateConnectionHandler.m
   #import "GetPaymentStateConnectionHandler.h"
   #import "Parameters.h"
   #import "BarionGetPaymentStateResponse.h"

   @implementation GetPaymentStateConnectionHandler

   - (id)initWithDelegate:(id<GetPaymentStateConnectionHandlerDelegate>)delegate paymentId:(NSString*)paymentId
   {
      self = [super init];
      if(self)
      {
         _delegate = delegate;
        
         NSString* urlString = [NSString stringWithFormat: @"%@%@",[Parameters getBarionPaymentStateUrl], paymentId];
         [self startGETWithUrlString: urlString];
      }
      return self;
   }

   - (void)requestComplete:(NSDictionary *)result
   {
      [_delegate getPaymentStateSuccessed: result];
   }

   - (void)error:(NSArray *)barionErrors
   {
      if(barionErrors == nil) {
         [_delegate networkError];
      } else {
         [_delegate getPaymentStateFailed: barionErrors];
      }
   }

   @end

4. step - Initiate payment

At any point in the mobile application – where the products you want to pay are available - you should initiate the payment in the Barion System. The Barion IOS Library will check the parameters and if something is wrong, itwill return with an error. The possible error codes can be found on this page.


   - (IBAction)payWithBarion:(id)sender
   {
      _progressAlert = [[UIAlertView alloc] initWithTitle: @"Communicating with the Barion system" message:nil 
         delegate:nil cancelButtonTitle:nil otherButtonTitles:nil, nil];
      [_progressAlert show];
      startPaymentHandler = [[StartPaymentConnectionHandler alloc] initWithDelegate:self products: _products];
   }

5. step - Get results and redirect

If the payment initiation was successful you need to redirect the user to the Barion mobile application or to Barion's website.

In order for the user to be redirected to the Barion mobile application, the following requirements must be met:

  • The device must have Barion mobile application installed.
  • The installed Barion mobile application version must be above of 3.1.0.

After you successfully initiated the payment you got a PaymentId parameter in the response. Start the Bairon mobile application with this PaymentId by the following:

   - (void)paymentStartSuccessed:(NSDictionary*)result
   {
      startPaymentHandler = nil;
      [_progressAlert dismissWithClickedButtonIndex:0 animated: YES];
      BarionStartPaymentResponse* response = [[BarionStartPaymentResponse alloc] initWithJsonDictionary: result];
      NSURL* url = [NSURL URLWithString:
         [NSString stringWithFormat: @"barion://?pid=%@&redirectUrl=%@", response.paymentId, [Parameters redirectUrl]]];
      if([[UIApplication sharedApplication] canOpenURL: url]) {
         [[UIApplication sharedApplication] openURL: url];
      } else {
         UIStoryboard* sb = [UIStoryboard storyboardWithName: @"Main" bundle:nil];
         WebViewController* web = [sb instantiateViewControllerWithIdentifier:@"webViewController"];
         web.paymentId = response.paymentId;
         web.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
         [self.navigationController setViewControllers: [NSArray arrayWithObject: web]];
      }
   }

   - (void)paymentStartFailed:(NSArray*)errors
   {
      startPaymentHandler = nil;
      [_progressAlert dismissWithClickedButtonIndex:0 animated: YES];
   }

   - (void)networkError
   {
      startPaymentHandler = nil;
      [_progressAlert dismissWithClickedButtonIndex:0 animated: YES];
   }

When the Barion mobile application isn't installed on the user's device, you need to load Barion's website in a UIWebView.

5.1. step - Setup UIWebView

We create a new viewcontroller with a full screen Webview and with a UIActivityIndicatorView. In the Interface Builder this will look something like this:

center|200px

Here is the content of the UIViewController for that view above:

   // WebViewController.h
   #import <UIKit/UIKit.h>

   @interface WebViewController : UIViewController <UIWebViewDelegate>

   @property (weak, nonatomic) IBOutlet UIWebView* webView;
   @property (weak, nonatomic) IBOutlet UIActivityIndicatorView* loadIndicator;

   @property (nonatomic, retain) NSString* paymentId;

   @end

   // WebViewController.m
   #import "WebViewController.h"
   #import "PaymentResultViewController.h"
   #import "Parameters.h"
   #import "AppDelegate.h"

   @implementation WebViewController

   - (void)viewDidLoad {
      [super viewDidLoad];
    
      self.title = @"Barion Smart Books";
    
      [_webView setHidden: YES];
      [_loadIndicator setHidden: NO];
      [_loadIndicator startAnimating];
    
      NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:
         [NSURL URLWithString:
            [NSString stringWithFormat: @"https://barion.com/Pay?Id=%@", _paymentId]]];
    
      [_webView loadRequest: request];
   }

   - (void)webViewDidFinishLoad:(UIWebView *)webView
   {
      [_loadIndicator stopAnimating];
      [_loadIndicator setHidden: YES];
      [_webView setHidden: NO];
   }

   - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
   {
      [_loadIndicator stopAnimating];
      UIAlertView* alert = [[UIAlertView alloc] initWithTitle: @"Error" message: @"The requested page could not load" 
         delegate: nil cancelButtonTitle: @"OK" otherButtonTitles: nil, nil];
      [alert show];
      UIStoryboard* sb = [UIStoryboard storyboardWithName: @"Main" bundle:nil];
      PaymentResultViewController* result = [sb instantiateViewControllerWithIdentifier:@"paymentResultViewController"];
      result.url = [NSURL URLWithString: [NSString stringWithFormat:@"%@?paymentId=%@", [Parameters redirectUrl], _paymentId]];
      [self.navigationController setViewControllers: [NSArray arrayWithObject: result]];
   }

   - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
   {
      if ([[request.URL absoluteString] hasPrefix: [Parameters redirectUrl]]) {
         [((AppDelegate*)[UIApplication sharedApplication].delegate) openPaymentResultViewControllerWithUrl: request.URL];
         return NO;
      }
      return YES;
   }

   @end

6. step - Handling redirect

You need to implement these methods in your application's AppDelegate.m file.

   - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler {
      [self openPaymentResultViewControllerWithUrl: userActivity.webpageURL];
      return YES;
   }

   - (void)openPaymentResultViewControllerWithUrl:(NSURL*)url
   {
      UIStoryboard* sb = [UIStoryboard storyboardWithName: @"Main" bundle:nil];
      PaymentResultViewController* result = [sb instantiateViewControllerWithIdentifier:@"paymentResultViewController"];
      result.url = url;
      result.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
      UINavigationController* controller = (UINavigationController*)self.window.rootViewController;
      [controller setViewControllers: [NSArray arrayWithObject: result]];
   }

7. step - Get payment's result

In the Barion mobile application the user can pay or reject the payment. After that, the application closes itself. Then the user will be redirected to the integrator mobile application and you need to request the payment's status from your server:

   // PaymentResultViewController.h
   #import <UIKit/UIKit.h>
   #import "GetPaymentStateConnectionHandler.h"

   @interface PaymentResultViewController : UIViewController <GetPaymentStateConnectionHandlerDelegate>
   {
      GetPaymentStateConnectionHandler* _getPaymentStateHandler;
   }

   @property (nonatomic, retain) NSURL* url;

   @end

   // PaymentResultViewController.m
   #import "PaymentResultViewController.h"
   #import "BarionError.h"
   #import "BarionGetPaymentStateResponse.h"
   #import "Parameters.h"

   @implementation PaymentResultViewController

   - (void)viewDidLoad {
      [super viewDidLoad];

      NSString* paymentId = [[_url absoluteString] stringByReplacingOccurrencesOfString: [NSString stringWithFormat: @"%@?paymentId=", [Parameters redirectUrl]] withString:@""];
      _getPaymentStateHandler = [[GetPaymentStateConnectionHandler alloc] initWithDelegate: self paymentId: paymentId];
   }

   - (void)getPaymentStateSuccessed:(NSDictionary*)response
   {
      _getPaymentStateHandler = nil;
      BarionGetPaymentStateResponse* paymentState = [[BarionGetPaymentStateResponse alloc] initWithJsonDictionary: response];
      // you can find the payment details in this object
   }

   - (void)getPaymentStateFailed:(NSArray*)barionErrors
   {
      _getPaymentStateHandler = nil;
   }

   - (void)networkError
   {
      _getPaymentStateHandler = nil;
   }

   @end

From the BarionGetPaymentStateResponse object you can easily get the payment details.