2023年2月28日火曜日

PayPay Open Payment APIの呼び出しに必要なHMACオブジェクトを生成する

 PayPayのOpen Payment APIのSDKとして提供されているのは、Python、Node、Java、PHPのみでPL/SQLは含まれていません。また、ドキュメントのサンプル・コードも同様です。

Oracle APEXにPayPayのWeb決済を組み込むには、API認証をできるようにする必要があります。PayPayが提供しているAPI認証の説明を読んで、HMACオブジェクトを生成するPL/SQLパッケージを作ってみました。

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を呼び出してみます。

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==

実装を検証するために、PayPayのAPI認証のドキュメントに記載されているJavaのサンプルを、同じ引数を与えて実行してみます。

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を呼び出せるでしょう。

LINE公式アカウントから返信および受信した写真を保存する

 LINE Messaging APIを発行するためのWeb資格証明の作成方法について、以前の記事で紹介しています。折角なので、この記事で作成したWeb資格証明(静的識別子はLINE_CHANNEL_ACCESS_TOKEN)を使用したAPEXアプリケーションを作ってみます。

作成するAPEXアプリケーションに、以下の2つの操作を実装します。

  • LINE公式アカウントが受信したメッセージに返答する
  • LINE公式アカウントが受信した写真を保存する
LINE公式アカウントが受信したメッセージは、Webhookによって表LINE_MESSAGESに保存されます。この処理はすでに作成済みで、そのまま流用します。

作成したアプリケーションは以下のように動作します。LINE公式アカウントがメッセージを受信したことはAPEXアプリケーションでは検知しないため、手動でページをリロードしてレポートを再表示しています。

 

LINEアプリケーションの画面です。メッセージありがとうございます!とあるのは、LINE公式アカウントにデフォルトで設定されている応答メッセージです。


以下よりAPEXアプリケーションの作成手順を紹介します。

クイックSQLの以下のモデルから、受信した写真を保存する表LINE_IMAGESを作成します。
# prefix: line
images
    message_id vc20 /pk
    content blob
    content_type vc80
SQLワークショップユーティリティクイックSQLより、表の作成を行います。左ペインにモデルを記述し、SQLの生成SQLスクリプトを保存レビューおよび実行を順次クリックします。表LINE_IMAGESの作成までを実施し、APEXアプリケーションは作成しません。


LINE公式アカウントが受信したメッセージは、対話モード・レポートで表示します。以下のDDLを実行し、対話モード・レポートのソースとして使用するビューLINE_MESSAGES_Vを作成します。

create or replace view line_messages_v
as
select
cm.id, cm.is_valid, cm.received_date
,cm.source_type, cm.source_user_id, cm.reply_token
,cm.message_type, cm.message_text
,cm.message_id, cm.content_provider_type
,dbms_lob.getlength(ig.content) content, ig.content_type
from
(
select m.id, m.is_valid, m.received_date
,c.source_type, c.source_user_id, c.reply_token
,c.message_type, c.message_text
,c.message_id, c.content_provider_type
from line_messages m,
json_table(m.content, '$'
columns
(
source_type varchar2(20) path '$.events.source.type'
,source_user_id varchar2(100) path '$.events.source.userId'
,reply_token varchar2(100) path '$.events.replyToken'
,message_type varchar2(20) path '$.events.message.type'
,message_text varchar2(4000) path '$.events.message.text'
,message_id varchar2(100) path '$.events.message.id'
,content_provider_type varchar2(20) path '$.events.message.contentProvider.type'
)
) as c
where c.message_id is not null
) cm left outer join line_images ig
on cm.message_id = ig.message_id
/

1行のDDLなので、SQLコマンドに貼り付けて実行します。


アプリケーション作成ウィザードを起動し、空のアプリケーションを作成します。

名前LINE応答とします。それ以外は変更せず、アプリケーションの作成をクリックします。


アプリケーションが作成されたら、ページの作成を実行します。


 作成するページのタイプとして、対話モード・レポートを選択します。


ページ定義ページ番号とします。後ほど記述するコードにページ番号が含まれているため、ここでは必ず2を指定します。レポート・ページの名前LINEメッセージページ・モード標準とします。

フォーム・ページを含めるONにします。フォーム・ページ番号フォーム・ページ名受信メッセージフォーム・ページ・モードモーダル・ダイアログを選択します。受信したメッセージへの返信と受信した写真の保存は、フォームのページに実装します。

データ・ソース表/ビューの名前として、先ほど作成したビューLINE_MESSAGES_Vを選択します。

ナビゲーションブレッドクラムの使用ナビゲーションの使用ともにデフォルトのONを選択します。

へ進みます。


主キー主キー列1としてMESSAGE_ID(Varchar2)を選択します。

以上で、ページの作成をクリックします。


対話モード・レポートとフォームのページが作成されます。

作成された対話モード・レポートの列CONTENTを選択し、タイプBLOBのダウンロードに変更します。LINE公式アカウントが受信した写真を列CONTENTからダウンロードできるようにします。

BLOB属性表名LINE_IMAGESBLOB列CONTENT主キー列1MESSAGE_IDMIMEタイプ列CONTENT_TYPEを選択します。


