Build Your Own Checkout Using iOS

  • This page contains details of the latest version of the PortOne Merchant's Checkout iOS SDK, version 2

  • The PortOne Merchant's Checkout iOS SDK is designed to enable you to add PortOne Checkout services into your native iOS apps quickly and easily.


How to Setup iOS Native SDK

Please follow steps below to setup iOS Native SDK and start accepting payment via PortOne.

  • Download the latest SDK from this link.

  • After downloading the .xcframework inside the SDK folder, drag and drop it in the project.

  • Add the SDK to your application and in your Xcode project, select your target and add PortOnePaymentSDK.xcframework to:

    • In Xcode navigate to General tab and select the Frameworks, Library and Embedded Content section and add or drop the framework and change the Embed to Embed & sign.

    • For acessing the PortOnePaymentSDk at required places, use the command given here to import:

      
      import ChaiPayPaymentSDK
      
  • In AppDelegate, to initialize the checkout instance in didFinishLaunchingWithOptions method to get the available methods in SDK as below:

    var checkout = Checkout(delegate: self, environment: "sandbox", redirectURL: "chaiport/checkout")
    
         // redirectURL: your app scheme
    
  • In AppDelegate pass the delegate to the initialization and implement the delegate methods as given 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

    var viewController: UIViewController? {
        return self
    }

    func transactionErrorResponse(error: Error?) {
        print("Error",error)
    }

}
  • In the info.plist add the new Array type node LSApplicationQueriesSchemes as shown below to open the other applications:
     <key>LSApplicationQueriesSchemes</key>
        <array>
            <string>itms-appss</string>
            <string>momo</string>
            <string>zalopay</string>
            <string>chaipay</string>
        </array>
    
  • Include the URLType in info.plist to redirect it to your app(deep linking) as given below
    <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>
    

How to Handle Deep Link

To handle the deep link follow the steps given below:

  • In sceneDelegate, redirect the deep link url to checkout instance

    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        if let url = URLContexts.first?.url, url.absoluteString.hasPrefix("chaiport:") {
            // call the handle url method from checkout instance
            AppDelegate.shared.checkout?.handleUrl(url: url)
        }
    
    }
    

Initiate Payments

Payment flows can be initiated in the following way:

  • Initiate payment with tokenization flow:
    • New credit card payment
    • Saved credit card payment
  • Initiate payment with non tokenization flow:
    • Wallets, BNPL, net banking

Initiate the Wallet Payment

  • Initialize the wallet payment with transactionRequest as below:

    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)
        }
        
        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: chaipayKey , key: chaipayKey , 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
                        
        }
    
  • Pass the TransactionRequest to initiateWalletPayments as below:

    // payload with proper payment channel and method
    
    let config = prepareConfig()
    checkOut?.initiatePayment(config) { 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

{
"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
}
{
  "chaipay_order_ref": "1wa0choxhAy2QtE9ix8aNt8T3Mf",
  "channel_order_ref": "0",
  "merchant_order_ref": "MERCHANT1628681469666",
  "status": "Initiated",
  "status_code": "4000",
  "status_reason": "INVALID_TRANSACTION_ERROR"
}

Initiate New Credit Card Payment

Initialize the new card payment with transactionRequest as shown below:

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(
                            chaipayKey: "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: "chaipay://" )
    }
  • Implement the New card Details as shown below:

    let cardDetails = CardDetails(token: nil, key: UserDefaults.getChaipayKey!, cardNumber: cardNumber, expiryMonth: expiryMonth, expiryYear: expiryYear, cardHolderName: cardHolderName, type: cardType, cvv: cvv)
           
            let config = prepareConfig() 
    
  • Pass the TransactionRequest and cardDetails to initiateNewCardPayment as given below:

    let config = prepareConfig(type: .NewCreditCard)
    
            checkOut?.initiateNewCardPayment(config, cardDetails: cardDetails, jwtToken: jwtToken, clientKey: UserDefaults.getChaipayKey!, 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
                }
            }
    
  • Handle the success and failure cases from the delegate method as shown below:

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

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

