- 나이 : 96년생
- 특이사항 : 조금씩 늙어가고 있음
- 좋아하는 음식 : 햄부기, 치킨, 고기
🥷기술
🐱 우리집 고양이 소개


- 이름 : 콜라
- 나이 : 14년생
- 종 : Nado moreum
📱 개인 프로젝트
🏢 참여한 프로젝트
🌱 내 잔디밭
cocos2d-x | IAP [1] 아이템 구매하기 본문
| Play Billing Library - Preview
구글에서 소개한 새로운 결제 라이브러리 Play Billing Library 를 사용해보기로 합니다.
그런데 현재 Play Billing Library 가 아직 preview 버전이라서 나중에 버전이 올라가면서 구현방식이 크게 바뀔 가능성이 있고 그렇게 되면 코드를 수정해야하는데 당연히 그 몫은 개발자의 몫입니다.
현재 시점, 이미 preview 버전이 종료되고 정식으로 사용 가능한 라이브러리로 버전업이 되었습니다. 이 글을 작성했을 때 당시 preview 기준으로 코드를 작성했기 때문에 약간의 코드 수정이 필요할 수 있습니다. 아래 링크를 통해 수정사항을 확인하세요.
※ play billing library page URL
| 안드로이드 스튜디오에서의 작업
먼저 프로젝트에서 빌링 라이브러리를 사용하기 위해 gradle 의존성에 라이브러리를 추가해야 합니다.
앱 수준에서의 build.gradle 의 dependencies 부분에 다음 내용을 추가합니다.
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile project(':libcocos2dx')
compile 'com.android.billingclient:billing:dp-1' // 추가
}
이제 AppActivity.java 에 결제에 필요한 코드를 구현합니다.
package org.cocos2dx.cpp;
import android.os.Bundle;
import android.widget.Toast;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.ConsumeResponseListener;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import org.cocos2dx.lib.Cocos2dxActivity;
import java.util.List;
public class AppActivity extends Cocos2dxActivity {
public static AppActivity appActivity;
private BillingClient mBillingClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
appActivity = this;
initInAppBillingService();
}
@Override
public void onDestroy() {
mBillingClient.endConnection();
super.onDestroy();
}
public void initInAppBillingService() {
mBillingClient = new BillingClient.Builder(getContext()).setListener(new PurchasesUpdatedListener() {
@Override
public void onPurchasesUpdated(int responseCode, List purchases) {
if (responseCode == BillingClient.BillingResponse.OK && purchases != null) {
//결제가 요청이 완료되면 호출되는 영역
for (Purchase purchase : purchases) {
//주문 정보 를 받아와서 처리하는 부분
String sku = purchase.getSku();
Toast.makeText(AppActivity.this, "주문한 상품 sku : "+sku, Toast.LENGTH_SHORT).show();
/*
다시 주문할 수 있도록 주문한 아이템의 소진을 요청한다.
만약, 한번 구매한 아이템을 다시 구매하지 못하게하려면 아래 consumeItem() 을 사용하지 않고
여기서 바로 nativeCallBackSuccessBuyItem 함수를 호출하면 된다.
*/
consumeItem(sku);
}
} else if (responseCode == BillingClient.BillingResponse.USER_CANCELED) {
// 유저가 도중에 결제를 취소할 경우 호출되는 영역
Toast.makeText(AppActivity.this, "결제를 취소하였습니다.", Toast.LENGTH_SHORT).show();
} else {
// 예기치 못한 결제 에러가 발생 되면 호출되는 영역
Toast.makeText(AppActivity.this, "결제 실패하였습니다.", Toast.LENGTH_SHORT).show();
}
}
}).build();
//이 과정을 통해 구글 플레이 앱과 내 앱이 통신이 가능해진다.
mBillingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(int billingResponseCode) {
if (billingResponseCode == BillingClient.BillingResponse.OK) {
// 성공적으로 준비가 끝나면 호출되는 영역
Toast.makeText(AppActivity.this, "결제 클라이언트 연결 성공", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onBillingServiceDisconnected() {
// 구글 플레이 앱과 연결되지 못하면 호출되는 영역
Toast.makeText(AppActivity.this, "결제 클라이언트 연결 실패", Toast.LENGTH_SHORT).show();
}
});
}
public void buyItem(String skuId) {
BillingFlowParams.Builder builder = new BillingFlowParams.Builder()
.setSku(skuId).setType(BillingClient.SkuType.INAPP); //결제 유형을 설정 INAPP 은 소모 아이템, SUBS 는 구독 아이템
mBillingClient.launchBillingFlow(this, builder.build());
}
public void consumeItem(String skuId) {
ConsumeResponseListener listener = new ConsumeResponseListener() {
@Override
public void onConsumeResponse(String outToken, int responseCode) {
if (responseCode == BillingClient.BillingResponse.OK) {
//성공적으로 아이템을 소모요청이 되었으면 호출되는 영역
Toast.makeText(AppActivity.this, "outToken :"+outToken+"responseCode :"+responseCode, Toast.LENGTH_SHORT).show();
}
}
};
Purchase.PurchasesResult purchasesResult = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP);
List purchases = purchasesResult.getPurchasesList();
for (Purchase purchase : purchases) {
if (purchase.getSku().compareTo(skuId) == 0) {
//여기서 C++ 콜백 함수를 호출시킨다.
nativeCallBackSuccessBuyItem(purchase.getSku());
mBillingClient.consumeAsync(purchase.getPurchaseToken(), listener);
}
}
}
public static void Native_BuyItem(final String sku) {
appActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
appActivity.buyItem(sku);
}
});
}
private static native void nativeCallBackSuccessBuyItem(String skuId);
}
위 코드를 이해하려면 JNI 에 대한 이해가 기본적으로 깔려 있어야 하는데 여기서는 안드로이드(자바) 문법이나 JNI 대한 이야기는 하지 않기 때문에 위 코드에 대한 이해가 어렵다면 다른 글을 찾아보세요!..
| C++ 에서의 구현
코코스2d-x 에서 사용하려면 C++ 상으로 코드 호출이 가능해야 합니다. 따라서 JNI를 사용해서 HelloWorldScene.h 에 다음과 같이 구현했습니다.
#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__
#include "cocos2d.h"
#include "ui/CocosGUI.h" //추가
using namespace cocos2d::ui; //Button 클래스를 사용하기 위해 헤더와 네임스페이스를 선언했다.
class HelloWorld : public cocos2d::Layer
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
// a selector callback
void menuCloseCallback(cocos2d::Ref* pSender);
// implement the "static create()" method manually
CREATE_FUNC(HelloWorld);
// <-----아이템 구매 관련
static void callBackSuccessBuyItem(std::string skuId);
void buyItemCallBack(Ref* target, Widget::TouchEventType eventType);
void requestBuyItem(const char* sku);
//---->
};
#endif // __HELLOWORLD_SCENE_H__
위와 같이 아이템 구매 관련 함수를 선언합니다.
callBackSuccssBuyItem : 성공적으로 결제가 진행된 후 호출되는 콜백 함수로 사용합니다. (매개변수로 결제 성공한 skuId 를 전달 받습니다)
buyItemCallBack : 아이템 구매 버튼을 터치했을때 호출되는 콜백 함수로 사용합니다.
requestBuyItem : 인앱상품 결제를 진행하는 함수
HelloWorldScene.cpp 에서 다음과 같은 내용을 구현한다. init() 함수에 구매버튼으로 사용할 버튼을 하나 만들어 줍니다.
// on "init" you need to initialize your instance
bool HelloWorld::init()
{
//////////////////////////////
// 1. super init first
if ( !Layer::init() )
{
return false;
}
auto visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
/////////////////////////////
// 2. add a menu item with "X" image, which is clicked to quit the program
// you may modify it.
// add a "close" icon to exit the progress. it's an autorelease object
auto closeItem = MenuItemImage::create(
"CloseNormal.png",
"CloseSelected.png",
CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));
closeItem->setPosition(Vec2(origin.x + visibleSize.width - closeItem->getContentSize().width/2 ,
origin.y + closeItem->getContentSize().height/2));
// create menu, it's an autorelease object
auto menu = Menu::create(closeItem, NULL);
menu->setPosition(Vec2::ZERO);
this->addChild(menu, 1);
/////////////////////////////
// 3. add your codes below...
// add a label shows "Hello World"
// create and initialize a label
auto label = Label::createWithTTF("Hello World", "fonts/Marker Felt.ttf", 24);
// position the label on the center of the screen
label->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - label->getContentSize().height));
// add the label as a child to this layer
this->addChild(label, 1);
// add "HelloWorld" splash screen"
auto sprite = Sprite::create("HelloWorld.png");
// position the sprite on the center of the screen
sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
// add the sprite as a child to this layer
this->addChild(sprite, 0);
//구매 버튼 추가
Button* buyButton = Button::create("ButtonBuy.png");
buyButton->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
buyButton->setPositionX(origin.x + visibleSize.width / 2);
buyButton->setPositionY(origin.y + visibleSize.height / 2);
buyButton->addTouchEventListener(CC_CALLBACK_2(HelloWorld::buyItemCallBack, this));
addChild(buyButton);
return true;
}
그리고 헤더에서 선언한 함수들과 jni 를 통해 호출받을 네이티브 함수를 구현합니다.
void HelloWorld::callBackSuccessBuyItem(std::string skuId)
{
//결제 성공하면 들어오는 콜백
Director::getInstance()->getRunningScene()->runAction(
RepeatForever::create(
RotateBy::create(15.0f, 360.0f)));
}
void HelloWorld::buyItemCallBack(Ref* target, Widget::TouchEventType eventType)
{
if (eventType == Widget::TouchEventType::BEGAN)
{
requestBuyItem("sample_item_1");
}
}
void HelloWorld::requestBuyItem(const char* sku)
{
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
JniMethodInfo mInfo;
if (JniHelper::getStaticMethodInfo(mInfo
, "org/cocos2dx/cpp/AppActivity"
, "Native_BuyItem"
, "(Ljava/lang/String;)V"))
{
jstring _sku = mInfo.env->NewStringUTF(sku);
mInfo.env->CallStaticVoidMethod(mInfo.classID, mInfo.methodID, _sku);
mInfo.env->DeleteLocalRef(mInfo.classID);
mInfo.env->DeleteLocalRef(_sku);
}
#endif
}
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
extern "C"
{
JNIEXPORT void JNICALL Java_org_cocos2dx_cpp_AppActivity_nativeCallBackSuccessBuyItem(JNIEnv* env, jobject thiz, jstring skuId)
{
const char *nativeString = env->GetStringUTFChars(skuId, nullptr);
std::string _skuId = std::string(nativeString);
env->ReleaseStringUTFChars(skuId, nativeString);
HelloWorld::callBackSuccessBuyItem(_skuId.c_str());
}
};
#endif
위 코드에서는 결제에 대한 성공여부를 명확히 확인하기 위해서 결제가 성공할경우 씬이 빙글빙글 돌아가도록 했습니다(?)
| 결제 테스트
작업이 끝난 후 apk 를 뽑아서 실행하면 위와 같이 '요청하신 항목은 구매할 수 없습니다.' 라는 메세지와 함께 결제 진행이 되지 않네요..
작업이 끝난 APK 를 반드시 알파버전 이상으로 구글 개발자 콘솔에 업로드 되어 있어야 하기 때문이랍니다.
버전 관리 메뉴에 들어가서 테스트 참여 대상 관리 의 사용자 목록에 테스터 계정을 등록하면 됩니다.
테스트 참여 대상 관리 메뉴의 아래에 있는 URL 을 통해서 받으시 앱을 다운로드 받아야 그때부터 결제를 진행할 수 있습니다.
위와같이 테스트 주문이 진행됩니다.
결제가 성공적으로 진행되네요!
'글 묶음 > 거를때가 된 Cocos2d-x' 카테고리의 다른 글
cocos2d-x | IAP [0] 준비하기 (0) | 2019.02.14 |
---|---|
cocos2d-x | Admob [2] 보상형광고 호출하기 (7) | 2019.02.14 |
cocos2d-x | Admob [1] 배너광고 호출하기 (0) | 2019.02.13 |
cocos2d-x | Admob [0] 애드몹 준비하기 (0) | 2019.02.13 |
완전 좋은 Firebase 시작하기 (0) | 2019.02.13 |