💳 PassKit & Apple Pay
지갑 앱 통합과 Apple Pay 결제를 구현합니다. 탭 한 번으로 결제 완료!
⭐ 난이도: ⭐⭐⭐
⏱️ 예상 시간: 2-3h
📂 App Services
✨ PassKit이란?
PassKit은 두 가지 주요 기능을 제공합니다: (1) Wallet Pass 생성 (멤버십 카드, 쿠폰, 티켓 등), (2) Apple Pay 결제 처리
💳 Apple Pay 결제 구현
ApplePayManager.swift
import PassKit class ApplePayManager: NSObject, PKPaymentAuthorizationViewControllerDelegate { static let shared = ApplePayManager() // Apple Pay 지원 여부 확인 func canMakePayments() -> Bool { return PKPaymentAuthorizationViewController.canMakePayments() } // 결제 요청 생성 func createPaymentRequest() -> PKPaymentRequest { let request = PKPaymentRequest() request.merchantIdentifier = "merchant.com.yourcompany.app" request.supportedNetworks = [.visa, .masterCard, .amex] request.merchantCapabilities = .capability3DS request.countryCode = "KR" request.currencyCode = "KRW" // 결제 항목 let item = PKPaymentSummaryItem( label: "프리미엄 멤버십", amount: NSDecimalNumber(value: 9900) ) request.paymentSummaryItems = [item] return request } // 결제 시트 표시 func startPayment(from viewController: UIViewController) { let request = createPaymentRequest() guard let paymentVC = PKPaymentAuthorizationViewController(paymentRequest: request) else { return } paymentVC.delegate = self viewController.present(paymentVC, animated: true) } // 결제 완료 처리 func paymentAuthorizationViewController( _ controller: PKPaymentAuthorizationViewController, didAuthorizePayment payment: PKPayment, handler completion: @escaping (PKPaymentAuthorizationResult) -> Void ) { // 백엔드로 payment.token 전송 processPayment(payment.token) { success in if success { completion(PKPaymentAuthorizationResult(status: .success, errors: nil)) } else { completion(PKPaymentAuthorizationResult(status: .failure, errors: nil)) } } } func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) { controller.dismiss(animated: true) } func processPayment(_ token: PKPaymentToken, completion: @escaping (Bool) -> Void) { // 실제 결제 처리 로직 completion(true) } }
🎨 SwiftUI에서 Apple Pay 버튼
ApplePayButton.swift
import SwiftUI import PassKit struct ApplePayButtonView: UIViewRepresentable { var action: () -> Void func makeUIView(context: Context) -> PKPaymentButton { let button = PKPaymentButton( paymentButtonType: .buy, paymentButtonStyle: .black ) button.addTarget( context.coordinator, action: #selector(Coordinator.buttonTapped), for: .touchUpInside ) return button } func updateUIView(_ uiView: PKPaymentButton, context: Context) {} func makeCoordinator() -> Coordinator { Coordinator(action: action) } class Coordinator: NSObject { var action: () -> Void init(action: @escaping () -> Void) { self.action = action } @objc func buttonTapped() { action() } } } // 사용 예시 struct CheckoutView: View { var body: some View { VStack { Text("총 9,900원") .font(.title) ApplePayButtonView { // Apple Pay 결제 시작 ApplePayManager.shared.startPayment(from: UIApplication.shared.windows.first!.rootViewController!) } .frame(height: 50) .padding() } } }
🎫 Wallet Pass 생성
멤버십 카드, 쿠폰, 티켓 등을 Wallet 앱에 추가할 수 있습니다.
CreatePass.swift
import PassKit func addPassToWallet(passURL: URL) { guard let passData = try? Data(contentsOf: passURL) else { return } guard let pass = try? PKPass(data: passData) else { return } let addPassVC = PKAddPassesViewController(pass: pass) // present addPassVC } // Pass 업데이트 푸시 알림 func sendPassUpdateNotification(passTypeIdentifier: String, serialNumber: String) { // 서버에서 Apple Push Notification 전송 // 사용자 Wallet의 Pass가 자동으로 업데이트됨 }
📱 Pass Library 접근
PassLibrary.swift
import PassKit class PassManager { let library = PKPassLibrary() // 사용자의 Wallet에 특정 Pass가 있는지 확인 func hasPass(passTypeIdentifier: String, serialNumber: String) -> Bool { return library.containsPass( withPassTypeIdentifier: passTypeIdentifier, serialNumber: serialNumber ) } // 특정 Pass 가져오기 func getPass(passTypeIdentifier: String, serialNumber: String) -> PKPass? { return library.pass( withPassTypeIdentifier: passTypeIdentifier, serialNumber: serialNumber ) } // 모든 Pass 가져오기 func getAllPasses() -> [PKPass] { return library.passes } // Pass 삭제 func removePass(_ pass: PKPass) { library.removePass(pass) } }
🔄 Pass 업데이트 감지
PassNotifications.swift
import PassKit class PassObserver { let library = PKPassLibrary() func observePassChanges() { NotificationCenter.default.addObserver( self, selector: #selector(passLibraryDidChange), name: .PKPassLibraryDidChange, object: library ) NotificationCenter.default.addObserver( self, selector: #selector(passLibraryRemoteDidChange), name: .PKPassLibraryRemotePaymentPassesDidChange, object: library ) } @objc func passLibraryDidChange() { print("Pass 라이브러리 변경됨") // UI 업데이트 } @objc func passLibraryRemoteDidChange() { print("원격 결제 Pass 변경됨") } }
💰 배송 정보 요청
ShippingMethods.swift
func createPaymentRequest() -> PKPaymentRequest { let request = PKPaymentRequest() // ... 기본 설정 // 배송 정보 요청 request.requiredShippingContactFields = [.name, .postalAddress, .phoneNumber] // 배송 방법 let standard = PKShippingMethod( label: "일반 배송", amount: NSDecimalNumber(value: 3000) ) standard.identifier = "standard" standard.detail = "3-5일 소요" let express = PKShippingMethod( label: "빠른 배송", amount: NSDecimalNumber(value: 5000) ) express.identifier = "express" express.detail = "1-2일 소요" request.shippingMethods = [standard, express] return request } // 배송 정보 변경 시 func paymentAuthorizationViewController( _ controller: PKPaymentAuthorizationViewController, didSelect shippingMethod: PKShippingMethod, handler completion: @escaping (PKPaymentRequestShippingMethodUpdate) -> Void ) { // 배송 방법에 따라 총액 업데이트 let item = PKPaymentSummaryItem(label: "상품", amount: 9900) let shipping = PKPaymentSummaryItem(label: shippingMethod.label, amount: shippingMethod.amount) let total = PKPaymentSummaryItem( label: "총 합계", amount: item.amount.adding(shipping.amount) ) let update = PKPaymentRequestShippingMethodUpdate(paymentSummaryItems: [item, shipping, total]) completion(update) }
🎯 쿠폰 적용
CouponCode.swift
func createPaymentRequest() -> PKPaymentRequest { let request = PKPaymentRequest() // ... 기본 설정 // 쿠폰 코드 입력 지원 (iOS 15+) request.supportsCouponCode = true return request } func paymentAuthorizationViewController( _ controller: PKPaymentAuthorizationViewController, didChangeCouponCode couponCode: String, handler completion: @escaping (PKPaymentRequestCouponCodeUpdate) -> Void ) { // 쿠폰 유효성 검사 if couponCode == "WELCOME10" { let discount = PKPaymentSummaryItem( label: "할인 (-10%)", amount: NSDecimalNumber(value: -990) ) let item = PKPaymentSummaryItem(label: "상품", amount: 9900) let total = PKPaymentSummaryItem(label: "총 합계", amount: 8910) let update = PKPaymentRequestCouponCodeUpdate( paymentSummaryItems: [item, discount, total] ) completion(update) } else { let error = PKPaymentRequest.Error( .couponCodeInvalid, localizedDescription: "유효하지 않은 쿠폰 코드입니다" ) let update = PKPaymentRequestCouponCodeUpdate(errors: [error], paymentSummaryItems: [], shippingMethods: []) completion(update) } }
💡 HIG 체크리스트
✅ 공식 Apple Pay 버튼 사용 (직접 디자인 금지)
✅ 결제 전 총액 명확히 표시
✅ 배송비, 세금 등 추가 비용 명시
✅ 에러 메시지는 사용자 친화적으로
✅ 결제 완료 후 명확한 피드백