ファイル名列を指定すると、写真をダウンロードする際にファイル名が付きます。LINE公式アカウントが受信した写真には名前が付いていないため、ファイル名列は指定していません。

ここで一覧されるメッセージはLINE公式アカウントが受信し、Webhookの処理で表に書き込まれます。フォームからメッセージを投入することは無いため、ボタンCREATE削除します。


以上で、受信したメッセージを一覧する対話モード・レポートはできました。

続いてページ番号3のフォームのページを変更します。

非表示になっている主キーのページ・アイテムP3_MESSAGE_IDを除いて、すべてのページ・アイテムを表示のみに変更します。


少しフォームを見やすくするため、ページ・アイテムP3_IS_VALIDP3_RECEIVED_DATEP3_SOURCE_TYPEP3_CONTENTP3_CONTENT_TYPEを選択し、レイアウト新規行の開始OFFに変更します。


返信する文字列を保持するページ・アイテムP3_REPLY_TEXTを作成します。

識別名前P3_REPLY_TEXTタイプとしてテキスト領域を選択します。ラベルReply Textとします。

返信するときだけページ・アイテムが表示されるように、サーバー側の条件としてタイプアイテム = 値を選択し、アイテムP3_MESSAGE_TYPEtextを指定します。


このフォームに実装する処理は返信写真の保存です。その処理に合わせてボタンを変更します。

最初にボタンDELETE削除します。


ボタンSAVEラベル保存に変更します。動作データベース・アクション- 選択 -に変更し、データベース・アクションとして未指定の状態にします。

サーバー側の条件タイプとしてアイテム = 値を選択します。アイテムP3_CONTENT_PROVIDER_TYPEを選択し、lineを指定します。


ボタンCREATEボタン名REPLYに変更し、ラベル返信とします。動作データベース・アクション- 選択 -に変更します。

サーバー側の条件タイプアイテム = 値を選択します。アイテムとしてP3_MESSAGE_TYPEを選択し、textを指定します。


左ペインでプロセス・ビューを開きます。これよりLINE Messaging APIを発行し、返信と写真の保存を行うプロセスを実装します。

ウィザードにより作成されたプロセスプロセス・フォーム受信メッセージ削除します。


プロセスを作成しダイアログを閉じるの上に配置します。

LINE Messaging APIのReply APIについては、以下を参照しています。応答メッセージは応答トークン(replyToken)を使って送信しています。ドキュメントによると応答トークンは1分以内で使用する必要がある、とのことなので実際にはボットなどによる自動返信に用いるもので、今回のサンプルのように人による返信の場合は、応答メッセージ以外を使用することになるでしょう。
https://developers.line.biz/ja/reference/messaging-api/#send-reply-message

Messaging APIのReply APIはメッセージとしてカウントされませんが、Push APIはカウントされます。
https://www.linebiz.com/jp/service/line-official-account/plan/

識別名前Replyとします。タイプとしてコードの実行を選択し、ソースPL/SQLコードとして以下を記述します。

declare
C_ENDPOINT constant varchar2(80) := 'https://api.line.me/v2/bot/message/reply';
l_request json_object_t;
l_request_clob clob;
l_messages json_array_t;
l_message json_object_t;
l_response_clob clob;
begin
l_request := json_object_t();
l_request.put('replyToken', :P3_REPLY_TOKEN);
l_message := json_object_t();
l_message.put('type','text');
l_message.put('text', :P3_REPLY_TEXT);
l_messages := json_array_t();
l_messages.append(l_message);
l_request.put('messages', l_messages);
l_request_clob := l_request.to_clob();
/* 返信 */
apex_debug.info(l_request_clob);
apex_web_service.clear_request_headers();
apex_web_service.set_request_headers('Content-Type','application/json',p_reset = false);
l_response_clob := apex_web_service.make_rest_request(
p_url => C_ENDPOINT
,p_http_method => 'POST'
,p_body => l_request_clob
,p_credential_static_id => 'LINE_CHANNEL_ACCESS_TOKEN'
);
if apex_web_service.g_status_code <> 200 then
raise_application_error(-20001, 'LINE Reply Error = ' || apex_web_service.g_status_code);
end if;
end;
view raw line_reply.sql hosted with ❤ by GitHub

サーバー側の条件ボタン押下時REPLYを指定します。


同様の手順にてプロセスSaveを作成します。ソースPL/SQLコードとして以下を記述します。LINE Messaging APIのドキュメントの以下を参照しています。
https://developers.line.biz/ja/docs/messaging-api/receiving-messages/#getting-content-sent-by-users

