cordova-plugin-purchase 을 연동한 capacitor 결제 연동

cordova-plugin-purchase 을 연동한 capacitor 결제 연동 updated_at: 2023-10-18 11:45

결제 시스템 달기

이전에는 @ionic-native/in-app-purchase-2 와 cordova-plugin-purchase 의 최신 버젼과 호환이 되지 않아 cordova-plugin-purchase v 11.x.x 를 사용하였다.
하지만 Play 결제 라이브러리 v4 가 지원중단됨에 따라 최근 버젼( PBL 버전 5 이상)으로 변경해야 함으로 cordova-plugin-purchase 의 최신 버젼을 통한 직접 연결을 시도하여보았습니다.
아래는 간단한 소스와 예제입니다.

참조 : https://github.com/j3k0/cordova-plugin-purchase/blob/master/src/example/example.ts
참조 : https://medium.com/@arpitpareek058/setup-cordova-plugin-purchase-v13-in-ionic-61b751aabbfa

install

"cordova-plugin-purchase": "^13.8.6",

npm i cordova-plugin-purchase

순서

import "cordova-plugin-purchase"; // 1. plugin import
private store!: CdvPurchase.Store; // 2. Store 정의
store.verbosity = LogLevel.DEBUG; // 3. console logging에 대한 verbosity level 설정
store.validator = yourValidatorMethodOrApiRoute; // 4. validator url 설정 (없을 경우 주석처리)
sotre.register() // (중요) 5. 스토어에 상품을 등록하는 단계입니다. 이때는 google play console 에서 등록한 정보와 일치 시켜야 합니다.
store.error(), store.ready().. // 6. 다양한 콜백 처리
store.initialize() // 7. 플랫폼 정보를 넘긴다.
store.update(); // 상품가격과 구매상태를 가져옮니다.

예제 1

import { Injectable} from '@angular/core';
import { Capacitor } from '@capacitor/core'; //
import 'cordova-plugin-purchase';
import * as _ from 'underscore';
const PRODUCT_PRO_KEY='no_ad';

@Injectable()
export class InappPurchaseService {
  public isPro = false;
  private store!: CdvPurchase.Store;
  constructor(
  ) {

    if (Capacitor.isNativePlatform()) {

      this.store  = CdvPurchase.store;

      this.store.error( (error) => {
        console.error('ERROR ' + error.code + ': ' + error.message);
      });
      this.store.ready( () => {
      });

      this.registerProducts();
    }
  }

  private registerProducts() {
    const { ProductType, Platform } = CdvPurchase;
    this.store.applicationUsername = () => "USER_ID";
    this.store.verbosity = 0;

    this.store.register([{
      id: PRODUCT_PRO_KEY,
      type: ProductType.NON_CONSUMABLE,
      platform: Platform.GOOGLE_PLAY
    },{
      id: PRODUCT_PRO_KEY,
      type: ProductType.NON_CONSUMABLE,
      platform: Platform.APPLE_APPSTORE
    }]);

    
    if (Capacitor.getPlatform() === 'ios') { // init apple
      this.store.initialize([{ platform: Platform.APPLE_APPSTORE }]);
    } else if (Capacitor.getPlatform() === 'android') { // init google
      this.store.initialize([{ platform: Platform.GOOGLE_PLAY }]);
    } else {// not mobile
      this.store.initialize([{platform: Platform.TEST}])
    }

    this.store.when()
    .productUpdated(product => { // store.register 시 이 부분이 trigger 된다.
    })
    .approved(transaction => {
      const monitor = this.store.monitor(transaction, state => {
        if(state === 'approved') {

        }
        if (state === 'finished') {
          monitor.stop();
        }
      }, 'monitor')
      transaction.verify().then(() => {
      })
    })
    .pending(transaction => {
    })
    .verified(receipt => {
      receipt.finish().then(() => {
        this.removeBanner();
      })
    })
    .unverified(receipt => {
      if(receipt.payload.ok === false) {

      }
    })
    .receiptUpdated((receipt) => {
    })
  }


