// GraalJS Script (engine type: 2)
/*
NOTES
- Invoicer can automate billing operations using the PayPal Invoicing via Invoices API.
- https://www.paypal.com/merchantapps/appcenter/acceptpayments/invoicing
- The invoices are controlled by a 24-character ID (e.g. "INV2-Z56S-5LLA-Q52L-CPZ5")
- This addon requires the CLIENT-ID and SECRET. (Get on the dashboard)
- Developer Dashboard -> My apps & credentials -> REST API Apps (Live mode)
- https://developer.paypal.com/developer/applications/
- Terminology is based on Invoices API v2 (2019-04).
- Be careful when migrating from implementations prior to April 2019.
- e.g. "merchant" to "invoicer"
- e.g. "billing info" to "recipient"
- When PayPal emails the recipient, the invoice moves from draft to payable state.
- To move from a draft to payable state, the SEND INVOICE action is required.
- Invoice Status: DRAFT, SCHEDULED, SENT, PAID, MARKED_AS_PAID, CANCELLED, REFUNDED,,
- https://developer.paypal.com/docs/api/invoicing/v2/#invoices_create
- However, if "invoice_date" is set, automatically sent at 7:00 of Invoicer time zone
NOTE-ja
- 請求人は、請求書ツール(Invoicing)による請求業務を自動化できます(Invoices API経由)
- https://www.paypal.com/jp/webapps/mpp/merchant/solutions/invoicing
- 請求書は24文字のIDでコントロールされます(e.g. "INV2-Z56S-5LLA-Q52L-CPZ5")
- このアドオンの利用には CLIENT-ID と SECRET が必要です。(ダッシュボードで取得)
- Developer Dashboard -> My apps & credentials -> REST API Apps (Live mode)
- https://developer.paypal.com/developer/applications/
- 表記は Invoices API v2 〔2019-04〕ベースの用語にて記載されています。
- 2019-04 以前の実装から移行する場合は、特にご注意ください。
- "merchant"(販売人) ではなく "invoicer"(請求人)
- "billing info"(請求先) ではなく "recipient"(受取人)
- "請求書ドラフト" は原則、PayPalから受取人に対しメールされた際に決済可能な状態になります
- すなわち請求書ドラフトを有効化するには、別途「送信」アクションが必要です。
- Invoice Status: DRAFT, SCHEDULED, SENT, PAID, MARKED_AS_PAID, CANCELLED, REFUNDED,,
- https://developer.paypal.com/docs/api/invoicing/v2/#invoices_create
- ただし "請求日" がセットされた場合は、請求人タイムゾーンの 7:00 に自動送信されます
*/
/*
APPENDIX
- To request API in Sandbox mode (instead of Live mode)
- Set CLIENT-ID and SECRET for Sandbox
- Edit Access URLs to "api.sandbox.paypal.com" ('postUri1' and 'postUri2')
- PayPal API Reference "Show invoice details" /v2/invoicing/invoices
- https://developer.paypal.com/docs/invoicing/full-integration/show-invoice-details/
- https://developer.paypal.com/docs/api/invoicing/v2/#invoices_get
- status
- DRAFT. The invoice is in draft state. It is not yet sent to the payer.
- SENT. The invoice has been sent to the payer. The payment is awaited from the payer.
- SCHEDULED. The invoice is scheduled on a future date. It is not yet sent to the payer.
- PAID. The payer has paid for the invoice.
- MARKED_AS_PAID. The invoice is marked as paid by the invoicer.
- CANCELLED. The invoice has been cancelled by the invoicer.
- REFUNDED. The invoice has been refunded by the invoicer.
- PARTIALLY_PAID. The payer has partially paid for the invoice.
- PARTIALLY_REFUNDED. The invoice has been partially refunded by the invoicer.
- MARKED_AS_REFUNDED. The invoice is marked as refunded by the invoicer.
- UNPAID. The invoicer is yet to receive the payment from the payer for the invoice.
- PAYMENT_PENDING. The invoicer is yet to receive the payment for the invoice. It is under pending review.
APPENDIX-ja
- テストのために(Live モードではなく)Sandbox モードで API 通信させたい場合
- Sandbox 用の CLIENT-ID と SECRET をセット
- スクリプト内のアクセスURLを "api.sandbox.paypal.com" に ('postUri1' と 'postUri2')
- PayPal API Reference "Show invoice details" /v2/invoicing/invoices
- https://developer.paypal.com/docs/invoicing/full-integration/show-invoice-details/
- https://developer.paypal.com/docs/api/invoicing/v2/#invoices_get
- status
- DRAFT. The invoice is in draft state. It is not yet sent to the payer.
- SENT. The invoice has been sent to the payer. The payment is awaited from the payer.
- SCHEDULED. The invoice is scheduled on a future date. It is not yet sent to the payer.
- PAID. The payer has paid for the invoice.
- MARKED_AS_PAID. The invoice is marked as paid by the invoicer.
- CANCELLED. The invoice has been cancelled by the invoicer.
- REFUNDED. The invoice has been refunded by the invoicer.
- PARTIALLY_PAID. The payer has partially paid for the invoice.
- PARTIALLY_REFUNDED. The invoice has been partially refunded by the invoicer.
- MARKED_AS_REFUNDED. The invoice is marked as refunded by the invoicer.
- UNPAID. The invoicer is yet to receive the payment from the payer for the invoice.
- PAYMENT_PENDING. The invoicer is yet to receive the payment for the invoice. It is under pending review.
*/
//////// START "main()" /////////////////////////////////////////////////////////////////
main();
function main(){
//// == Config Retrieving / 工程コンフィグの参照 ==
const strAuthzSetting = configs.get( "AuthzConfA1" ); // required (Authz by Basic_AuthN)
engine.log( " AutomatedTask Config: Authz Setting: " + strAuthzSetting );
const strInvoiceid = configs.get( "StrConfB1" ) + ""; // required
if( strInvoiceid === "" ){
throw new Error( "\n AutomatedTask ConfigError:" +
" Config {B1 Invoice Id} not specified \n" );
}
const strPocketInvoiceStatus = configs.getObject( "SelectConfC1" );
const multiPocketPaymentDate = configs.getObject( "SelectConfD1" );
const numPocketPaymentAmount = configs.getObject( "SelectConfD2" );
const strPocketPaymentCurrency = configs.getObject( "SelectConfD3" );
const strPocketJson = configs.getObject( "SelectConfE1" );
//// == Data Retrieving / ワークフローデータの参照 ==
// (Nothing. Retrieved via Expression Language in Config Retrieving)
//// == Calculating / 演算 ==
// prepare request1: Get an Access Token
// (PayPal OAuth 2.0 credentials / Client Credentials)
// https://developer.paypal.com/docs/api/overview#get-credentials
// https://developer.paypal.com/docs/api/get-an-access-token-curl/
let postUri1 = "https://api.paypal.com/v1/oauth2/token";
let request1 = httpClient.begin(); // HttpRequestWrapper
request1 = request1.authSetting( strAuthzSetting );
request1 = request1.formParam( "grant_type", "client_credentials" );
engine.log( " AutomatedTask ApiRequest1 Prepared" );
// try request1
const response1 = request1.post( postUri1 ); // HttpResponseWrapper
engine.log( " AutomatedTask ApiRequest1 Start: " + postUri1 );
const response1Code = response1.getStatusCode() + ""; // (primitive string)
const response1Body = response1.getResponseAsString() + "";
engine.log( " AutomatedTask ApiResponse1 Status: " + response1Code );
if( response1Code !== "200"){
throw new Error( "\n AutomatedTask UnexpectedResponseError: " +
response1Code + "\n" + response1Body + "\n" );
}
// parse response1
/*** engine.log( response1Body ); // debug
response sample
{
"scope":"https://api.paypal.com/v1/payments/.※ https://uri.paypal.com/services/invoicing
openid https://uri.paypal.com/payments/payouts ....",
"access_token":"A21AAO9wpfXXXXXXXXXXL1P96HvZkRXXXXXXXXXXDlSVGSvUjlXXXXX...XXXXX",
"token_type":"Bearer",
"app_id":"APP-6BKXXX232XXX3090N",
"expires_in":32400,
"nonce":"2021-01-14T06:13:03Z1dgXXXCnnXXXgLKXXXgn0XXXcrJXXXoALXXXXBUXXXs"
}
***/
const response1Obj = JSON.parse( response1Body );
const strBearerToken = response1Obj.access_token;
// prepare request2: Show Invoice Detail
let getUri2 = "https://api.paypal.com/v2/invoicing/invoices/" + strInvoiceid;
let request2 = httpClient.begin(); // HttpRequestWrapper
request2 = request2.bearer( strBearerToken );
engine.log( " AutomatedTask ApiRequest2 Prepared" );
// try request2
const response2 = request2.get( getUri2 ); // HttpResponseWrapper
engine.log( " AutomatedTask ApiRequest2 Start: " + getUri2 );
const response2Code = response2.getStatusCode() + ""; // (primitive string)
const response2Body = response2.getResponseAsString() + "";
engine.log( " AutomatedTask ApiResponse2 Status: " + response2Code );
if( response2Code !== "200"){
throw new Error( "\n AutomatedTask UnexpectedResponseError: " +
response2Code + "\n" + response2Body + "\n" );
}
// https://developer.paypal.com/docs/api-basics/HTMLStatusCodes/
// parse response2
/*** engine.log( response2Body ); // debug
response sample
{
"id":"INV2-XXXX-5MHS-XXXX-MXJH",
"status":"PAID",
"detail":{
"currency_code":"JPY",
"note":"ご決済完了後、24時間程度でワークフロー基盤の更新が完了します。",
"terms_and_conditions":"利用規約 https://questetra.com/ja/license-agreement/",
"memo":"BPMS Process ID: 1100005",
"category_code":"SHIPPABLE",
"invoice_number":"20200629-1100005",
"invoice_date":"2020-06-28",
"payment_term":{
"term_type":"DUE_ON_DATE_SPECIFIED",
"due_date":"2020-06-30"
},
"viewed_by_recipient":false,
"group_draft":false,
"metadata":{
"create_time":"2020-06-29T01:33:54Z",
"last_update_time":"2020-07-01T07:05:01Z",
"first_sent_time":"2020-06-29T10:01:35Z",
"last_sent_time":"2020-06-29T10:01:35Z",
"created_by_flow":"REGULAR_SINGLE",
"recipient_view_url":"https://www.paypal.com/invoice/p/#WUFZXXXXGGFLXXXX",
"invoicer_view_url":"https://www.paypal.com/invoice/details/INV2-XXXX-5MHS-XXXX-MXJH",
"caller_type":"UNKNOWN"
},
"archived":false
},
"invoicer":{
"business_name":"株式会社クエステトラ",
"address":{
"address_line_1":"中京区御池通間之町東入",
"address_line_2":"高宮町206 御池ビル4階",
"admin_area_2":"京都市",
"admin_area_1":"京都府",
"postal_code":"604-0835",
"country_code":"JP"
},
"email_address":"support@questetra.com",
"phones":[{
"country_code":"81",
"national_number":"0752055007",
"phone_type":"MOBILE"
}],
"website":"www.questetra.com",
"logo_url":"https://questetra.com/wp-content/uploads/2018/11/Corporate-logo-100x100.png"
},
"primary_recipients":[{
"billing_info":{
"business_name":"京都大学大学院情報学研究科同窓会",
"name":{
"given_name":"スズキ イチロー",
"surname":"会長",
"full_name":"スズキ イチロー 会長"
},
"email_address":"example@example.net",
"language":"ja-JP"
}
}],
"items":[{
"id":"ITEM-9HP85000J5100010H",
"name":"Yoga Mat",
"description":"Elastic mat to practice yoga.",
"quantity":"24",
"unit_amount":{
"currency_code":"JPY",
"value":"4032"
},
"tax":{
"id":"TAX-58K20007V50000009",
"name":"TAX",
"percent":"10",
"amount":{
"currency_code":"JPY",
"value":"9677"
}
}
}],
"configuration":{
"tax_calculated_after_discount":false,
"tax_inclusive":false,
"allow_tip":false,
"template_id":"TEMP-130000003M300003N"},
"amount":{
"breakdown":{
"item_total":{
"currency_code":"JPY",
"value":"96768"
},
"discount":{
"invoice_discount":{
"amount":{
"currency_code":"JPY",
"value":"0"
}
},
"item_discount":{"
currency_code":"JPY",
"value":"0"
}
},
"tax_total":{
"currency_code":"JPY",
"value":"9677"
}
},
"currency_code":"JPY",
"value":"106445"
},
"due_amount":{
"currency_code":"JPY",
"value":"0"
},
"payments":{
"paid_amount":{
"currency_code":"JPY",
"value":"106445"
},
"transactions":[{
"type":"PAYPAL",
"payment_id":"9EA40005PY8100033",
"transaction_type":"SALE",
"payment_date":"2020-07-01",
"method":"PAYPAL",
"amount":{
"currency_code":"JPY",
"value":"106445"
}
}
]},
"links":[{ ◆ }]
}
***/
const response2Obj = JSON.parse( response2Body );
let strInvoiceStatus = response2Obj.status;
let strPaymentDate = "2099-12-31";
let strPaymentAmount = "0";
let strPaymentCurrency = "";
if( strInvoiceStatus === "PAID" ){
strPaymentDate = response2Obj.payments.transactions[0].payment_date;
strPaymentAmount = response2Obj.payments.transactions[0].amount.value;
strPaymentCurrency = response2Obj.payments.transactions[0].amount.currency_code;
}
//// == Data Updating / ワークフローデータへの代入 ==
if( strPocketInvoiceStatus !== null ){
engine.setData( strPocketInvoiceStatus, strInvoiceStatus );
}
if( multiPocketPaymentDate !== null ){ // STRING or DATE
if( multiPocketPaymentDate.matchDataType( "STRING" ) ){
engine.setData( multiPocketPaymentDate, strPaymentDate );
}else{
engine.setData( multiPocketPaymentDate,
java.sql.Date.valueOf( strPaymentDate )
);
}
}
if( numPocketPaymentAmount !== null ){
let numPaymentValue = parseFloat( strPaymentAmount );
engine.setData( numPocketPaymentAmount, new java.math.BigDecimal( numPaymentValue ) );
}
if( strPocketPaymentCurrency !== null ){
engine.setData( strPocketPaymentCurrency, strPaymentCurrency );
}
if( strPocketJson !== null ){
engine.setData( strPocketJson, response2Body );
}
} //////// END "main()" /////////////////////////////////////////////////////////////////
Pingback: PayPal Invoicing Status – Questetra Support
Pingback: PayPal: Invoice, Send – Questetra Support
Pingback: PayPal: Invoice, Create Draft – Questetra Support
Pingback: PayPal Billing Process – Questetra Support