feat(can-charge): 이롬넷(Payverse) 통합결제 추가

This commit is contained in:
Yu Sung
2025-10-01 01:48:18 +09:00
parent e62c89d7bc
commit d045722b8d
13 changed files with 639 additions and 72 deletions

View File

@@ -11,12 +11,14 @@ import BootpayUI
struct CanPgPaymentView: View {
@StateObject var viewModel = CanPgPaymentViewModel()
@EnvironmentObject var viewModel: CanPgPaymentViewModel
let canResponse: GetCanResponse
let refresh: () -> Void
let afterCompletionToGoBack: Bool
@State private var showExitConfirm: Bool = false
init(canResponse: GetCanResponse, refresh: @escaping () -> Void, afterCompletionToGoBack: Bool) {
self.canResponse = canResponse
self.refresh = refresh
@@ -64,6 +66,37 @@ struct CanPgPaymentView: View {
DEBUG_LOG("onClose")
viewModel.isShowPaymentView = false
}
} else if viewModel.isShowPayversePaymentView {
ZStack(alignment: .topLeading) {
PayverseWebView(startPayloadJson: viewModel.payversePayloadJson)
.ignoresSafeArea(edges: .bottom)
HStack(spacing: 8) {
Button(action: { showExitConfirm = true }) {
Text("닫기")
.font(.custom(Font.bold.rawValue, size: 14))
.foregroundColor(.white)
.padding(.horizontal, 12)
.padding(.vertical, 8)
.background(Color.black.opacity(0.6))
.clipShape(Capsule())
}
.padding(.leading, 16)
.padding(.top, 12)
Spacer()
}
}
.background(Color.black.ignoresSafeArea())
.alert("결제를 종료할까요?", isPresented: $showExitConfirm) {
Button("계속", role: .cancel) { }
Button("종료", role: .destructive) {
DEBUG_LOG("Payverse: user requested to exit")
viewModel.isShowPayversePaymentView = false
//
AppState.shared.back()
}
} message: {
Text("진행 중인 결제를 중단하고 이전 화면으로 돌아갑니다.")
}
} else {
GeometryReader { proxy in
VStack(spacing: 0) {
@@ -102,15 +135,14 @@ struct CanPgPaymentView: View {
.frame(width: screenSize().width - 26.7, alignment: .leading)
.padding(.top, 26.7)
HStack(spacing: 13.3) {
Text("카드")
.font(.custom( viewModel.paymentMethod == .card ? Font.bold.rawValue : Font.medium.rawValue, size: 16.7))
.foregroundColor(viewModel.paymentMethod == .card ? Color.button : Color.grayee)
HStack(spacing: 16.7) {
Text("통합 결제")
.font(.custom( viewModel.paymentMethod == .unified ? Font.bold.rawValue : Font.medium.rawValue, size: 16.7))
.foregroundColor(viewModel.paymentMethod == .unified ? Color.button : Color.grayee)
.frame(maxWidth: .infinity)
.padding(.vertical, 16.7)
.background(
viewModel.paymentMethod == .card ?
viewModel.paymentMethod == .unified ?
Color.button.opacity(0.3) :
Color.gray23
)
@@ -118,60 +150,11 @@ struct CanPgPaymentView: View {
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(lineWidth: 1)
.foregroundColor(viewModel.paymentMethod == .card ? Color.button : Color.gray77)
.foregroundColor(viewModel.paymentMethod == .unified ? Color.button : Color.gray77)
)
.onTapGesture {
if viewModel.paymentMethod != .card {
viewModel.paymentMethod = .card
}
}
Text("계좌이체")
.font(.custom( viewModel.paymentMethod == .bank ? Font.bold.rawValue : Font.medium.rawValue, size: 16.7))
.foregroundColor(viewModel.paymentMethod == .bank ? Color.button : Color.grayee)
.frame(maxWidth: .infinity)
.padding(.vertical, 16.7)
.background(
viewModel.paymentMethod == .bank ?
Color.button.opacity(0.3) :
Color.gray23
)
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(lineWidth: 1)
.foregroundColor(viewModel.paymentMethod == .bank ? Color.button : Color.gray77)
)
.onTapGesture {
if viewModel.paymentMethod != .bank {
viewModel.paymentMethod = .bank
}
}
}
.frame(width: screenSize().width - 26.7)
.padding(.top, 16.7)
HStack(spacing: 13.3) {
Text("휴대폰 결제")
.font(.custom( viewModel.paymentMethod == .phone ? Font.bold.rawValue : Font.medium.rawValue, size: 16.7))
.foregroundColor(viewModel.paymentMethod == .phone ? Color.button : Color.grayee)
.frame(maxWidth: .infinity)
.padding(.vertical, 16.7)
.background(
viewModel.paymentMethod == .phone ?
Color.button.opacity(0.3) :
Color.gray23
)
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(lineWidth: 1)
.foregroundColor(viewModel.paymentMethod == .phone ? Color.button : Color.gray77)
)
.onTapGesture {
if viewModel.paymentMethod != .phone {
viewModel.paymentMethod = .phone
if viewModel.paymentMethod != .unified {
viewModel.paymentMethod = .unified
}
}
@@ -195,6 +178,28 @@ struct CanPgPaymentView: View {
viewModel.paymentMethod = .kakaopay
}
}
Text("휴대폰 결제")
.font(.custom( viewModel.paymentMethod == .phone ? Font.bold.rawValue : Font.medium.rawValue, size: 16.7))
.foregroundColor(viewModel.paymentMethod == .phone ? Color.button : Color.grayee)
.frame(maxWidth: .infinity)
.padding(.vertical, 16.7)
.background(
viewModel.paymentMethod == .phone ?
Color.button.opacity(0.3) :
Color.gray23
)
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(lineWidth: 1)
.foregroundColor(viewModel.paymentMethod == .phone ? Color.button : Color.gray77)
)
.onTapGesture {
if viewModel.paymentMethod != .phone {
viewModel.paymentMethod = .phone
}
}
}
.frame(width: screenSize().width - 26.7)
.padding(.top, 16.7)
@@ -299,12 +304,16 @@ struct CanPgPaymentView: View {
viewModel.errorMessage = "결제진행에 동의하셔야 결제가 가능합니다."
viewModel.isShowPopup = true
} else {
viewModel.chargeCan(canId: canResponse.id) {
viewModel.payload.orderName = canResponse.title
viewModel.payload.price = Double(canResponse.price)
viewModel.payload.taxFree = 0
viewModel.isShowPaymentView = true
if viewModel.paymentMethod == .unified {
viewModel.payverseChargeCan(canId: canResponse.id)
} else {
viewModel.chargeCan(canId: canResponse.id) {
viewModel.payload.orderName = canResponse.title
viewModel.payload.price = Double(canResponse.price)
viewModel.payload.taxFree = 0
viewModel.isShowPaymentView = true
}
}
}
}
@@ -348,6 +357,21 @@ struct CanPgPaymentView: View {
LoadingView()
}
}
.onAppear {
viewModel.canResponse = canResponse
viewModel.refresh = refresh
viewModel.afterCompletionToGoBack = afterCompletionToGoBack
}
.onDisappear {
viewModel.canResponse = nil
viewModel.refresh = nil
viewModel.afterCompletionToGoBack = nil
viewModel.paymentMethod = nil
viewModel.isTermsAgree = false
viewModel.isShowPaymentView = false
viewModel.isShowPayversePaymentView = false
}
}
}

