iOS InApp Purchase - Sizuha/devdog GitHub Wiki

OS In-App Purchase

์ค€๋น„

ํ™•์ธ์‚ฌํ•ญ

  • ์€ํ–‰๊ณ„์ขŒ๊ฐ€ ์‚ฌ์ „์— ๋“ฑ๋ก ํ•„์ˆ˜ --- ์—†์œผ๋ฉด ํ…Œ์ŠคํŠธ ํ™•์ธ๋ถˆ๊ฐ€
  • ์•„์ดํฐ ์„ค์ •/store์˜ ๊ณ„์ • ๋กœ๊ทธ์•„์›ƒ(๋ฐ˜๋“œ์‹œ) ---- ํ…Œ์ŠคํŠธ ๊ณ„์ • ์‚ฌ์šฉ์„ ์œ„ํ•จ
  • ์ƒํ’ˆ ํŒ๋งค ์ง€์—ญ ์–ธ์–ด ์ผ์น˜ ํ•„์ˆ˜ --- ๊ฐœ๋ฐœ์–ดํ”Œ๊ณผ ์• ํ”Œ iTunes Connect ์‚ฌ์ดํŠธ์˜ Manage Your Applications์—์„œ
  • ์• ํ”Œ ๊ฒฐ์ œ ์–ผ๋Ÿฟ์ฐฝ์€ ์ง€์—ฐ์‹œ๊ฐ„์ด ๋ฐœ์ƒํ•˜์—ฌ ๋А๋ฆฌ๊ฒŒ ํ‘œ์‹œ ๋  ์ˆ˜ ์žˆ์Œ. (์ด์œ ๋Š” ๋„คํŠธ์›Œํฌ์™€ ์• ํ”Œ ์„œ๋ฒ„์˜ ์˜ํ–ฅ์ธ๋“ฏ ํ•จ) ์ตœ๋Œ€ 15์ดˆ๊นŒ์ง€ ์ง€์—ฐ ๋ฐœ์ƒ ๊ฐ€๋Šฅ ํ™•์ธ ๋จ.

In-app ์ƒํ’ˆ ๋“ฑ๋ก

iTunes Connect > Manage Your Apps์—์„œ ํ•ด๋‹น ์•ฑ์„ ์„ ํƒ.

Manage In-App Purchases ๋ฒ„ํŠผ ํด๋ฆญ.

โ€ป ์ƒํ’ˆ ๊ฐ€๊ฒฉ์€ ํ•œ๋ฒˆ ์ •ํ•˜๋ฉด ๋ฐ”๊พธ๊ธฐ ์–ด๋ ต๋‹ค. ๊ธฐ๊ฐ„ ํ•œ์ •์œผ๋กœ๋งŒ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋Š” ๋“ฏ.

์ƒˆ ์ƒํ’ˆ ๋“ฑ๋ก

Create New ๋ฒ„ํŠผ ํด๋ฆญ.

ํŒ๋งค ์ƒํ’ˆ ์œ ํ˜•์€ ์•„๋ž˜ 3๊ฐ€์ง€ ์œ ํ˜•์ค‘ ํ•˜๋‚˜ ์„ ํƒ

  • ๋ณด์ถฉ ๊ฐ€๋Šฅ(Consumable): ์ด ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ๋‚ด ๊ตฌ์ž… ๊ธฐ๋Šฅ์€ ๋งค๋ฒˆ ๊ตฌ์ž…ํ•ด์•ผ ํ•˜๊ณ  ๋ฌด๋ฃŒ๋กœ ๋‹ค์‹œ ๋‹ค์šด๋กœ๋“œํ•  ์ˆ˜ ์—†๋Š” ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.
    • ์ถ”๊ฐ€ ์ฒด๋ ฅ
    • ์ถ”๊ฐ€ ๊ฒฝํ—˜์น˜
  • ๋ณด์ถฉ ๋ถˆ๊ฐ€๋Šฅ(Non-Consumable): ์ด ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ๋‚ด ๊ตฌ์ž… ๊ธฐ๋Šฅ์€ ํ•œ ๋ฒˆ๋งŒ ๊ตฌ์ž…ํ•ด์•ผ ํ•˜๊ณ , ๋™์ผํ•œ iTunes Store ๊ณ„์ •์œผ๋กœ ์ธ์ฆ๋œ ์—ฌ๋Ÿฌ ์žฅ๋น„์— ์ „์†กํ•  ์ˆ˜ ์žˆ๋Š” ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.
    • ๋ณด๋„ˆ์Šค ๊ฒŒ์ž„ ๋ ˆ๋ฒจ
    • ๋„์‹œ ์•ˆ๋‚ด ์ง€๋„
  • ๊ตฌ๋…(Subscriptions): ๊ตฌ๋… ๊ธฐ๊ฐ„์ด ๋งŒ๋ฃŒ๋  ๊ฒฝ์šฐ ๋‹ค์‹œ ๊ตฌ์ž…ํ•ด์•ผ ํ•˜๋Š” ์ผํšŒ์„ฑ ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค.
    • ํ•œ ๋‹ฌ ๊ตฌ๋…
    • ์œ„์น˜ ์„œ๋น„์Šค ๊ตฌ๋…

