Notice
Recent Posts
Recent Comments
Link
관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] 5. 애플페이 (Apply Pay) - PassKit을 사용하여 구현하기 (Green, Yellow, Orange 플로우) 본문

iOS 응용 (swift)

[iOS - swift] 5. 애플페이 (Apply Pay) - PassKit을 사용하여 구현하기 (Green, Yellow, Orange 플로우)

jake-kim 2026. 6. 29. 00:34

Green, Yellow, Orange 플로우

흐름 (Flow) 진입 경로 리스크 수준 인증 절차 최종 상태
Green-Flow 발급사 인앱 (In-App) Low 앱 로그인으로 대체 즉시 활성화
Yellow-Flow Apple 월렛 직접 입력 Medium 발급사 앱 인증 / SMS / ARS 인증 후 활성화
Orange-Flow 의심 기기 / 분실 카드 High 없음 (거절 또는 강제 차단) 등록 실패

 

애플페이 (Issuer App 입장)의 기능

발급사(Issuer) 앱 관점에서 애플페이 대응을 할 때 핵심이 되는 개념은 크게 In-App ProvisioningVerification 두 가지로 나뉨.

1) In-App Provisioning (인앱 프로비저닝)

  • 개념: 사용자가 우리 앱 내부에서 "애플 월렛에 추가" 버튼을 눌렀을 때, 안전하게 카드 정보를 암호화하여 Apple Wallet에 카드를 밀어 넣어주는(발급하는) 기능
  • 특징:
    • 앱 내부에서 PKAddPaymentPassViewController라는 Apple 표준 시트를 띄워 처리하므로 화면 이탈 없이 매끄러운 UX를 제공. (Green-Flow)
    • 앱이 카드 번호(PAN) 원장을 가공되지 않은 상태로 다루지 않고, 기기가 생성한 일회성 토큰(Nonce)을 기반으로 발급사 백엔드와 TSP(Visa, Mastercard 등)를 거쳐 암호화된 페이로드 형태로 Apple 서버에 패스스루(Pass-through)하는 구조.

2) Verification (신원 확인 및 인증)

  • 개념: 반대로 사용자가 애플 월렛 앱 안에서 직접 카드를 추가하려고 할 때, "이 사람이 진짜 실소유주가 맞는지 확인해라"라며 월렛이 발급사 앱을 깨워 본인 인증을 요구하는 기능. (Yellow-Flow)
  • 특징:
    • Step-up Authentication이라고도 부르며, 유니버설 링크(Universal Link)를 통해 월렛 앱에서 우리 앱으로 사용자를 전환시킴.
    • 우리 앱이 켜지면 Face ID나 간편비밀번호 등으로 본인 확인을 수행한 뒤, 발급사 백엔드를 통해 TSP에 "인증 완료" 신호를 쏘고 다시 shoebox:// 스킴을 통해 사용자를 월렛으로 안전하게 돌려보내 줌.

PassKit을 사용하여 구현하기

Step 0. 권한 설정

  • 1) PKPassLibrary: 애플월렛관련 데이터를 가져오기 위한 라이브러리이고 애플 개발자 페이지의 capability에서 Wallet 활성화해야함
  • 2) 애플 in-app provisioining 권한
    • PKAddPaymentPassViewController를 사용하려면 애플 개발자 페이지 -> Capability -> In-App Provisioning 권한이 필요
    • Xcode에서도 entitlements 파일에 아래 설정도 필요
// {your_app}.entitlements

<dict>
    <key>com.apple.developer.passkit.in-app-provisioning</key>
    <true/>
</dict>

Step 1. 권한 확인 및 Configuration 설정

가장 먼저 현재 디바이스가 카드 등록을 지원하는지 체크하고, 카드 소유자 이름 및 뒤 4자리 정보를 담은 설정을 빌드

*주의) PKAddPaymentPassViewController를 사용하려면 애플 개발자 페이지 -> Capability -> In-App Provisioning 권한이 필요하고, 

