샘성의 iOS 개발 일지

[인앱결제] StoreKit2로 마이그레이션 하기 (+ 유실 방지 로직) 본문

iOS/Swift

[인앱결제] StoreKit2로 마이그레이션 하기 (+ 유실 방지 로직)

SamusesApple 2024. 7. 23. 17:13
728x90

StoreKit1 -> StoreKit2로 마이그레이션 하기 + 결제 정보를 서버 DB에서 안전하게 유지하도록 유실 방지 로직 추가하기

 

StoreKit 에서 StoreKit2로 변경

StoreKit은 Apple에서 제공하는 인앱 결제 라이브러리이다. 기존 StoreKit에서는 영수증 검증을 위해 transactionId endpoint를 사용했지만 deprecated되면서 영수증 검증을 자동으로 해주는 StoreKit2로 변경하게 되었다....

 

StoreKit2로 마이그레이션

StoreKit에서 StoreKit2로 변경하면서 기본적인 결제를 처리하는 코드

public func purchaseSubscription(_ productId: String) async throws -> Transaction? {
    // 제품 정보 가져오기
    guard let product = try await fetchProductById(productId) else {
        throw PurchaseError.productNotFound
    }
    // 구매 프로세스 시작
    let result = try await product.purchase()

    switch result {
    case .success(let verification):
        // 결제 검증
        let transaction = try await checkTransaction(verification)
        // 서버에 결제 정보 전송
        await sendTransactionToServer(transaction)

        return transaction

    case .userCancelled, .pending:
        // 유저가 결제를 취소하거나 결제가 진행 중인 경우
        return nil

    @unknown default:
        print("Unexpected Error")
        return nil
    }
}

코드 설명

  1. 제품 정보 가져오기:여기서는 productId를 기반으로 제품 정보를 가져오기! 해당되는 제품 정보를 가져오지 못하면 PurchaseError.productNotFound 에러를 던진다.
  2. guard let product = try await fetchProductById(productId) else { throw PurchaseError.productNotFound }
  3. 구매 프로세스 시작:product.purchase() 메서드를 사용하여 구매 프로세스를 시작하기. 이 메서드는 async 메서드로, 비동기로 앱스토어 구매를 진행한다.
  4. let result = try await product.purchase()
  5. 결제 검증:purchase() 메서드가 성공적으로 끝나면, 결제 검증하기. checkTransaction 메서드는 VerificationResult를 매개변수로 받아서 검증된 Transaction 객체를 반환한다.
  6. let transaction = try await checkTransaction(verification)
  7. 서버에 결제 정보 전송: 마지막으로 결제된 정보를 서버로 전송한다. 이를 통해 서버에서도 결제 정보를 업데이트할 수 있다!
  8. await sendTransactionToServer(transaction)

 

유실 방지 로직 추가

결제 정보가 서버에 제대로 반영되지 않으면 앱스토어 결제 자체는 성공했지만, 서버에는 기록되지 않는 경우가 발생할 수 있다. 이를 방지하기 위해 유실 방지 로직을 추가하자!

유실 방지 로직 구현

서버에 결제 정보를 안전하게 전송하는 로직

public func restoreSubscriptionPurchaseDataToServer() async {
    guard await shouldCheckIfPurchaseIsMissing() else {
        print("Need not restore purchase data")
        return
    }
    guard let lastTransaction = try await fetchLastTransaction(), !lastTransaction.isExpired else {
        print("No valid transaction found")
        return
    }
    await sendTransactionToServer(lastTransaction)
}

코드 설명

  1. 결제 유실 여부 확인:결제가 유실되었는지 확인하기. 유실되지 않았다면 로직 종료.
  2. guard await shouldCheckIfPurchaseIsMissing() else { print("Need not restore purchase data") return }
  3. 마지막 트랜잭션 가져오기:마지막 트랜잭션을 가져와 유효한 결제 정보인지 확인하기. 만약 유효하지 않다면 로직을 종료.
  4. guard let lastTransaction = try await fetchLastTransaction(), !lastTransaction.isExpired else { print("No valid transaction found") return }
  5. 서버로 결제 정보 전송: 마지막 트랜잭션 정보를 서버로 전송하여 결제 정보 업데이트.
  6. await sendTransactionToServer(lastTransaction)

 

서버상 트랜잭션 정보 DB 유효성 검사

private func shouldCheckIfPurchaseIsMissing() async -> Bool {
    // 유저가 로그인했는지 확인
    guard let userId = fetchUserId() else { return false }

    // 유저의 결제 정보 확인 필요 여부
    let shouldCheck = UserDefaults.standard.bool(forKey: "SHOULD_CHECK_PURCHASE")
    return shouldCheck
}

유저가 로그인했는지 확인하고, 결제 정보 확인이 필요한지 UserDefaults에서 값을 가져온다.

만약, true값이 온다면 다시 서버에 유저의 마지막 결제 정보를 보내는 POST 요청을 보내면 된다.

 

 

728x90