TIL: RN | 인앱 결제 react-native-iap (3) 구매 - 221129

Lumpen·2022년 11월 29일
1

RN인앱결제

목록 보기
2/16

구매

구매 흐름 재설계

재설계 이유
1. 지불을 요청할 때 하나 이상의 응답이 있을 수 있기 때문
2. 구매는 세션간 비동기 작업이므로 요청을 완료하는 데 시간이 필요하고, 앱을 종료하거나 충돌이 일어난 경우에도 계속 동작할 수 있다
3. 구매가 보류중이면 수행된 작업을 추적하기 어려울 수 있다
4. 청구 흐름은 event 패턴이 아닌 callback 패턴

getProducts()를 호출 후 유효한 응답이 있다면
requestPurchase() 를 호출할 수 있다
구독 가능 상품은 소모품과 동일하게 구매 가능, 사용자는 iOS 시스템 설정으로 구독 취소 가능

구매 요청을 (purchase) 보내기 전에
react-native-iap의 purchaseUpdatedListener 를 설정해야 한다
애플리케이션이 실행될 때 즉시 업데이트 수신을 시작하는 것이 좋다
앱이 종료된 동안 완료되었거나 네트워크 오류 또는 버그로 인해 확인에 실패한 경우에도 구매에 성공할 수 있다

import {
  initConnection,
  purchaseErrorListener,
  purchaseUpdatedListener,
  type ProductPurchase,
  type PurchaseError,
  flushFailedPurchasesCachedAsPendingAndroid,
} from 'react-native-iap';

const App = () => {
  let purchaseUpdateSubscription = null;
  let purchaseErrorSubscription = null;

useEffect(() => {
	initConnection().then(() => {
      // we make sure that "ghost" pending payment are removed
      // (ghost = failed pending payment that are still marked as pending in Google's native Vending module cache)
      flushFailedPurchasesCachedAsPendingAndroid()
        .catch(() => {
          // exception can happen here if:
          // - there are pending purchases that are still pending (we can't consume a pending purchase)
          // in any case, you might not want to do anything special with the error
        })
        .then(() => {
          purchaseUpdateSubscription = purchaseUpdatedListener(
            (purchase: SubscriptionPurchase | ProductPurchase) => {
              console.log('purchaseUpdatedListener', purchase);
              const receipt = purchase.transactionReceipt;
              if (receipt) {
                yourAPI
                  .deliverOrDownloadFancyInAppPurchase(
                    purchase.transactionReceipt,
                  )
                  .then(async (deliveryResult) => {
                    if (isSuccess(deliveryResult)) {
                      // Tell the store that you have delivered what has been paid for.
                      // Failure to do this will result in the purchase being refunded on Android and
                      // the purchase event will reappear on every relaunch of the app until you succeed
                      // in doing the below. It will also be impossible for the user to purchase consumables
                      // again until you do this.

                      // If consumable (can be purchased again)
                      await finishTransaction({purchase, isConsumable: true});
                      // If not consumable
                      await finishTransaction({purchase, isConsumable: false});
                    } else {
                      // Retry / conclude the purchase is fraudulent, etc...
                    }
                  });
              }
            },
          );

          purchaseErrorSubscription = purchaseErrorListener(
            (error: PurchaseError) => {
              console.warn('purchaseErrorListener', error);
            },
          );
        });
    });

}, [])
  return () => {
  	if (purchaseUpdateSubscription) {
      purchaseUpdateSubscription.remove();
    }

    if (purchaseErrorSubscription) {
      purchaseErrorSubscription.remove();
    }
  }
  }
}

아래와 같이 메소드를 정의하고 사용자가 버튼을 누를 때 호출

const App = () => {
  let requestPurchase;
  let requestSubscription;
  requestPurchase = async (sku: string) => {
    try {
      await requestPurchase({
        sku,
        andDangerouslyFinishTransactionAutomaticallyIOS: false,
      });
    } catch (err) {
      console.warn(err.code, err.message);
    }
  };

  requestSubscription = async (sku: string, offerToken: string?) => {
    try {
      await requestSubscription(
        {sku},
        ...(offerToken && {subscriptionOffers: [{sku, offerToken}]}),
      );
    } catch (err) {
      console.warn(err.code, err.message);
    }
  };

  /**
   * For one-time products
   */
  render() {
    return (
      <Pressable onPress={() => requestPurchase(product.productId)}>
        {/* ... */}
      </Pressable>
    );
  }

  /**
   * For subscriptions products
   */
  render() {
    if (Platform.OS == 'android') {
      return product.subscriptionOfferDetails.map((offer) => (
        <Pressable
          onPress={() =>
            requestSubscription(product.productId, offer.offerToken)
          }
        >
          {/* ... */}
        </Pressable>
      ));
    } else {
      return (
        <Pressable
          onPress={() => requestSubscription(product.productId, null)}
        >
          {/* ... */}
        </Pressable>
      );
    }
  }
}
/// 작성 예정

New Purchase Flow 새로운 구매 플로우

대부분의 경우 "스토어 키트 흐름"을 처리해야 합니다.[2][apple-store-kit-flow]는 예를 들어 신용카드 정보가 만료되었을 때 사용자가 계정 문제를 해결한 후 성공적으로 결제할 때 발생합니다.
위와 같은 이유로 우리는 buy product를 제거하고 requestPurchase를 사용하기로 결정했습니다. purchaseUpdateListener는 성공적인 구매를 수신하고 purchaseErrorListener는 구매 시도 중 발생한 모든 실패 결과를 수신합니다.

구매 완료

구매는 구매를 완료할 때까지 모든 앱을 다시 시작할 때마다 업데이트된 청취자에게 계속 전송됩니다.
모든 구매는 finishTransaction()을 호출하여 소비해야 합니다.

  • 소모품의 경우: 항목을 사용하면 getAvailablePurchases()에서 제거되므로 finishTransaction()을 호출하기 전에 구입을 데이터베이스에 기록하는 것은 사용자에게 달려 있습니다.
  • 비소모 구매의 경우 Android에서 확인이 필요합니다. 그렇지 않으면 며칠 후에 자동으로 환불됩니다. 이 방법은 사용자에게 구매를 전달하면 구매를 승인합니다.
  • iOS 비소모 구매는 자동으로 완료되지만 앞으로는 변경될 것이므로 비소모품에서도 이 방법을 호출하여 준비하는 것이 좋습니다.
  • 두 플랫폼 모두에서 작동합니다. iOS + 소비를 위한 종료 트랜잭션과 동일Android용 구매를 구매하고 승인합니다.
profile
떠돌이 생활을 하는. 실업자는 아니지만, 부랑 생활을 하는

0개의 댓글