iOS Embed
Integrate with Native iOS SDK
You can use PortOne SDK to integrate the PortOne Payment Gateway with your native iOS Application.
Video Tutorial
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
-
To open your application, add the url schemes to the app, Go to ProjectSettings -> info
-
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> ```
-
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.
-
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**
2. **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.
-
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.
- Algorithm to use for generating token - “HS256”
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) ?? "")" +
"¤cy=\(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.
- Import library of portone SDK
import PortoneSDK
-
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) } }
Checkout using web
To get the payment link to use the SDK checkout UI, call the checkOutUI method from SDK as below:
let token = createJWTToken()
let payload = prepareConfig()
checkout?.checkOutUI(config: payload, jwtToken: token, onCompletionHandler: { (result) in
switch result{
case .success(let data):
print("Data", data)
case .failure(let error):
print("error", error)
}
})
sample payload
func prepareConfig() -> WebTransactionRequest {
let billingAddress = BillingAddress(city: "VND", countryCode: "VN", locale: "en", line1: "address1", line2: "address2", postalCode: "400202", state: "Mah")
let merchantDetails = MerchantDetails(name: "Downy", logo: "https://upload.wikimedia.org/wikipedia/commons/a/a6/Logo_NIKE.svg", backUrl: "https://demo.portone.cloud/checkout.html", promoCode: "Downy350", promoDiscount: 0, shippingCharges: 0.0)
let billingDetails = BillingDetails(billingName: "Test mark", billingEmail: "[email protected]", billingPhone: UserDefaults.getMobileNumber ?? "", 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)
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)
}
let transactionRequest = WebTransactionRequest(chaipayKey: selectedEnvironment?.key ?? "", merchantDetails: merchantDetails, merchantOrderId: "MERCHANT\(Int(Date().timeIntervalSince1970 * 1000))", amount: getTotalAmount(), currency: "VND", signatureHash: "123", billingAddress: billingDetails, shippingAddress: shippingDetails, orderDetails: orderDetails, successURL: "https://test-checkout.portone.cloud/success.html", failureURL: "https://test-checkout.portone.cloud/failure.html", redirectURL: "chaipay://checkout", countryCode: "VN", expiryHours: 2, source: "api", description: "test dec", showShippingDetails: true, showBackButton: false, defaultGuestCheckout: false, isCheckoutEmbed: false )
print(transactionRequest)
return transactionRequest
}
Should implement the delegate methods as below to get the response of failure and success callbacks from the webView.
extension AppDelegate: CheckoutDelegate {
func transactionErrorResponse(_ error: Error?) {
print("Eror",error)
}
var viewController: UIViewController? {
return AppDelegate.shared.window?.rootViewController
}
func transactionResponse(_ webViewResponse: WebViewResponse?) {
// receives the response of the transaction
NotificationCenter.default.post(name: NSNotification.Name("webViewResponse"), object: webViewResponse)
print("webview response", webViewResponse)
}
}
sample Response
Success
{
"merchant_order_ref" : "MERCHANT1630665361511",
"message" : "",
"is_success" : "true",
"order_ref" : "1xcrkpVPNq5vuqQDe3eqrHD3OcG",
"deep_link" : "",
"channel_order_ref" : "1xcrkpVPNq5vuqQDe3eqrHD3OcG",
"additional_data" : null,
"redirect_url" : ""
}
Failed
{
"chaipay_order_ref": "1wa0choxhAy2QtE9ix8aNt8T3Mf",
"channel_order_ref": "0",
"merchant_order_ref": "MERCHANT1628681469666",
"status": "Initiated",
"status_code": "4000",
"status_reason": "INVALID_TRANSACTION_ERROR"
}
Payload
Payload
All of the web checkout request's parameters are listed here, along with the appropriate data type.
Parameters | Data Type | |
---|---|---|
portOneKey | String | mandatory |
merchantDetails | object MerchantDetails | |
merchantOrderId | String | mandatory |
signatureHash | String | mandatory |
amount | Double | mandatory |
currency | String | mandatory |
countryCode | String | mandatory |
billingDetails | object BillingDetails | Optional |
shippingDetails | object ShippingDetails | Optional |
orderDetails | array [OrderDetail] | Optional |
successUrl | String | mandatory |
failureUrl | String | mandatory |
expiryHours | Int | mandatory |
source | String | mandatory |
description | String | Optional |
showShippingDetails | Boolean | Optional |
showBackButton | Boolean | Optional |
defaultGuestCheckout | Boolean | Optional |
isCheckoutEmbed | Boolean | Optional |
redirectUrl | String | mandatory |
environment | String | mandatory |
bankDetails | BankDetails | Optional |
directBankTransfer | DirectBankTransferDetails | Optional |
MerchantDetails
Parameters | Data Type | |
---|---|---|
name | String | Optional |
logo | String | Optional |
backUrl | String | Optional |
promoCode | String | Optional |
promoDiscount | Int | Optional |
shippingCharges | Double | Optional |
**ShippingDetails**
Parameters | Data Type | |
---|---|---|
shippingName | String | Optional |
shippingEmail | String | Optional |
shippingPhone | String | Optional |
shippingAddress | Object Address | Optional |
**BillingDetails**
Parameters | Data Type | |
---|---|---|
billingName | String | Optional |
billingEmail | String | Optional |
billingPhone | String | Optional |
billingAddress | Object Address | Optional |
Address
Parameters | Data Type | |
---|---|---|
city | String | Optional |
countryCode | String | Optional |
locale | String | Optional |
line1 | String | Optional |
line2 | String | Optional |
postalCode | String | Optional |
state | String | Optional |
**Direct Bank transfer details**
Parameters | Data Type | mandatory |
---|---|---|
customerName | String | yes |
transactionTime | String | yes |
amountPaid | Double | yes |
**Bank details**
Parameters | Data Type | mandatory |
---|---|---|
bankName | String | yes |
bankCode | String | yes |
isMerchantSponsored | bool | yes |
instalmentPeriod | InstalmentPeriod | yes |
InstalmentPeriod
Parameters | Data Type | mandatory |
---|---|---|
month | int | yes |
interest | double | yes |
OrderDetail
Parameters | Data Type | |
---|---|---|
id | String | Optional |
price | Double | Optional |
name | String | Optional |
quantity | Int | Optional |
image | String (in the form of web url) | Optional |
Probable Errors:
INVALID_UNAUTHORIZED_JWT_TOKEN_ERROR
INVALID_UNAUTHORIZED_JWT_TOKEN_ERROR
- Check whether PortOne Key and the Secret Key are of the same account
- Check whether the Secret Key is not modified
- Check whether
Bearer
keyword is added before the generated token with a white space.Bearer $jwtToken
- Verify if the expiration time should be greater than the current time
INVALID_UNAUTHORISED_TRANSACTION_SIGNATURE_ERROR
INVALID_UNAUTHORISED_TRANSACTION_SIGNATURE_ERROR
- Check whether all params match with the payload/request
- Check whether the portone key match with the payload and the account
INVALID_UNAUTHORISED_TRANSACTION_IAMPORTKEY_ERROR
INVALID_UNAUTHORISED_TRANSACTION_IAMPORTKEY_ERROR
- Check whether the portone key match with the payload and the account
INVALID_PAYMENT_CHANNEL
INVALID_PAYMENT_CHANNEL
- Make sure the payment channels and payment methods which are added in the payload are enable from the portone portal
INVALID_ENVIRONMENT
INVALID_ENVIRONMENT
- Make sure you have added environment either
sandbox
orlive
Summation of order value, tax, duties, shipping and discount is equal to amount
Summation of order value, tax, duties, shipping and discount is equal to amount
- If items are provided then please verify the values provided should match the total amount:
sum(items price * items quantity) + shipping charge - discount = amount
- Mandatory params in payload:
price
promo_discount (0 is also acceptable)
shipping_charges (0 is also acceptable)
JWT Token Generation
-
Algorithm: HS256
-
Generate JWT token by using the payload and portone secretKey
-
Required payload:
var payload = { iss: 'CHAIPAY', sub: 'lzrYFPfyMLROallZ', // Portone Key iat: new Date().getTime(), exp: new Date().getTime() + 100 * 1000, }; var secretKey = " " // Provide secret key for the account
Parameter Description Values iss Issuer Default Value: "CHAIPAY" sub Subject portone Key iat Issued Timestamp in sec Timestamp in sec exp Expiry Timestamp in sec Timestamp in sec+100
-
Updated 12 days ago