iOS Custom


Integrate with Native iOS SDK


You can use PortOne SDK to integrate the PortOne Payment Gateway with your native iOS Application.


Video Tutorial

Loom Message - 8 March 2024


Sample App

Check the sample app to integrate on GitHub


Prerequisites

  • Create an account on PortOne
  • Enable Payment Channels and Methods which you want to use
  • Login to the portone portal where you can access the API Keys (client key and secret key) for integrations under Settings -> API tab.

Integration

Steps to integrate your native iOS application with PortOne iOS SDK.

1. Enable deep link in iOS

  1. To open your application, add the url schemes to the app, Go to ProjectSettings -> info

  2. Add url schemes and identifier inside the URL types.
    You can see the scheme and identifier details in info.plist as below:

    ```json
    <key>CFBundleURLTypes</key>
    <array>
    		<dict>
    			<key>CFBundleTypeRole</key>
    			<string>Editor</string>
    			<key>CFBundleURLName</key>
    			<string>checkout</string>
    			<key>CFBundleURLSchemes</key>
    			<array>
    				<string>portone</string>
    			</array>
    		</dict>
    	</array>
    ```
    
  3. To open the other applications, should include the url schemes in info.plist

    <key>LSApplicationQueriesSchemes</key>
    	<array>
    		<string>itms-appss</string>
    		<string>zalopay</string>
    		<string>line</string>
    		<string>ascendmoney</string>
    	</array>
    

    LSApplicationQueriesSchemes - Specifies the URL schemes you want the app to be able to use with the canOpenURL: method of the UIApplication class.

  4. To support HTTP connections, add this source code in app info.plist

    <key>NSAppTransportSecurity</key>
    	<dict>
    		<key>NSAllowsArbitraryLoads</key>
    		<true/>
    		<key>NSAllowsLocalNetworking</key>
    		<true/>
    		<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
    		<true/>
    	</dict>
    

2. **Steps to integrate iOS SDK**

  1. Download the latest framework from here

  2. After downloading the .xcframework inside the version folder, drag and drop it in the project

    • Go to General → Frameworks, Library and Embedded Content and then drop the framework and change the Embed to Embed & sign.
  3. Import the PortOneSDK as below at the required places.

    import PortoneSDK
    

4. Have a setup to get JWT token from the server

On the server side, a JWT token must be constructed that accepts portoneKey as input. Further information on JWT token generation is described in the link below.

Authentication | PortOne


5. Generate Signture Hash

Generate a Signature Hash using HmacSHA256 which needs to be included in the payload.

 func createHash(_ config: TransactionRequest) -> String {
        var message = ""
        message =
        "amount=\(config.amount)" +
        "&client_key=\(config.portOneKey.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "")" +
        "&currency=\(config.currency!.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "")" +
        "&failure_url=\(config.failureURL!.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "")" +
        "&merchant_order_id=\(config.merchantOrderId.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "")" +
        "&success_url=\(config.successURL!.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "")"
        
        let secretString = secretKey
        let key = SymmetricKey(data: secretString.data(using: .utf8)!)
        
        let signature = HMAC<SHA256>.authenticationCode(for: message.data(using: .utf8)!, using: key)
        let base64 = Data(signature).toBase64String()
        return base64
    }

6. Initialise PortOne iOS SDK and authorise it.

  1. Import library of portone SDK

import PortoneSDK
  1. Initialize the checkout instance to get the available methods in SDK as below

    var checkout = Checkout(delegate: self, environment: "sandbox", redirectURL: "chaiport//checkout")
    // redirectURL: your app scheme
    
    
  • Should pass the delegate to the initialization

  • Should implement the delegate methods as below to get the response of failure and success callbacks from the webView.

     extension ViewController: CheckoutDelegate {
        func transactionResponse(transactionResponse: TransactionResponse?) {
            if let response = transactionResponse {
       // Do the needful with response
            }
        }
        //For webview to be open on top of the View controller.
        //Pass the UINavigation controller
    
        var viewController: UIViewController? {
            return self
        }
    
        func transactionErrorResponse(error: Error?) {
            print("Error",error)
        }
    
    }
    

Different Payment Methods

