今日はウェブ決済サービスを提供するpay.jpのAPIをたたいてみます。管理画面からだと取引履歴が10件づつしか見れなく・・不便すぎて今ここにいます。。
オフィシャルドキュメントはこちら
https://pay.jp/docs/api/
決済一覧を取得
時間を指定して決済データ一覧を取得してみます。
こちらが決済データ一覧を取得するメソッドのドキュメント
https://pay.jp/docs/api/#%E6%94%AF%E6%89%95%E3%81%84%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E5%8F%96%E5%BE%97
リクエスト
API側に渡せるパラメターは、下記。
名前 | タイプ | 備考 |
---|---|---|
imit | Integer | 取得するデータ数の最大値(1~100まで)。指定がない場合は 10 となる。 |
offset | Integer | 基準点からのデータ取得を行う開始位置。指定がない場合は 0 となる。 |
since | Integer | タイムスタンプ、指定したタイムスタンプ以降に作成されたデータのみ取得 |
until | Integer | タイムスタンプ、指定したタイムスタンプ以前に作成されたデータのみ取得 |
customer | String | 絞り込みたい顧客ID |
subscription | String | 絞り込みたい定期課金ID |
レスポンス
レスポンスオブジェクトは下記
public class Card
{
public string address_city { get; set; }
public string address_line1 { get; set; }
public string address_line2 { get; set; }
public string address_state { get; set; }
public string address_zip { get; set; }
public string address_zip_check { get; set; }
public string brand { get; set; }
public string country { get; set; }
public int created { get; set; }
public string cvc_check { get; set; }
public object customer { get; set; }
public int exp_month { get; set; }
public int exp_year { get; set; }
public string fingerprint { get; set; }
public string id { get; set; }
public string last4 { get; set; }
public string name { get; set; }
public string @object { get; set; }
}
public class Datum
{
public int amount { get; set; }
public int amount_refunded { get; set; }
public bool captured { get; set; }
public int captured_at { get; set; }
public Card card { get; set; }
public int created { get; set; }
public string currency { get; set; }
public string customer { get; set; }
public string description { get; set; }
public object expired_at { get; set; }
public object failure_code { get; set; }
public object failure_message { get; set; }
public string id { get; set; }
public bool livemode { get; set; }
public object metadata { get; set; }
public string @object { get; set; }
public bool paid { get; set; }
public object refund_reason { get; set; }
public bool refunded { get; set; }
public object subscription { get; set; }
}
public class TransactionList
{
public int count { get; set; }
public List<Datum> data { get; set; }
public bool has_more { get; set; }
public string @object { get; set; }
public string url { get; set; }
}
オーソライズ、認証
PAY.JPのAPIを利用するには、ユーザー登録を行い、APIページからAPIキーを取得してください。 テスト用のキーでは、本番の支払い処理を行うサーバーへは接続されず、実際の支払い情報として計上されることもありません。本番用のキーは、本番申請を行うことで利用できるようになります。
とのことです。
パブリックキー HTML内に埋め込むトークン生成用のパブリックキー
シークレットキー サーバー側からBasic認証のユーザーネームとして渡すシークレットキー
通常の認証は、シークレットキーをユーザーネームとして扱い、Basic認証経由で行われます。パブリックキーは、あなたの決済画面のHTML内に組み込む公開用のAPIキーで、クレジットカードのトークンを生成する際に使用します。 シークレットキーは、全てのAPIリクエスト操作が可能となる重要なキーなので、くれぐれ も取扱いにご注意ください。
シークレットキーとパスワードをBasic認証経由で行うとのことで、下記のコードを用意
// Set Token
var base64authorization = Convert.ToBase64String(Encoding.ASCII.GetBytes(token + ":" + pass));
APIを叩いてみる
今回はシンプルにペラアプリケーションとしてHomeコントローラー/Indexビューページに作成しました。一度100件づつしか取得できないので、100以上ある場合はhas_moreがfalseになるまで100件づつ取得しています。あまり大量のデータを取得しようとするとPay.jpへの負担となるかもしれないので利用上の注意が必要です。今回は制限をかけていませんが、なにかしらの制限を設ける事をお勧めします。
/// <summary>
/// Get specific result
/// </summary>
/// <param name="token"></param>
/// <param name="pass"></param>
/// <param name="fromDateTime"></param>
/// <param name="toDateTime"></param>
/// <param name="inCsv"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> Index(string token, string pass, DateTime fromDateTime, DateTime toDateTime, bool inCsv)
{
// Set Date, if not set
if (fromDateTime <= DateTime.MinValue && toDateTime <= DateTime.MinValue)
{
fromDateTime = DateTime.UtcNow.AddHours(-4);
toDateTime = DateTime.UtcNow.AddHours(-1);
}
// Set ViewData
ViewData["FROMDATETIME"] = fromDateTime;
ViewData["TODATETIME"] = toDateTime;
ViewData["TOKEN"] = token;
ViewData["PASS"] = pass;
// Token must be set first
if (string.IsNullOrEmpty(token))
{
return View();
}
// Token
var payjpToken = token + ":" + pass;
// Convert to DateTimeOffset
var fromDateTimeOffset = new DateTimeOffset(fromDateTime.Ticks, new TimeSpan(+09, 00, 00));
var toDateTimeOffset = new DateTimeOffset(toDateTime.Ticks, new TimeSpan(+09, 00, 00));
// To Unix seconds
var fromDateUnixSeconds = fromDateTimeOffset.ToUnixTimeSeconds();
var toDateUnixSeconds = toDateTimeOffset.ToUnixTimeSeconds();
// Set Path
var path = "charges?";
path += "since=" + fromDateUnixSeconds + "&until=" + toDateUnixSeconds;
path += "&offset=0&limit=100";
// Set Token
var base64authorization = Convert.ToBase64String(Encoding.ASCII.GetBytes(payjpToken));
// Call API
var client = new HttpClient();
var result = new ChargeListResponse();
client.BaseAddress = new Uri(payJpBaseUrl);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64authorization);
HttpResponseMessage response = await client.GetAsync(path);
// Make sure the call was a success
if (response.IsSuccessStatusCode)
{
result = await response.Content.ReadAsAsync<ChargeListResponse>();
}
// see if there are more
for (int i = 1; result.has_more; i++)
{
path = "charges?";
path += "since=" + fromDateUnixSeconds + "&until=" + toDateUnixSeconds;
path += "&offset=" + i * 100 + "&limit=100";
response = await client.GetAsync(path);
// Make sure the code was a success
if (response.IsSuccessStatusCode)
{
var tresult = await response.Content.ReadAsAsync<ChargeListResponse>();
// Add to result
result.count += tresult.count;
result.data.AddRange(tresult.data);
result.has_more = tresult.has_more;
}
}
client.Dispose();
return View(result);
}
結果を表示する
Viewです。
@model ChargeListResponse
@{
ViewData["Title"] = "レポート";
}
<div class="container-fluid py-2">
<h1>@ViewData["Title"]</h1>
<hr />
@using (Html.BeginForm("Index", "PayJp", FormMethod.Post))
{
<div class="input-group mb-3">
@Html.TextBox("token", ViewData["TOKEN"], new { @class = "form-control", @placeholder = "本番秘密鍵" })
@Html.TextBox("pass", ViewData["PASS"], new { @type = "password", @class = "form-control", @placeholder = "パスワード" })
</div>
<div class="input-group mb-3">
@Html.TextBox("fromDateTime", ViewData["FROMDATETIME"], new { @class = "form-control" })
@Html.TextBox("toDateTime", ViewData["TODATETIME"], new { @class = "form-control" })
</div>
<div class="small">
in JST
</div>
<div class="form-group">
<button class="btn btn-primary rounded-0" type="submit">検索</button>
</div>
}
<hr />
@if (Model != null)
{
<div class="d-flex justify-content-between">
<div>
@Model.count<text>データ</text>
</div>
</div>
<hr />
@if (Model.data != null)
{
<div class="table-responsive">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Amount</th>
<th>Captured</th>
<th>Captured_at</th>
<th>Card.address_city</th>
<th>Card.address_line1</th>
<th>Card.address_line2</th>
<th>Card.address_state</th>
<th>Card.address_zip</th>
<th>Card.address_zip_check</th>
<th>Card.brand</th>
<th>Card.country</th>
<th>Card.created</th>
<th>Card.customer</th>
<th>Card.cvc_check</th>
<th>Card.exp_month</th>
<th>Card.exp_year</th>
<th>Card.fingerprint</th>
<th>Card.id</th>
<th>Card.name</th>
<th>Card.last4</th>
<th>Created</th>
<th>Currency</th>
<th>Customer</th>
<th>Description</th>
<th>Expired_at</th>
<th>Failure_code</th>
<th>Failure_message</th>
<th>Id</th>
<th>Livemode</th>
<th>Metadata</th>
<th>Paid</th>
<th>Refunded</th>
<th>Refund_reason</th>
<th>Subscription</th>
</tr>
</thead>
<tbody>
@foreach (var record in Model.data)
{
<tr>
<td>@record.amount</td>
<td>@record.captured</td>
<td>@record.captured_at</td>
<td>@record.card.address_city</td>
<td>@record.card.address_line1</td>
<td>@record.card.address_line2</td>
<td>@record.card.address_state</td>
<td>@record.card.address_zip</td>
<td>@record.card.address_zip_check</td>
<td>@record.card.brand</td>
<td>@record.card.country</td>
<td>@record.card.created</td>
<td>@record.card.customer</td>
<td>@record.card.cvc_check</td>
<td>@record.card.exp_month</td>
<td>@record.card.exp_year</td>
<td>@record.card.fingerprint</td>
<td>@record.card.id</td>
<td>@record.card.name</td>
<td>@record.card.last4</td>
<td>@record.created</td>
<td>@record.currency</td>
<td>@record.customer</td>
<td>@record.description</td>
<td>@record.expired_at</td>
<td>@record.failure_code</td>
<td>@record.failure_message</td>
<td>@record.id</td>
<td>@record.livemode</td>
<td>@record.metadata</td>
<td>@record.paid</td>
<td>@record.refunded</td>
<td>@record.refund_reason</td>
<td>@record.subscription</td>
</tr>
}
</tbody>
</table>
</div>
}
}
else
{
<div>
本番秘密鍵とパスワードを入力してください。
</div>
}
</div>
おわり
一旦以上です。