Git Product home page Git Product logo

saltedge-ios-swift's Introduction

CocoaPods Compatible Twitter URL

Salt Egde Logo

iOS Example application

This application is a Proof Of Concept, designated to demonstrate (simulate) communication between Salt Edge API and Mobile Client.

Requirements

  • iOS 12.0+ / macOS 10.13+
  • Swift 5+

Salt Edge iOS / macOS Swift SDK

A handful of classes to help you interact with the Salt Edge API from your iOS / macOS app. Last SDK version (3+) supports Salt Edge API v5.

Requirements

  • iOS 10.0+ / macOS 10.13+
  • Swift 5+

Installation via CocoaPods

Add the pod to your Podfile

for Salt Edge API v5 use

pod 'SaltEdge-iOS-Swift', '~> 3.5.2'

for Salt Edge API v4 (Deprecated) use

pod 'SaltEdge-iOS-Swift', '~> 1.1.2'

Install the pod

$ pod install

Import SDK into your app

import SaltEdge

Init SDK

Replace the appId, appSecret.

To setup Salt Edge API, call in AppDelegate.swift:

SERequestManager.shared.set(appId: appId, appSecret: appSecret)

Note: You can find your appId and appSecret on your secrets page (Eligible only for Salt Edge API). customerId - is the unique identifier of the new customer.

SERequestManager

A class designed with convenient methods for interacting with and querying the Salt Edge API. Contains methods for fetching entities (Connections, Transactions, Accounts, etc.), for requesting connect url for creating, reconnecting and refreshing Connections via a SEWebView, and also for creating Connections via the REST API.

Each successful request via SERequestManager returns SEResponse containing data and meta.

Each failed request returns standard Swift Error .

Use the manager to interact with the provided API:

let connectionParams = SEConnectionParams(
    consent: SEConsent(scopes: ["account_details", "transactions_details"]),
    countryCode: "XF",
    providerCode: "fakebank_simple_xf",
    credentials: ["login": "username", "password": "secret"]
)
SERequestManager.shared.createConnection(with: connectionParams) { response in
    switch response {
    case .success(let value):
        // value.data is a valid SEConnection
    case .failure(let error):
        // Handle error
    }
}

Get customer secret

To access the Salt Edge SDK functionality, it is necсessary to get a Customer secret. To get a customer secret, you can make a request via SERequestManager.

After successful request, you should link the received customer secret with the request manager:

SERequestManager.shared.set(customerSecret: "received-customer-secret")

Note: You must save the received customer secret in you storage(e.g. UserDefaults) and for all future Salt Edge SDK usage, you should link the stored customer secret.

Example

let defaults = UserDefaults.standard
if let secret = defaults.string(forKey: "customerSecret") {
    SERequestManager.shared.set(customerSecret: secret)
} else {
    let params = SECustomerParams(identifier: "your-customer-unique-id")
    SERequestManager.shared.createCustomer(with: params) { response in
        switch response {
        case .success(let value):
            // Save customer secret to your storage and then link it with API manager
            defaults.set(value.data.secret, forKey: "customerSecret")

            SERequestManager.shared.set(customerSecret: value.data.secret)
            case .failure(let error):
            // Handle error
        }
    }
}

SEWebView

A small WKWebView subclass for using Salt Edge Connect within your iOS app.

Example

Let your view controller conform to the SEWebViewDelegate protocol.

class MyViewController : UIViewController, SEWebViewDelegate {
  // ... snip ...
}

Instantiate a SEWebView and add it to the controller:

let connectWebView = SEWebView(frame: self.view.bounds)
connectWebView.stateDelegate = self
self.view.addSubview(connectWebView)

Implement the SEWebViewDelegate methods in the controller:

// ... snip ...

func webView(_ webView: SEWebView, didReceiveCallbackWithResponse response: SEConnectResponse) {
    switch response.stage {
    case .success:
	    // Connection successfully connected
    case .fetching:
	    // Connection is fetching. You can safe connection secret if it is present.
    case .error:
	    // Handle error
    }
}

func webView(_ webView: SEWebView, didReceiveCallbackWithError error: Error) {
	// Handle error
}

