サーバサイド JavaScript による自動化
前回記事(※)で『Questetra BPM Suite の「自動工程」では(簡単な数値演算だけでなく)「OAuth2 通信」も実装可能』と書きました。※第524話:サーバサイド JavaScript による業務の自動化
そこで今回の記事では「具体的にどの様な外部サービスと OAuth2 通信が可能なのか」と「具体的にどの様なプログラミングが必要なのか」について、簡単にまとめてみたいと思います。(ワークフローシステム側からリクエストする視点)
そもそも OAuth2 通信とは
OAuth (おーおーす)とは「サーバへのリクエストを認可する仕組み」です。参考) 『みんな知るべきクラウド技術「OAuth」ってナニ?』 (2014-01-27)
OAuth2 が2012年に登場して以来、Google Drive や Dropbox など多くの「リソース・サーバ」の API が『OAuth2 アクセス』に対応するようになっています。つまり、外部のシステムは「リソース・サーバ」に対して、様々な「参照リクエスト」や「更新リクエスト」を投げることが可能となっています。 (RFC6749/6750)
クラウド型ワークフローである『Questetra BPM Suite』では、業務プロセス定義の中に配置した自動工程に、この「外部のシステム」(OAuth2 クライアント)として振る舞わせることが可能です。すなわち、案件が自動工程(スクリプト工程/サービス工程)に到達する度に以下の様なリクエストが自動的に実行されるよう、設定することができます。
- a. Google Sheets API v4 (Sheet の読み書き)
- b. PayPal Invoicing API REST API 版 (請求書の生成送信)
- c. Dropbox API v1
- ファイル更新メソッド で、ファイルをアップロードする
- ※ スクリプト工程: 業務設計者がサーバサイド JavaScript をセットできる工程(HttpClientWrapper を使った通信も実装可能) M230
- ※ サービス工程(Addon): スクリプト工程をパッケージ化した工程。業務設計者はコンフィグ設定のみ可能 M415 M416
参考)
- M230 業務データの複雑なデータ加工が自動実行されるように設定する(ECMAスクリプト)
- M415 業務プロセス定義で利用可能な自動工程を追加する
- M416 業務プロセス定義で利用可能な自動工程を自作する
アクセストークンの取得方法
このリクエスト通信には「アクセス・トークン」が必要になります。「アクセス・トークン」とは、「リソースの所有者によって認可されたリクエストであることの証(あかし)」であり、その実態は数十文字から200文字程度の文字列です。ただ、アクセス・トークンの取得方式は「リソース・サーバ」によって異なります。主だった方法としては、
- A. 所有者に「許可ボタン」をクリックさせ、その後は自動取得する (Authorization Code Grant)
- B. 秘密クレデンシャルをセットしておき、その後は自動取得する (Client Credentials Grant)
- C. アクセストークンそのものをセットしてしまう (有効期限が無い Access Token が可能な場合など)
注意) 接続先 API が実装可能な OAuth2 認可方式であっても、「HTTP ヘッダ」や「Content-Type フォーマット」に関する実装制約(Questetra側)により、自動通信を設定できない場合があります。
[請求書送信プロセス]
PayPal システムに請求データを送る
上記のワークフローには「PayPal 請求書を自動作成させるリクエスト」が送信される自動工程(※)が配置されています。(※スクリプト工程『PayPal 請求書の自動生成』)すなわち請求案件がこの工程に到達する度に、『PayPal システム』に対して「PayPal 請求書を生成せよ」というリクエストが送信されます。
この例では簡単のため、上流工程には「承認」の工程しかありませんが、現実の社内業務にあっては、少額の請求について上司承認を省略する分岐を設置したり、見積プロセスとの連携イベントを設置したりするかもしれません。また下流工程に「PayPal システムからの送信」などを組み込むケースも想定されるところです。
サンプル・コード
「請求書ドラフトを生成せよ」というリクエストを送るスクリプトです。ここで利用する「PayPal Invoicing API」は、秘密のクレデンシャル(clientId と secret)を使い、毎回「アクセス・トークン」を取得するBのパターンです。そこで、あらかじめ「管理者ダッシュボード」からクレデンシャルを取得しておきます。
※「REST API 版」(2014~)の他にも「NVP/SOAP API 版」(2011~)が存在するので注意が必要です
このスクリプトでは、中段の演算を開始する部分に「Getting OAuth2 Token via client_credentials grant」とあります。これが、「Access Tokenの取得」のための通信となります(59行目~62行目)。その後、「取得した Access Token」を使って、請求書生成のリクエスト通信が行われる段取りとなります(109行目~112行目)。
- 1度目のHTTP通信: "https://api.paypal.com/v1/oauth2/token"
- 2度目のHTTP通信: "https://api.paypal.com/v1/invoicing/invoices/"
機会があれば、「ワークフローシステムと PayPal システムのあるべきコラボレーション」についても記事にしたいと思います。なお本論とは関係ありませんが…、「PayPal 請求書」における消費税の端数処理は「四捨五入」です。「切り捨て処理」を美徳とする向きのある日本企業にとっては「予め覚悟しておくべき個性」と言えるのかも知れません。
追記(2017-03-08)
現在 PayPal REST Invoicing API に幾つかの不具合を確認しています。
(当該不具合については既に報告し PayPal 社においても再現が確認されています)
現在 PayPal REST Invoicing API に幾つかの不具合を確認しています。
(当該不具合については既に報告し PayPal 社においても再現が確認されています)
- テンプレート請求書を指定しても「請求書」にロゴ画像等が反映されない
( template_id ) - 電話番号情報の送信が成功しても「請求書」に表示されない
( phone.country_code や phone.national_number )
追記(2017-06-14)
(PayPal社のマニュアルはまだ修正されていませんが)、電話番号情報は「住所情報の子要素」として送信することで反映されます。
(PayPal社のマニュアルはまだ修正されていませんが)、電話番号情報は「住所情報の子要素」として送信することで反映されます。
- invoiceObj.merchant_info.address.phone.country_code = merchantPhoneCC;
- invoiceObj.merchant_info.address.phone.national_number = merchantPhoneNum;
// PayPal Invoicing (ver. 20170228)
// (c) 2017, Questetra, Inc. (the MIT License)
var sandboxmode = false; // true or false
//// == コンフィグ&参照 / Config & Retrieving ==
// Your "REST API app" on PayPal Dashboard
// https://developer.paypal.com/developer/applications/
var clientId = "HogeHoge-SampleSample-YourId-HogeHoge-SampleSample-YourId-HogeHoge-SampleSample-";
var secret = "HogeHoge-SampleSample-YourSecret-HogeHoge-SampleSample-YourId-HogeHoge-SampleSam";
var clientIdSandbox = "HogeHoge-SampleSample-YourId-HogeHoge-SampleSample-YourId-HogeHoge-SampleSample-";
var secretSandbox = "HogeHoge-SampleSample-YourSecret-HogeHoge-SampleSample-YourId-HogeHoge-SampleSam";
if( sandboxmode ){ clientId = clientIdSandbox; secret = secretSandbox; }
// Your Info
var merchantEmail = "your@example.com";
var merchantEmailSandbox = "your-facilitator@example.com";
if( sandboxmode ){ merchantEmail = merchantEmailSandbox; }
var merchantBusinessName = "Example Company, Inc.";
var merchantAddress1 = "206 Takamiya-cho Nakagyo-ku";
var merchantAddress2 = "Oike Bldg. 4th Fl.";
var merchantAddressCity = "Kyoto-shi";
var merchantAddressState = "KYOTO";
var merchantAddressPC = "604-0835";
var merchantAddressCC = "JP";
var merchantWebsite = "https://www.questetra.com/corp/";
// Customer Info
var billingLanguage = "en_US";
var billingEmail = data.get( "q_billingEmail" ) + "";
var billingName1 = data.get( "q_billingName1" ) + "";
var billingName2 = data.get( "q_billingName2" ) + "";
var billingBusinessName = data.get( "q_billingBusinessName" ) + "";
// Items Info
var itemUnitPriceCurrency = "JPY";
var itemTaxName = "JCT";
var itemTaxPercent = 8;
var itemName = data.get( "q_itemName" ) + "";
var itemDescription = data.get( "q_itemDescription" ) + ""; // Max: 1000
var itemQuantity = data.get( "q_itemQuantity" ) - 0; // number
var itemUnitPrice = data.get( "q_itemUnitPrice" ) + ""; // string
// Invoice Info
var invoiceNumber = processInstance.getProcessInstanceId() + ""; // string
var invoiceDate = data.get( "q_invoiceDate" ) + " JST"; // yyyy-MM-dd z
var paymentTermDueDate = data.get( "q_paymentTermDueDate" ) + " JST";
var invoiceNote = data.get( "q_invoiceNote" ) + ""; // Max: 4000
var invoiceTerms = data.get( "q_invoiceTerms" ) + ""; // Max: 4000
//// == 演算 / Calculating ==
var accessLog = "";
// Getting OAuth2 Token via client_credentials grant
var uriToken = "https://api.paypal.com/v1/oauth2/token";
var uriTokenSandbox = "https://api.sandbox.paypal.com/v1/oauth2/token";
if( sandboxmode ){ uriToken = uriTokenSandbox; }
var response = httpClient.begin()
.basic( clientId, secret )
.formParam( "grant_type", "client_credentials" )
.post( uriToken );
accessLog += "---POST request--- " + response.getStatusCode() + "\n";
accessLog += response.getResponseAsString() + "\n";
var oauthTokenObj = JSON.parse( response.getResponseAsString() );
var oauthToken = oauthTokenObj.access_token;
accessLog += "oauthToken: " + oauthToken + "\n";
// Create invoice
var invoiceObj = {};
invoiceObj.number = invoiceNumber;
invoiceObj.invoice_date = invoiceDate;
invoiceObj.payment_term = {};
invoiceObj.payment_term.due_date = paymentTermDueDate;
invoiceObj.note = invoiceNote;
invoiceObj.terms = invoiceTerms;
invoiceObj.merchant_info = {};
invoiceObj.merchant_info.email = merchantEmail;
invoiceObj.merchant_info.business_name = merchantBusinessName;
invoiceObj.merchant_info.address = {};
invoiceObj.merchant_info.address.line1 = merchantAddress1;
invoiceObj.merchant_info.address.line2 = merchantAddress2;
invoiceObj.merchant_info.address.city = merchantAddressCity;
invoiceObj.merchant_info.address.state = merchantAddressState;
invoiceObj.merchant_info.address.postal_code = merchantAddressPC;
invoiceObj.merchant_info.address.country_code = merchantAddressCC;
invoiceObj.merchant_info.website = merchantWebsite;
invoiceObj.billing_info = [];
invoiceObj.billing_info[0] = {};
invoiceObj.billing_info[0].email = billingEmail;
invoiceObj.billing_info[0].first_name = billingName1;
invoiceObj.billing_info[0].last_name = billingName2;
invoiceObj.billing_info[0].business_name = billingBusinessName;
invoiceObj.billing_info[0].language = billingLanguage;
invoiceObj.items = [];
invoiceObj.items[0] = {};
invoiceObj.items[0].name = itemName;
invoiceObj.items[0].description =itemDescription;
invoiceObj.items[0].quantity = itemQuantity;
invoiceObj.items[0].unit_price = {};
invoiceObj.items[0].unit_price.currency = itemUnitPriceCurrency;
invoiceObj.items[0].unit_price.value = itemUnitPrice;
invoiceObj.items[0].tax = {};
invoiceObj.items[0].tax.name = itemTaxName;
invoiceObj.items[0].tax.percent = itemTaxPercent;
var uriCreate = "https://api.paypal.com/v1/invoicing/invoices/";
var uriCreateSandbox = "https://api.sandbox.paypal.com/v1/invoicing/invoices/";
if( sandboxmode ){ uriCreate = uriCreateSandbox; }
var responseCreate = httpClient.begin()
.bearer( oauthToken )
.body( JSON.stringify( invoiceObj ), "application/json" )
.post( uriCreate );
accessLog += "---POST request--- " + responseCreate.getStatusCode() + "\n";
accessLog += responseCreate.getResponseAsString() + "\n";
//// == ワークフローデータへの代入 / Data Updating ==
retVal.put( "q_accessLog", accessLog );
[請求書送信プロセス:「1.請求内容の登録」画面]
<完成したPayPal請求書の例>
<データ項目一覧画面>
[雛形ダウンロード (無料)]
- 業務テンプレート:請求書送信プロセス
- 第504話:監査書類を Dropbox に自動保存! (2016-10-11)
- 第503話:ユラギを無くすには「マスター参照」でしょ!(kintone編) (2016-10-03)
- 第498話:自動工程におけるデータ加工、アレコレ(その5) (2016-08-29)
- M230 自動工程: 業務データの複雑なデータ加工が自動実行されるように設定する(ECMAスクリプト) (使い方)
- M415 自動工程: 業務プロセス定義で利用可能な自動工程を追加する (使い方)
- M416 自動工程: 業務プロセス定義で利用可能な自動工程を自作する (使い方)
[英文記事 (English Entry) ]







0 件のコメント :
コメントを投稿