Visaの新サービス(Visa Developer)のサンプルコードをObjective−Cに変換してみた。

Visaの新サービス(Visa Developer)のサンプルコードをObjective−Cに変換してみた。

Clock Icon2016.02.19

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

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のページに、このサービスを利用する際のスタート要領が紹介されています。

Getting Started Guide

これによると、概ね次のとおりです。

(1) API検索

トップページにAPIの検索とドキュメントへのリンクがあります。

001

(2) サンドボックス

同社で提供するサンドボックス(ブラウザインターフェース)でそれをテストできます。

002

(3) 開発者登録

独自のテストアプリを作成し、WebAPIでアクセスするには、認証コードが必要なため、開発者として登録が必要になります。 アカウント作成は、無料であり、好きなAPIを組合わせて任意のアプリを作成し、試験用のWebAPIサーバで試験ができます。

003

(4) アプリ申請

作成したアプリは、審査が必要です。連絡してレビューを受け、契約、価格設定等の手順へ進みます。

004

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

005

しかし、残念ながら、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;
}

github サンプルコード(http://github.com/furuya02/CybersourceSample/blob/master/CybersourceSample/ViewController.m)

実行結果は、次の通りです。

013

6 デバッグ

簡単にできたと思いますか〜 それが、中々難しかったです。

データの送受信ぐらいまでは、あっさり行ったのですが、認証エラー(401)が中々消えませんでした。

つまらない話しですが、今回、既存で用意されているC#のコードと比較しながら行った、デバッグ要領を紹介させて下さい。

(1) SHA256

他の言語だと、結構、関数一発で生成できるSHA256ですが、Objective-Cは、ちょっと手間がかかりました。 最終的には、既存サンプル(C#)での出力と同じになることを目指して進めました。

既存サンプル(C#)の実行結果

006

Objective−Cの実行結果

007

(2) x-pay-token

今回、選択したShared Secret認証では、x-pay-tokenという認証用の文字列を生成して、ヘッダに入れる必要があるのですが、これが、結構ややこしかったです。

簡単に言うと、「シークレットキー」と「現在時間」と「APIURL」と「レクエストの内容」を繋げて、先ほどのSHA256でハッシュ化し、再び現在時刻と繋げたものです。

どれか、1つでも間違うと認証が通らないので、どこで間違っているのか突き止めるのは、結構大変でした。

結論的には、現在時間以外は、同じに出来るので、timeStampを返すところだけモックで固定値にして、比較しながら作業しました。

現在時間を固定

012

既存サンプル(C#)の実行結果

010

Objectiv−Cの実行結果

011

現在時間の生合成も認証の一つらしく、サーバへのアクセス時間と数10秒ぐらいずれると、やはり認証エラーとなるので、忙しかったです。

7 最後に

今回は、単純に他の言語を移植するというだけの作業でしたが、色々考えさせられるものがありました。

既に、正常に動作している物が1つでもる場合・・・「何処をモック化し、効率良く比較しながらデバッグできるのか?」面白い課題だと思いました。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.