View File

@@ -11,8 +11,7 @@ import Combine
import Bootpay
enum PaymentMethod: String {
case card = "카드"
case bank = "계좌이체"
case unified = "통합 결제"
case phone = "휴대폰"
case kakaopay = "카카오페이"
}
@@ -28,9 +27,15 @@ final class CanPgPaymentViewModel: ObservableObject {
@Published var isLoading = false
@Published var isShowPaymentView = false
@Published var isShowPayversePaymentView = false
@Published var paymentMethod: PaymentMethod? = nil
let payload = Payload()
var payversePayloadJson: String = ""
var canResponse: GetCanResponse? = nil
var refresh: (() -> Void)? = nil
var afterCompletionToGoBack: Bool? = nil
func chargeCan(canId: Int, onSuccess: @escaping () -> Void) {
isLoading = true
@@ -137,4 +142,139 @@ final class CanPgPaymentViewModel: ObservableObject {
isShowPopup = true
}
}
func payverseChargeCan(canId: Int) {
isLoading = true
repository.payverseChargeCan(canId: canId)
.sink { result in
switch result {
case .finished:
DEBUG_LOG("finish")
case .failure(let error):
ERROR_LOG(error.localizedDescription)
}
} receiveValue: { [unowned self] response in
self.isLoading = false
let responseData = response.data
do {
let jsonDecoder = JSONDecoder()
let decoded = try jsonDecoder.decode(ApiResponse<PayverseChargeResponse>.self, from: responseData)
if let data = decoded.data, decoded.success {
if let payloadJson = data.payloadJson.data(using: .utf8) {
var obj = try JSONSerialization.jsonObject(with: payloadJson, options: []) as? [String: Any] ?? [:]
obj["returnUrl"] = "\(APPSCHEME)://payverse/result"
obj["webhookUrl"] = "\(BASE_URL)/charge/payverse/webhook"
obj["appScheme"] = APPSCHEME
let merged = try JSONSerialization.data(withJSONObject: obj, options: [])
self.payversePayloadJson = String(data: merged, encoding: .utf8)!
self.isShowPayversePaymentView = true
} else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowPopup = true
return
}
} else {
if let message = decoded.message {
self.errorMessage = message
} else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
}
self.isShowPopup = true
}
} catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowPopup = true
}
}
.store(in: &subscription)
}
func handleVerifyOpenURL(_ url: URL) {
guard let comps = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowPopup = true
return
}
let q = comps.queryItems?.reduce(into: [String:String]()) { $0[$1.name] = $1.value } ?? [:]
let tid = q["tid"] ?? q["tx_id"]
let orderId = q["orderId"]
let resultStatus = q["resultStatus"]
if resultStatus == "FAILED" ||
resultStatus == "DECLINE" ||
orderId == nil || orderId?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true ||
tid == nil || tid?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowPopup = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
AppState.shared.back()
}
return
} else {
payverseVerify(transactionId: tid!, orderId: orderId!)
}
}
func payverseVerify(transactionId: String, orderId: String) {
isLoading = true
repository.payverseVerify(transactionId: transactionId, orderId: orderId)
.sink { result in
switch result {
case .finished:
DEBUG_LOG("finish")
case .failure(let error):
ERROR_LOG(error.localizedDescription)
}
} receiveValue: { [unowned self] response in
self.isLoading = false
let responseData = response.data
do {
let jsonDecoder = JSONDecoder()
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
if decoded.success {
if let canResponse = self.canResponse {
let can = UserDefaults.int(forKey: .can)
UserDefaults.set(can + canResponse.can + canResponse.rewardCan, forKey: .can)
if let refresh = refresh {
refresh()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
if let afterCompletionToGoBack = self.afterCompletionToGoBack, afterCompletionToGoBack {
AppState.shared.back()
AppState.shared.back()
} else {
if let refresh = self.refresh {
AppState.shared.setAppStep(step: .canStatus(refresh: refresh))
} else {
AppState.shared.setAppStep(step: .canStatus(refresh: {}))
}
}
}
} else {
AppState.shared.setAppStep(step: .canStatus(refresh: {}))
}
} else {
if let message = decoded.message {
self.errorMessage = message
} else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
}
self.isShowPopup = true
}
} catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowPopup = true
}
}
.store(in: &subscription)
}
}

