Skip to main content

iOS Native SDK(v2) - Merchant's Checkout

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


Steps to integrate iOS SDK:#

  • Download the latest framework from here

  • 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.

      9.png

  • Import the PortOnePaymentSDK as below at the required places.

    import ChaiPayPaymentSDK
  • 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
    var viewController: UIViewController? {
    return self
    }
    func transactionErrorResponse(error: Error?) {
    print("Error",error)
    }
    }
  • In the info.plist add the new Array type node LSApplicationQueriesSchemes as below to open the other applications:

    <key>LSApplicationQueriesSchemes</key>
    <array>
    <string>itms-appss</string>
    <string>momo</string>
    <string>zalopay</string>
    <string>chaipay</string>
    </array>

    10.png

  • Include the URLType in info.plist to redirect to your app(deep linking) as 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>

    11.png

Handle the deep link#

  • 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 in following ways#

  • Initiate 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: "markweins@gmail.com", 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: "xyz@gmail.com", 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#

    Success callback :

    {
    "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 Callback:

    {
    "chaipay_order_ref": "1wa0choxhAy2QtE9ix8aNt8T3Mf",
    "channel_order_ref": "0",
    "merchant_order_ref": "MERCHANT1628681469666",
    "status": "Initiated",
    "status_code": "4000",
    "status_reason": "INVALID_TRANSACTION_ERROR"
    }

    Initiate with new credit card Payment

    • Initialize the new card payment with transactionRequest as 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: "markweins@gmail.com", 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: "xyz@gmail.com", 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://" )
      }
    • cardDetails:

      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 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 below:

      extension ViewController: CheckoutDelegate {
      func transactionResponse(_ transactionResponse: TransactionResponse?) {
      if let response = webViewResponse {
      //Todo: Polulate date or do the needful
      }
      }
      }
    • 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"
      }

      Initiate with Saved credit card Payment#

    • Initialize the saved card payment with transactionRequest as 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: "markweins@gmail.com", 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: "xyz@gmail.com", 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://" )
      }
    • cardDetails:

      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 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 below:

      extension ViewController: CheckoutDelegate {
      func transactionResponse(_ transactionResponse: TransactionResponse?) {
      if let response = webViewResponse {
      //Todo: Polulate date or do the needful
      }
      }
      }
    • *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"
      }

Fetch the saved cards for a particular number#

  • Capture the mobile number and OTP to fetch the saved credit cards for a particular user.

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

      Parameter Description

      ParameterData typeMandatoryDescription
      mobileNumberstringyespass the mobile number with country code

      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
    • Sample Response :

      {
      "additional_details": {
      "MessageId": "ae348f19-3522-5690-a981-d3c531b6066d",
      "SequenceNumber": null
      },
      "status_code": "2000",
      "status_reason": "SUCCESS"
      }
  • 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.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 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.

    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

    Sample Success and failure response:

    //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"
    }

Get Available Payment methods#

  • Get the available payment methods with portOne key as 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 typeMandatoryDescription
    portOneKeystringyespass the portOne key
    currencystringyescurrency. (eg: VND, THB)

Sample Response:

  • Success and failure case

    // 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"
    }

Pre-auth Transactions:#

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

    • transaction-type values are "PURCHASE" || "PREAUTH".

      • 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 typeMandatoryDescription
    transactionOrderRefstringyesTransaction order Id
    clientKeyStringyesPortOne key

    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
    is_successBooltrue/false

Failover routing:#

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

    Parameter Description

    ParameterData typeMandatoryDescription
    routingEnabledBoolyestrue / false (if true, then it will capture as a failover routing)
    routingParamsObjectyes, if routingEnable is truetype: "failover", route_ref: String(capture from merchant portal or via fetch routes api)
  • 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 typeMandatoryDescription
    clientKeyStringyesPortOne key
    jwtTokenstringyesgenerate token with portone key and secret key.

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 typeMandatoryDescription
      customerIdStringyesCustomer Id
      clientKeyStringyesPortOne key
      cardDetailsObjectyesPass the card details (card holder name, card number , expiry month, year and cvv )
      jwtTokenstringyesgenerate token with portone key and secret key.

      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

Sample success and failed response code

//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"
}
  • 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 typeMandatoryDescription
      customerIdStringyesCustomer Id
      clientKeyStringyesPortOne key
      cardDataObjectyesPass the card token
      jwtTokenstringyesgenerate token with portone key and secret key.

      Response Description

      ParameterData typeDescription
      messageStringReadable message
      status_reasonstringstatus reason
      status_codestringprovides the code based on the transaction status (e.g: "2000", "3000")

      Sample success and failed response code

      //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"
      }
  • 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 typeMandatoryDescription
      customerIdStringyesCustomer Id
      clientKeyStringyesPortOne key
      jwtTokenstringyesgenerate token with portone key and secret key.

      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 ignore (PSP related data)

Sample success and failed response code

//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"
}

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": "markweins@gmail.com",
"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": "xyz@gmail.com",
"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 success and failure response#

//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": "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

    // To generate Signature Hash
    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

    // To generate createJWTToken
    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
    }