New Credit card for a particular number

  1. Initialize the new card payment with transactionRequest as below:

            let config = prepareConfig()
    
            checkOut?.initiateNewCardPayment(config, cardDetails: cardDetails, jwtToken: jwtToken, clientKey: clientKey, onCompletionHandler: { [weak self] result in
                guard let self = self else {
                    return
                }
                switch result {
                case .success(let response):
                                    // Do the needful with the response
    
                case .failure(let error):
                    print(error)
                                    // Handle the error cases
                }
            }
    

    All the data types in the form of an object

    ParametersData Typemandatory
    cardDetailsObjectYes
    JWTTokenstringYes
    clientKeyStringyes
    On completionHandlerfuncyes
    func prepareConfig(type: PaymentMethod) -> TransactionRequest {
    
            let billingAddress = BillingAddress(city: "VND", countryCode: "VN", locale: "en", line1: "address1", line2: "address2", postalCode: "400202", state: "Mah")
            let billingDetails = BillingDetails(billingName: "Test mark", billingEmail: "[email protected]", billingPhone: "+918341469169", billingAddress: billingAddress )
    
            let shippingAddress = ShippingAddress(city: "abc", countryCode: "VN", locale: "en", line1: "address_1", line2: "address_2", postalCode: "400202", state: "Mah")
            let shippingDetails = ShippingDetails(shippingName: "xyz", shippingEmail: "[email protected]", shippingPhone: "1234567890", shippingAddress: shippingAddress)
    
            let orderDetails = OrderDetails(id: "knb", name: "kim nguyen bao", price: 1000, quantity: 1)
    
            return TransactionRequest(
                                portOneKey: "lzrYFPfyMLROallZ",
                                paymentChannel: type.paymentMethod, //"MASTERCARD"
                                paymentMethod: type.paymentMethod, //"MASTERCARD_CARD"
                                merchantOrderId: "MERCHANT\(Int(Date().timeIntervalSince1970 * 1000))",
                                amount: 180000,
                                currency: "VND",
                                signatureHash: "123",
                                billingAddress: billingDetails,
                                shippingAddress: shippingDetails,
                                orderDetails: [orderDetails],
                                successURL: "chaipay://",
                                failureURL: "chaipay://",
                                redirectURL: "portone://checkout" )
        }
    
    • Card details
    let cardDetails = CardDetails(token: nil, key: clientKey, cardNumber: cardNumber, expiryMonth: expiryMonth, expiryYear: expiryYear, cardHolderName: cardHolderName, type: cardType, cvv: cvv)
            let config = prepareConfig()        ```
    
  2. Response will be given back to the callbackFunction

    afterCheckout = transactionDetails => {
        console.log('Response from webview', transactionDetails);
        // Do the needful
      };
    
  3. Handle the success and failure cases from the delegate method as below:

        extension ViewController: CheckoutDelegate {
            func transactionResponse(_ transactionResponse: TransactionResponse?) {
                if let response = webViewResponse {
                    //Todo: Polulate date or do the needful
                }
            }
        }
    
  4. Sample Success and failure callback :

        // Success
        {
        "merchant_order_ref" : "MERCHANT1630665361511",
        "message" : "",
        "is_success" : "true",
        "order_ref" : "1xcrkpVPNq5vuqQDe3eqrHD3OcG",
        "deep_link" : "",
        "channel_order_ref" : "1xcrkpVPNq5vuqQDe3eqrHD3OcG",
        "additional_data" : null,
        "redirect_url" : ""
        }
        // Failure
        {
            "chaipay_order_ref": "1wa0choxhAy2QtE9ix8aNt8T3Mf",
            "channel_order_ref": "0",
            "merchant_order_ref": "MERCHANT1628681469666",
            "status": "Initiated",
            "status_code": "4000",
            "status_reason": "INVALID_TRANSACTION_ERROR"
        }

sample JWT token

let jwtToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJDSEFJUEFZIiwic3ViIjoibHpyWUZQZnlNTFJPYWxsWiIsImlhdCI6MTYzMjM5MDkyMCwiZXhwIjoyNzMyMzkwOTIwfQ.IRgiM-zjAdJEVDuPSNfxmDszZQi_csE1q7xjVRvPvoc';

Fetch Saved cards

  1. To get the saved card details , Capture the mobile number and OTP to fetch the saved credit cards for a particular user.

    1. To generate the OTP, call the method as below:

      checkOut?.getOTP(self.numberTextField.text ?? "") {result in
                  switch result {
                  case .success(let data):
                      print("data" , data)
                  case .failure(let error):
                      print("error", error)
                      break
                  }
              }
      
    2. After receiving the OTP to the given mobile number, the captured mobile number and OTP should pass to the fetchSavedCards as below to fetch the saved credit cards for the particular number.

      checkOut?.fetchSavedCards(portOneKey: UserDefaults.getPortoneKey!, mobileNumber, otp: OTP, token: nil,  onCompletionHandler: { (result) in
                  switch result {
                  case .success(let data):
                      //Do the needful
      
                  case .failure(let error):
                                      // handle the error cases
                      print("errror", error)
                  }
              }
      
    3. sample Success and failure cases

        //Success case
        {
        "content": [
            {
                "token": "97daee740bb84a6d907dfe46fca1139e",
                "partial_card_number": "4242 4242 4242 4242",
                "expiry_month": "09",
                "expiry_year": "2034",
                "type": "visa",
                "payment_channel_token": {
                    "OMISE_CUSTOMER_TOKEN": "cust_test_5va5euv4r4cf68gwcmm",
                    "STRIPE_CHANNEL_TOKEN": "pm_1Mqyk7CzOuxzGzz2njn9bFFE",
                    "STRIPE_CUSTOMER_TOKEN": "cus_NcDA43aAVrjiMp"
                }
            },
            {
                "token": "fb104ae5e67f48dc96119e24a15382e5",
                "partial_card_number": "4242 4242 4242 4242",
                "expiry_month": "05",
                "expiry_year": "2043",
                "type": "visa",
                "payment_channel_token": {
                    "OMISE_CUSTOMER_TOKEN": "cust_test_5va5bi1s74v1lx83p1c",
                    "STRIPE_CHANNEL_TOKEN": "pm_1N6x36CyiCSpXZcy4wCTC97z",
                    "STRIPE_CUSTOMER_TOKEN": "cus_NsiUmQBa5K0skZ"
                }
            }
        ],
        "status_code": "2000",
        "status_reason": "SUCCESS"
        }
        
        //Failure case:
        {
        "message": "Invalid JWT Token / Client Key. Error in parsing JWT Token.",
        "status_code": "4010",
        "status_reason": "INVALID_UNAUTHORISED_TRANSACTION_ERROR"
        }
    
            
            
    

Params:

ParametersData TypeMandatoryDescription
portOneKeystringyespass the portOne key
formattedTextstringYesContains mobile number with Country code. (eg: ⁨‭+16625655248 , +918341234123)
OTPStringYesOtp received to the given mobile number
tokenStringYesToken received from this api, if token is passed. then we can skip entering the otp part until the token is expiry.

Saved Card for a particular number

Initialize the saved card payment with transactionRequest as below:

 let cardDetails = CardDetails(token: savedCard.token, cardNumber: savedCard.partialCardNumber, expiryMonth: savedCard.expiryMonth, expiryYear: savedCard.expiryYear, cardHolderName: "", type: savedCard.type, cvv: "100")
    let config = prepareConfig(type: PaymentMethod.NewCreditCard)
    
    
 let payload = prepareConfig()
checkout?.initiateSavedCardPayment(config: payload, cardDetails: cardDetails, onCompletionHandler: { result in
            guard let self = self else {
                return
            }
            switch result {
            case .success(let response):
                                // Do the needful with the response

            case .failure(let error):
                print(error)
                                // Handle the error cases
            }
        }

Sample Response

func prepareConfig(type: PaymentMethod) -> TransactionRequest {

                let billingAddress = BillingAddress(city: "VND", countryCode: "VN", locale: "en", line1: "address1", line2: "address2", postalCode: "400202", state: "Mah")
        let billingDetails = BillingDetails(billingName: "Test mark", billingEmail: "[email protected]", billingPhone: "+918341469169", billingAddress: billingAddress )

        let shippingAddress = ShippingAddress(city: "abc", countryCode: "VN", locale: "en", line1: "address_1", line2: "address_2", postalCode: "400202", state: "Mah")
        let shippingDetails = ShippingDetails(shippingName: "xyz", shippingEmail: "[email protected]", shippingPhone: "1234567890", shippingAddress: shippingAddress)

        let orderDetails = OrderDetails(id: "knb", name: "kim nguyen bao", price: 1000, quantity: 1)

        return TransactionRequest(
                            portOneKey: "lzrYFPfyMLROallZ",
                            paymentChannel: type.paymentMethod, //"MASTERCARD"
                            paymentMethod: type.paymentMethod, //"MASTERCARD_CARD"
                            merchantOrderId: "MERCHANT\(Int(Date().timeIntervalSince1970 * 1000))",
                            amount: 180000,
                            currency: "VND",
                            signatureHash: "123",
                            billingAddress: billingDetails,
                            shippingAddress: shippingDetails,
                            orderDetails: [orderDetails],
                            successURL: "chaipay://",
                            failureURL: "chaipay://",
                            redirectURL: "portone://checkout" )
    }

Wallet

Pass the TransactionRequest as payload to initiateWalletPayments as below:

// payload with proper payment channel and method
let payload = prepareConfig()

checkOut?.initiatePayment(payload) { result in
            switch result {
            case .success(let data):
                // Do nothing
            case .failure(let error):
               // Handle the error part
                               print("error", error)
                 break
            }
        }

Handle the success and failure cases from the delegate method as below:

extension ViewController: CheckoutDelegate {
    func transactionResponse(_ transactionResponse: TransactionResponse?) {
        if let response = transactionResponse {
            //Todo: Populate date or do the needful
        }
    }
}

Sample Response

// Success
{
"is_success": true,
"redirect_url": "https://api.omise.co/payments/paym_test_5vs49nt75qk2i3jbkvi/authorize",
"channel_order_ref": "chrg_test_5vs49nqfr5bqq2er4ae",
"merchant_order_ref": "MERCHANT1683899406422",
"order_ref": "2PpGPD8ccxL5thPjMwwPN6KaNMc",
"message": "3DS is forced for this transaction",
"deep_link": "",
"additional_data": null
}
// failure
{
  "chaipay_order_ref": "1wa0choxhAy2QtE9ix8aNt8T3Mf",
  "channel_order_ref": "0",
  "merchant_order_ref": "MERCHANT1628681469666",
  "status": "Initiated",
  "status_code": "4000",
  "status_reason": "INVALID_TRANSACTION_ERROR"
}

Sample Payload

func prepareConfig(type: PaymentMethod) -> TransactionRequest {

      let countryCode = UserDefaults.getCurrency.code
    let billingDetails = BillingDetails(billingName: "Test mark", billingEmail: "[email protected]", billingPhone: number ?? "+66900002001", billingAddress: getBillingadress())
    
    let shippingAddress = ShippingAddress(city: "abc", countryCode: "TH", locale: "en", line1: "address_1", line2: "address_2", postalCode: "400202", state: "Mah")
    
    let shippingDetails = ShippingDetails(shippingName: "xyz", shippingEmail: "[email protected]", shippingPhone: "1234567890", shippingAddress: shippingAddress)
    
    var orderDetails: [OrderDetails] = []
    
    for details in self.selectedProducts {
        let product = OrderDetails(id: details.id ?? "", name: details.title ?? "", price: details.price ?? 0, quantity: 1, imageUrl: details.imageName ?? "")
        orderDetails.append(product)
        totalAmount = totalAmount + ((details.price ?? 0)* details.quantity)
    }
    
    let merchantDetails = MerchantDetails(name: "Downy", logo: "https://upload.wikimedia.org/wikipedia/commons/a/a6/Logo_NIKE.svg", backUrl: "https://demo.chaiport.io/checkout.html", promoCode: "Downy350", promoDiscount: 35000, shippingCharges: 0.0)
    print("UserDefaults.getTransactionType.code",UserDefaults.getTransactionType.code)
    var transactionRequest = TransactionRequest(portOneKey: clientKey , key: clientKey , merchantDetails: merchantDetails, paymentChannel: selectedPaymentMethod?.paymentChannelKey ?? "", paymentMethod: selectedPaymentMethod?.paymentChannelKey == "VNPAY" ? "VNPAY_ALL" : selectedPaymentMethod?.paymentMethodKey ?? "", merchantOrderId: "MERCHANT\(Int(Date().timeIntervalSince1970 * 1000))", amount: Int(self.totalAmount), currency: countryCode, signatureHash: "123", billingAddress: billingDetails, shippingAddress: shippingDetails, orderDetails: orderDetails, successURL: "https://test-checkout.chaiport.io/success.html", failureURL: "https://test-checkout.chaiport.io/failure.html", redirectURL: "chaiport://checkout", countryCode: countryCode, routingEnabled: false, routingParams: nil, transactionType: UserDefaults.getTransactionType.code)
    let signatureHash = createSignatureHash(transactionRequest)
    transactionRequest.signatureHash = signatureHash
    return transactionRequest
                    
    }

Direct Bank Transfer

For Direct Bank Transfer checkout the following steps are required to be followed:

  1. Fetch the Direct Bank Transfer details using the following method

    
     checkout?.fetchDBTDetails(clientKey: UserDefaults.getChaipayKey! ,completionHandler: { [weak self] result in
    
                guard let self = self else { return }
                switch result {
                case .success(let response):
                    self.paymentMethodResponse = response as DBTResposneObject
                    self.setupInitialData()
                    self.reloadData()
                case .failure(let error):
                    print("error", error)
                    self.showSwiftMessagesView(isSuccess: false, selectedProducts: self.selectedProducts, message: "\(error)")
                    break
                }
            })
    
  2. After getting the bank details we can pass the details to payload and process checkout.

    let payload = prepareConfig()
    checkOut?.initiatePayment(payload) { result in
                switch result {
                case .success(let data):
                    // Do nothing
                case .failure(let error):
                   // Handle the error part
                                   print("error", error)
                     break
                }
            }
    

Handle the success and failure cases from the delegate method as below:

extension ViewController: CheckoutDelegate {
    func transactionResponse(_ transactionResponse: TransactionResponse?) {
        if let response = transactionResponse {
            //Todo: Populate date or do the needful
        }
    }
}

Sample Payload

func prepareConfig(type: PaymentMethod) -> TransactionRequest {

      let countryCode = UserDefaults.getCurrency.code
    let billingDetails = BillingDetails(billingName: "Test mark", billingEmail: "[email protected]", billingPhone: number ?? "+66900002001", billingAddress: getBillingadress())
    
    let shippingAddress = ShippingAddress(city: "abc", countryCode: "TH", locale: "en", line1: "address_1", line2: "address_2", postalCode: "400202", state: "Mah")
    
    let shippingDetails = ShippingDetails(shippingName: "xyz", shippingEmail: "[email protected]", shippingPhone: "1234567890", shippingAddress: shippingAddress)
    
    var orderDetails: [OrderDetails] = []
    
    for details in self.selectedProducts {
        let product = OrderDetails(id: details.id ?? "", name: details.title ?? "", price: details.price ?? 0, quantity: 1, imageUrl: details.imageName ?? "")
        orderDetails.append(product)
        totalAmount = totalAmount + ((details.price ?? 0)* details.quantity)
    }
    
    let merchantDetails = MerchantDetails(name: "Downy", logo: "https://upload.wikimedia.org/wikipedia/commons/a/a6/Logo_NIKE.svg", backUrl: "https://demo.chaiport.io/checkout.html", promoCode: "Downy350", promoDiscount: 35000, shippingCharges: 0.0)
   
   **let directBankTransferDetails = DirectBankTransferDetails(amountPaid: 1123, customerName: "sam", paymentSlip: nil, transactionTime: "2024-04-17T07:39:44.000Z")**
    var transactionRequest = TransactionRequest(portOneKey: clientKey , key: clientKey , merchantDetails: merchantDetails, paymentChannel: selectedPaymentMethod?.paymentChannelKey ?? "", paymentMethod: selectedPaymentMethod?.paymentChannelKey == "VNPAY" ? "VNPAY_ALL" : selectedPaymentMethod?.paymentMethodKey ?? "", merchantOrderId: "MERCHANT\(Int(Date().timeIntervalSince1970 * 1000))", amount: Int(self.totalAmount), currency: countryCode, signatureHash: "123", billingAddress: billingDetails, shippingAddress: shippingDetails, orderDetails: orderDetails, successURL: "https://test-checkout.chaiport.io/success.html", failureURL: "https://test-checkout.chaiport.io/failure.html", redirectURL: "chaiport://checkout", countryCode: countryCode, routingEnabled: false, routingParams: nil, transactionType: UserDefaults.getTransactionType.code, bankDetails: nil, directBankTransferDetails: **directBankTransferDetails**)
    let signatureHash = createSignatureHash(transactionRequest)
    transactionRequest.signatureHash = signatureHash
    return transactionRequest
                    
    }

**Direct Bank transfer details**

ParametersData Typemandatory
customerNameStringyes
transactionTimeStringyes
amountPaidDoubleyes

**Instalments**

  1. To process the instalment fetching the bank list which provides instalments is required.

    checkout?.postbankDetails(paymentChannel: channelName, bankListBody: bankListBody,completionHandler: { [weak self] result in
                print("result", result)
                guard let self = self else { return }
                switch result {
                case .success(let response):
                    self.paymentMethodResponse = response
    
                case .failure(let error):
                    print("error", error)
                    break
                }
            })
    

    Bank list body Params

    ParametersData Typemandatory
    amountDoubleyes
    environmentStringyes
    portoneKeyStringyes
    isMerchantSponsoredBooleanyes
    paymentMethodStringyes
    overrideDefaultBooleanyes
    currencyStringyes

    In the response a list of banks and terms will be provided which are needed to be pass in the request body.

    func prepareConfig(type: PaymentMethod) -> TransactionRequest {
    
          let countryCode = UserDefaults.getCurrency.code
        let billingDetails = BillingDetails(billingName: "Test mark", billingEmail: "[email protected]", billingPhone: number ?? "+66900002001", billingAddress: getBillingadress())
        
        let shippingAddress = ShippingAddress(city: "abc", countryCode: "TH", locale: "en", line1: "address_1", line2: "address_2", postalCode: "400202", state: "Mah")
        
        let shippingDetails = ShippingDetails(shippingName: "xyz", shippingEmail: "[email protected]", shippingPhone: "1234567890", shippingAddress: shippingAddress)
        
        var orderDetails: [OrderDetails] = []
        
        for details in self.selectedProducts {
            let product = OrderDetails(id: details.id ?? "", name: details.title ?? "", price: details.price ?? 0, quantity: 1, imageUrl: details.imageName ?? "")
            orderDetails.append(product)
            totalAmount = totalAmount + ((details.price ?? 0)* details.quantity)
        }
        
        let merchantDetails = MerchantDetails(name: "Downy", logo: "https://upload.wikimedia.org/wikipedia/commons/a/a6/Logo_NIKE.svg", backUrl: "https://demo.chaiport.io/checkout.html", promoCode: "Downy350", promoDiscount: 35000, shippingCharges: 0.0)
       
       **let bankDetails = BankDetails(bankName: "Krungsri Bank", bankCode: "installment_bay", isMerchantSponsored: false, instalmentPeriod: InstalmentPeriod(interest: 0.8, month: 4))**
    
    var transactionRequest = TransactionRequest(portOneKey: clientKey , key: clientKey , merchantDetails: merchantDetails, paymentChannel: selectedPaymentMethod?.paymentChannelKey ?? "", paymentMethod: selectedPaymentMethod?.paymentChannelKey == "VNPAY" ? "VNPAY_ALL" : selectedPaymentMethod?.paymentMethodKey ?? "", merchantOrderId: "MERCHANT\(Int(Date().timeIntervalSince1970 * 1000))", amount: Int(self.totalAmount), currency: countryCode, signatureHash: "123", billingAddress: billingDetails, shippingAddress: shippingDetails, orderDetails: orderDetails, successURL: "https://test-checkout.chaiport.io/success.html", failureURL: "https://test-checkout.chaiport.io/failure.html", redirectURL: "chaiport://checkout", countryCode: countryCode, routingEnabled: false, routingParams: nil, transactionType: UserDefaults.getTransactionType.code, bankDetails: **bankDetails**, directBankTransferDetails: nil)
        let signatureHash = createSignatureHash(transactionRequest)
        transactionRequest.signatureHash = signatureHash
        return transactionRequest
                        
        }
    

**Fetch Available payment methods**

Collect the portOneKey, currency and provide them to the getAvailablePaymentMethods method as below:

    checkout?.getAvailablePaymentGateways(portOneKey: UserDefaults.getPortoneKey!, currency: UserDefaults.getCurrency.code ,completionHandler: { [weak self] result in
        print("result", result)
        guard let self = self else { return }
        switch result {
        case .success(let response):
            // Do the needful
            return
        case .failure(let error):
            // Do the needful
            break
        }
    })

Sample Response

        // Success case
        {
        "ALL": [],
        "BANK_TRANSFER": [],
        "BNPL": [
            {
                "payment_channel_key": "ATOME",
                "payment_method_key": "ATOME_BNPL",
                "sub_type": "BNPL",
                "logo": "https://chaiport-pg-icons-latest-nov.s3.ap-southeast-1.amazonaws.com/atome_short.png",
                "display_name": "Atome",
                "is_default": false,
                "is_enabled": true,
                "is_merchant_sponsored": false,
                "collect_payslip": false,
                "tokenization_possible": false,
                "name": "Atome",
                "country": "TH",
                "currency": "THB"
            }
        ],
        "CARD": [
            {
                "payment_channel_key": "STRIPE",
                "payment_method_key": "STRIPE_CARD",
                "sub_type": "INT_CREDIT_CARD",
                "logo": "https://chaiport-pg-icons-latest-nov.s3.ap-southeast-1.amazonaws.com/card.png",
                "display_name": "Stripe",
                "is_default": true,
                "is_enabled": true,
                "is_merchant_sponsored": false,
                "collect_payslip": false,
                "tokenization_possible": true,
                "name": "Stripe CreditCard",
                "country": "GLOBAL",
                "currency": "USD,VND,SGD,THB,INR,PHP,IDR,MYR,AUD,EUR,HKD"
            }
        ],
        "COD": [],
        "CRYPTO": [],
        "DIRECT_BANK_TRANSFER": [],
        "DIRECT_DEBIT": [],
        "INSTALLMENT": [
            {
                "payment_channel_key": "OMISE",
                "payment_method_key": "OMISE_INSTALLMENT",
                "sub_type": "INSTALLMENT",
                "logo": "https://chaiport-pg-icons-latest-nov.s3.ap-southeast-1.amazonaws.com/omise_short.png",
                "display_name": "Omise",
                "is_default": true,
                "is_enabled": true,
                "is_merchant_sponsored": false,
                "collect_payslip": false,
                "tokenization_possible": false,
                "name": "Omise installment",
                "country": "TH",
                "currency": "THB"
            }
        ],
        "NET_BANKING": [],
        "OTC": [],
        "QR_CODE": [
            {
                "payment_channel_key": "OMISE",
                "payment_method_key": "OMISE_PROMPTPAY",
                "sub_type": "QR_CODE",
                "logo": "https://chaiport-pg-icons-latest-nov.s3.ap-southeast-1.amazonaws.com/promptpay_short.png",
                "display_name": "PromptPay",
                "is_default": false,
                "is_enabled": true,
                "is_merchant_sponsored": false,
                "collect_payslip": false,
                "tokenization_possible": false,
                "name": "PromptPay via Omise",
                "country": "TH",
                "currency": "THB"
            }
        ],
        "VA_BANK_TRANSFER": [],
        "WALLET": [
            {
                "payment_channel_key": "OMISE",
                "payment_method_key": "OMISE_RABBIT_LINEPAY",
                "sub_type": "WALLET",
                "logo": "https://chaiport-pg-icons-latest-nov.s3.ap-southeast-1.amazonaws.com/linepay_short1.png",
                "display_name": "Rabbit LinePay",
                "is_default": false,
                "is_enabled": true,
                "is_merchant_sponsored": false,
                "collect_payslip": false,
                "tokenization_possible": false,
                "name": "RabbitLinepay via Omise",
                "country": "TH",
                "currency": "THB"
            }
        ]
    }
    // Failed case
    {
    "message": "Invalid JWT Token / Client Key. Error in parsing JWT Token.",
    "status_code": "4010",
    "status_reason": "INVALID_UNAUTHORISED_TRANSACTION_ERROR"
    }

Merchant centric card vault

  1. Collect the customerId, clientKey, JWTToken and cardData and provide them to the addCardForCustomerId method as below:

    1. Add customer card

      To add the card data for a customer, need to pass the customerId, client key, jwtToken and card details to the below method

      
      checkout?.addCustomerCard(customerId: customerId, clientKey: UserDefaults.getPortoneKey ?? " ", cardData: self.cardDetails, jwtToken: token, onCompletionHandler: { (result) in
                      switch result {
                      case .success(let data):
                          // Do the needful
                          return
                      case.failure(let error):
                          // Do the needful
                          break
                      }
                  })
      

      cardData:

      CardDetails(
      token: "",  
      cardNumber: "424242******4242", 
      expiryMonth: "07", 
      expiryYear: "2031", 
      cardHolderName: "sam", 
      type: "visa", 
      cvv: "123")
      

    Sample response

            //Success
            {
                "content": {
                    "token": "cdec91449d3a4b4bae9144d586a2b972",
                    "partial_card_number": "424242******4242",
                    "expiry_month": "07",
                    "expiry_year": "2031",
                    "type": "visa",
                    "payment_channel_token": {}
                },
                "message": "Card record added successfully for customer!",
                "status_code": "2000",
                "status_reason": "SUCCESS"
            }
            // failed
            {
                "message": "Card with given token already exists in the database for this customer",
                "status_code": "4001",
                "status_reason": "INVALID_PAYMENT_REQUEST"
            }
    
  2. Delete customer card

    To delete the card data for a customer, need to pass the card token, customer id, client key, jwtToken to the below method

    
    checkout?.deleteCardForCustomerId(customerId: customerId, clientKey: clientKey, jwtToken: createJWTToken(), cardData: DeleteCardDataObject(token: token),  onCompletionHandler: { (result) in
                    switch result {
                    case .success(let data):
                        // Do the needful
                        return
                    case.failure(let error):
                        // Do the needful
                        break
                    }
                })
    
    • DeleteCardDataObject
    
    let token = "cdec91449d3a4b4bae9144d586a2b972",
    
    DeleteCardDataObject(token: token)
    

    Sample response:

        //Success
        {
            "message": "Card record deleted successfully for the customer!",
            "status_code": "2000",
            "status_reason": "SUCCESS"
        }
        // failed
        {
            "message": "Customer card not found with the token: cdec91449d3a4b4bae9144d586a2b972",
            "status_code": "4016",
            "status_reason": "MERCHANT_NOT_FOUND"
        }
    
    
  3. Fetch all customer cards

    To fetch all the cards data for a customer, need to pass the customer id, client key, jwtToken to the below method

    
     checkout?.fetchCustomerCards(customerId: customerId, clientKey: clientKey, jwtToken: token, onCompletionHandler: {(result) in
                 switch result {
                 case .success(let data):
                     // Do the needful
                     return
                 case .failure(let error):
                     // Do the needful
                     break
                 }})
    

    Sample response

            //Success
            {
                "content": {
                    "data": [
                        {
                            "token": "a6a868835098431b83fc05edf16a2d81",
                            "partial_card_number": "424242******4242",
                            "expiry_month": "09",
                            "expiry_year": "2043",
                            "type": "visa",
                            "payment_channel_token": {}
                        }
                    ]
                },
                "message": "List of cards for Customer with UUID: 8e52c57d-9bda-437e-973d-fb4f9756d15f fetched successfully.",
                "status_code": "2000",
                "status_reason": "SUCCESS"
            }
            // failed
            {
                "message": "Customer not found with customerUUID: 8e52c57d-9bda-437e-973d-fb4f9756d15",
                "status_code": "4016",
                "status_reason": "MERCHANT_NOT_FOUND"
            }
    
  4. add Customer

    checkout?.addCustomer(clientKey: clientKey, customerData: AddCustomerObject(name: name.text, phoneNumber: phoneNumber.text, emailAddress: email.text, customerRef: customerRef.text), jwtToken: token, onCompletionHandler: { result in
                        switch result {
                        case .success(let response):
                            self.progressHUD?.show()
                            print("response", response)
                            break
                        case .failure(let error):
                            self.progressHUD?.show()
                            
                            break
                        }
                    })
    
    • customerData
      AddCustomerObject(name: name.text, phoneNumber: phoneNumber.text, emailAddress: email.text, customerRef: customerRef.text)
      
  5. get Customer Data

    Collect the customerID, customerData, clientKey, JWTToken and provide them to the getCustomerData method as below:

    checkout?.getCustomerData(customerID: self.customerRef.text?.removeWhitespace(), customerData: nil, clientKey: clientKey, jwtToken: token, onCompletionHandler: { result in
                    switch result {
                    case .success(let response):
                        print("response", response)
                        break
                    case .failure(let error):
                        print("Failure", error)
                        break
                    }
                })
    
  • customerID
    customerID : customerRef provided while adding a customer.
    

Failover Routing

  1. To support failover routing add the following two parameters to payload:

    • isRoutingEnabled= true
    • Routing Param type should be failover.
    • provide the Routing Ref which is set in the merchant portal.
          let payload = getDefaultPayload()
          payload.isRoutingEnabled = true
          payload.routingParams= RoutingParams(type: "failover", routeRef: UserDefaults.getRouteRef)
          
      checkOut?.initiatePayment(payload) { result in
                  switch result {
                  case .success(let data):
                      // Do nothing
                  case .failure(let error):
                     // Handle the error part
                                     print("error", error)
                       break
                  }
              }
      
  2. To Fetch the List of Routes which are created from merchant portal

    Collect the clientKey, JWTToken and provide them to the fetchRoutes method as below:

       checkout?.fetchRoutes(clientKey: clientKey, jwtToken: token, onCompletionHandler: {(result) in
                  switch result {
                  case .success(let data):
                      
                      //Do the needful
                      return
                  case .failure(let error):
                      // Do the needful
                      break
                  }})
    

PreAuth and Capture Payment

  1. To implement PreAuth one parameter has to be set: transactionType= PREAUTH || PURCHASE

        let payload = getDefaultPayload()
        payload.transactionType = "PREAUTH"
    
  2. To Capture the Payment

    • Collect the transactionOrderRef, clientKey, JWTToken and provide them to the captureTransactionAPI method as below:

    
    checkout?.captureTransactionAPI(transactionOrderRef: self.transactionId ?? "", clientKey: UserDefaults.getPortoneKey!, jwtToken: jwtToken) { result in
            switch result {
            case .success(let response):
                // Do the needful
                return
            case .failure(let error):
                // Do the needful
                break
            }
    }
    
    

Payload

All of the web checkout request's parameters are listed here, along with the appropriate data type.

ParametersData Type
portOneKeyStringmandatory
merchantDetailsobject MerchantDetails
merchantOrderIdStringmandatory
signatureHashStringmandatory
amountDoublemandatory
currencyStringmandatory
countryCodeStringmandatory
billingDetailsobject BillingDetailsOptional
shippingDetailsobject ShippingDetailsOptional
orderDetailsarray [OrderDetail]Optional
successUrlStringmandatory
failureUrlStringmandatory
expiryHoursIntmandatory
sourceStringmandatory
descriptionStringOptional
showShippingDetailsBooleanOptional
showBackButtonBooleanOptional
defaultGuestCheckoutBooleanOptional
isCheckoutEmbedBooleanOptional
redirectUrlStringmandatory
environmentStringmandatory
bankDetailsBankDetailsOptional
directBankTransferDirectBankTransferDetailsOptional

MerchantDetails

ParametersData Type
nameStringOptional
logoStringOptional
backUrlStringOptional
promoCodeStringOptional
promoDiscountIntOptional
shippingChargesDoubleOptional

**ShippingDetails**

ParametersData Type
shippingNameStringOptional
shippingEmailStringOptional
shippingPhoneStringOptional
shippingAddressObject AddressOptional

**BillingDetails**

ParametersData Type
billingNameStringOptional
billingEmailStringOptional
billingPhoneStringOptional
billingAddressObject AddressOptional

Address

ParametersData Type
cityStringOptional
countryCodeStringOptional
localeStringOptional
line1StringOptional
line2StringOptional
postalCodeStringOptional
stateStringOptional

**Direct Bank transfer details**

ParametersData Typemandatory
customerNameStringyes
transactionTimeStringyes
amountPaidDoubleyes

**Bank details**

ParametersData Typemandatory
bankNameStringyes
bankCodeStringyes
isMerchantSponsoredboolyes
instalmentPeriodInstalmentPeriodyes

InstalmentPeriod

ParametersData Typemandatory
monthintyes
interestdoubleyes

OrderDetail

ParametersData Type
idStringOptional
priceDoubleOptional
nameStringOptional
quantityIntOptional
imageString (in the form of web url)Optional

Probable Errors:

INVALID_UNAUTHORIZED_JWT_TOKEN_ERROR

  1. Check whether PortOne Key and the Secret Key are of the same account
  2. Check whether the Secret Key is not modified
  3. Check whether Bearer keyword is added before the generated token with a white space. Bearer $jwtToken
  4. Verify if the expiration time should be greater than the current time

INVALID_UNAUTHORISED_TRANSACTION_SIGNATURE_ERROR

  1. Check whether all params match with the payload/request
  2. Check whether the portone key match with the payload and the account

INVALID_UNAUTHORISED_TRANSACTION_IAMPORTKEY_ERROR

  1. Check whether the portone key match with the payload and the account

INVALID_PAYMENT_CHANNEL

  1. Make sure the payment channels and payment methods which are added in the payload are enable from the portone portal

INVALID_ENVIRONMENT

  1. Make sure you have added environment either sandbox or live

Summation of order value, tax, duties, shipping and discount is equal to amount

  1. If items are provided then please verify the values provided should match the total amount: sum(items price * items quantity) + shipping charge - discount = amount
  2. Mandatory params in payload:
price
promo_discount       (0 is also acceptable)
shipping_charges     (0 is also acceptable)