declare
C_ENDPOINT constant varchar2(80) := 'https://api-data.line.me/v2/bot/message/{messageId}/content';
l_url varchar2(200);
l_response_blob blob;
l_count number;
l_content_type varchar2(80);
begin
select count(*) into l_count from line_images where message_id = :P3_MESSAGE_ID;
if l_count > 0 then
return; -- すでに保存済みであれば何もしない。
end if;
/* データを取得する */
l_url := replace(C_ENDPOINT, '{messageId}', :P3_MESSAGE_ID);
-- apex_debug.info(l_url);
apex_web_service.clear_request_headers();
l_response_blob := apex_web_service.make_rest_request_b(
p_url => l_url
,p_http_method => 'GET'
,p_credential_static_id => 'LINE_CHANNEL_ACCESS_TOKEN'
);
if apex_web_service.g_status_code <> 200 then
raise_application_error(-20001, 'LINE Content Error = ' || apex_web_service.g_status_code);
end if;
/* Content-Typeヘッダーを取得する */
for i in 1..apex_web_service.g_headers.count
loop
if apex_web_service.g_headers(i).name = 'Content-Type' then
l_content_type := apex_web_service.g_headers(i).value;
exit;
end if;
end loop;
/* 取得したイメージを表LINE_IMAGESに保存する。 */
insert into line_images(message_id, content, content_type)
values(:P3_MESSAGE_ID, l_response_blob, l_content_type);
end;

サーバー側の条件ボタン押下時SAVEを指定します。


プロセスダイアログを閉じるを選択し、サーバー側の条件SAVE,REPLYに変更します。


以上でアプリケーションは完成です。アプリケーションを実行すると、記事の先頭のGIF動画のように動作します。

今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/line-messaging-api-sample.zip

Oracle APEXのアプリケーション作成の参考になれば幸いです。

2023年2月27日月曜日

OCI Languageサービスを使用して翻訳を行う

 オラクルのOCI Languageに無料枠があったので、APEXでOCI Languageの翻訳サービスを呼び出すアプリケーションを作ってみました。


OCIでのAPIユーザーの作成手順およびAPEXでのWeb資格証明の作成手順については、以前に書いた記事を参考にしています。

APEXからOCIオブジェクト・ストレージを操作する(1) - APIユーザーの作成
APEXからOCIオブジェクト・ストレージを操作する(4) - Web資格証明の作成

上記の手順に追加して、APIキーを持つユーザーにOCI Languageサービスの呼び出しを許可するため、ポリシーを作成します。

APIユーザーが含まれているグループAPEXObjectManagersである場合、ポリシー・ステートメントは以下になります。

allow group APEXObjectManagers to use ai-service-language-family in tenancy


APEX側の作業として、アプリケーション作成ウィザードを起動し、空のアプリケーションを作成します。

作成したアプリケーションのホーム・ページページ・デザイナで開き、ページ・アイテムボタンを作成します。

翻訳する文字列の言語を指定するページ・アイテムP1_SOURCE_LANGを作成します。タイプテキスト・フィールドラベルSource Langとします。ISO 639-1のコードを指定します。タイプ選択リストにすると、指定が容易になるでしょう。


翻訳する文字列を指定するページ・アイテムP1_SOURCE_TEXTを作成します。タイプテキスト領域ラベルSource Textとします。


同様に翻訳先の言語を指定するページ・アイテムP1_TARGET_LANGを作成します。


翻訳結果を保持するページ・アイテムP1_TARGET_TEXTを作成します。


最後に送信ボタンを作成します。

識別ボタン名SUBMITラベルSubmit動作アクションはデフォルトのページの送信のままとします。


左ペインでプロセス・ビューを開き、ボタンSUBMITを押した時に実行するプロセスTranslateを作成します。

ソースPL/SQLコードとして以下を記述します。

declare
/* OCI Languageの翻訳処理のエンドポイント */
C_ENDPOINT constant varchar2(120) := 'https://language.aiservice.us-ashburn-1.oci.oraclecloud.com/20221001/actions/batchLanguageTranslation';
l_request json_object_t;
l_request_clob clob;
l_response json_object_t;
l_response_clob clob;
l_documents json_array_t;
l_document json_object_t;
begin
/* 翻訳を依頼するメッセージを作成する。 */
l_request := json_object_t();
l_document := json_object_t();
l_document.put('key', '1');
l_document.put('text', :P1_SOURCE_TEXT);
l_document.put('languageCode',:P1_SOURCE_LANG);
l_documents := json_array_t();
l_documents.append(l_document);
l_request.put('documents',l_documents);
l_request.put('targetLanguageCode',:P1_TARGET_LANG);
l_request_clob := l_request.to_clob();
/* 翻訳リクエストの発行 */
apex_web_service.clear_request_headers();
apex_web_service.set_request_headers('Content-Type','application/json');
l_response_clob := apex_web_service.make_rest_request(
p_url => C_ENDPOINT
,p_http_method => 'POST'
,p_body => l_request_clob
,p_credential_static_id => 'OCI_API_ACCESS'
);
/* 応答のすべてをJSON形式で確認する。 */
-- :P1_TARGET_TEXT := l_response_clob;
/* 翻訳された文章だけを確認する。 */
l_response := json_object_t(l_response_clob);
l_documents := l_response.get_array('documents');
/* 送信したkeyは1だけなので、翻訳された文章も1つだけ */
l_document := json_object_t(l_documents.get(0));
:P1_TARGET_TEXT := l_document.get_string('translatedText');
end;

サーバー側の条件ボタン押下時SUBMITを選択します。