Initiate with Saved Credit Card Payment

Initialize the saved card payment with transactionRequest as shown below:

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(
                            chaipayKey: "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: "chaipay://" )
  																													 }
  • Implement the Saved card details as shown 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)
    
  • Pass the TransactionRequest and cardDetails to initiateSavedCardPayment as shown below:

    let config = prepareConfig(type: .SavedCard)
        checkout?.initiateSavedCardPayment(config: config, 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
                }
            }
    
  • Handle the success and failure cases from the delegate method as shown below:

    
          extension ViewController: CheckoutDelegate {
              func transactionResponse(_ transactionResponse: TransactionResponse?) {
                  if let response = webViewResponse {
                      //Todo: Populate date or do the needful
                  }
              }
          }
    
  • Sample Success and failure callback for saved credit card Payment:

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

How to Fetch Saved Cards for a Particular Number

Capture the mobile number and OTP to fetch the saved credit cards for a particular user and follow the steps given below.

  • To generate the OTP, call the method as shown 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
                      }
                }
    
  • Sample Response
    {
            "additional_details": {
                "MessageId": "ae348f19-3522-5690-a981-d3c531b6066d",
                "SequenceNumber": null
            },
            "status_code": "2000",
            "status_reason": "SUCCESS"
        }
    

Response code description:

ParameterData TypeDescription
status_codeStringprovides the code based on the transaction status (e.g: "2000", "3000")
status_reasonStringprovides the reason for failure
additional_detailsObjectprovides additional Information like message Id, SequenceNumber
  • After receiving the OTP to the given mobile number, the captured mobile number and OTP should pass to the fetchSavedCards as shown below to fetch the saved credit cards for the particular number.

     checkOut?.fetchSavedCards(portOneKey: UserDefaults.getChaipayKey!, 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)
                }
            }
    

Parameter Description:

ParameterData TypeDescription
portOneKey*Stringpass the portOne key
formattedText*StringContains mobile number with Country code. (eg: +16625655248 , +918341234123)
OTP*StringOTP received to the given mobile number
token*StringToken received from this api, if token is passed. then we can skip entering the otp part until the token is expiry.

Note:(*) marked parameters are mandatory.

Sample Response

{
    "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"
    }
{
    "message": "Invalid JWT Token / Client Key. Error in parsing JWT Token.",
    "status_code": "4010",
    "status_reason": "INVALID_UNAUTHORISED_TRANSACTION_ERROR"
    }

Response Description

ParameterData TypeDescription
status_codeStringprovides the code based on the transaction status (e.g: "2000", "3000")
status_reasonStringprovides the reason for failure
additional_detailsObjectprovides additional Information like message Id, SequenceNumber

How to get available payment methods

Get the available payment methods with portOne key as shown below:

checkout?.getAvailablePaymentGateways(portOneKey: UserDefaults.getChaipayKey!, 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
        }
    })

Parameter Description

ParameterData TypeDescription
portOneKeystringpass the portOne key
currencystringcurrency. (eg: VND, THB)

Sample Response for Success and failure case is shown below:

{
        "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"
            }
        ]
    }
{
    "message": "Invalid JWT Token / Client Key. Error in parsing JWT Token.",
    "status_code": "4010",
    "status_reason": "INVALID_UNAUTHORISED_TRANSACTION_ERROR"
    }

Response Description

ParameterData TypeDescription
status_codeStringprovides the code based on the transaction status (e.g: "2000", "3000")
status_reasonStringprovides the reason for failure
messageStringprovides user friendly message for the reason of failure

Pre-Auth Transaction

Pass the transaction-type value in payload as PREAUTH to make a transaction without capture

  • transaction-type values can be following:
    • PURCHASE - for normal transaction
    • PREAUTH - for without capture transactions
  • To capture the transaction, call the captureTransactionAPI from the sdk with portOneOrderRef , portOnekey, jwtToken as the params.
    checkout?.captureTransactionAPI(transactionOrderRef: self.transactionId ?? "", clientKey: UserDefaults.getChaipayKey!, jwtToken: jwtToken) { result in
            switch result {
            case .success(let response):
                // Do the needful
                return
            case .failure(let error):
                // Do the needful
                break
            }
    }
    

