feat(can-charge): 이롬넷(Payverse) 통합결제 추가
This commit is contained in:
31
SodaLive/Resources/payverse_starter.html
Normal file
31
SodaLive/Resources/payverse_starter.html
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
//
|
||||||
|
// Untitled.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 9/29/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="ko">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||||
|
<!-- PayVerse SDK -->
|
||||||
|
<script src="https://ui.payverseglobal.com/js/payments.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
// 안드로이드에서 JSON 문자열을 넘기면 이 함수를 호출
|
||||||
|
function startPay(payloadJson) {
|
||||||
|
try {
|
||||||
|
const p = JSON.parse(payloadJson);
|
||||||
|
// 즉시 실행: 페이지가 열리자마자 결제창 시작
|
||||||
|
window.payVerse.requestUI(p);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('startPay error', e);
|
||||||
|
alert('결제 초기화에 실패했습니다.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
58
SodaLive/Resources/payverse_starter_debug.html
Normal file
58
SodaLive/Resources/payverse_starter_debug.html
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="ko">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||||
|
<title>PayVerse Starter</title>
|
||||||
|
<style>
|
||||||
|
html, body { margin:0; padding:0; height:100%; background:#111; color:#fff; font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
|
||||||
|
#app { padding:16px; }
|
||||||
|
#back { position:fixed; top:16px; left:16px; background:#333; color:#fff; border:none; padding:10px 14px; border-radius:8px; }
|
||||||
|
.log { font-size:12px; opacity:.8; white-space:pre-wrap; word-break:break-word; }
|
||||||
|
</style>
|
||||||
|
<!-- PayVerse SDK -->
|
||||||
|
<script src="https://ui-snd.payverseglobal.com/js/payments.js"
|
||||||
|
onload="console.log('✅ PayVerse SDK loaded');"
|
||||||
|
onerror="console.error('❌ PayVerse SDK failed to load');"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<h2>PayVerse Starter</h2>
|
||||||
|
<p>결제 모듈 로드를 준비중입니다...</p>
|
||||||
|
<div class="log" id="log"></div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
// 간단한 로깅 유틸 (iOS 콘솔 브릿지와 함께 동작)
|
||||||
|
(function(){
|
||||||
|
var el = document.getElementById('log');
|
||||||
|
var old = console.log;
|
||||||
|
console.log = function(){
|
||||||
|
var msg = Array.prototype.slice.call(arguments).join(' ');
|
||||||
|
if (el) { el.textContent += msg + "\n"; }
|
||||||
|
try { window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.console && window.webkit.messageHandlers.console.postMessage(msg); } catch(e) {}
|
||||||
|
old && old.apply(console, arguments);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
// 네이티브에서 JSON 문자열을 넘기면 이 함수를 호출
|
||||||
|
function startPay(payloadJson) {
|
||||||
|
try {
|
||||||
|
console.log('startPay called with payload:', payloadJson);
|
||||||
|
var p = JSON.parse(payloadJson);
|
||||||
|
|
||||||
|
if (!window.payVerse || !window.payVerse.requestUI) {
|
||||||
|
console.log('❌ payVerse SDK not ready');
|
||||||
|
alert('결제 모듈 초기화 중입니다. 잠시 후 다시 시도해주세요.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('▶ calling payVerse.requestUI...');
|
||||||
|
window.payVerse.requestUI(p);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('❌ startPay error:', e && e.message ? e.message : e);
|
||||||
|
alert('결제 초기화에 실패했습니다.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -107,4 +107,8 @@ final class AppViewModel: ObservableObject {
|
|||||||
.store(in: &subscription)
|
.store(in: &subscription)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handlePayverseOpenURL(_ url: URL) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,11 @@ struct SodaLiveApp: App {
|
|||||||
|
|
||||||
@ObservedObject var viewModel = AppViewModel()
|
@ObservedObject var viewModel = AppViewModel()
|
||||||
|
|
||||||
|
@StateObject var canPgPaymentViewModel = CanPgPaymentViewModel()
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView()
|
ContentView(canPgPaymentViewModel: canPgPaymentViewModel)
|
||||||
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)) { _ in
|
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)) { _ in
|
||||||
CreatorCommunityMediaPlayerManager.shared.pauseContent()
|
CreatorCommunityMediaPlayerManager.shared.pauseContent()
|
||||||
}
|
}
|
||||||
@@ -34,8 +36,16 @@ struct SodaLiveApp: App {
|
|||||||
}
|
}
|
||||||
.onOpenURL { url in
|
.onOpenURL { url in
|
||||||
DEBUG_LOG("I have received a URL through a custom scheme! \(url.absoluteString)")
|
DEBUG_LOG("I have received a URL through a custom scheme! \(url.absoluteString)")
|
||||||
ApplicationDelegate.shared.application(UIApplication.shared, open: url, options: [:])
|
|
||||||
AppsFlyerLib.shared().handleOpen(url)
|
if let comps = URLComponents(url: url, resolvingAgainstBaseURL: false),
|
||||||
|
url.scheme?.lowercased() == APPSCHEME.lowercased(),
|
||||||
|
comps.host?.lowercased() == "payverse",
|
||||||
|
comps.path.lowercased() == "/result" {
|
||||||
|
canPgPaymentViewModel.handleVerifyOpenURL(url)
|
||||||
|
} else {
|
||||||
|
ApplicationDelegate.shared.application(UIApplication.shared, open: url, options: [:])
|
||||||
|
AppsFlyerLib.shared().handleOpen(url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
|
@ObservedObject var canPgPaymentViewModel: CanPgPaymentViewModel
|
||||||
@StateObject private var appState = AppState.shared
|
@StateObject private var appState = AppState.shared
|
||||||
|
|
||||||
@State private var isShowDialog = false
|
@State private var isShowDialog = false
|
||||||
@@ -86,6 +87,7 @@ struct ContentView: View {
|
|||||||
|
|
||||||
case .canPgPayment(let canResponse, let refresh, let afterCompletionToGoBack):
|
case .canPgPayment(let canResponse, let refresh, let afterCompletionToGoBack):
|
||||||
CanPgPaymentView(canResponse: canResponse, refresh: refresh, afterCompletionToGoBack: afterCompletionToGoBack)
|
CanPgPaymentView(canResponse: canResponse, refresh: refresh, afterCompletionToGoBack: afterCompletionToGoBack)
|
||||||
|
.environmentObject(canPgPaymentViewModel)
|
||||||
|
|
||||||
case .liveReservation:
|
case .liveReservation:
|
||||||
LiveReservationStatusView()
|
LiveReservationStatusView()
|
||||||
@@ -304,6 +306,6 @@ struct ContentView: View {
|
|||||||
|
|
||||||
struct ContentView_Previews: PreviewProvider {
|
struct ContentView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ContentView()
|
ContentView(canPgPaymentViewModel: CanPgPaymentViewModel())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,3 +19,6 @@ let BOOTPAY_APP_HECTO_ID = "667fca5d3bab7404f831c3e5"
|
|||||||
let NOTIFLY_PROJECT_ID = "5f7ebe90d1ce5f0392164b8a53a662bc"
|
let NOTIFLY_PROJECT_ID = "5f7ebe90d1ce5f0392164b8a53a662bc"
|
||||||
let NOTIFLY_USERNAME = "voiceon"
|
let NOTIFLY_USERNAME = "voiceon"
|
||||||
let NOTIFLY_PASSWORD = "c6c585db0aaa4189be44d0467c7d66b6@A"
|
let NOTIFLY_PASSWORD = "c6c585db0aaa4189be44d0467c7d66b6@A"
|
||||||
|
|
||||||
|
let APPSCHEME = "voiceon-test"
|
||||||
|
let PAYVERSE_HTML_RESOURCE = "payverse_starter_debug"
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ enum CanApi {
|
|||||||
case pgVerifyHecto(request: PgVerifyRequest)
|
case pgVerifyHecto(request: PgVerifyRequest)
|
||||||
|
|
||||||
case useCanCoupon(request: UseCanCouponRequest)
|
case useCanCoupon(request: UseCanCouponRequest)
|
||||||
|
|
||||||
|
case payverseChargeCan(request: PayverseChargeRequest)
|
||||||
|
case payverseVerify(transactionId: String, orderId: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CanApi: TargetType {
|
extension CanApi: TargetType {
|
||||||
@@ -60,6 +63,12 @@ extension CanApi: TargetType {
|
|||||||
|
|
||||||
case .useCanCoupon:
|
case .useCanCoupon:
|
||||||
return "/can/coupon/use"
|
return "/can/coupon/use"
|
||||||
|
|
||||||
|
case .payverseChargeCan:
|
||||||
|
return "/charge/payverse"
|
||||||
|
|
||||||
|
case .payverseVerify:
|
||||||
|
return "/charge/payverse/verify"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +77,7 @@ extension CanApi: TargetType {
|
|||||||
case .getCanStatus, .getCanChargeStatus, .getCanUseStatus, .getCans:
|
case .getCanStatus, .getCanChargeStatus, .getCanUseStatus, .getCans:
|
||||||
return .get
|
return .get
|
||||||
|
|
||||||
case .chargeCan, .verify, .pgChargeCan, .pgVerify, .pgVerifyHecto, .useCanCoupon:
|
case .chargeCan, .verify, .pgChargeCan, .pgVerify, .pgVerifyHecto, .useCanCoupon, .payverseChargeCan, .payverseVerify:
|
||||||
return .post
|
return .post
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,6 +121,14 @@ extension CanApi: TargetType {
|
|||||||
|
|
||||||
case .useCanCoupon(let request):
|
case .useCanCoupon(let request):
|
||||||
return .requestJSONEncodable(request)
|
return .requestJSONEncodable(request)
|
||||||
|
|
||||||
|
case .payverseChargeCan(let request):
|
||||||
|
return .requestJSONEncodable(request)
|
||||||
|
|
||||||
|
case .payverseVerify(let transactionId, let orderId):
|
||||||
|
return .requestJSONEncodable(
|
||||||
|
PayverseVerifyRequest(transactionId: transactionId, orderId: orderId)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,5 +53,13 @@ final class CanRepository {
|
|||||||
let request = UseCanCouponRequest(couponNumber: couponNumber)
|
let request = UseCanCouponRequest(couponNumber: couponNumber)
|
||||||
return api.requestPublisher(.useCanCoupon(request: request))
|
return api.requestPublisher(.useCanCoupon(request: request))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func payverseChargeCan(canId: Int) -> AnyPublisher<Response, MoyaError> {
|
||||||
|
return api.requestPublisher(.payverseChargeCan(request: PayverseChargeRequest(canId: canId)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func payverseVerify(transactionId: String, orderId: String) -> AnyPublisher<Response, MoyaError> {
|
||||||
|
return api.requestPublisher(.payverseVerify(transactionId: transactionId, orderId: orderId))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ import BootpayUI
|
|||||||
|
|
||||||
struct CanPgPaymentView: View {
|
struct CanPgPaymentView: View {
|
||||||
|
|
||||||
@StateObject var viewModel = CanPgPaymentViewModel()
|
@EnvironmentObject var viewModel: CanPgPaymentViewModel
|
||||||
|
|
||||||
let canResponse: GetCanResponse
|
let canResponse: GetCanResponse
|
||||||
let refresh: () -> Void
|
let refresh: () -> Void
|
||||||
let afterCompletionToGoBack: Bool
|
let afterCompletionToGoBack: Bool
|
||||||
|
|
||||||
|
@State private var showExitConfirm: Bool = false
|
||||||
|
|
||||||
init(canResponse: GetCanResponse, refresh: @escaping () -> Void, afterCompletionToGoBack: Bool) {
|
init(canResponse: GetCanResponse, refresh: @escaping () -> Void, afterCompletionToGoBack: Bool) {
|
||||||
self.canResponse = canResponse
|
self.canResponse = canResponse
|
||||||
self.refresh = refresh
|
self.refresh = refresh
|
||||||
@@ -64,6 +66,37 @@ struct CanPgPaymentView: View {
|
|||||||
DEBUG_LOG("onClose")
|
DEBUG_LOG("onClose")
|
||||||
viewModel.isShowPaymentView = false
|
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 {
|
} else {
|
||||||
GeometryReader { proxy in
|
GeometryReader { proxy in
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
@@ -102,15 +135,14 @@ struct CanPgPaymentView: View {
|
|||||||
.frame(width: screenSize().width - 26.7, alignment: .leading)
|
.frame(width: screenSize().width - 26.7, alignment: .leading)
|
||||||
.padding(.top, 26.7)
|
.padding(.top, 26.7)
|
||||||
|
|
||||||
|
HStack(spacing: 16.7) {
|
||||||
HStack(spacing: 13.3) {
|
Text("통합 결제")
|
||||||
Text("카드")
|
.font(.custom( viewModel.paymentMethod == .unified ? Font.bold.rawValue : Font.medium.rawValue, size: 16.7))
|
||||||
.font(.custom( viewModel.paymentMethod == .card ? Font.bold.rawValue : Font.medium.rawValue, size: 16.7))
|
.foregroundColor(viewModel.paymentMethod == .unified ? Color.button : Color.grayee)
|
||||||
.foregroundColor(viewModel.paymentMethod == .card ? Color.button : Color.grayee)
|
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.padding(.vertical, 16.7)
|
.padding(.vertical, 16.7)
|
||||||
.background(
|
.background(
|
||||||
viewModel.paymentMethod == .card ?
|
viewModel.paymentMethod == .unified ?
|
||||||
Color.button.opacity(0.3) :
|
Color.button.opacity(0.3) :
|
||||||
Color.gray23
|
Color.gray23
|
||||||
)
|
)
|
||||||
@@ -118,60 +150,11 @@ struct CanPgPaymentView: View {
|
|||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.stroke(lineWidth: 1)
|
.stroke(lineWidth: 1)
|
||||||
.foregroundColor(viewModel.paymentMethod == .card ? Color.button : Color.gray77)
|
.foregroundColor(viewModel.paymentMethod == .unified ? Color.button : Color.gray77)
|
||||||
)
|
)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
if viewModel.paymentMethod != .card {
|
if viewModel.paymentMethod != .unified {
|
||||||
viewModel.paymentMethod = .card
|
viewModel.paymentMethod = .unified
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,6 +178,28 @@ struct CanPgPaymentView: View {
|
|||||||
viewModel.paymentMethod = .kakaopay
|
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)
|
.frame(width: screenSize().width - 26.7)
|
||||||
.padding(.top, 16.7)
|
.padding(.top, 16.7)
|
||||||
@@ -299,12 +304,16 @@ struct CanPgPaymentView: View {
|
|||||||
viewModel.errorMessage = "결제진행에 동의하셔야 결제가 가능합니다."
|
viewModel.errorMessage = "결제진행에 동의하셔야 결제가 가능합니다."
|
||||||
viewModel.isShowPopup = true
|
viewModel.isShowPopup = true
|
||||||
} else {
|
} else {
|
||||||
viewModel.chargeCan(canId: canResponse.id) {
|
if viewModel.paymentMethod == .unified {
|
||||||
viewModel.payload.orderName = canResponse.title
|
viewModel.payverseChargeCan(canId: canResponse.id)
|
||||||
viewModel.payload.price = Double(canResponse.price)
|
} else {
|
||||||
viewModel.payload.taxFree = 0
|
viewModel.chargeCan(canId: canResponse.id) {
|
||||||
|
viewModel.payload.orderName = canResponse.title
|
||||||
|
viewModel.payload.price = Double(canResponse.price)
|
||||||
|
viewModel.payload.taxFree = 0
|
||||||
|
|
||||||
viewModel.isShowPaymentView = true
|
viewModel.isShowPaymentView = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -348,6 +357,21 @@ struct CanPgPaymentView: View {
|
|||||||
LoadingView()
|
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
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ import Combine
|
|||||||
import Bootpay
|
import Bootpay
|
||||||
|
|
||||||
enum PaymentMethod: String {
|
enum PaymentMethod: String {
|
||||||
case card = "카드"
|
case unified = "통합 결제"
|
||||||
case bank = "계좌이체"
|
|
||||||
case phone = "휴대폰"
|
case phone = "휴대폰"
|
||||||
case kakaopay = "카카오페이"
|
case kakaopay = "카카오페이"
|
||||||
}
|
}
|
||||||
@@ -28,9 +27,15 @@ final class CanPgPaymentViewModel: ObservableObject {
|
|||||||
@Published var isLoading = false
|
@Published var isLoading = false
|
||||||
|
|
||||||
@Published var isShowPaymentView = false
|
@Published var isShowPaymentView = false
|
||||||
|
@Published var isShowPayversePaymentView = false
|
||||||
@Published var paymentMethod: PaymentMethod? = nil
|
@Published var paymentMethod: PaymentMethod? = nil
|
||||||
|
|
||||||
let payload = Payload()
|
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) {
|
func chargeCan(canId: Int, onSuccess: @escaping () -> Void) {
|
||||||
isLoading = true
|
isLoading = true
|
||||||
@@ -137,4 +142,139 @@ final class CanPgPaymentViewModel: ObservableObject {
|
|||||||
isShowPopup = true
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
247
SodaLive/Sources/MyPage/Can/Payment/PayverseWebView.swift
Normal file
247
SodaLive/Sources/MyPage/Can/Payment/PayverseWebView.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,3 +19,6 @@ let BOOTPAY_APP_HECTO_ID = "664c1707b18b225deca4b42a"
|
|||||||
let NOTIFLY_PROJECT_ID = "765102ec85855aa680da35f1b0f55712"
|
let NOTIFLY_PROJECT_ID = "765102ec85855aa680da35f1b0f55712"
|
||||||
let NOTIFLY_USERNAME = "voiceon"
|
let NOTIFLY_USERNAME = "voiceon"
|
||||||
let NOTIFLY_PASSWORD = "c6c585db0aaa4189be44d0467c7d66b6@A"
|
let NOTIFLY_PASSWORD = "c6c585db0aaa4189be44d0467c7d66b6@A"
|
||||||
|
|
||||||
|
let APPSCHEME = "voiceon"
|
||||||
|
let PAYVERSE_HTML_RESOURCE = "payverse_starter"
|
||||||
|
|||||||
Reference in New Issue
Block a user