以上でアプリケーションは完成です。実行すると記事の先頭のGIF動画のように動作します。

今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/oci-language-translate.zip

Oracle APEXのアプリケーション作成の参考になれば幸いです。

レンダリング・ツリーにチェックボックスが表示されたときの対処

 ページ・デザイナの右ペインに表示されるレンダリング・ツリーに、チェックボックスが現れる場合があります。以下のような状態です。

タッチ操作が有効なデバイスの場合上記の表示になりますが、タッチ操作が有効でないデバイスでも上記のように表示される場合があります。

原因と対処について、Scott WesleyさんがForumsに投稿されていました。

https://forums.oracle.com/ords/apexds/post/checkboxes-appearing-next-to-components-in-page-builder-5413

TwitterでMagaly Irahetaさんが、ブラウザのセッション・ストレージにあるORA_WWV_apex.core.userHasTouchedというキーを削除すると直ると教えてくれています。



セッション記憶域に保存されるAPEX.userHasTouchedについては、APEXのリリース・ノートのプライバシに関する通知に記載があります。

フォーラムに書かれている原因と、Twitterでの対処方法について確認してみます。

ページ・デザイナを開いてチェックボックスが表示されている状態で、ブラウザの開発者ツールを開きます。アプリケーション・タブを選択し、セッション・ストレージの内容を確認します。

キーORA_WWV_apex.core.userHasTouchedに値yが設定されていることが確認できます。


このキーを削除してページ・デザイナをリロードすると、チェックボックスは表示されなくなります。


キーORA_WWV_apex.core.userHasTouchedが作成されるタイミングですが、開発者ツールにてデバイス・エミュレーションを有効にし、どこでも良いので画面上でポインタをクリックすると、キーが作成されます。


キーを削除しても上記の操作を行うと、再度ORA_WWV_apex.core.userHasTouchedが作成されてチェックボックスが表示されます。

開発者ツールでの操作以外に、JavaScriptコンソールより以下を実行し、キーORA_WWV_apex.core.userHasTouchedを削除する、または、nを設定することができます。

window.sessionStorage.removeItem("ORA_WWV_apex.core.userHasTouched");

または

window.sessionStorage.setItem("ORA_WWV_apex.core.userHasTouched","n");

タッチ操作ができないデバイスで、チェックボックスが表示されるケースでは上記で対応できるはずです。ページ・デザイナでデバイス・エミュレーションを行う必要はないためです。

タッチ操作が有効なデバイスの場合は、ページ・デザイナの画面に触った時点でタッチ操作が有効と認識されて、それ以降はタッチスクリーン対応の表示に切り替わるようです。

2023年2月23日木曜日

LINE Messaging APIを使用する

Oracle APEXのアプリケーションよりLINEのMessaging APIを呼び出し、LINE公式アカウントよりメッセージの送受信を行うために必要な準備を行います。

概ね、チャネルアクセストークンを取得するための作業になります。

LINE Developesコンソールよりプロバイダーが作成済みであることを前提とします。以前の記事LINEアカウントによる認証などを参考にしてください。

LINE Messaging APIを使用するために、チャネルタイプMessaging APIであるチャネルを新規に作成します。チャネルの作成方法はLINEから提供されているドキュメントを参照するか、過去記事チャネルタイプLINEログインの手順とほぼ同じですので、そちらを参照してください。

新規チャネル作成Messaging APIを選択します。



チャネルを新規作成すると、チャネル名と同名のLINE公式アカウントが作成されます。作成当初はフリーの月額プランが選択されるようです。フリーのプランでは1000通まで無料でメッセージが送信できるとのことです。

LINE Official Account ManagerよりLINE公式アカウントの状況について確認できます。お金にかかわることもありますが、LINE公式アカウント自体の設定が多数あるため、事前にLINE公式アカウントについて理解しておく方が良いようです。Messaging APIはLINE公式アカウントで使える機能のうちの、ごく一部です。

これからMessaging APIを使う設定について説明します。

作成したMessaging APIチャネル基本設定チャネルシークレットアサーション署名キーあなたのユーザーIDが表示されています。

チャネルシークレットはLINE公式アカウントが受信したメッセージの署名の検証に使用します。アサーション署名キー(のID)はメッセージを送信するAPIの認証のために使用します。あたなのユーザーIDは、メッセージの送信をテストする際の宛先として使用します。

この他にチャネルIDも、Messaging APIの呼び出し時に使用します。



これからの作業でJSON Web Key、JSON Web Tokenやチャネルアクセストークンを生成します。それらの処理を実装したPL/SQLのパッケージUTIL_LINE_APIを作成します。LINEのドキュメントには色々なコンピュータ言語でのサンプルが掲載されていますが、PL/SQLのサンプルはありません。

パッケージのコードは以下になります。DBMS_CRYPTOのRSA署名の機能を使用しているため、Oracle Databaseは19c以上であることが前提です。

Oracle APEXでは、SQLワークショップSQLスクリプトを使って実行できます。