View File

@@ -0,0 +1,20 @@
//
// PayverseChargeDto.swift
// SodaLive
//
// Created by klaus on 9/29/25.
//
struct PayverseChargeRequest: Encodable {
let canId: Int
}
struct PayverseChargeResponse: Decodable {
let chargeId: Int
let payloadJson: String
}
struct PayverseVerifyRequest: Encodable {
let transactionId: String
let orderId: String
}

View File

@@ -0,0 +1,247 @@
//
// PayverseWebView.swift
// SodaLive
//
// Created by klaus on 9/29/25.
//
import SwiftUI
import WebKit
struct PayverseWebView: UIViewRepresentable {
let startPayloadJson: String
func makeUIView(context: Context) -> WKWebView {
// WKWebView : JavaScript
let config = WKWebViewConfiguration()
config.defaultWebpagePreferences.allowsContentJavaScript = true
config.preferences.javaScriptCanOpenWindowsAutomatically = true
config.websiteDataStore = .default()
let webView = WKWebView(frame: .zero, configuration: config)
webView.navigationDelegate = context.coordinator
//
if #available(iOS 13.0, *) {
webView.overrideUserInterfaceStyle = .dark
}
webView.isOpaque = false
webView.backgroundColor = .black
webView.scrollView.backgroundColor = .black
webView.allowsBackForwardNavigationGestures = true
webView.scrollView.contentInsetAdjustmentBehavior = .never
// (PAYVERSE_HTML_RESOURCE.html)
if let url = Bundle.main.url(forResource: PAYVERSE_HTML_RESOURCE,
withExtension: "html") {
//
webView.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent())
DEBUG_LOG("[DEBUG_LOG] Loading local Payverse HTML: \(url.lastPathComponent)")
} else {
DEBUG_LOG("[ERROR_LOG] Payverse HTML resource not found: \(PAYVERSE_HTML_RESOURCE).html")
}
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(startPayloadJson: startPayloadJson)
}
final class Coordinator: NSObject, WKNavigationDelegate {
private let startPayloadJson: String
init(startPayloadJson: String) {
self.startPayloadJson = startPayloadJson
}
func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
guard let url = navigationAction.request.url else {
decisionHandler(.allow); return
}
updateBlindViewIfNaverLogin(webView, url.absoluteString)
// : myapp://payverse/result?...
if url.scheme?.lowercased() == APPSCHEME.lowercased() {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
decisionHandler(.cancel)
} else if(url.scheme?.lowercased() == "file") {
// (file://) WebView
decisionHandler(.allow)
} else if(url.absoluteString.starts(with: "about:blank")) {
decisionHandler(.allow)
} else if(isItunesURL(url.absoluteString)) {
startAppToApp(url)
decisionHandler(.cancel)
} else if(!url.absoluteString.starts(with: "http")) {
// http/https /App Store
startAppToApp(url)
decisionHandler(.cancel)
} else {
decisionHandler(.allow)
}
}
// +
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("document.readyState") { value, error in
if let state = value as? String {
DEBUG_LOG("[DEBUG_LOG] WebView readyState: \(state)")
}
if let error = error {
DEBUG_LOG("[ERROR_LOG] readyState eval error: \(error.localizedDescription)")
}
}
// startPay
webView.evaluateJavaScript("typeof startPay === 'function'") { value, _ in
if let ok = value as? Bool {
DEBUG_LOG("[DEBUG_LOG] startPay function available: \(ok)")
}
}
// JSON JS
func esc(_ s: String) -> String {
var r = s.replacingOccurrences(of: "\\", with: "\\\\")
r = r.replacingOccurrences(of: "'", with: "\\'")
r = r.replacingOccurrences(of: "\n", with: "\\n")
r = r.replacingOccurrences(of: "\r", with: "")
r = r.replacingOccurrences(of: "\u{2028}", with: "\\u2028")
r = r.replacingOccurrences(of: "\u{2029}", with: "\\u2029")
r = r.replacingOccurrences(of: "</", with: "<\\/")
return r
}
let escaped = esc(self.startPayloadJson)
let js = """
(function(){
try {
if (typeof startPay === 'function') {
startPay('\(escaped)');
console.log('iOS: startPay invoked');
} else {
setTimeout(function(){
try {
if (typeof startPay === 'function') {
startPay('\(escaped)');
console.log('iOS: startPay invoked (retry)');
}
} catch(e) {
console.log('iOS: startPay retry error: ' + (e && e.message ? e.message : e));
}
}, 300);
}
} catch(e) {
console.log('iOS: startPay call error: ' + (e && e.message ? e.message : e));
}
})();
"""
webView.evaluateJavaScript(js) { _, error in
if let error = error {
DEBUG_LOG("[ERROR_LOG] startPay invoke error: \(error.localizedDescription)")
} else {
DEBUG_LOG("[DEBUG_LOG] startPay invoked from iOS")
}
}
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
DEBUG_LOG("[ERROR_LOG] WebView didFail: \(error.localizedDescription)")
}
func updateBlindViewIfNaverLogin(_ webView: WKWebView, _ url: String) {
if(url.starts(with: "https://nid.naver.com")) { //show
webView.evaluateJavaScript("document.getElementById('back').remove();")
}
}
func isItunesURL(_ urlString: String) -> Bool {
return isMatch(urlString, "\\/\\/itunes\\.apple\\.com\\/")
}
func isMatch(_ urlString: String, _ pattern: String) -> Bool {
let regex = try! NSRegularExpression(pattern: pattern, options: [])
let result = regex.matches(in: urlString, options: [], range: NSRange(location: 0, length: urlString.count))
return result.count > 0
}
func startAppToApp(_ url: URL) {
UIApplication.shared.open(url, options: [:], completionHandler: { result in
if(result == false) {
self.startItunesToInstall(url)
}
})
}
func startItunesToInstall(_ url: URL) {
let sUrl = url.absoluteString
var itunesUrl = ""
if(sUrl.starts(with: "kfc-bankpay")) {
itunesUrl = "https://apps.apple.com/kr/app/%EB%B1%85%ED%81%AC%ED%8E%98%EC%9D%B4-%EA%B8%88%EC%9C%B5%EA%B8%B0%EA%B4%80-%EA%B3%B5%EB%8F%99-%EA%B3%84%EC%A2%8C%EC%9D%B4%EC%B2%B4-%EA%B2%B0%EC%A0%9C-%EC%A0%9C%EB%A1%9C%ED%8E%98%EC%9D%B4/id398456030"
} else if(sUrl.starts(with: "ispmobile")) {
itunesUrl = "https://apps.apple.com/kr/app/isp/id369125087"
} else if(sUrl.starts(with: "hdcardappcardansimclick") || sUrl.starts(with: "smhyundaiansimclick")) {
itunesUrl = "https://apps.apple.com/kr/app/%ED%98%84%EB%8C%80%EC%B9%B4%EB%93%9C/id702653088"
} else if(sUrl.starts(with: "shinhan-sr-ansimclick") || sUrl.starts(with: "smshinhanansimclick")) {
itunesUrl = "https://apps.apple.com/kr/app/%EC%8B%A0%ED%95%9C%ED%8E%98%EC%9D%B4%ED%8C%90/id572462317"
} else if(sUrl.starts(with: "kb-acp")) {
itunesUrl = "https://apps.apple.com/kr/app/kb-pay/id695436326"
} else if(sUrl.starts(with: "liivbank")) {
itunesUrl = "https://apps.apple.com/kr/app/%EB%A6%AC%EB%B8%8C/id1126232922"
} else if(sUrl.starts(with: "mpocket.online.ansimclick") || sUrl.starts(with: "ansimclickscard") || sUrl.starts(with: "ansimclickipcollect") || sUrl.starts(with: "samsungpay") || sUrl.starts(with: "scardcertiapp")) {
itunesUrl = "https://apps.apple.com/kr/app/%EC%82%BC%EC%84%B1%EC%B9%B4%EB%93%9C/id535125356"
} else if(sUrl.starts(with: "lottesmartpay")) {
itunesUrl = "https://apps.apple.com/us/app/%EB%A1%AF%EB%8D%B0%EC%B9%B4%EB%93%9C-%EC%95%B1%EC%B9%B4%EB%93%9C/id688047200"
} else if(sUrl.starts(with: "lotteappcard")) {
itunesUrl = "https://apps.apple.com/kr/app/%EB%94%94%EC%A7%80%EB%A1%9C%EC%B9%B4-%EB%A1%AF%EB%8D%B0%EC%B9%B4%EB%93%9C/id688047200"
} else if(sUrl.starts(with: "newsmartpib")) {
itunesUrl = "https://apps.apple.com/kr/app/%EC%9A%B0%EB%A6%AC-won-%EB%B1%85%ED%82%B9/id1470181651"
} else if(sUrl.starts(with: "com.wooricard.wcard")) {
itunesUrl = "https://apps.apple.com/kr/app/%EC%9A%B0%EB%A6%ACwon%EC%B9%B4%EB%93%9C/id1499598869"
} else if(sUrl.starts(with: "citispay") || sUrl.starts(with: "citicardappkr") || sUrl.starts(with: "citimobileapp")) {
itunesUrl = "https://apps.apple.com/kr/app/%EC%94%A8%ED%8B%B0%EB%AA%A8%EB%B0%94%EC%9D%BC/id1179759666"
} else if(sUrl.starts(with: "shinsegaeeasypayment")) {
itunesUrl = "https://apps.apple.com/kr/app/ssgpay/id666237916"
} else if(sUrl.starts(with: "cloudpay")) {
itunesUrl = "https://apps.apple.com/kr/app/%ED%95%98%EB%82%98%EC%B9%B4%EB%93%9C-%EC%9B%90%ED%81%90%ED%8E%98%EC%9D%B4/id847268987"
} else if(sUrl.starts(with: "hanawalletmembers")) {
itunesUrl = "https://apps.apple.com/kr/app/n-wallet/id492190784"
} else if(sUrl.starts(with: "nhappvardansimclick")) {
itunesUrl = "https://apps.apple.com/kr/app/%EC%98%AC%EC%9B%90%ED%8E%98%EC%9D%B4-nh%EC%95%B1%EC%B9%B4%EB%93%9C/id1177889176"
} else if(sUrl.starts(with: "nhallonepayansimclick") || sUrl.starts(with: "nhappcardansimclick") || sUrl.starts(with: "nhallonepayansimclick") || sUrl.starts(with: "nonghyupcardansimclick")) {
itunesUrl = "https://apps.apple.com/kr/app/%EC%98%AC%EC%9B%90%ED%8E%98%EC%9D%B4-nh%EC%95%B1%EC%B9%B4%EB%93%9C/id1177889176"
} else if(sUrl.starts(with: "payco")) {
itunesUrl = "https://apps.apple.com/kr/app/payco/id924292102"
} else if(sUrl.starts(with: "lpayapp") || sUrl.starts(with: "lmslpay")) {
itunesUrl = "https://apps.apple.com/kr/app/l-point-with-l-pay/id473250588"
} else if(sUrl.starts(with: "naversearchapp")) {
itunesUrl = "https://apps.apple.com/kr/app/%EB%84%A4%EC%9D%B4%EB%B2%84-naver/id393499958"
} else if(sUrl.starts(with: "tauthlink")) {
itunesUrl = "https://apps.apple.com/kr/app/pass-by-skt/id1141258007"
} else if(sUrl.starts(with: "uplusauth") || sUrl.starts(with: "upluscorporation")) {
itunesUrl = "https://apps.apple.com/kr/app/pass-by-u/id1147394645"
} else if(sUrl.starts(with: "ktauthexternalcall")) {
itunesUrl = "https://apps.apple.com/kr/app/pass-by-kt/id1134371550"
} else if(sUrl.starts(with: "supertoss")) {
itunesUrl = "https://apps.apple.com/kr/app/%ED%86%A0%EC%8A%A4/id839333328"
} else if(sUrl.starts(with: "kakaotalk")) {
itunesUrl = "https://apps.apple.com/kr/app/kakaotalk/id362057947"
} else if(sUrl.starts(with: "chaipayment")) {
itunesUrl = "https://apps.apple.com/kr/app/%EC%B0%A8%EC%9D%B4/id1459979272"
} else if(sUrl.starts(with: "ukbanksmartbanknonloginpay")) {
itunesUrl = "https://itunes.apple.com/kr/developer/%EC%BC%80%EC%9D%B4%EB%B1%85%ED%81%AC/id1178872626?mt=8"
} else if(sUrl.starts(with: "newliiv")) {
itunesUrl = "https://apps.apple.com/us/app/%EB%A6%AC%EB%B8%8C-next/id1573528126"
} else if(sUrl.starts(with: "kbbank")) {
itunesUrl = "https://apps.apple.com/kr/app/kb%EC%8A%A4%ED%83%80%EB%B1%85%ED%82%B9/id373742138"
}
if(itunesUrl.count > 0) {
if let appstore = URL(string: itunesUrl) {
startAppToApp(appstore)
}
}
}
}
}