// // CanPgPaymentView.swift // SodaLive // // Created by klaus on 2023/08/11. // import SwiftUI import Bootpay import BootpayUI struct CanPgPaymentView: View { @StateObject var viewModel = CanPgPaymentViewModel() let canResponse: GetCanResponse let refresh: () -> Void let afterCompletionToGoBack: Bool init(canResponse: GetCanResponse, refresh: @escaping () -> Void, afterCompletionToGoBack: Bool) { self.canResponse = canResponse self.refresh = refresh self.afterCompletionToGoBack = afterCompletionToGoBack } var body: some View { ZStack { Color.black.ignoresSafeArea() if viewModel.isShowPaymentView { BootpayUI(payload: viewModel.payload, requestType: BootpayRequest.TYPE_PAYMENT) .onConfirm { DEBUG_LOG("onConfirm: \($0)") return true } .onCancel { DEBUG_LOG("onCancel: \($0)") } .onError { DEBUG_LOG("onError: \($0)") viewModel.isShowPaymentView = false viewModel.errorMessage = "결제 중 오류가 발생했습니다." viewModel.isShowPopup = true } .onDone { DEBUG_LOG("onDone: \($0)") viewModel.verifyPayment($0) { let can = UserDefaults.int(forKey: .can) UserDefaults.set(can + canResponse.can + canResponse.rewardCan, forKey: .can) self.refresh() DispatchQueue.main.asyncAfter(deadline: .now() + 1) { if afterCompletionToGoBack { AppState.shared.back() AppState.shared.back() } else { AppState.shared.setAppStep(step: .canStatus(refresh: refresh)) } } } } .onClose { DEBUG_LOG("onClose") viewModel.isShowPaymentView = false } } else { GeometryReader { proxy in VStack(spacing: 0) { DetailNavigationBar(title: "결제하기") ScrollView(.vertical, showsIndicators: false) { VStack(spacing: 0) { HStack(spacing: 0) { Image("ic_can") .resizable() .scaledToFill() .frame(width: 26.7, height: 26.7, alignment: .top) Text(canResponse.title) .font(.custom(Font.bold.rawValue, size: 15.3)) .foregroundColor(Color(hex: "eeeeee")) .padding(.leading, 13.3) Spacer() Text("\(canResponse.price) 원") .font(.custom(Font.bold.rawValue, size: 15.3)) .foregroundColor(Color(hex: "eeeeee")) } .padding(.horizontal, 13.3) .padding(.vertical, 23.3) .background(Color(hex: "222222")) .cornerRadius(16.7) .padding(.horizontal, 13.3) .frame(width: screenSize().width) .padding(.top, 13.3) Text("결제 수단 선택") .font(.custom(Font.bold.rawValue, size: 16.7)) .foregroundColor(Color(hex: "eeeeee")) .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(Color(hex: viewModel.paymentMethod == .card ? "9970ff" : "eeeeee")) .frame(width: (screenSize().width - 40) / 2) .padding(.vertical, 16.7) .background( Color(hex: viewModel.paymentMethod == .card ? "9970ff" : "232323") .opacity(viewModel.paymentMethod == .card ? 0.3 : 1) ) .cornerRadius(10) .overlay( RoundedRectangle(cornerRadius: 10) .stroke(lineWidth: 1) .foregroundColor(Color(hex: viewModel.paymentMethod == .card ? "9970ff" : "777777")) ) .onTapGesture { if viewModel.paymentMethod != .card { viewModel.paymentMethod = .card } } Text("계좌이체") .font(.custom( viewModel.paymentMethod == .bank ? Font.bold.rawValue : Font.medium.rawValue, size: 16.7)) .foregroundColor(Color(hex: viewModel.paymentMethod == .bank ? "9970ff" : "eeeeee")) .frame(width: (screenSize().width - 40) / 2) .padding(.vertical, 16.7) .background( Color(hex: viewModel.paymentMethod == .bank ? "9970ff" : "232323") .opacity(viewModel.paymentMethod == .bank ? 0.3 : 1) ) .cornerRadius(10) .overlay( RoundedRectangle(cornerRadius: 10) .stroke(lineWidth: 1) .foregroundColor(Color(hex: viewModel.paymentMethod == .bank ? "9970ff" : "777777")) ) .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(Color(hex: viewModel.paymentMethod == .phone ? "9970ff" : "eeeeee")) .frame(width: (screenSize().width - 40) / 2) .padding(.vertical, 16.7) .background( Color(hex: viewModel.paymentMethod == .phone ? "9970ff" : "232323") .opacity(viewModel.paymentMethod == .phone ? 0.3 : 1) ) .cornerRadius(10) .overlay( RoundedRectangle(cornerRadius: 10) .stroke(lineWidth: 1) .foregroundColor(Color(hex: viewModel.paymentMethod == .phone ? "9970ff" : "777777")) ) .onTapGesture { if viewModel.paymentMethod != .phone { viewModel.paymentMethod = .phone } } Spacer() } .frame(width: screenSize().width - 26.7) .padding(.top, 16.7) HStack(spacing: 6.7) { Image(viewModel.isTermsAgree ? "btn_select_checked" : "btn_select_normal") .resizable() .frame(width: 20, height: 20) Text("구매조건 확인 및 결제 진행 동의") .font(.custom(Font.medium.rawValue, size: 14.7)) .foregroundColor(Color(hex: "eeeeee")) } .frame(width: screenSize().width - 53.4, alignment: .leading) .padding(.top, 16.7) .onTapGesture { viewModel.isTermsAgree.toggle() } VStack(spacing: 6.7) { HStack(alignment: .top, spacing: 0) { Text("- ") .font(.custom(Font.medium.rawValue, size: 12)) .foregroundColor(Color(hex: "777777")) Text("충전된 캔의 유효기간은 충전 후 5년 입니다.") .font(.custom(Font.medium.rawValue, size: 12)) .foregroundColor(Color(hex: "777777")) .fixedSize(horizontal: false, vertical: true) } .frame(width: screenSize().width - 53.4, alignment: .leading) .padding(.top, 26.7) HStack(alignment: .top, spacing: 0) { Text("- ") .font(.custom(Font.medium.rawValue, size: 12)) .foregroundColor(Color(hex: "777777")) Text("결제 취소는 결제 후 7일 이내에만 할 수 있습니다.\n단, 캔의 일부를 사용하면 결제 취소를 할 수 없습니다.") .font(.custom(Font.medium.rawValue, size: 12)) .foregroundColor(Color(hex: "777777")) .fixedSize(horizontal: false, vertical: true) } .frame(width: screenSize().width - 53.4, alignment: .leading) HStack(alignment: .top, spacing: 0) { Text("- ") .font(.custom(Font.medium.rawValue, size: 12)) .foregroundColor(Color(hex: "777777")) Text("광고성 이벤트 등 회사가 무료로 지급한 포인트는 환불되지 않습니다.") .font(.custom(Font.medium.rawValue, size: 12)) .foregroundColor(Color(hex: "777777")) .fixedSize(horizontal: false, vertical: true) } .frame(width: screenSize().width - 53.4, alignment: .leading) HStack(alignment: .top, spacing: 0) { Text("- ") .font(.custom(Font.medium.rawValue, size: 12)) .foregroundColor(Color(hex: "777777")) Text("자세한 내용은 소다라이브 이용약관에서 확인할 수 있습니다.") .font(.custom(Font.medium.rawValue, size: 12)) .foregroundColor(Color(hex: "777777")) .fixedSize(horizontal: false, vertical: true) } .frame(width: screenSize().width - 53.4, alignment: .leading) } } } Spacer() HStack(spacing: 0) { VStack(alignment: .leading, spacing: 5) { Text("결제금액") .font(.custom(Font.medium.rawValue, size: 13.3)) .foregroundColor(Color(hex: "eeeeee")) HStack(spacing: 0) { Text("\(canResponse.price) 원") .font(.custom(Font.bold.rawValue, size: 23.3)) .foregroundColor(Color(hex: "eeeeee")) } } Spacer() Text("결제하기") .font(.custom(Font.bold.rawValue, size: 18.3)) .foregroundColor(.white) .padding(.vertical, 16) .frame(minWidth: 200) .background(Color(hex: "9970ff")) .cornerRadius(10) .onTapGesture { if viewModel.paymentMethod == nil { viewModel.errorMessage = "결제수단을 선택해 주세요." viewModel.isShowPopup = true } else if !viewModel.isTermsAgree { 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 } } } } .padding(.leading, 22) .padding(.trailing, 13.3) .padding(.vertical, 13.3) .background(Color(hex: "222222")) .cornerRadius(16.7, corners: [.topLeft, .topRight]) if proxy.safeAreaInsets.bottom > 0 { Rectangle() .foregroundColor(Color(hex: "222222")) .frame(width: proxy.size.width, height: 15.3) } } .popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) { GeometryReader { geo in HStack { Spacer() Text(viewModel.errorMessage) .padding(.vertical, 13.3) .padding(.horizontal, 6.7) .frame(width: geo.size.width - 66.7, alignment: .center) .font(.custom(Font.medium.rawValue, size: 12)) .background(Color(hex: "9970ff")) .foregroundColor(Color.white) .multilineTextAlignment(.leading) .fixedSize(horizontal: false, vertical: true) .cornerRadius(20) .padding(.top, 66.7) Spacer() } } } .edgesIgnoringSafeArea(.bottom) } } if viewModel.isLoading { LoadingView() } } .popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) { GeometryReader { geo in HStack { Spacer() Text(viewModel.errorMessage) .padding(.vertical, 13.3) .padding(.horizontal, 6.7) .frame(width: geo.size.width - 66.7, alignment: .center) .font(.custom(Font.medium.rawValue, size: 12)) .background(Color(hex: "9970ff")) .foregroundColor(Color.white) .multilineTextAlignment(.leading) .fixedSize(horizontal: false, vertical: true) .cornerRadius(20) .padding(.top, 66.7) Spacer() } } } } } struct CanPgPaymentView_Previews: PreviewProvider { static var previews: some View { CanPgPaymentView( canResponse: GetCanResponse(id: 1, title: "300 캔", can: 300, rewardCan: 0, price: 7500), refresh: {}, afterCompletionToGoBack: false ) } }