create or replace package util_line_api
as
/*
* チャネルアクセストークンv2.1を発行するURL
*/
C_TOKEN_URL constant varchar2(160) := 'https://api.line.me/oauth2/v2.1/token';
/**
* PKCSのフォーマットからアサーション署名キーとして登録する
* JSON Web Tokenを生成するのは大変なので、
* その部分の処理を行うファンクションを定義。
*
* 参照: https://developers.line.biz/ja/docs/messaging-api/generate-json-web-token/#create-an-assertion-signing-key
* useにsig、またはkey_opsに["verify"]のどちらを指定、とのことなのでuseを使うことにしています。
*
* @param p_kty 鍵で使用されている暗号アルゴリズムファミリー。RSA必須。
* @param p_alg 鍵で使用されるアルゴリズム。RS256必須。
* @param p_use 鍵の用途。sig必須。
* @param p_e 公開鍵を復元するための絶対値。概ね65537ですがデフォルト指定はしません。
* @param p_n 公開鍵を復元するための暗号指数。
* @return 生成されたJSON Web Key
*/
function generate_json_web_key(
p_kty in varchar2 default 'RSA'
,p_alg in varchar2 default 'RS256'
,p_use in varchar2 default 'sig'
,p_e in number
,p_n in varchar2
)
return varchar2;
/**
*  LINE公式アカウントが受信したメッセージの署名を確認する。
*
* 参照: https://developers.line.biz/ja/docs/messaging-api/receiving-messages/
*
* @param p_channel_secret Messaging APIのチャネルのチャネルシークレット
* @param p_signature HTTPヘッダーx-line-signatureとして渡される署名の値
* @param p_content 受信したメッセージ本体
* @return 受信した署名とメッセージから生成した署名が一致したら真を返す。
*/
function verify_response_signature(
p_channel_secret in varchar2
,p_signature in varchar2
,p_content in blob
)
return boolean;
/**
* アサーション署名キーに対応する秘密鍵を使ってJWTを作る。
*
* 元はGoogleのプロジェクトに登録したサービス・アカウントのキーより
* JWTを生成したコード。
*
* 参照:https://developers.line.biz/ja/docs/messaging-api/generate-json-web-token/#generate-jwt
*
* @param p_secret アサーション署名キーに対応した秘密キー。PKCS#1またはPKCS#8 - PEM
* @param p_kid チャネルに登録したアサーション署名キーのキーID
* @param p_channel_id Messaging APIのチャネルID
* @param p_aud https://api.line.me/ 変更不可
* @param p_iat トークンが有効になる開始時刻 - unixtimeではなくtimestamp型 - デフォルトは現在時刻
* @param p_duration JWTの有効期限。秒で指定する。最大30分。
* @param p_token_exp チャネルアクセストークンの有効期間。秒で指定する。最大30日。
* @return 生成されたJSON Web Token
*/
function generate_jwt(
p_secret in varchar2
,p_kid in varchar2 -- アサーション署名キーのkid
,p_channel_id in varchar2 -- チャネルIDがissとsubの値になる
,p_aud in varchar2 default 'https://api.line.me/'
/*
* Googleを踏襲してp_iatとp_durationを引数とするが、LINEでは
* iatは指定せず iat + duration を計算してexpに設定する。
*/
,p_iat in timestamp default current_timestamp
,p_duration in number default 1800 -- 秒で指定する
/*
* token_expはLINEでチャネルアクセストークンを取得するときに使用される。
*/
,p_token_exp in number
)
return varchar2;
/**
* LINEのトークンURLを呼び出して、チャネルアクセストークンを取得する。
* Web資格証明の静的IDが指定されてれば、取得したチャネルアクセストークンで更新する。
*
* 参照:https://developers.line.biz/ja/docs/messaging-api/generate-json-web-token/#issue_a_channel_access_token_v2_1
*
* @param p_jwt generare_jwtを呼び出して生成したJWT
* @param p_credential_static_id APEXに作成したWeb資格証明の静的ID
* @param p_expires_in 有効期限が切れるまでの秒数。出力値。
* @param p_key_id チャネルアクセストークンを識別するキーID。出力値。
* @return Authorizationヘッダーに指定するBearerで始まるチャネルアクセストークンの値
*/
function get_token(
p_jwt in varchar2
,p_credential_static_id in varchar2 default null
,p_expires_in out number
,p_key_id out varchar2
)
return varchar2;
end util_line_api;
/
create or replace package body util_line_api
as
/*
* 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;
/* BASE64のデコード */
function from_base64(t in varchar2) return varchar2 is
begin
return utl_raw.cast_to_varchar2(utl_encode.base64_decode(utl_raw.cast_to_raw(t)));
end from_base64;
/* BASE64へのエンコード - RAWより */
function to_base64_from_raw(t in raw) return varchar2 is
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), '');
return l_base64;
end to_base64_from_raw;
/* BASE64へのエンコード - VARCHAR2 */
function to_base64(t in varchar2) return varchar2 is
begin
return to_base64_from_raw(utl_raw.cast_to_raw(t));
end to_base64;
/* 秘密鍵を一行にする。 */
function convert_to_single_line(
p_string in varchar2
)
return varchar2
as
begin
return regexp_replace(
p_string
,'(-+((BEGIN|END) (RSA )?(PUBLIC|PRIVATE) KEY)-+\s?|\s)'
,''
);
end convert_to_single_line;
/*
* 以下LINE向けの実装。
*/
function generate_json_web_key(
p_kty in varchar2
,p_alg in varchar2
,p_use in varchar2
,p_e in number
,p_n in varchar2
)
return varchar2
as
l_jwt json_object_t;
l_result varchar2(32767);
l_e_raw raw(4);
l_e_str varchar2(16);
l_n_raw raw(256);
l_n_str varchar2(1028);
begin
l_jwt := json_object_t();
l_jwt.put('kty',p_kty);
l_jwt.put('alg',p_alg);
l_jwt.put('use',p_use);
/* e */
l_e_raw := hextoraw(trim(to_char(p_e,'XXXXXXX')));
l_e_str := to_base64_from_raw(l_e_raw);
/*
* nと同様に本来であればtranslateが必要だがデータが短いので省略。
*/
l_jwt.put('e',l_e_str);
/* n */
l_n_raw := hextoraw(p_n);
l_n_str := to_base64_from_raw(l_n_raw);
l_n_str := trim(translate(l_n_str, '+/=', '-_ '));
l_jwt.put('n',l_n_str);
l_result := l_jwt.to_string();
return l_result;
end generate_json_web_key;
function verify_response_signature(
p_channel_secret in varchar2
,p_signature in varchar2
,p_content in blob
)
return boolean
as
l_key raw(32);
l_mac raw(32);
l_content_signature varchar2(64);
begin
l_key := utl_raw.cast_to_raw(p_channel_secret);
l_mac := dbms_crypto.mac(
src => p_content
,typ => DBMS_CRYPTO.HMAC_SH256
,key => l_key
);
l_content_signature := to_base64_from_raw(l_mac);
return p_signature = l_content_signature;
end verify_response_signature;
/* JWTを生成する実装 */
function generate_jwt(
p_secret in varchar2
,p_kid in varchar2
,p_channel_id in varchar2
,p_aud in varchar2
,p_iat in timestamp
,p_duration in number -- second
,p_token_exp in number
)
return varchar2
as
l_iat pls_integer;
l_exp pls_integer;
l_header_json json_object_t;
l_header_str varchar2(32767);
l_header_base64 varchar2(32767); -- 1st part of JWT
l_payload_json json_object_t;
l_payload_str varchar2(32767);
l_payload_base64 varchar2(32767); -- 2nd part of JWT
l_data varchar2(32767);
l_hmac_raw raw(32767);
l_hmac varchar2(32767); -- 3rd part of JWT
l_jwt varchar2(32767);
begin
/* iatとexpとなる値を求める。 */
l_iat := unixtime(p_iat);
l_exp := l_iat + p_duration;
/* ヘッダーを手作業で作成し、BASE64でエンコードする。 */
l_header_json := json_object_t();
l_header_json.put('alg','RS256');
l_header_json.put('typ','JWT');
l_header_json.put('kid', p_kid);
l_header_str := l_header_json.to_string();
l_header_base64 := to_base64(l_header_str); -- ヘッダー
/* ペイロードを手作業で作成し、BASE64でエンコードする。 */
l_payload_json := json_object_t();
if p_channel_id is not null then
l_payload_json.put('iss', p_channel_id);
l_payload_json.put('sub', p_channel_id);
end if;
if p_aud is not null then
l_payload_json.put('aud', p_aud);
end if;
l_payload_json.put('exp', l_exp);
l_payload_json.put('token_exp', p_token_exp);
l_payload_str := l_payload_json.to_string();
l_payload_base64 := to_base64(l_payload_str); -- ペイロード
-- シグネチャを手作業で作成する。
l_data := l_header_base64 || '.' || l_payload_base64;
l_hmac_raw := dbms_crypto.sign(
src => utl_i18n.string_to_raw(l_data,'AL32UTF8'),
prv_key => utl_i18n.string_to_raw(convert_to_single_line(p_secret),'AL32UTF8'),
pubkey_alg => DBMS_CRYPTO.KEY_TYPE_RSA,
sign_alg => DBMS_CRYPTO.SIGN_SHA256_RSA
);
l_hmac := to_base64_from_raw(l_hmac_raw);
l_hmac := trim(translate(l_hmac, '+/=', '-_ ')); -- HMAC
/* JSON Web Tokenを返す。 */
l_jwt := l_header_base64 || '.' || l_payload_base64 || '.' || l_hmac;
return l_jwt;
end generate_jwt;
/* トークンを取得する実装 */
function get_token(
p_jwt in varchar2
,p_credential_static_id in varchar2
,p_expires_in out number
,p_key_id out varchar2
)
return varchar2
as
l_token_clob clob;
l_token_json json_object_t;
l_token varchar2(32767);
l_request varchar2(32767);
l_length number;
begin
/*
* POSTで送信するリクエスト本体を作成する。
*/
l_request := 'grant_type=client_credentials&';
l_request := l_request || 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&';
l_request := l_request || 'client_assertion=' || p_jwt;
l_length := lengthb(l_request);
/*
* トークンURLの呼び出し。
*/
apex_web_service.clear_request_headers;
apex_web_service.set_request_headers('Content-Length',l_length,p_reset = false);
apex_web_service.set_request_headers('Content-Type', 'application/x-www-form-urlencoded',p_reset = false);
l_token_clob := apex_web_service.make_rest_request(
p_url => C_TOKEN_URL
,p_http_method => 'POST'
,p_body => l_request
);
if apex_web_service.g_status_code <> 200 then
raise_application_error(-20001,'Failed to get channel access token v2.1');
end if;
-- dbms_output.put_line(l_token_clob);
l_token_json := json_object_t(l_token_clob);
l_token := l_token_json.get_string('token_type') || ' ' || l_token_json.get_string('access_token');
p_expires_in := l_token_json.get_number('expires_in');
p_key_id := l_token_json.get_string('key_id');
/*
* Web資格証明の静的IDが指定されている場合は、アップデートする。
*/
if p_credential_static_id is not null then
apex_credential.set_persistent_credentials(
p_credential_static_id => p_credential_static_id
,p_username => 'Authorization'
,p_password => l_token
);
end if;
return l_token;
end get_token;
end util_line_api;
/
アサーション署名キーを作成します。