Load the Salt Edge Connect URL into the web view and you're good to go:

SERequestManager.shared.createConnectSession(params: connectSessionParams) { response in
	switch response {
	case .success(let value):
		if let url = URL(string: value.data.connectUrl) {
		    self.webView.load(URLRequest(url: url))
		}
	case .failure(let error):
		// Handle error
	}
}

Models

There are some provided models for serializing the objects received in the API responses. These represent the Providers, Connections, Accounts, Transactions, provider fields and their options. Whenever you request a resource that returns one of these types, they will always get serialized into Swift structures. For instance, the getAllTransactions(for connectionSecret: String, params: SETransactionParams? = nil, completion: SEHTTPResponse<[SETransaction]>) method has a SEResponse containing data and meta where data is [SETransaction] in it's success callback.

Models, contained within the components:

  • SEProvider
  • SEConnection
  • SEAccount
  • SETransaction
  • SEConnectSessionResponse
  • SEAttempt
  • SEConsent
  • SECountry
  • SECustomer
  • SEStage
  • SEError
  • SEProviderField
  • SEProviderFieldOption

For a supplementary description of the models listed above that is not included in the sources docs, feel free to visit the Salt Edge API Reference

Models extra

Some of models have extra field of type [String: Any]. In case you need to get any field, you can do the following:

let account = SEAccount()

guard let accountExtra = account.extra else { return }

let anyValue = accountExtra["string_key"]

Some of key-pairs have predifinied extensions:

let transaction = SETransaction()

guard let transactionExtra = transaction.extra else { return }

let possibleDuplicate: Bool = transactionExtra.possibleDuplicate

Documentation

Documentation is available for all of the components. Use quick documentation (Alt+Click) to get a quick glance at the documentation for a method or a property.

Running the demo

To run the contained here demo app, you have to set the demo with your App ID, App Secret and choose desired API.

To setup Salt Edge API, call:

SERequestManager.shared.set(appId: appId, appSecret: appSecret)

Set up the appId, appSecret and customerId constants to your App ID and corresponding App Secret in AppDelegate.swift:69-72.

Versioning

The current version of the SDK is 3.5.2, and supports the latest available version of Salt Edge API. Any backward-incompatible changes in the API will result in changes to the SDK.

Changelog

See the Changelog file.

License

See the LICENSE file.

References

  1. How to... instructions
  2. Salt Edge API General
  3. Salt Edge Client Dashboard
  4. Salt Edge API v5 Reference

For more information, feel free to contact us

Copyright © 2014 Salt Edge Inc. https://www.saltedge.com

saltedge-ios-swift's People

Contributors

alisnic avatar baller784 avatar constantinkv avatar petalvlad avatar pio341 avatar v-somov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

saltedge-ios-swift's Issues

The given data was not valid JSON

Hey.

When I try to download a list of providers (including fake ones), I get the error "The given data was not valid JSON". Code = 3840

[Swift.DecodingError.dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.})))]

It looks like there is an error parsing incoming json, can you check your model?

ConsentRouter.show() parameters bug