Parameter Description

ParameterData TypeDescription
transactionOrderRef*StringTransaction order Id
clientKey*StringPortOne key

Note: (*) marked are mandatory parameters.

Failover Routing

For failover routing, need to pass the params of routingEnabled and routingParams in the payload while doing a transaction.

Parameter Description

ParameterData TypeDescription
routingEnabled*Booleantrue / false (if true, then it will capture as a failover routing)
routingParams**Objecttype: "failover", route_ref: String(capture from merchant portal or via fetch routes API)

Note:(*) marked are mandatory parameters. (**) marked are mandatory parameter if routingEnable is true.

Fetch Routes with Portone key.

 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
              }})

Parameter Description

ParameterData TypeDescription
clientKey*StringPortOne key
jwtToken*Stringgenerate token with PortOne key and secret key.

Note:(*) marked are mandatory parameters.

Merchant Centric Card Vault Methods

  • 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.getChaipayKey ?? " ", 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
                      }
                  })
      

Parameter Description

ParameterData TypeDescription
customerId*StringCustomer ID
clientKey*StringPortOne Key
cardDetails*ObjectPass the card details (Card Holder Name, Card Number , Expiry Month, Year and CVV)
jwtToken*StringGenerate token with portone key and secret key.

Note:(*) marked are mandatory parameters.

Sample Response Code

{
            "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"
 }
{
            "message": "Card with given token already exists in the database for this customer",
            "status_code": "4001",
            "status_reason": "INVALID_PAYMENT_REQUEST"
}

Response Description

ParameterData TypeDescription
messageStringReadable message
status_reasonStringStatus reason
status_codeStringProvides the code based on the transaction status (e.g: "2000", "3000")
contentObjectprovides the card details with token

How to 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
                }
            })

Parameter Description

ParameterData TypeDescription
customerId*StringCustomer Id
clientKey*StringPortOne key
cardData*ObjectPass the card token
jwtToken*StringGenerate token with Portone key and secret key.

Note:(*) marked are mandatory parameters.

{
        "message": "Card record deleted successfully for the customer!",
        "status_code": "2000",
        "status_reason": "SUCCESS"
    }
{
        "message": "Customer card not found with the token:   cdec91449d3a4b4bae9144d586a2b972",
        "status_code": "4016",
        "status_reason": "MERCHANT_NOT_FOUND"
    }

Response Description

ParameterData TypeDescription
messageStringReadable message
status_reasonStringStatus reason
status_codeStringProvides the code based on the transaction status (e.g: "2000", "3000")

How to 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
             }})

Parameter Description

ParameterData TypeDescription
customerId*StringCustomer Id
clientKey*StringPortOne key
jwtToken*StringGenerate token with Portone key and secret key.

Note:(*) marked are mandatory parameters.

Sample Response Code

 {
            "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"
  }
{
            "message": "Customer not found with customerUUID: 8e52c57d-9bda-437e-973d-fb4f9756d15",
            "status_code": "4016",
            "status_reason": "MERCHANT_NOT_FOUND"
 }

Response Description

ParameterData TypeDescription
messageStringReadable message
status_reasonStringStatus reason
status_codeStringProvides the code based on the transaction status (e.g: "2000", "3000")
contentObjectProvides the list of card details in data object
dataArrayArray of card details
tokenStringCard token
partial_card_numberStringCard number
expiry_monthStringCard expiry month
expiry_yearStringCard expiry year
typeStringCard type
payment_channel_tokenObjectCan be ignored (PSP related data)

Sample Payload Request