import PassKit

func startInAppProvisioning() {
    // 1. 디바이스가 인앱 프로비저닝을 지원하는지 체크
    guard PKAddPaymentPassViewController.canAddPaymentPass() else {
        print("이 디바이스는 카드 추가를 지원하지 않거나 제한된 계정임")
        return
    }
    
    // 2. 발급할 카드의 메타데이터 구성 (암호화 스킴은 보통 ECC_V2 또는 RSA_V2 사용)
    guard let configuration = PKAddPaymentPassRequestConfiguration(encryptionScheme: .ECC_V2) else {
        return
    }
    
    configuration.cardholderName = "홍길동"
    configuration.primaryAccountSuffix = "1234" // 카드 뒷 4자리
    configuration.localizedDescription = "내 발급사 대표 카드"
    
    // 3. VC 생성 및 델리게이트 위임
    guard let addPaymentPassVC = PKAddPaymentPassViewController(requestConfiguration: configuration, delegate: self) else {
        print("컨트롤러 생성 실패 - Entitlement 권한 확인 필요")
        return
    }
    
    // 4. Apple 표준 카드 등록 시트 모달 표시
    self.present(addPaymentPassVC, animated: true, completion: nil)
}

Step 2. PKAddPaymentPassViewControllerDelegate 구현

시트가 뜨면 Apple 서버-발급사 서버 간 암호화 핸드셰이크가 일어남. 이 구간이 구현의 핵심

extension CardManagementViewController: PKAddPaymentPassViewControllerDelegate {
    
    // Apple 이 인증서 체인과 Nonce 를 넘겨주며 암호화 데이터를 요청하는 메서드
    func addPaymentPassViewController(_ controller: PKAddPaymentPassViewController,
                                      generateRequestWithCertificateChain certificates: [Data],
                                      nonce: Data,
                                      nonceSignature: Data,
                                      completionHandler: @escaping (PKAddPaymentPassRequest) -> Void) {
        
        // 1. 수신한 certificates, nonce, nonceSignature 를 우리 백엔드 API 로 전송
        let requestModel = IssuerAmhohwaRequest(
            certificates: certificates,
            nonce: nonce,
            nonceSignature: nonceSignature
        )
        
        // 2. 백엔드 통신 (백엔드는 TSP 와 연동하여 암호화된 데이터를 내려줌)
        NetworkManager.shared.requestEncryptedPayload(requestModel) { result in
            switch result {
            case .success(let response):
                // 3. 백엔드가 준 데이터를 암호화 요청 객체에 매핑
                let addPassRequest = PKAddPaymentPassRequest()
                addPassRequest.encryptedPassData = response.encryptedPassData
                addPassRequest.activationData = response.activationData
                addPassRequest.ephemeralPublicKey = response.ephemeralPublicKey
                
                // 4. 완성된 request 를 completionHandler 에 담아 Apple SDK 에 반환
                completionHandler(addPassRequest)
                
            case .failure:
                // 에러 발생 시 빈 객체를 던져 실패 처리
                completionHandler(PKAddPaymentPassRequest())
            }
        }
    }
    
    // 프로세스가 완전히 끝났을 때 (성공 혹은 취소/실패) 호출되는 메서드
    func addPaymentPassViewController(_ controller: PKAddPaymentPassViewController,
                                      didFinishWith pass: PKPaymentPass?,
                                      error: Error?) {
        // 우선 띄웠던 모달을 닫아줌
        controller.dismiss(animated: true) {
            if let error = error {
                print("카드 등록 실패 혹은 유저 취소 발생: \(error.localizedDescription)")
                return
            }
            
            if let successfullyAddedPass = pass {
                print("카드 등록 최종 성공: \(successfullyAddedPass.primaryAccountNumberSuffix)")
                // UI 상의 '애플 월렛에 추가' 버튼을 숨기거나 완료 상태로 전환하는 비즈니스 로직 수행
            }
        }
    }
}

 

Comments