PL/SQLよりDBMS_CRYPTO.SIGNを呼び出す際、秘密鍵はPKCS#1またはPKCS#8の形式で受け付けます。そのため、opensslを使って鍵ペアを生成し、その鍵ペアよりLINEのアサーション署名キーとして登録できるJSON Web Keyを作ります。

ビット長2048で鍵ペアを生成し、private.pemとして保存します。

openssl genrsa -out private.pem 2048

% openssl genrsa -out private.pem 2048

Generating RSA private key, 2048 bit long modulus

...................................................................+++++

.....+++++

e is 65537 (0x10001)

% 


最初にeの値を取り出します。通常は65537です。

openssl rsa -in private.pem -text -noout | grep publicExponent

% openssl rsa -in private.pem -text -noout | grep publicExponent

publicExponent: 65537 (0x10001)

% 


次にmodulusの値を取り出します。

openssl rsa -in private.pem -modulus -noout

Modulus=以降の値が暗号指数です。

% openssl rsa -in private.pem -modulus -noout

Modulus=BBABF46FC55DF62DCDAC79C00A978BF5B06041CEA20A19FD1CA95B0522ACE8CAD1CE81AC7F970307BD3191B20E8EB9DC356242A8304C6F2580E5C53EEACB53763033F436B7E7A1982435E6BBDC7927EC8E59F2EF5EA8FFCD76A2F66D290C68DFDEDDFE6CF1A8859700FCA25235C1662EC26C94A68379736F3C1F1492871D17D5A1507FC050AB83769FAF39D0E406DDCA695947142B010F5FF8AC99CFBA734FE63C965EA54C8A5CB61C60D34C35769F10C1443B3912830A0C112F03A9CAADC49956820B2E0A54095E5E6E5D6F769698FA8778C6264C8DE6C14704CA9BF0DC7B0BE63940497080618BFEEFC7854DCB6FF0262A968B2B7F6460244F9AFAE0295951

