第525話:自動工程から OAuth2 リクエストを送出させる方法

2017年3月6日

サーバサイド 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 クライアント)として振る舞わせることが可能です。すなわち、案件が自動工程(スクリプト工程/サービス工程)に到達する度に以下の様なリクエストが自動的に実行されるよう、設定することができます。

  • ※ スクリプト工程: 業務設計者がサーバサイド JavaScript をセットできる工程(HttpClientWrapper を使った通信も実装可能) M230
  • ※ サービス工程(Addon): スクリプト工程をパッケージ化した工程。業務設計者はコンフィグ設定のみ可能 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/"
PS:
機会があれば、「ワークフローシステムと PayPal システムのあるべきコラボレーション」についても記事にしたいと思います。なお本論とは関係ありませんが…、「PayPal 請求書」における消費税の端数処理は「四捨五入」です。「切り捨て処理」を美徳とする向きのある日本企業にとっては「予め覚悟しておくべき個性」と言えるのかも知れません。

追記(2017-03-08)
現在 PayPal REST Invoicing API に幾つかの不具合を確認しています。
(当該不具合については既に報告し PayPal 社においても再現が確認されています)
  • テンプレート請求書を指定しても「請求書」にロゴ画像等が反映されない
    ( template_id )
  • 電話番号情報の送信が成功しても「請求書」に表示されない
    ( phone.country_code や phone.national_number )
ちなみに「タイムゾーンに何を指定しても無意味」(PSTとして登録される)なのは、現時点においては「仕様」とのことです。( invoice_date や due_date など)

追記(2017-06-14)
(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請求書の例>


<データ項目一覧画面>


[雛形ダウンロード (無料)]
<類似プロセス>
≪関連記事≫

[英文記事 (English Entry) ]

0 件のコメント :

コメントを投稿