I think there is a bug in ConsentRouter.show() parameters. Both .show() and .revoke() uses SEBaseConsentsParams (that are Encodable, ParametersEncodable). While with revoke() its ok I think (although I haven't tested it), but this causes problems with .show(), that uses GET method - Encodable parameters are being put into urlRequest's httpBody property.
So, as far as I understand, parameters used in .show() (and probably in .list(), that is also using GET method) should be URLEncodable (but not Encodable). That way parameters will be appended to the request's url.

SEConsentListParams dictionaryRepresentation bug

SEConsentListParams is a subclass of SEBaseConsentsParams, however dictionaryRepresentation method (of a protocol URLEncodable) takes properties only from SEConsentListParams class itself (it does not take inherited properties from SEBaseConsentsParams).

Lets say you create SEConsentListParams(perPage:"1000", connectionId:"someID"). When encoded to url it will look like ".../consents/?per_page=1000" - there will be no parameter connection_id in the URL.

As far as I can tell Mirror(reflecting:self) (used in URLEncodable.dictionaryRepresentation) takes properties only from self. Looks like one way to get properties from the parent class could be later calling mirror.superclassMirror and process it the same way as mirror.

Service provider logo

Hello.

I am using your SDK for iOS, and I see that the SEProvider model does not contain an attribute with a link to the provider's logo.

This attribute logo_url is listed with the API providers-attributes, but I can’t use it in my project

Please add it to the provider model.
Thanks

missing? duplicated connection callback

Am I missing something or is there no duplicated connection callback anymore (that was in the earlier - deprecated - saltedge-ios (https://github.com/saltedge/saltedge-ios) API)? Try creating a new connection to some bank, enter some invalid credentials (connection fails, but it still being created). Then select that provider from the providers list again and try entering the same credentials - an error message "Server Error. You have already used these credentials to connect this provider" is being displayed in the webview, but no callback being received by api. I tried with the sample app. I think the same thing happens if first connection is being created successfully. If you try select provider and enter same credentials again - an error is being displayed on top of the webview, but no callback with duplicated connection id/secret is being received by api.
Is it a bug or am I missing something?

Reached login limit (100) in current mode

Hello I make some test with my IOS app with a fake account but now when I make an test after I put the login(username) and password(secret) in the webview; the web view show an server error ( Reached login limit (100) in current mode )!
please how can I make some more test?

CreateConnectSession Params, Invalid customer id.

let connectSessionsParams = SEConnectSessionsParams(
attempt: SEAttempt(returnTo: "https://www.google.com"), javascriptCallbackType: "iframe", consent: SEConsent(scopes: ["account_details", "transactions_details"])
)

    SERequestManager.shared.createConnectSession(params: connectSessionsParams) { response in
        switch response {
        case .success(let value):
            if let url = URL(string: value.data.connectUrl) {
                self.webView.load(URLRequest(url: url))
            }
        case .failure(let error):
            // Handle error
        }
    }

After successfully creating a customer, trying to connect to a session is failing because of an invalid customer id. I cannot find a param for sending the customer id. How should I add it to the API call?

ConnectionRouter wrong update connection url

ConnectionRouter shows case .update(_, let id, _): return "connection/update/\(id)" as update url. But url in the documentation is https://www.saltedge.com/api/v5/connection. So, IMO it should be something like
case .update(_, _, _): return "connection". Unless I am missing something here.

Redirect

Hello, I use 3.1.3 in my app IOS
It's not an issue,
I follow all the instruction and I follow the example but my problem is : after the webview display in the last step I can't redirect to my app, I even try to print the response in the SEWebViewDelegate but there's no response until it reached "Done" there's no response. Thank you

import UIKit
import SaltEdge
import WebKit
import PKHUD

class SaltEdgeVC: UIViewController {

@IBOutlet weak var webView: SEWebView!
private var provider: SEProvider?
private lazy var attempt = SEAttempt(returnTo: "http://httpbin.org/")
private lazy var consent = SEConsent(scopes: ["account_details", "transactions_details"])

override func viewDidLoad() {
    super.viewDidLoad()
    
    let url = URL (string: "\(SaltEdgeService.instance.connectURL)")
    let requestObj = URLRequest(url: url!)
    webView.load(requestObj)
    webView.stateDelegate = self
    
}


@IBAction func backBtnWasPressed(_ sender: Any) {
    dismiss(animated: true, completion: nil)
}

func requestToken(connection: SEConnection? = nil, refresh: Bool = false) {
    HUD.show(.labeledProgress(title: "Requesting Token", subtitle: nil))
    if let connection = connection {
        if refresh {
            // Set javascriptCallbackType to "iframe" to receive callback with connection_id and connection_secret
            // see https://docs.saltedge.com/guides/connect
            let params = SERefreshSessionsParams(
                attempt: attempt,
                javascriptCallbackType: "iframe"
            )
            SERequestManager.shared.refreshSession(with: connection.secret, params: params) { [weak self] response in
                self?.handleConnectSessionResponse(response)
            }
        } else {
            // Set javascriptCallbackType to "iframe" to receive callback with connection_id and connection_secret
            // see https://docs.saltedge.com/guides/connect
            let params = SEReconnectSessionsParams(
                attempt: attempt,
                javascriptCallbackType: "iframe",
                consent: consent
            )
            SERequestManager.shared.reconnectSession(with: connection.secret, params: params) { [weak self] response in
                self?.handleConnectSessionResponse(response)
            }
        }
    } else {
        createSession()
    }
}

private func handleConnectSessionResponse(_ response: SEResult<SEResponse<SEConnectSessionResponse>>) {
    switch response {
    case .success(let value):
        HUD.hide(animated: true)
        if let url = URL(string: value.data.connectUrl) {
            let request = URLRequest(url: url)
            webView.load(request)
            webView.isHidden = false
        }
    case .failure(let error):
        HUD.flash(.labeledError(title: "Error", subtitle: error.localizedDescription), delay: 3.0)
    }
}

private func handleLeadSessionResponse(_ response: SEResult<SEResponse<SELeadSessionResponse>>) {
    switch response {
    case .success(let value):
        HUD.hide(animated: true)
        if let url = URL(string: value.data.redirectUrl) {
            let request = URLRequest(url: url)
            webView.load(request)
            webView.isHidden = false
        }
    case .failure(let error):
        HUD.flash(.labeledError(title: "Error", subtitle: error.localizedDescription), delay: 3.0)
    }
}

private func createSession() {
    guard let provider = provider else { return }
    
    if SERequestManager.shared.isPartner {
        let leadSessionParams = SELeadSessionParams(
            consent: consent,
            providerCode: provider.code,
            attempt: attempt,
            javascriptCallbackType: "iframe"
        )
        
        // Check if SERequestManager will call handleConnectSessionResponse
        SERequestManager.shared.createLeadSession(params: leadSessionParams) { [weak self] response in
            self?.handleLeadSessionResponse(response)
        }
    } else {
        let connectSessionsParams = SEConnectSessionsParams(
            attempt: attempt,
            providerCode: provider.code,
            javascriptCallbackType: "iframe",
            consent: consent
        )
        
        // Check if SERequestManager will call handleConnectSessionResponse
        SERequestManager.shared.createConnectSession(params: connectSessionsParams) { [weak self] response in
            self?.handleConnectSessionResponse(response)
        }
    }
}

}
extension SaltEdgeVC: SEWebViewDelegate {
func webView(_ webView: SEWebView, didReceiveCallbackWithResponse response: SEConnectResponse) {
print("Response :", response)
print("Response Stage:", response.stage)
switch response.stage {
case .success:
// Connection successfully connected
let bankListVC = self.storyboard?.instantiateViewController(withIdentifier: "BankListVC")
self.present(bankListVC!, animated: true, completion: nil)
print("All good")
case .fetching:
// Connection is fetching. You can safe connection secret if it is present.
guard let connectionSecret = response.secret else { return }

        if var connections = UserDefaultsHelper.connections {
            if !connections.contains(connectionSecret) {
                connections.append(connectionSecret)
                UserDefaultsHelper.connections = connections
            }
        } else {
            UserDefaultsHelper.connections = [connectionSecret]
        }
    case .error:
        // Handle error
        HUD.flash(.labeledError(title: "Cannot Fetch Connection", subtitle: nil), delay: 3.0)
    }
}

func webView(_ webView: SEWebView, didReceiveCallbackWithError error: Error) {
    alert(message: error.localizedDescription, title: Spare.ValidationMessage.warning)
}

func webView(_ webView: SEWebView, didHandleRequestUrl url: URL) {
    if url.absoluteString == attempt.returnTo {
        webView.isHidden = true
    }
}

}

Regarding URL scheme callback

Certainly it is not a issue, but unable to understand the use of code written in Appdelegate, Please check it below :

func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
        if let createVC = createViewController {
            HUD.show(.labeledProgress(title: "Fetching OAuth Login", subtitle: nil))
            SERequestManager.shared.handleOpen(url: url, loginFetchingDelegate: createVC)
        }
        return true
    }

Could you please explain when it get called.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.