%


SQLコマンドよりUTIL_LINE_API.GENERATE_JSON_WEB_KEYを実行します。
begin
    dbms_output.put_line(
        util_line_api.generate_json_web_key(
            p_e => 65537
            ,p_n => 'Modulus=以降の値'
        )
    );
end;
結果JSON Web Keyが表示されます。これをLINEのMessaging APIのチャネルのアサーション署名キー公開鍵として登録します。


続いて、LINE公式アカウントがメッセージを受信したときに呼び出されるWebhookを設定します。

LINEからは、Oracle REST Data ServicesのRESTサービスをWebhookとして呼び出すようにします。

Webhookでは、受信したメッセージの署名の検証と、表LINE_MESSAGESに受信したメッセージを保存を行います。

表LINE_MESSAGESは、以下のクイックSQLのモデルより作成します。受信するメッセージはJSON形式ですが、BLOBで保存します。
# prefix: line
messages
    signature vc4000
    content blob
    is_valid vc1 /nn /default Y
    received_date date /default sysdate
SQLワークショップユーティリティクリックSQLより、表の作成を行います。


RESTサービスのハンドラに記述するコードを減らすため、ファンクションLINE_CALLBACKを作成します。署名の検証には、Messaging APIのチャネルシークレットが必要です。

create or replace function line_callback(
p_signature in varchar2
,p_content in blob
)
return number
as
C_CHANNEL_SECRET constant varchar2(64) := 'Messaging APIのチャネルシークレット';
begin
if not util_line_api.verify_response_signature(C_CHANNEL_SECRET, p_signature, p_content) then
/*
* 署名が正しくないので、それを記録する。それ以上の処理はせず、正常終了とする。エラーは返さない。
*/
insert into line_messages(signature, content, is_valid) values(p_signature, p_content, 'N');
else
insert into line_messages(signature, content) values(p_signature, p_content);
end if;
return 200;
end line_callback;

RESTサービスを作成します。

SQLワークショップRESTfulサービスを開きます。