var payload = {
    "chaipay_key": "FdbbkOFFgGdaocap",
    "merchant_details": {
        "shipping_charges": 0,
        "promo_discount": 35000,
        "promo_code": "Downy350",
        "name": "Downy",
        "back_url": "https:\/\/demo.chaiport.io\/checkout.html",
        "logo": "https:\/\/upload.wikimedia.org\/wikipedia\/commons\/a\/a6\/Logo_NIKE.svg"
    },
    "source": "mobile",
    "signature_hash": "gzYEOKt1kv99t6SE8rUVYyDK08GIWsUpZ66MYPVVwGo=",
    "redirect_url": "chaiport:\/\/checkout",
    "amount": 2100,
    "transaction_type": "PURCHASE",
    "billing_details": {
        "billing_name": "Test mark",
        "billing_email": "[email protected]",
        "billing_address": {
            "locale": "en",
            "postal_code": "400202",
            "city": "THB",
            "line_1": "address1",
            "line_2": "address2",
            "country_code": "TH",
            "state": "Mah"
        },
        "billing_phone": "+918341469169"
    },
    "failure_url": "https:\/\/test-checkout.chaiport.io\/failure.html",
    "pmt_method": "OMISE_CREDIT_CARD",
    "country_code": "THB",
    "currency": "THB",
    "response_type": "redirect_url_only",
    "token_params": {
        "card_holder_name": " ",
        "cvv": "100",
        "expiry_year": "2034",
        "partial_card_number": "4242 4242 4242 4242",
        "save_card": true,
        "expiry_month": "09",
        "type": "visa",
        "token": "97daee740bb84a6d907dfe46fca1139e",
        "key": "FdbbkOFFgGdaocap"
    },
    "routing_enabled": true,
    
    "success_url": "https:\/\/test-checkout.chaiport.io\/success.html",
    "order_details": [
        {
            "quantity": 1,
            "id": "H4E354",
            "name": "Sririri Toes",
            "price": 2100,
            "image": "https:\/\/demo.chaiport.io\/images\/bella-toes.jpg"
        }
    ],
    "pmt_channel": "OMISE",
    "environment": "sandbox",
    "routing_params": {
        "type": "failover",
        "route_ref": "Route_2PalH1cb7sHbV25ZQR11zc0g6yO"
    },
    "shipping_details": {
        "shipping_name": "xyz",
        "shipping_phone": "1234567890",
        "shipping_email": "[email protected]",
        "shipping_address": {
            "locale": "en",
            "postal_code": "400202",
            "city": "abc",
            "line_1": "address_1",
            "line_2": "address_2",
            "country_code": "TH",
            "state": "Mah"
        }
    },
    "merchant_order_id": "MERCHANT1683899406422"
}

Sample Response

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

Steps for Signature Hash Generation

Sample creation of signature hash. Follow the steps from the above link for more understanding.

func createSignatureHash(_ 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
        }

Steps for generating JWT Token Generation

Sample creation of JWT token. Follow the steps from the above link for more understanding.

func createJWTToken() -> String {
    
    struct Header: Encodable {
        let alg = "HS256"
        let typ = "JWT"
    }
    func generateCurrentTimeStamp (extraTime: Int = 0) -> Int {
        let currentTimeStamp = Date().timeIntervalSince1970 + TimeInterval(extraTime)
        let token = String(currentTimeStamp)
        return Int(currentTimeStamp)
    }
    let payload = Payload(iss: "CHAIPAY", sub: UserDefaults.getChaipayKey! ?? "", iat: generateCurrentTimeStamp(), exp: generateCurrentTimeStamp(extraTime: 1000000))
    print("UserDefaults.getChaipayKey",UserDefaults.getChaipayKey)
    let secret = UserDefaults.getSecretKey!
    let privateKey = SymmetricKey(data: secret.data(using: .utf8)!)

    let headerJSONData = try! JSONEncoder().encode(Header())
    let headerBase64String = headerJSONData.urlSafeBase64EncodedString()

    let payloadJSONData = try! JSONEncoder().encode(payload)
    let payloadBase64String = payloadJSONData.urlSafeBase64EncodedString()

    let toSign = (headerBase64String + "." + payloadBase64String).data(using: .utf8)!

    let signature = HMAC<SHA256>.authenticationCode(for: toSign, using: privateKey)
    let signatureBase64String = Data(signature).urlSafeBase64EncodedString()

    let token = [headerBase64String, payloadBase64String, signatureBase64String].joined(separator: ".")
    return token
}