  public removeAd() {
    if (Capacitor.isNativePlatform()) {

      const product = _.find(this.store.products, (product: any) =>{
        return product.id === PRODUCT_PRO_KEY;
      });

      if (product) {
        this.store.order(product.offers[0]); 
      } else {
        console.error('결제 상품이 존재 하지 않습니다.');
      };
    }
  }

  private removeBanner() {
    this.isPro = true;
  }
}


예제 2

import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
import { Capacitor } from '@capacitor/core';
import 'cordova-plugin-purchase';
import * as _ from 'underscore';
..........
export class SamplePage implements OnInit {
  private store!: CdvPurchase.Store;

  constructor(
    private ref: ChangeDetectorRef,
  ) {
    
    this.store  = CdvPurchase.store;

    // 각각의 이벤트에대한 callback 처리
    this.store.error( (error) => {
      console.log('ERROR ' + error.code + ': ' + error.message);
    });
    this.store.ready( () => {
      console.log('CdvPurchase is ready');
    });
  }

  ngOnInit() {
    this.registerAbo();
  }

  private registerAbo() {
    const { ProductType, Platform } = CdvPurchase;

    // for the backend, we send the userId with this method
    this.store.applicationUsername = () => "USER_ID";
    // this.store.validator = "MY_CUSTOM_APPLE_VALIDATOR_URL"; // validator 할 url을 입력(없을 경우 주석처리)
    this.store.verbosity = 4;

    // 상품을 등록한다.
    this.store.register([{
        id: 'ProductId1',
        type: ProductType.CONSUMABLE,
        platform: Platform.GOOGLE_PLAY,
      },
      ..........
      {
        id: 'ProductId1',
        type: ProductType.CONSUMABLE,
        platform: Platform.APPLE_APPSTORE,
      }
    ]);

    if(this.store.isReady) {
      this.updateStore();
    } else {
      if (Capacitor.getPlatform() === 'ios') {  // init apple
        this.store.initialize([{ platform: Platform.APPLE_APPSTORE }]);
      } else if (Capacitor.getPlatform() === 'android') { // init google
        this.store.initialize([{ platform: Platform.GOOGLE_PLAY }]);
      } else {// not mobile
        this.store.initialize([{platform: Platform.TEST}])
      }
    }

    this.store.when()
    .productUpdated(product => { // store.register 시 이 부분이 trigger 된다.
      this.ref.detectChanges();
    })
    .approved(transaction => {
      const monitor = this.store.monitor(transaction, state => {
        if(state === 'approved') {
        }
        if (state === 'finished') {
          monitor.stop();
        }
      })
      transaction.verify().then(() => {
      })
    })
    .pending(transaction => {
    })
    .verified(receipt => {
      receipt.finish().then(() => {
        // 이곳에서 receipt를 이용하여 결과처리를 한다
        this.afterApproved(receipt);
        this.ref.detectChanges();
      })
    })
    .unverified(receipt => {
      if(receipt.payload.ok === false) {

      }
    })
    .receiptUpdated((receipt) => {
      this.ref.detectChanges();
    })

  } // registerAbo() {

  async updateStore() {
    this.store.update().then(() => {
      console.log('store update done')
    });
  }

  public payment(id: string) { // html에서 call하는 부분으로 id는 product 아이디를 string으로 받아서 현재 poducts중에 찾아 offers를 가져오는 방식을 취했다.
    const product = _.find(this.store.products, (product) =>{
      return product.id === id;
    });

    if (product) {
      this.store.order(product.offers[0]); 
    } else {
      console.log('결제 상품이 존재 하지 않습니다.');
    };

  }

  private async afterApproved(receipt: any) {
    const purchase = receipt.sourceReceipt.transactions[0].nativePurchase;
    // {purchase.productId, purchase.orderId, purchase.appStoreReceipt(ios), purchase.purchaseToken(android)}  
  }
}

LogLevel

store.verbosity LogLeve 참조

LogLevel.QUIET (0) to disable all logging (default)
LogLevel.ERROR (1) to display only error messages
LogLevel.WARNING (2) to show warnings and errors
LogLevel.INFO (3) to show informational messages
LogLevel.DEBUG (4) to enable internal debugging messages
평점을 남겨주세요
평점 : 5.0
총 투표수 : 1

질문 및 답글