์ƒํ’ˆ ์œ ํ˜•์„ ์„ ํƒํ•˜๋ฉด, In-app ์ƒํ’ˆ์— ๋Œ€ํ•œ ์ƒ์„ธ ์„ค์ • ํŽ˜์ด์ง€๊ฐ€ ๋‚˜์˜จ๋‹ค.

  • Reference Name: iTunes Connect ์‚ฌ์ดํŠธ ๋‚ด์—์„œ ํ‘œ์‹œ๋˜๋Š” ์ด๋ฆ„. ๊ด€๋ฆฌ์ž๊ฐ€ ์•Œ์•„๋ณผ ์ˆ˜ ์žˆ๊ฒŒ ์ž‘์„ฑ.
  • Product ID: ์ƒํ’ˆ ID. ์œ ๋‹ˆํฌํ•œ ์ด๋ฆ„์ด ๋˜๋„๋ก ์ž‘์„ฑ.
  • Cleared for Sale: ํŒ๋งคํ•ด์•ผ๋  ์ƒํ’ˆ์ด๋ฏ€๋กœ Yes๋กœ ์„ค์ •ํ•ด ๋†“์ž.
  • In-App Purchase Details: ''Add Language'' ๋ฒ„ํŠผ์„ ํด๋ฆญํ•ด์„œ ํ•ด๋‹น ์–ธ์–ด์— ๋งž๊ฒŒ ์ถ”๊ฐ€.
    • Language: ์–ธ์–ด
    • Display Name: ์‚ฌ์šฉ์ž์—๊ฒŒ ํ‘œ์‹œ๋˜๋Š” ์ƒํ’ˆ ์ด๋ฆ„.
    • Description: ์ƒํ’ˆ ์„ค๋ช…. ์‹ค์ œ ์‚ฌ์šฉ์ž์—๊ฒŒ๋Š” ๋ณด์ด์ง€ ์•Š๋Š” ๋“ฏ.
    • Publication Name: ๊ตฌ๋… ์ƒํ’ˆ์ธ ๊ฒฝ์šฐ, ๋งค๊ฑฐ์ง„์ด๋‚˜ ๋‰ด์Šค ๋งค์ฒด์˜ ํƒ€์ดํ‹€.
  • Review Notes (Optional)
  • Screenshot for Review : ๊ฒ€์ˆ˜ ๊ณผ์ •์—์„œ, ๊ฒ€์ˆ˜์ž๊ฐ€ ์ฐธ๊ณ ํ•  ์ˆ˜ ์žˆ๋Š” ์Šคํฌ๋ฆฐ ์ƒท.{{ note | ์ฐธ๊ณ  | In-app ๊ฒฐ์žฌ์‹œ, ๊ฒฐ์žฌ๋ฅผ ์ง„ํ–‰ํ•˜๊ธฐ ์ „์— ์‚ฌ์šฉ์ž์—๊ฒŒ ํ™•์ธ์„ ์š”๊ตฌํ•˜๋Š” ๊ณผ์ •์„ ํ•œ๋ฒˆ ๋ฐŸ์•„์ฃผ๋Š”๊ฒŒ ์ข‹๋‹ค.
    ๋ณดํ†ต yes/no ํŒ์—…์„ ํ•˜๋‚˜ ๋„์›Œ์ฃผ๋ฉด ๋  ๊ฒƒ์ด๋‹ค. }}

In-app ๊ฒฐ์žฌ ํ…Œ์ŠคํŠธ

์•„์ดํฐ์—์„œ ๊ฒฐ์ œํ•˜๋Š” ๋ฐฉ์‹์ด๋ฏ€๋กœ ๋กœ๊ทธ์ธ ์ด๋ฉ”์ผ ๊ณ„์ •์œผ๋กœ ์ž„์˜๋กœ ์ƒ์„ฑ ํ•จ. ๋‹จ์ง€, ์ด๋ฏธ iTunes ๊ณ„์ •์œผ๋กœ ๋“ฑ๋ก ๋œ ๊ณ„์ •์€ ์ƒ์„ฑ ํ•˜๋ฉด ์•ˆ๋จ. ์‹ค์ œ ๊ฒฐ์ œ๋  ์ˆ˜ ์žˆ์Œ.