最初にモジュールを作成します。左ペインのモジュールを選択し、モジュールの作成を実行します。


モジュール名LINEベース・パス/line/としました。公開ONにします。

モジュールの作成をクリックします。


モジュールLINEが作成されます。

続けてテンプレートの作成をクリックします。


Autonomous Databaseを使用している場合の注意点があります。完全なURLのパスがワークスペース名でなく、wksp_で始まるワークスペース・スキーマ名になっている場合があります。

その場合は、RESTfulデータ・サービスの画面の構成を開き、スキーマ別名ワークスペース名(wksp_を除く)に変更します。


テンプレートの作成では、URLテンプレートとしてcallbackを指定します。

テンプレートの作成をクリックします。


テンプレートcallbackが作成されます。

続けてハンドラの作成をクリックします。


ORDSハンドラ定義メソッドとしてPOSTを選びます。ソースに以下のコードを記述します。

declare
l_sig varchar2(4000);
begin
l_sig := owa_util.get_cgi_env('x-line-signature');
:status_code := line_callback(l_sig, :body);
end;

以上を設定して、ハンドラの作成をクリックします。


以上でWebhookの処理を行うRESTサービスが作成できました。

完全なURLをコピーし、LINEのチャネルのMessaging API設定Webhook設定Webhook URLに設定します。

検証をクリックし、Webhookの動作確認を行います。QRコードをLINEで読み込み、公式アカウントを友達として登録することで、LINEからメッセージを送信することもできます。


検証をクリックしてOKと表示されたら、Webhookの呼び出しが成功しています。

SQLコマンドより送信されたメッセージを確認します。

select id, signature, json_serialize(content), is_valid, received_date from line_messages;

IS_VALIDの値がYであれば、署名の検証も成功しています。


メッセージの受信はできたので、今度はメッセージの送信の方の設定を行います。

メッセージの送信の認証に使用するWeb資格証明を作成します。AuthenticationヘッダーをWeb資格証明として使用します。

ワークスペース・ユーティリティWeb資格証明を開き、作成をクリックします。

Web資格証明名前LINE Channel Access Tokenとします。静的識別子としてLINE_CHANNEL_ACCESS_TOKENを指定します。認証タイプとしてHTTPヘッダーを選択します。

資格証明名ヘッダー名であるAuthorization資格証明シークレット適当な文字列を入力します。資格証明名と資格証明シークレットは、LINEのトークンURLを呼び出して取得したチャネルアクセストークンにより、置き換えられます。

以上で作成をクリックします。


メッセージの送信を行う準備ができました。

SQLコマンドより以下のコードを実行し、LINEのチャネルアクセストークンを取得します。取得したトークンはWeb資格証明のLINE_CHANNEL_ACCESS_TOKENに保存します。

declare
C_RSA_KEY constant varchar2(32767) := q'~
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAv/U8xG3mIfYn2gGI39e4f5yG3w0aTCrOercbkVq+pR4fWVCe
アサーション署名キーの元となった秘密鍵のデータ
ZFsGzmTKr43RtEAX24VWsbZqve0sERVAyEwRZ6EYpryUpbNfJ2RHJg==
-----END RSA PRIVATE KEY-----~';
l_jwt varchar2(32767);
l_token varchar2(32767);
l_expires_in number;
l_key_id varchar2(32767);
begin
/* JWTの生成 */
l_jwt := util_line_api.generate_jwt(
p_secret => C_RSA_KEY
,p_kid => 'アサーション署名キーのID'
,p_channel_id => 'Messaging APIのチャネルID'
,p_token_exp => 3600
);
/* トークンの取得 */
l_token := util_line_api.get_token(
p_jwt => l_jwt
,p_credential_static_id => 'LINE_CHANNEL_ACCESS_TOKEN'
,p_expires_in => l_expires_in
,p_key_id => l_key_id
);
dbms_output.put_line(l_key_id);
end;
view raw get_jwt.sql hosted with ❤ by GitHub
キーIDが表示されれば、チャネルアクセストークンの取得は成功です。


 メッセージの送信をテストします。以下のコードを実行します。

あなたのユーザーIDの部分は、チャネル基本設定あなたのユーザーIDとして記載されているIDに置き換えます。開発者自身にLINE公式アカウントよりメッセージが届きます。

declare
l_request clob;
l_response clob;
begin
l_request := q'~{
"to": "あなたのユーザーID",
"messages":[
{
"type":"text",
"text":"Hello, world1"
},
{
"type":"text",
"text":"Hello, world2"
}
]
}~';
apex_web_service.clear_request_headers;
apex_web_service.set_request_headers('Content-Type','application/json',p_reset = false);
l_response := apex_web_service.make_rest_request(
p_url => 'https://api.line.me/v2/bot/message/push'
,p_http_method => 'POST'
,p_body => l_request
,p_credential_static_id => 'LINE_CHANNEL_ACCESS_TOKEN'
);
dbms_output.put_line(l_response);
end;



LINEでメッセージが受信できれば、メッセージの送信についても確認が完了しました。

以上になります。

LINE公式アカウントでの基本的なメッセージの送受信ができるようになりました。