Visaの新サービス(Visa Developer)のサンプルコードをObjective−Cに変換してみた。
1 はじめに
今回は、Visaで新しく提供されたWebAPIを試して見るために、サンプルで公開されているコードをObjective−Cに変換して、iPhoneでアクセスしてみました。
2 Visa Developer
Visa(Visa Inc.)は、今年2月初めに、同社の決済処理のプラットフォームを外部の開発者に開放するプログラム「Visa Developer」を公開しました。
ZDNet -Visa opens up its payment network to developers-
このプログラムは、WebAPIとして公開されており、ドキュメントを見ると「Visa Checkout(支払いサービス)」や「Visa Direct(資金移動やスマフォによる支払い)」などの基本的な機能の他にも、 Foreign Exchange Rates(外国為替レート)、ATM Locator(ATM所在地)、Coupon(クーポン)、 Inventory Management(在庫管理)、Sales Policy(販売政策)、Shopping Cart(ショッピングカート)などなど、非常に多岐にわたるサービス(約150種類)を展開しているのが分かります。
Explore our APIs and discover how you can use them
3 スタートアップ
同サービスのGetting Started Guideのページに、このサービスを利用する際のスタート要領が紹介されています。
これによると、概ね次のとおりです。
(1) API検索
トップページにAPIの検索とドキュメントへのリンクがあります。
(2) サンドボックス
同社で提供するサンドボックス(ブラウザインターフェース)でそれをテストできます。
(3) 開発者登録
独自のテストアプリを作成し、WebAPIでアクセスするには、認証コードが必要なため、開発者として登録が必要になります。 アカウント作成は、無料であり、好きなAPIを組合わせて任意のアプリを作成し、試験用のWebAPIサーバで試験ができます。
(4) アプリ申請
作成したアプリは、審査が必要です。連絡してレビューを受け、契約、価格設定等の手順へ進みます。
4 認証方法
上記の手順の中で、(3)まで進むと、実際にWebAPIにアクセスする必要がありますが、この時のアクセス時に認証方法として、次の2種類が提供されています。
- Using Two-Way SSL (Mutual Authentication)
- Using API Key – Shared Secret (X-Pay-Token)
最初の方は、SSLによる相互認証で、2つ目が、API用に発行されたAPIキーとシークレットキーでアクセスする方法です。
SSL相互認証は、クライアント側の証明書の用意が必要なので、今回は、手軽な2番目の方法を試してみました。
2番目の要領は、非常にトリッキーな要領で、APIキーやシークレットキーをリクエストに埋め込むため、サンプルとして、GitHubにコードが公開されています。
https://github.com/VisaDeveloperProgram/SampleCode
しかし、残念ながら、iPhoneでそのまま使える、SwiftやObjective−Cはありません。
5 Objective−Cによるサンプル
できるだけ忠実にサンプルコードをObjective−Cに移植してみました。
- (IBAction)tapButton:(id)sender { NSString *baseUri = @"cybersource/"; NSString *resourcePath = @"payments/v1/authorizations"; NSString *url = [NSString stringWithFormat:@"https://sandbox.api.visa.com/%@%@?apikey=%@",baseUri,resourcePath,API_KEY]; NSString *body = @"{\"amount\": \"0\", \"currency\": \"USD\", \"payment\": { \"cardNumber\": \"4111111111111111\", \"cardExpirationMonth\": \"10\", \"cardExpirationYear\": \"2016\" }}"; NSString *xPayToken = [self getXPayToken:resourcePath queryString:[NSString stringWithFormat:@"apikey=%@",API_KEY] requestBody:body]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; [request setURL:[NSURL URLWithString:url]]; [request setHTTPMethod:@"POST"]; [request addValue:@"application/json" forHTTPHeaderField: @"Content-Type"]; [request addValue:xPayToken forHTTPHeaderField: @"x-pay-token"]; NSMutableData *postBody = [NSMutableData data]; [postBody appendData:[body dataUsingEncoding:NSUTF8StringEncoding]]; [request setHTTPBody:postBody]; NSURLSession *session = [NSURLSession sharedSession]; [[session dataTaskWithRequest: request completionHandler: ^(NSData *data, NSURLResponse *response, NSError *error) { if (response && ! error) { NSString *responseString = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding]; NSLog(responseString); dispatch_async(dispatch_get_main_queue(), ^{ self.textView.text = responseString; }); } else { NSLog(@"ERROR: %@",error); } }] resume]; } - (NSString *)getXPayToken:(NSString *)apiNameURI queryString:(NSString *)queryString requestBody:(NSString *)requestBody { NSString *timestamp = [self getTimestamp]; NSString *sourceString = [NSString stringWithFormat:@"%@%@%@%@%@",SHARED_SECRET,timestamp,apiNameURI,queryString,requestBody];; NSString *hash = [self getDigest:sourceString]; NSString *token = [NSString stringWithFormat:@"x:%@:%@",timestamp,hash]; return token; } - (NSString *)getTimestamp { long timeStamp = (long)[[NSDate date] timeIntervalSince1970]; return [NSString stringWithFormat : @"%ld", timeStamp]; } - (NSString *)getDigest:(NSString *)date { NSData *d = [date dataUsingEncoding:NSASCIIStringEncoding]; uint8_t bytes[64]; CC_SHA256(d.bytes, d.length, bytes); NSMutableString* digest = [NSMutableString stringWithCapacity:64]; for(int i = 0; i < 32; i++) { [digest appendFormat:@"%02x", bytes[i]]; } return digest; }
サンプルコード(http://github.com/furuya02/CybersourceSample/blob/master/CybersourceSample/ViewController.m)
実行結果は、次の通りです。
6 デバッグ
簡単にできたと思いますか〜 それが、中々難しかったです。
データの送受信ぐらいまでは、あっさり行ったのですが、認証エラー(401)が中々消えませんでした。
つまらない話しですが、今回、既存で用意されているC#のコードと比較しながら行った、デバッグ要領を紹介させて下さい。
(1) SHA256
他の言語だと、結構、関数一発で生成できるSHA256ですが、Objective-Cは、ちょっと手間がかかりました。 最終的には、既存サンプル(C#)での出力と同じになることを目指して進めました。
既存サンプル(C#)の実行結果
Objective−Cの実行結果
(2) x-pay-token
今回、選択したShared Secret認証では、x-pay-tokenという認証用の文字列を生成して、ヘッダに入れる必要があるのですが、これが、結構ややこしかったです。
簡単に言うと、「シークレットキー」と「現在時間」と「APIURL」と「レクエストの内容」を繋げて、先ほどのSHA256でハッシュ化し、再び現在時刻と繋げたものです。
どれか、1つでも間違うと認証が通らないので、どこで間違っているのか突き止めるのは、結構大変でした。
結論的には、現在時間以外は、同じに出来るので、timeStampを返すところだけモックで固定値にして、比較しながら作業しました。
現在時間を固定
既存サンプル(C#)の実行結果
Objectiv−Cの実行結果
現在時間の生合成も認証の一つらしく、サーバへのアクセス時間と数10秒ぐらいずれると、やはり認証エラーとなるので、忙しかったです。
7 最後に
今回は、単純に他の言語を移植するというだけの作業でしたが、色々考えさせられるものがありました。
既に、正常に動作している物が1つでもる場合・・・「何処をモック化し、効率良く比較しながらデバッグできるのか?」面白い課題だと思いました。