ํ…Œ์ŠคํŠธ ์‚ฌ์šฉ์ž ๋“ฑ๋ก

  1. iTunes Connect > Manage Users
  2. Test User ํด๋ฆญ.
  3. Add New user ํด๋ฆญ.
  4. ํ•ญ๋ชฉ๋“ค์„ ์ž˜ ์ฑ„์šฐ๊ณ  Save ํด๋ฆญ.
    1. ์ด๋•Œ ๋“ฑ๋กํ•œ E-mail์ด ์‚ฌ์šฉ์ž ID๊ฐ€ ๋œ๋‹ค.

ํ…Œ์ŠคํŠธ ์ง„ํ–‰

  1. In-App ๊ฒฐ์žฌ ํ…Œ์ŠคํŠธ์— ์•ž์„œ, App Store๋ฅผ ๋กœ๊ทธ ์•„์›ƒ ์ƒํƒœ๋กœ ๋งŒ๋“ค์–ด ๋†“๋Š”๋‹ค.
  2. App์„ ์„ค์น˜ํ•œ๋‹ค.
  3. App์—์„œ ๊ฒฐ์žฌ๋ฅผ ์‹œ๋„ํ•œ๋‹ค. ์ด๋•Œ ์ƒŒ๋“œ๋ฐ•์Šค ํ…Œ์ŠคํŠธ ์•„์ด๋””๋กœ ๋กœ๊ทธ์ธ ํ•œ๋‹ค.

๊ตฌํ˜„

ํ—ค๋”

#import <StoreKit/StoreKit.h>

๋ธ๋ฆฌ๊ฒŒ์ด๋ธŒ์™€ ํŠธ๋žœ์ ์…˜ ์˜ต์ ธ๋ฒ„

@interface XXXXX :  NSObject <SKPaymentTransactionObserver, KProductsRequestDelegate> {
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions;
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
  • ์‹ค์ œ ์ƒํ’ˆ๊ฒฐ์ œ ์ง„ํ–‰์— ๋”ฐ๋ฅธ ๊ฒฐ๊ณผ ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ ๋ฐœ์ƒ ์‹œํ‚ด. (ํ”„๋กœ๊ทธ๋žจ ์ดˆ๊ธฐํ™”๋‚˜ ๋กœ๋“œ์‹œ, ๋˜๋Š” ๊ฒฐ์ œ๋ฒ„ํŠผ ํด๋ฆญ์‹œ ์ฒ˜๋ฆฌ)
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];

์• ํ”Œ๊ฒฐ์ œ ์ด์šฉ๊ฐ€๋Šฅ ์ƒํƒœ๋ฅผ ํ™•์ธ

if ([SKPaymentQueue canMakePayments])
{
   ... // Display a store to the user.
}
else
{
   ... // Warn the user that purchases are disabled.
}

์ƒํ’ˆ์ •๋ณด ์œ ๋ฌด ํ™•์ธ

- (void)requestProductData
{
   SKProductsRequest *request= [[SKProductsRequest alloc] initWithProductIdentifiers: [NSSet setWithObject: @"com.testman.inappdemo.001"]];
   request.delegate = self;
   [request start];
}

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
    NSArray *myProduct = response.products;
    // populate UI
    [request autorelease];
}

์ƒํ’ˆ๊ฒฐ์ œ ์š”์ฒญ

SKPayment *payment = [SKPayment paymentWithProductIdentifier:@"com.testman.inappdemo.001"];
[[SKPaymentQueue defaultQueue] addPayment:payment];

๊ฒฐ์ œ ๊ฒฐ๊ณผ์— ๋”ฐ๋ฅธ ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    for (SKPaymentTransaction *transaction in transactions)
    {
        switch (transaction.transactionState)
        {
            case SKPaymentTransactionStatePurchased:
                [self completeTransaction:transaction];
                break;
            case SKPaymentTransactionStateFailed:
                [self failedTransaction:transaction];
                break;
            case SKPaymentTransactionStateRestored:
                [self restoreTransaction:transaction];
            default:
                break;
        }
    }
}

- (void) completeTransaction: (SKPaymentTransaction *)transaction
{
// Your application should implement these two methods.
   //์ƒ๋žต๊ฐ€๋Šฅ [self recordTransaction: transaction];
   //์ƒ๋žต๊ฐ€๋Šฅ [self provideContent: transaction.payment.productIdentifier];
// Remove the transaction from the payment queue.
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}

- (void) restoreTransaction: (SKPaymentTransaction *)transaction
{
    //์ƒ๋žต ๊ฐ€๋Šฅ[self recordTransaction: transaction];
    //์ƒ๋žต๊ฐ€๋Šฅ [self provideContent: transaction.originalTransaction.payment.productIdentifier];
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}

- (void) failedTransaction: (SKPaymentTransaction *)transaction
{
    if (transaction.error.code != SKErrorPaymentCancelled)
    {
        // Optionally, display an error here.
    }
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}

