PayPayのOpen Payment APIのSDKとして提供されているのは、Python、Node、Java、PHPのみでPL/SQLは含まれていません。また、ドキュメントのサンプル・コードも同様です。
Oracle APEXにPayPayのWeb決済を組み込むには、API認証をできるようにする必要があります。PayPayが提供しているAPI認証の説明を読んで、HMACオブジェクトを生成するPL/SQLパッケージを作ってみました。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
create or replace package util_paypay_api | |
as | |
/** | |
* PayPayのAPI認証に必要なHMAC authヘッダーに使用する | |
* HMACオブジェクトを生成する。 | |
* | |
* 参照: https://www.paypay.ne.jp/opa/doc/jp/v1.0/webcashier#tag/API | |
* | |
* @param p_request_body リクエストで渡されるbody。 | |
* @param p_content_type リクエストヘッダーで渡されるコンテンツタイプ。 | |
* @param p_request_url リクエストURL | |
* @param p_http_method HTTPメソッド | |
* @param p_nonce ランダムに生成された文字列。8文字(多分8バイト)が推奨。null指定でランダムに生成した値を返す。 | |
* @param p_epoch 現在のエポックタイムスタンプ。サーバー時刻との差が2分未満。null指定で現在時刻を返す。 | |
* @param p_apikey PayPayから発行されたAPI Key。 | |
* @param p_apikey_secret PayPayから発行されたAPI Key secret。 | |
* | |
* @p_hash Base64でエンコーディングされたコンテンツのハッシュ値。Step1の値。 | |
* @p_macdata Base64でエンコーディングされたHMACオブジェクト。 | |
* @p_header HMAC authヘッダーに与える値。 | |
*/ | |
procedure generate_hmac_auth( | |
p_request_body in varchar2 default null | |
,p_content_type in varchar2 default null | |
,p_request_url in varchar2 | |
,p_http_method in varchar2 | |
,p_nonce in out varchar2 | |
,p_epoch in out varchar2 | |
,p_apikey in varchar2 | |
,p_apikey_secret in varchar2 | |
,p_hash out varchar2 | |
,p_macdata out varchar2 | |
,p_header out varchar2 | |
); | |
/** | |
* generate_hmac_authの簡易版。 | |
*/ | |
function generate_hmac_auth_header( | |
p_request_body in varchar2 default null | |
,p_content_type in varchar2 default null | |
,p_request_url in varchar2 | |
,p_http_method in varchar2 | |
,p_apikey in varchar2 | |
,p_apikey_secret in varchar2 | |
) | |
return varchar2; | |
end util_paypay_api; | |
/ | |
create or replace package body util_paypay_api | |
as | |
C_DELIMITER constant varchar2(1) := CHR(10); | |
/* | |
* OracleのTIMESTAMP型のデータをUNIX時間に変換する。 | |
*/ | |
function unixtime(p_timestamp in timestamp) | |
return pls_integer | |
is | |
l_date date; | |
l_epoc number; | |
begin | |
l_date := sys_extract_utc(p_timestamp); | |
l_epoc := l_date - date'1970-01-01'; | |
return l_epoc * 24 * 60 * 60; | |
end unixtime; | |
/** | |
* RAW型をBASE64エンコードした文字列として返す。 | |
*/ | |
function to_base64_from_raw(t in raw) | |
return varchar2 | |
as | |
l_base64 varchar2(32767); | |
begin | |
l_base64 := utl_raw.cast_to_varchar2(utl_encode.base64_encode(t)); | |
l_base64 := replace(l_base64, chr(13)||chr(10), ''); | |
/* translateは不要 */ | |
-- l_base64 := trim(translate(l_base64, '+/=', '-_ ')); | |
return l_base64; | |
end to_base64_from_raw; | |
/** | |
* 一時LOBに文字列を追記する。 | |
* | |
* @param src 一時BLOB | |
* @param str 追記する文字列。デフォルトはラインフィード。 | |
*/ | |
procedure append_varchar2( | |
src in out blob | |
,str in varchar2 default C_DELIMITER | |
) | |
as | |
l_raw raw(32767); | |
begin | |
/* strがnullの場合は何もしない。 */ | |
if str is null then | |
return; | |
end if; | |
l_raw := utl_i18n.string_to_raw(str,'AL32UTF8'); | |
dbms_lob.writeappend( | |
lob_loc => src | |
,amount => utl_raw.length(l_raw) | |
,buffer => l_raw | |
); | |
end append_varchar2; | |
procedure generate_hmac_auth( | |
p_request_body in varchar2 | |
,p_content_type in varchar2 | |
,p_request_url in varchar2 | |
,p_http_method in varchar2 | |
,p_nonce in out varchar2 | |
,p_epoch in out varchar2 | |
,p_apikey in varchar2 | |
,p_apikey_secret in varchar2 | |
,p_hash out varchar2 | |
,p_macdata out varchar2 | |
,p_header out varchar2 | |
) | |
as | |
l_content_type varchar2(32767); | |
l_source_blob blob; | |
l_hash raw(32767); | |
l_key raw(32767); | |
l_mac raw(32767); | |
begin | |
/* epochがnullであれば、現在時刻のepochを設定する。 */ | |
if p_epoch is null then | |
p_epoch := to_char(unixtime(current_timestamp)); | |
end if; | |
/* nonceがnullであれば、8文字程度の文字列を設定する。 */ | |
if p_nonce is null then | |
p_nonce := lower(rawtohex(dbms_crypto.randombytes(4))); | |
end if; | |
/* Step 1 */ | |
if p_http_method <> 'GET' and p_request_body is not null then | |
dbms_lob.createTemporary( | |
lob_loc => l_source_blob | |
,cache => true | |
,dur => dbms_lob.call | |
); | |
append_varchar2(l_source_blob, p_content_type); | |
append_varchar2(l_source_blob, p_request_body); | |
l_hash := dbms_crypto.hash( | |
src => l_source_blob | |
,typ => dbms_crypto.hash_md5 | |
); | |
dbms_lob.freeTemporary(l_source_blob); | |
p_hash := to_base64_from_raw(l_hash); | |
l_content_type := p_content_type; | |
else | |
/* 本体がないときはemptyとする。 */ | |
p_hash := 'empty'; | |
l_content_type := 'empty'; | |
end if; | |
/* Step 2 */ | |
dbms_lob.createTemporary( | |
lob_loc => l_source_blob | |
,cache => true | |
,dur => dbms_lob.call | |
); | |
append_varchar2(l_source_blob, p_request_url); | |
append_varchar2(l_source_blob); | |
append_varchar2(l_source_blob, p_http_method); | |
append_varchar2(l_source_blob); | |
append_varchar2(l_source_blob, p_nonce); | |
append_varchar2(l_source_blob); | |
append_varchar2(l_source_blob, p_epoch); | |
append_varchar2(l_source_blob); | |
append_varchar2(l_source_blob, l_content_type); | |
append_varchar2(l_source_blob); | |
append_varchar2(l_source_blob, p_hash); | |
/* Step 3 */ | |
l_key := utl_i18n.string_to_raw(p_apikey_secret,'AL32UTF8'); | |
l_mac := dbms_crypto.mac( | |
src => l_source_blob | |
,typ => dbms_crypto.HMAC_SH256 | |
,key => l_key | |
); | |
dbms_lob.freeTemporary(l_source_blob); | |
p_macdata := to_base64_from_raw(l_mac); | |
/* ヘッダーの生成 */ | |
p_header := p_apikey || ':' || p_macdata || ':' || p_nonce || ':' || p_epoch || ':' || p_hash; | |
end generate_hmac_auth; | |
function generate_hmac_auth_header( | |
p_request_body in varchar2 | |
,p_content_type in varchar2 | |
,p_request_url in varchar2 | |
,p_http_method in varchar2 | |
,p_apikey in varchar2 | |
,p_apikey_secret in varchar2 | |
) | |
return varchar2 | |
as | |
l_nonce varchar2(32767) := null; | |
l_epoch varchar2(32767) := null; | |
l_hash varchar2(32767); | |
l_macdata varchar2(32767); | |
l_header varchar2(32767); | |
begin | |
generate_hmac_auth( | |
p_request_body => p_request_body | |
,p_content_type => p_content_type | |
,p_request_url => p_request_url | |
,p_http_method => p_http_method | |
,p_nonce => l_nonce | |
,p_epoch => l_epoch | |
,p_apikey => p_apikey | |
,p_apikey_secret => p_apikey_secret | |
,p_hash => l_hash | |
,p_macdata => l_macdata | |
,p_header => l_header | |
); | |
return 'hmac OPA-Auth:' || l_header; | |
end generate_hmac_auth_header; | |
end util_paypay_api; | |
/ |
API認証のドキュメントにある認証オブジェクトに必要なパラメータに記載されているExampleの値を引数として、作成したプロシージャgenerate_hmac_authを呼び出してみます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
declare | |
l_hash varchar2(32767); | |
l_macdata varchar2(32767); | |
l_header varchar2(32767); | |
l_nonce varchar2(80); | |
l_epoch varchar2(80); | |
begin | |
l_nonce := 'acd028'; | |
l_epoch := '1579843452'; | |
util_paypay_api.generate_hmac_auth( | |
p_request_body => q'~{"sampleRequestBodyKey1":"sampleRequestBodyValue1","sampleRequestBodyKey2":"sampleRequestBodyValue2"}~' | |
,p_content_type => q'~application/json;charset=UTF-8;~' | |
,p_request_url => '/v2/codes' | |
,p_http_method => 'POST' | |
,p_nonce => l_nonce | |
,p_epoch => l_epoch | |
,p_apikey => 'APIKeyGenerated' | |
,p_apikey_secret => 'APIKeySecretGenerated' | |
,p_hash => l_hash | |
,p_macdata => l_macdata | |
,p_header => l_header | |
); | |
dbms_output.put_line(l_hash); | |
dbms_output.put_line(l_macdata); | |
dbms_output.put_line(l_header); | |
end; |
結果として以下が印刷されました。
1j0FnY4flNp5CtIKa7x9MQ==
NW1jKIMnzR7tEhMWtcJcaef+nFVBt7jjAGcVuxHhchc=
APIKeyGenerated:NW1jKIMnzR7tEhMWtcJcaef+nFVBt7jjAGcVuxHhchc=:acd028:1579843452:1j0FnY4flNp5CtIKa7x9MQ==
Base64でエンコーディングされたHMACオブジェクトの結果が同じなので、PL/SQLのコードも概ね正しい実装になっていそうです。
実装を検証するために、PayPayのAPI認証のドキュメントに記載されているJavaのサンプルを、同じ引数を与えて実行してみます。
1j0FnY4flNp5CtIKa7x9MQ==
NW1jKIMnzR7tEhMWtcJcaef+nFVBt7jjAGcVuxHhchc=
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.security.MessageDigest; | |
import java.nio.charset.StandardCharsets; | |
import java.util.Base64; | |
import javax.crypto.spec.SecretKeySpec; | |
import javax.crypto.Mac; | |
public class PayPayAuth { | |
public static void main(String[] args) | |
{ | |
try { | |
/* Step 1 */ | |
String requestBody = "{\"sampleRequestBodyKey1\":\"sampleRequestBodyValue1\",\"sampleRequestBodyKey2\":\"sampleRequestBodyValue2\"}"; | |
String contentType = "application/json;charset=UTF-8;"; | |
MessageDigest md = MessageDigest.getInstance("MD5"); | |
md.update(contentType.getBytes(StandardCharsets.UTF_8)); | |
md.update(requestBody.getBytes(StandardCharsets.UTF_8)); | |
String hash = new String( | |
Base64.getEncoder().encode(md.digest()), | |
StandardCharsets.UTF_8); | |
System.out.println(hash); | |
/* Step 2 */ | |
String requestUrl = "/v2/codes"; | |
String httpMethod = "POST"; | |
String nonce = "acd028"; | |
String epoch = "1579843452"; | |
String DELIMITER = "\n"; | |
byte[] hmacData = new StringBuffer() | |
.append(requestUrl) | |
.append(DELIMITER) | |
.append(httpMethod) | |
.append(DELIMITER) | |
.append(nonce) | |
.append(DELIMITER) | |
.append(epoch) | |
.append(DELIMITER) | |
.append(contentType) | |
.append(DELIMITER) | |
.append(hash != null ? hash : "") | |
.toString() | |
.getBytes(StandardCharsets.UTF_8); | |
/* Step 3 */ | |
String apiKeySecret = "APIKeySecretGenerated"; | |
byte[] dataToSign = hmacData; | |
SecretKeySpec signingKey = new SecretKeySpec(apiKeySecret.getBytes(StandardCharsets.UTF_8), | |
"HmacSHA256"); | |
Mac sha256HMAC = Mac.getInstance("HmacSHA256"); | |
sha256HMAC.init(signingKey); | |
byte[] rawHmac = sha256HMAC.doFinal(dataToSign); | |
System.out.println(Base64.getEncoder().encodeToString(rawHmac)); | |
} catch (Exception e) { | |
e.printStackTrace(System.err); | |
} | |
} | |
} |
% javac PayPayAuth.java
% java PayPayAuth
1j0FnY4flNp5CtIKa7x9MQ==
NW1jKIMnzR7tEhMWtcJcaef+nFVBt7jjAGcVuxHhchc=
%
以下の結果が得られます。
1j0FnY4flNp5CtIKa7x9MQ==
NW1jKIMnzR7tEhMWtcJcaef+nFVBt7jjAGcVuxHhchc=
Base64でエンコーディングされたHMACオブジェクトの結果が同じなので、PL/SQLのコードも概ね正しい実装になっていそうです。
API認証に使用するHMACオブジェクトを生成できたので、APEXからPayPay Open Payment APIを呼び出せるでしょう。
完