サーバサイド 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 件のコメント :
コメントを投稿