Sample

๋‹ค์Œ์€ Unity ์—ฐ๋™์„ ์œ„ํ•ด ๋งŒ๋“  ๊ตฌํ˜„ ์ƒ˜ํ”Œ ์ฝ”๋“œ์ด๋‹ค.

//
//  iOSPlugins.h
//
#ifndef Unity_iPhone_iOSPlugins_h
#define Unity_iPhone_iOSPlugins_h

#import <StoreKit/StoreKit.h>

@interface InappObserver : NSObject<SKPaymentTransactionObserver, SKPaymentTransactionObserver> {
}
#pragma mark Store Kit
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions;
-(void)completeTransaction: (SKPaymentTransaction *)transaction;
-(void)failedTransaction: (SKPaymentTransaction *)transaction;
@end

#endif
//
//  iOSPlugins.m
//
#import <Foundation/Foundation.h>
#include "iOSPlugins.h"


InappObserver* g_inapp;

void initPlugins()
{
    g_inapp = [[InappObserver alloc] init];
    
    if ([SKPaymentQueue canMakePayments]) {
        [[SKPaymentQueue defaultQueue] addTransactionObserver: g_inapp];
    }
}

void purchase_ios(int product_id, const char* market_pid)
{
    NSLog(@"purchase_ios: %d, %s", product_id, market_pid);
    
    NSString* pid = [NSString stringWithUTF8String: market_pid];
    
    //-- 1.์ƒํ’ˆ์š”์ฒญ
    NSSet *set = [NSSet setWithObjects:pid, nil];
    SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
    productsRequest.delegate = g_inapp;
    [productsRequest start];
}

@implementation InappObserver

#pragma mark Store Kit

//-- 2.์ƒํ’ˆ์‘๋‹ต
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
    
    if ([response.products count] > 0) {
        SKProduct *product = [response.products objectAtIndex:0];
        NSLog(@"Title:%@", product.localizedTitle);
        NSLog(@"Description:%@", product.localizedDescription);
        NSLog(@"Price:%@", product.price);
        
        //-- 3.๊ตฌ๋งค์š”์ฒญ
        SKPayment *payment = [SKPayment paymentWithProduct:product];
        [[SKPaymentQueue defaultQueue] addPayment: payment];
    }
}

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    //-- 4.๊ตฌ๋งค์‘๋‹ต
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            // Call the appropriate custom method for the transaction state.
                
            case SKPaymentTransactionStateFailed:
                NSLog(@"่ณผๅ…ฅๅคฑๆ•—: %@, %@", transaction.transactionIdentifier, transaction.error);
                [self failedTransaction:transaction];
                break;
                
            case SKPaymentTransactionStatePurchased:
                NSLog(@"่ณผๅ…ฅๆˆๅŠŸ");
                [self completeTransaction:transaction];
                
                const char* product_id = [[transaction.payment productIdentifier] UTF8String];
                UnitySendMessage("ShopList", "storeBuyCallBack", product_id);
                break;
                
            case SKPaymentTransactionStateRestored:
                // ใƒชใ‚นใƒˆใ‚ขๅ‡ฆ็†
                // NSLog(@"ไปฅๅ‰ใซ่ณผๅ…ฅใ—ใŸๆฉŸ่ƒฝใ‚’ๅพฉๅ…ƒ");
                //[queue finishTransaction:transaction];
                // TODO: ใ‚ขใ‚คใƒ†ใƒ ่ณผๅ…ฅใ—ใŸๅ‡ฆ็†๏ผˆใ‚ขใƒƒใƒ—ใ‚ฐใƒฌใƒผใƒ‰็‰ˆใฎๆฉŸ่ƒฝๅˆถ้™่งฃ้™คๅ‡ฆ็†็ญ‰๏ผ‰
                break;
                
            default:
                // For debugging
                NSLog(@"Unexpected transaction state %@", @(transaction.transactionState));
                break;                
        }        
    }
}

- (void) completeTransaction: (SKPaymentTransaction *)transaction
{
    NSString *receiptStr = [transaction.transactionReceipt base64Encoding];
    NSLog(@"completeTransaction");
    NSLog(@"Trasaction Identifier : %@", transaction.transactionIdentifier);
    NSLog(@"Trasaction Date : %@", [transaction.transactionDate description]);
    
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
    
    //LOG(@"Trasaction transactionReceipt : %@", receiptStr);  
}

- (void) failedTransaction: (SKPaymentTransaction *)transaction
{
    NSLog(@"failedTransaction");
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
    
    const char* msg =  [[transaction.error localizedDescription] UTF8String];
    UnitySendMessage("ShopList", "storeFailCallBack", msg);
 }
@end
โš ๏ธ **GitHub.com Fallback** โš ๏ธ