高機能割勘計算アプリ「ハバダッチ」
この「ハバダッチ」は計算機だけではちょっとめんどくさい割勘の計算をする為のアプリです。
例えば、男女6人でオートキャンプ場に出かけました。
車を出したA君がガソリン代や高速代、駐車料金などを立て替え、
その他のメンバーがそれぞれ食材やお酒などを持ち寄ったとします。
それぞれが何かを買って来ています。
キャンプ場の利用料も含めて割勘の計算をします。
計算にはかなりの手間がかかります。
やっと計算したと思ったら、車を出したA君が
「お酒代はお酒を飲んだ人で割るんだよね?」と言いだします。
更に計算が複雑になります。
そんなケースでも「ハバダッチ」があなたの代わりに計算します。
BBQモードでは支払を登録する時に立替者を登録する事ができ、割勘の計算も支払った額を相殺して計算します。
また、「全員で払う」か「特定の人が払う」を選択することができ、
「特定の人が払う」を選択した場合には追加支払するメンバを選択できます。
#選べるモード#
合コンモード、パーティーモード、BBQモードから画面の表示パターンを選択することができます。
#5つのパターンから選べる端数計算#
1円単位、10円単位、100円単位、500円単位、1000円単位端数処理のパターンを選択できます。
それぞれの単位になるように計算結果を切り上げします。
#固定額#
特定のメンバを割勘から除外して固定額にすることができます。
#重みを加えた計算#
登録したメンバーにどのくらいの割引/割増をするかを設定する事ができます。100%が通常時でパーセンテージで数字を設定します。
#女子割引#
ボタン一つでプリセットされた割引率を女性のメンバーに適用することができます。
※重みが100%以外に登録されているメンバーは、登録された重みが適用されます。
#支払額相殺計算# ※BBQモードのみ
複数の人が複数の物を購入した場合に、支払った額より一人当たりの割勘金額をマイナスした数字をそれぞれのメンバーに対して計算します。
#清算済みチェック#
清算画面にてメンバリストをタップすると、清算済みのチェックマークが表示されます。清算済みのメンバにチェックを入れていく事により、集金忘れを防止することに役立ちます。
*********************** 使い方 ***************************
#合コンモード#
ちょっと計算が複雑な飲み会の会計をサポートします。
合コンの席で男女で支払する額を分けたい。そんな場合は設定画面にて女子割引率を設定します。あとは清算画面にてそれぞれの支払額を確認するだけ。
プリセットされた割引率以外の割引率を設定したい場合は、メンバ登録の際に重みをパーセンテージで入力します。
さらにもうちょっと複雑な計算の場合はどうでしょう。
たとえば、会社での飲み会であなたが幹事だったとします。
4500円などの飲み放題コースを予約して、参加した人数ぴったりできっちり4500円ずつ集めればよい場合は計算機すらいりませんね。
ところが、上司の方が気を利かせて「じゃあ、僕が2万円払うから残りをみんなで割ってくれ」と言い出したとします。そんな男前の上司が一人くらいだったらいいでしょうが、何人もいたらどうしますか?
もしくは「このお金で新入社員の分に当ててくれ。彼らは1000円ずつでいいだろう」
少し計算が面倒ですね。
そんな時に威力を発揮するのがこのアプリ。
事前に飲み会の参加者をメンバー登録しておき、突然多く払うことになった上司の固定額に金額を入力します。
1000円だけ払えばいいことになった、ラッキーな新入社員たちも固定額に1000円を入力します。
清算画面より詳細を開くとどういう計算になっていて、足りているか足りていないか。一人いくらかなどが分かります。
また、清算画面のメンバリストにて集金した人の名前をタップすると、リストにチェックがつくので清算済みの人の確認が一目でできます。
#パーティーモード#
パーティモードでは、会費を集めるような場合をサポートします。
結婚式の二次会の受付を想定して作りました。
まずメンバーを一人作ります。
固定額に会費を入れて、性別を選択して登録。
もう一度そのメンバーを開いて「コピーして登録」ボタンを人数分押します。
名前で管理が必要なものは名前も入力します。
支払登録では、実際にかかる会場費などを入力します。
集金できた人からメンバリストにて名前をタップして印をつけていきます。人数が多くてなかなか見つけられない場合には、検索機能を使うとすぐに印をつける人が検索できます。
詳細画面を見ると、現在集まった金額と収支が一目でわかります。
#BBQモード#
BBQモードでは、お金を出した人が複数いるような複雑な割り勘をサポートします。
例えば、男女6人でオートキャンプ場に出かけました。
車を出したA君がガソリン代や高速代、駐車料金などを立て替え、
その他のメンバーがそれぞれ食材やお酒などを持ち寄ったとします。
それぞれが何かを買って来ています。
キャンプ場の利用料も含めて割勘の計算をします。
計算にはかなりの手間がかかります。
やっと計算したと思ったら、車を出したA君が
「お酒代はお酒を飲んだ人で割るんだよね?」と言いだします。
更に計算が複雑になります。
そんなケースでも「ハバダッチ」があなたの代わりに計算します。
BBQモードでは支払を登録する時に立替者を登録する事ができ、割勘の計算も支払った額を相殺して計算します。
また、「全員で払う」か「特定の人が払う」を選択することができ、
「特定の人が払う」を選択した場合には追加支払するメンバを選択できます。
Glyphish icons are Copyright 2010 Joseph Wain.
アプリ内課金 実装編
プロダクト取得のコードが書けてテストも成功したら、今度は購入処理のコードを記述してみる。
- まず、ヘッダにSKPaymentTransactionObserverを追加する。僕の場合はAlertViewも使っているのでこんな感じ
@interface InAppPurchaseViewController : UIViewController <SKProductsRequestDelegate,SKPaymentTransactionObserver,UIAlertViewDelegate>{
- ViewDidloadでPaymentQueueObserverになる処理を書く。Delegate=self的なノリ。これにより、一連の購入処理をハンドリングして逐次オブザーバーメソッドが呼ばれるようになる。
//SKPaymentQueueのオブザーバーとして登録 [[SKPaymentQueue defaultQueue]addTransactionObserver:self];
- 次にアプリケーションが購入可能かどうか調べる。
これには+(BOOL)SKPaymentQueueで調べる。//デバイスの機能制限かかっているかどうか調べる事ができる。 if([SKPaymentQueue canMakePayments]==NO){ UIAlertView *alert =[[UIAlertView alloc]initWithTitle:@"" message:@"iphoneの機能が制限されています。[設定]>[一般]>[機能制限]>[App内での購入]をONにいていただいたうえ再度お試しください" delegate:self cancelButtonTitle:@"YES" otherButtonTitles:nil]; alert.tag =1; [alert show]; return; }
- 下のコードではReachablityというクラスを使って、デバイスのネットワークの機能が使えるかどうかも判定している。これはiPhoneSDK開発のレシピを参考にした。
- 次は-(void)productsRequest:didReceiveResponse:(プロダクトの取得で返ってくるDelegateメソッド)の中で、プロダクトの情報取得のコードとかをごちゃごちゃ書かずに購入処理させる。
プロダクト情報を表示させるところを[self purchase];に差し替え、purchaseメソッドを実装。このメソッドで購入処理を実行する。-(void)purchase{ [indicator showIndicator:self.view]; SKPayment *payment = [SKPayment paymentWithProductIdentifier:product_.productIdentifier]; [[SKPaymentQueue defaultQueue] addPayment:payment];
- 購入処理結果はSKPaymentQueueObserverで返ってくる。購入処理を受け取る為に定義されているメソッドは2つあり、paymentQueue:updatedTransactionsとpaymentQueue:removedTransactions:だ。その後、レシートの確認や購入履歴復元の処理を書いたらfinishTransaction:メソッドで購入手続きを完了する。参考にした書籍は木下さんの本が主で、レシートの確認の処理まわりや全体のまとまりはエリカ本のサンプルがGithubにあったのでそれを参考にしました。
- また実際にリリースしてみると、本番環境でプロダクトの取得がまれに失敗したりするので3回失敗するまでは繰り返しリクエストを投げるようにコーディングしてます。
- ituneConnectの利用規約が変わったりして、agreeをするのが漏れていたりするとsandboxで成功しても実際のアプリでプロダクトの取得の部分で失敗するようです。
規約に同意して、さらにitunesconnectから問い合わせをして、24時間後にアプリ内課金が動きだすという悲惨な結果に終わったので注意してください。
InAppPurchaseViewController.m
#define SANDBOX YES @implementation InAppPurchaseViewController @synthesize errString; @synthesize product = product_; @synthesize requestCount; #pragma mark - #pragma mark viewDidLoad - (void)viewDidLoad { [super viewDidLoad]; indicator=[[IndicatorView alloc]init]; [indicator showIndicator:self.view]; //SKPaymentQueueのオブザーバーとして登録 [[SKPaymentQueue defaultQueue]addTransactionObserver:self]; Reachability* curReach = [Reachability reachabilityForInternetConnection]; NSParameterAssert([curReach isKindOfClass:[Reachability class]]); //ネットワーク判定 if ([curReach currentReachabilityStatus]==NotReachable){ UIAlertView *alert =[[UIAlertView alloc]initWithTitle:@"" message:@"ネットワークに接続できません。インターネットに接続をご確認のうえ再度お試しください。" delegate:self cancelButtonTitle:@"YES" otherButtonTitles:nil]; alert.tag =1; [alert show]; return; } //デバイスの機能制限 if([SKPaymentQueue canMakePayments]==NO){ UIAlertView *alert =[[UIAlertView alloc]initWithTitle:@"" message:@"iphoneの機能が制限されています。[設定]>[一般]>[機能制限]>[App内での購入]をONにいていただいたうえ再度お試しください" delegate:self cancelButtonTitle:@"YES" otherButtonTitles:nil]; alert.tag =1; [alert show]; return; } requestCount = 0;//requestCountを初期化 [self productRequest]; } -(void)productRequest{ //プロダクトIDの集合を生成 NSSet *productIds; productIds = [NSSet setWithObject:PRODUCT]; //SKProductRequestを作成 SKProductsRequest *preq = [[SKProductsRequest alloc] initWithProductIdentifiers:productIds]; preq.delegate = self; requestCount++; NSLog(@"%d",requestCount); //Store Kitへのアクセスを開始 [preq start]; } #pragma mark viewWillAppear - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; } #pragma mark - #pragma mark SKProductsRequest Delegate - (void)request:(SKRequest *)request didFailWithError:(NSError *)error { if(requestCount>2){ [indicator removeIndicator]; errString = [NSMutableString stringWithFormat:@"Error: Could not contact App Store properly, %@", [error localizedDescription]]; UIAlertView *alert =[[UIAlertView alloc]initWithTitle:@"" message:@"エラーが発生しました。お手数ですが再度お試しください" delegate:self cancelButtonTitle:@"無視する" otherButtonTitles:nil]; alert.tag =1; [alert show]; }else { [self productRequest]; } } - (void)requestDidFinish:(SKRequest *)request { [request release]; NSLog(@"Request finished."); } -(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{ product_ = [[[response products] lastObject]retain]; if (!product_) { if(requestCount>2){ [indicator removeIndicator]; //NSlog(@"no product ids:%@",[[response invalidProductIdentifiers] description]); errString = [NSMutableString stringWithFormat:@"no product ids:%@",[[response invalidProductIdentifiers] description]]; UIAlertView *alert =[[UIAlertView alloc]initWithTitle:@"" message:@"商品が購入いただけません。お手数ですが開発者へ連絡をしていただきますようお願い申し上げます。" delegate:self cancelButtonTitle:@"無視する" otherButtonTitles:@"通知する",nil]; alert.tag =2; [alert show]; return; }else { [self productRequest]; return; } } [indicator removeIndicator]; [self purchase]; } #pragma mark - #pragma mark SKPaymentQueue observerMethod -(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray*)transactions{ //購入処理を取得 for(SKPaymentTransaction* transaction in transactions){ //購入処理の状態を調べる const char *sql = "Update UserInfo Set isPurchase =1 Where id = 1"; switch (transaction.transactionState) { case SKPaymentTransactionStatePurchasing://購入中の場合 [self repurchase]; break; case SKPaymentTransactionStatePurchased://購入処理が成功した場合 //TODO 購入処理 appDelegate = (WorldAppDelegate *)[[UIApplication sharedApplication] delegate]; appDelegate.isPurchase = YES; [Coffee Update_stmt:[appDelegate getDBPath] SQL:(char *)sql]; bt1.hidden = YES; [self completedPurchaseTransaction:transaction]; //購入処理完了 [queue finishTransaction:transaction]; [indicator removeIndicator]; break; //購入処理失敗 case SKPaymentTransactionStateFailed: if(requestCount>2){ [self handleFailedTransaction:transaction]; [indicator removeIndicator]; }else { requestCount++; [self purchase]; } break; //購入処理復元 case SKPaymentTransactionStateRestored: //ロック解除処理 appDelegate = (WorldAppDelegate *)[[UIApplication sharedApplication] delegate]; appDelegate.isPurchase = YES; const char *sql = "Update UserInfo Set isPurchase =1 Where id = 1"; [Coffee Update_stmt:[appDelegate getDBPath] SQL:(char *)sql]; bt1.hidden = YES; //購入処理を終了 [queue finishTransaction:transaction]; [indicator removeIndicator]; break; default: NSLog(@"Other transaction"); break; } } } #pragma mark - #pragma mark payment - (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions { } - (void) handleFailedTransaction: (SKPaymentTransaction *) transaction { [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; } - (void) completedPurchaseTransaction: (SKPaymentTransaction *) transaction { // PERFORM THE SUCCESS ACTION THAT UNLOCKS THE FEATURE HERE // Check the receipt NSString *json = [NSString stringWithFormat:@"{\"receipt-data\":\"%@\"}", [transaction.transactionReceipt base64Encoding]]; NSString *urlsting = SANDBOX ? @"https://sandbox.itunes.apple.com/verifyReceipt" : @"https://buy.itunes.apple.com/verifyReceipt"; NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString: urlsting]]; if (!urlRequest) NOTIFY_AND_LEAVE(@"Error creating the URL Request"); [urlRequest setHTTPMethod: @"POST"]; [urlRequest setHTTPBody:[json dataUsingEncoding:NSUTF8StringEncoding]]; NSError *error; NSURLResponse *response; NSData *result = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:&response error:&error]; NSString *resultString = [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding]; CFShow(resultString); [resultString release]; // Finish transaction [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; // do not call until you are actually finished } #pragma mark Others - (void) repurchase { [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; } #pragma mark - #pragma mark alertView Delegate -(void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { if (alertView.tag ==1) { switch (buttonIndex) { case 0: [self dismissModalViewControllerAnimated:YES]; break; case 1: break; } }else if(alertView.tag == 2) { NSString *htmlBody; NSString *escapedBody; switch (buttonIndex) { case 0: [self dismissModalViewControllerAnimated:YES]; break; case 1: htmlBody = @"[error-noprodust(shunkan)"; //// First escape the body using a CF call escapedBody = [(NSString*)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)htmlBody, NULL, CFSTR("?=&+"), kCFStringEncodingUTF8) autorelease]; // //// Then escape the prefix using the NSString method NSString *mailtoPrefix = [@"mailto:oktechinfo@gmail.com?subject=" stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; // //// Finally, combine to create the fully escaped URL string NSString *mailtoStr = [mailtoPrefix stringByAppendingString:escapedBody]; // And let the application open the merged URL [[UIApplication sharedApplication] openURL:[NSURL URLWithString:mailtoStr]]; break; } } } @end
中国語の単語は勉強するな-無料で中国語1358単語を聞き流して耳を慣らすiPhoneアプリ
リリース記念で4月末まで無料でダウンロードできます。
「瞬間中単」は初級レベルの中国語単語を効率的に学べる、音声とテキストが連動した単語カード型のアプリです。
中検4級、新HSK1〜4級単語に完全準拠のネイティブにより厳選された1358の単語を品詞別に学べます。
全単語/例文にピンイン表記、全単語に音声データを収録しているので、中国語の初心者の単語学習にもご利用いただけます。
基礎単語をしっかり押さえ、瞬時に出せる回路を養うことそ、言語マスターの近道です。
連続再生でiPodのように1358単語を聞き流しての復習ができるので、音楽を聴く感覚で移動中にも学習が可能です。
※例文の音声データについてはアドオンコンテンツとして別途ご購入いただけます。
使い方
<メニュー画面>学習したい品詞のボタンを押すと学習画面へ移動します。また、品詞の他に全ての単語を学習できる「全て」ボタンと、お気に入りに設定した単語を学習する「しおり」ボタンがあります。
矢印のイラストのボタンはトップ画面に戻る為のボタンです。歯車のイラストのボタンを押すと設定画面に移動します。<設定画面>
- 発音ボタンは「単語」、「例文」、「なし」から一つだけ選択できます。それぞれ選んだカテゴリの音声が、単語カード表示時と単語移動時に再生されます。
- ランダム表示をONにすると、品詞を選んだときに単語カードの順番がランダムになります。OFFの時は単語に振られているID順にならんでいます。
- 画面スリープOFFをONにすると単語表示画面で操作をしなくてもiPhoneの画面がロックしないようになります。画面スリープOFFをOFFにした状態ではiPhoneの画面がロックされても音声は再生されるので、音声のみを聞きたい場合はOFFにしておいた方がバッテリーの消耗をおさえられます。
- 日本語を隠す・ピンインを隠すは、それぞれON時に単語カードの対応する場所を表示しないようにします。自分の記憶を確認するときなどに使用します。
- ループ再生をONにすると、選択した品詞の最後の単語の後に最初の単語に戻ります。ループ再生がOFF
時は最後の単語で自動再生が停止します。
- 単語表示時間を設定できます。自動再生時にここで設定した時間だけ単語が表示されます。設定した時間よりも音声の再生時間の方が長い場合は音声の再生が終了してから次の単語に切り替わります。
- ▶の再生ボタンを押すと単語の自動めくりが始まります。
- 自動めくりが動作していない状態で左にフリックすると次の単語に、表示が切り替わります。
- 単語、例文それぞれの発音ボタンを押すと音声を再生します。
- 音声再生時は発音ボタンが非表示になり、画面がロックされます。
- ブックマークボタンでお気に入りを登録できます。メニュー画面の「しおり」ボタンで見る事ができます。
ピザを作る
ピザ.h
//材料 作業する場所 のばし棒 クッキングシート(オーブン用) ピザ生地 ビン詰めのトマトソース チーズ 具(好きな物) 今日の具 その1 {ベーコン、茸(しめじ、舞茸)、タマネギ} その2 { ブラックオリーブ(種無し) 、 アンチョビ }
ピザ.m
//手順 具材を仕込む その1{ ベーコンを適当な大きさにカット きのこ類のイシヅキをカットし、大きすぎるものは適当な大きさにカット タマネギをスライスする。 } その2{ ブラックオリーブの缶を空けてスライスする アンチョビ3本くらいを5ミリ間隔くらいに切る } 作業場所を濡らしてラップを引く。端を重ねるように2枚くらいあれば当面の作業場としては十分 オーブンの余熱 [230 - 250度くらいで10分から20分] 作業台に小麦粉を引く //生地がしたにくっつかないように 生地を完全な球体になるように丸めてから、台の上でまっすぐにつぶす //この時点で円になっていないようだと、綺麗な円のピザを作るのは難しいと思う 生地を伸ばす。//必要があればののばし棒があると便利 men'sは手でがんばっても良いかも。 //適宜、台に粉をうってくっつかないようにのばす 生地の下にクッキングシートもしくはアルミホイルを敷く 生地の中心にトマトソース 大さじ1.5くらいを乗せる //僕はカレー用のスプーン一杯位を目安にしている 中心から円を書くようにソースをのばす。 チーズをまぶし具を乗せる。 クッキングシートごとオーブンへ 焼く // 10分から20分くらいと思われるが、生地の厚さや具材の大きさで異なる。 // おおよその目安として、チーズがとろけて生地がこんがり焼けてきたらOK♪
アプリ内課金 その3 プロダクトの取得
SKRequestのサブクラスのSKProductRequestを使ってプロダクト情報を取得する。
インスタンスには下記のメソッドを使う
-(id)initWithProductIdentifiers:(NSSet*)ProductIdenTifiers;
プロダクトIDはNSSetととして設定。
ということは、複数のプロダクトを取得もできそうだけれど、今回は一個だけ。
//プロダクトIDの集合を生成 NSSet *productIds; productIds = [NSSet setWithObject:PRODUCT]; //SKProductRequestを作成 SKProductsRequest *preq = [[SKProductsRequest alloc]initWithProductIdentifiers:productIds];
次に、AppSoreにアクセスしてプロダクト情報を取得する。
- (void)starメソッドでアクセスを開始。キャンセルは-(void)cancelメソッドを使う。
結果はデリゲートで受け取る事になる。
SKRequestDelegateプロトコルと、そのサブクラスのSKPruductsRequestDelegateで定義される。
実用上はSKProductsRequestDelegateとして設定することになるので、ヘッダーにSKProductsRequestDelegateを追加。
定義されているデリゲートメソッドは下記の3つ
-(void)requestDidFinish:(SKRequest*)request //----終了通知 -(void)request:(SKRequest*)request didFailWithError:(NSError*)error //---アクセス中のエラー -(void)productsRequest(SKProductsRequest*)request didReceiveResponse:(SKProductsResponce*)response //--レスポンスの受信通知
アクセス結果はSKProductsResponseオブジェクトで受け取る。
このクラスにはプロダクト情報を配列で持っているProdustsプロパティを持つ。
取得に失敗した場合はinvalidProductIdentifiersという配列に入れられる。
アプリケーションでプロダクトの価格を表示する場合、どの地域の通貨か意識する必要がある。
priceプロパティとpriceLocalプロパティを使う事により変換が可能。
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];//フォーマットを作成 //フォーマットを設定 [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4]; [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle]; //PriceLocalを設定 [numberFormatter setLocale:product.priceLocale]; NSString *formattedString = [numberFormatter stringFromNumber:product.price];//価格を表示する文字列 [numberFormatter release];
下記にはあえてSKPaymentQueueのオブザーバーは登録しない。
これは購入処理結果の取得で使うものなので。
#import <UIKit/UIKit.h> #import "Reachability.h" #import <StoreKit/StoreKit.h> @class WorldAppDelegate; @interface InAppPurchaseViewController : UIViewController <SKProductsRequestDelegate>{ IBOutlet UILabel *lb1; } @property(nonatomic,retain)UILabel *lb1; -(IBAction)purchase; -(IBAction)closeAction; @end #import "InAppPurchaseViewController.h" #define PRODUCT @"XXXX" @implementation InAppPurchaseViewController @synthesize lb1; #pragma mark - #pragma mark View lifecycle - (void)viewDidLoad { [lb1 release]; [super viewDidLoad]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; } #pragma mark - #pragma mark button -(IBAction)purchase{ Reachability* curReach = [Reachability reachabilityForInternetConnection]; NSParameterAssert([curReach isKindOfClass:[Reachability class]]); //ネットワーク判定 if ([curReach currentReachabilityStatus]==NotReachable){ NSLog(@"No Network"); return; } //デバイスの機能制限 // if([SKPaymentQueue canMakePayments]==NO){ // NSLog(@"device lock"); // return; // } //SKPaymentQueue * //プロダクトIDの集合を生成 NSSet *productIds; productIds = [NSSet setWithObject:PRODUCT]; //SKProductRequestを作成 SKProductsRequest *preq = [[SKProductsRequest alloc]initWithProductIdentifiers:productIds]; preq.delegate = self; //Store Kitへのアクセスを開始 [preq start]; } #pragma mark - #pragma mark SKProductsRequest Delegate - (void)request:(SKRequest *)request didFailWithError:(NSError *)error { NSLog(@"Error: Could not contact App Store properly, %@", [error localizedDescription]); lb1.text = [NSString stringWithFormat:@"Error: Could not contact App Store properly, %@", [error localizedDescription]]; } - (void)requestDidFinish:(SKRequest *)request { [request release]; NSLog(@"Request finished."); } -(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{ SKProduct *product = [[response products] lastObject]; if (!product) { NSLog(@"no product ids:%@",[[response invalidProductIdentifiers] lastObject]); return; } // Retrieve the localized price NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4]; [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle]; [numberFormatter setLocale:product.priceLocale]; NSString *formattedString = [numberFormatter stringFromNumber:product.price]; [numberFormatter release]; // Create a description that gives a heads up about // a non-consumable purchase NSString *buyString = formattedString; NSString *describeString = [NSString stringWithFormat:@"%@\n\nIf you have already purchased this item, you will not be charged again.", product.localizedDescription]; NSLog(@"%@",[product productIdentifier]); NSLog(@"%@",[product localizedTitle]); NSLog(@"%@",[product localizedDescription]); NSLog(@"%@",buyString); NSLog(@"%@",describeString); }
色々なパターンでプロダクトを登録してみたが、
SKProductsRequestで指定したproductIdentifierがinvalidProductIdentifiersになって返ってくきてしまう。
色々調べてみた結果、下記の事項を調べて確認してみた。
僕の場合はDebugのコードサインが違っていたみたい。。。
上に示したコードで見事にプロダクトの取得が成功♪
- App IDでIn-App Purchasesは有効になっているか。
- そのプロダクトは「Cleared for Sale」になっているか。
- そのアプリの新しいバージョンをiTunes Connect上で追加しているか。(バイナリも上げておく必要があるらしい)
- Xcodeプロジェクトの.plistのBundle IDはApp IDと一致していますか?
- そのApp IDで新しいProvisioning Profileを作成してインストールしているか。
- アプリがそのProvisioning Profileでコードサインがされるように設定されているか。
- iOS 3.0以降が対象になるようにビルドされているか。
- SKProductRequestに渡しているProduct IDはiTunes Connectで指定したものと一致しているか。
- iTunes Connectでプロダクトを追加してから数時間待つ必要あり。
- iTunes Connectに正しく銀行口座情報が登録されているか。
- アプリをいったんデバイスから削除して再インストールしてみる。
- XcodeのDebugでビルドしないとsandboxにはつながらないらしい。
次回は購入処理を実装します。
参考
http://n-lights.co.jp/iphone-dev/category/%E6%9C%AA%E5%88%86%E9%A1%9E
アプリ内課金 その2 実装の手順
実装までの手順は以下の通り
- プロダクトの登録
- プロダクトの取得
- プロダクトの購入
- レシートの確認
プロダクトの登録
プロダクトの登録はiTunesConnectにて設定。これは前回完了しているので、
Xcodeでの実装はプロダクトの取得以降となる。
アプリケーション側で登録したプロダクトIDを持っておく必要がある。
コンテンツサーバーからリストをダウンロードしてもいいのだけれど、一番単純にコード内に埋め込む事にする。
プロダクトの取得
プロダクトIDを指定してサーバーにリクエストを投げて登録したプロダクトを取得する。
サーバーからのレスポンスで登録したプロダクト情報を取得をする。
プロダクトの購入
取得したプロダクトを表すオブジェクトを使って購入要求をサーバーに投げる。
もしくはプロダクトIDを指定して購入要求を作成もできる。
課金処理が完了するとサーバーから通知がくる。
これをトリガーとしてコンテンツのダウンロードや機能制限の解除の処理を実行する。
購入処理が終わったらサーバーに終了通知を投げる。
それによって、サーバーのキューから購入要求が削除され手続きが完了する。
レシートの確認
課金処理を行うと、その詳細を表すレシートを取得できる。
過去に購入したプロダクトに対するレシートも確認ができる。
レシートはAppStoreから暗号化された状態で送られてくるので、一度AppStoreのサーバーに戻して復号化する必要がある。
これはサーバーの偽装を防ぐ為の処理らしい。
レシートに含まれる情報は(プロダクトID,購入処理のID、購入日、購入数)