PayPal #Invoice: Create Draft

PayPal: Invoice, Create Draft
PayPal: Invoice, Create Draft
Creates a draft invoice on the payment platform PayPal. To move the invoice from a draft to payable state, the SEND action is required. Billing automation and paperless operations are realized.
Configs
  • A1: Select HTTP_Authz (BasicAuthn User:ClientID Passwd:SECRET) *
  • A2: Set Boolean Tax-Inclusive (true: includes, false: DEFAULT)#{EL}
  • A3: Set Currency-Code (“USD”) *#{EL}
  • A4: Set Invoice-Num and Due-Date (in 2 lines)#{EL}
  • A5: Set Invoice-Date#{EL}
  • B1: Set INVOICER’s Registered Email-Address *#{EL}
  • B2: Set INVOICER’s Bizname/Name1/Name2/AdditionalInfo (4 lines)#{EL}
  • B3: Set INVOICER’s LogoUrl/WebsiteUrl/TaxId (in 3 lines)#{EL}
  • C1: Set RECIPIENT’s Email-Address *#{EL}
  • C2: Set RECIPIENT’s Language-Code for email message (eg “en-US”)#{EL}
  • C3: Set RECIPIENT’s Bizname/Name1/Name2 (in 3 lines)#{EL}
  • D: Set ADDITIONAL_RECIPIENTS Cc-Addresses (up to 10 lns)#{EL}
  • E: Set Tsv String for Items (ItemName/Qty/UnitAmount…) *#{EL}
  • F: Set Note to Recipient (Also displayed in notification)#{EL}
  • G: Set Terms and Conditions#{EL}
  • H: Set Memo (e.g. “BPMS pid: #{processInstanceId}”)#{EL}
  • I: Select STRING DATA for Paypal Invoice ID (update)
  • J: Select STRING DATA for invoicer_view_url (update)
  • K: Select STRING DATA for Invoice Status (update)
Script (click to open)
// GraalJS Script (engine type: 2)
/*
NOTES
- An invoice will be automatically generated when a process reaches this step in the workflow.
    - PayPal Invoicing: https://www.paypal.com/merchantapps/appcenter/acceptpayments/invoicing
    - The invoicer does not need to log in to the "PayPal Invoicing".
    - The invoices are controlled by a 24-character ID (e.g. "INV2-Z56S-5LLA-Q52L-CPZ5")
- This addon (Automated Step) requires the CLIENT-ID and SECRET. (Get on your dashboard)
    - Developer Dashboard -> My apps & credentials -> REST API Apps (Live mode)
    - https://developer.paypal.com/developer/applications/
- Terminology in this addon 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
- Consider a pre-mail system such as "You will receive an invoice from paypal.com".
    - e.g. "PayPal Invoice ID:123, From: 'Questetra, Inc. <service-jp@paypal.com>'"
- Recipients with a PayPal account can log in and pay the invoice with PayPal. 
    - Alternatively, recipients can pay as a guest with a debit card or credit card.
    - The invoice status in PayPal will be PAID.

NOTE-ja
- ワークフロー内の当該工程に請求案件が到達した際、電子請求書がPayPal内に自動生成されます。
    - 請求書ツールとは https://www.paypal.com/jp/webapps/mpp/merchant/solutions/invoicing
    - 請求人は "PayPal 請求書ツール"(PayPal Invoicing)にログインする必要はありません。
    - "PayPal請求書" は24文字のIDでAPIコントロールされます("INV2-Z56S-5LLA-Q52L-CPZ5")
- このアドオン(自動処理)の設定には CLIENT-ID と SECRET が必要です。(ダッシュボードで取得)
    - PayPal Developer Dashboard -> My apps & credentials -> REST API Apps (Live mode)
    - https://developer.paypal.com/developer/applications/
- このアドオンの表記は PayPal 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
- 必要あれば "paypal.com から請求書が届きます" といった事前メールの仕組みも検討します。
    - 例「"株式会社クエステトラ <service-jp@paypal.com>" から ID=123 の請求が届きます」
- 受取人は、PayPalアカウントを持つ場合、受け取った請求書に対してPayPal支払いが可能です。
    - あるいはゲストとして、クレジットカード/デビットカードで支払うことも可能です。
    - 支払いが完了すれば、PayPal 内の請求書ステータスは PAID の状態になります。
*/

/*
APPENDIX
- "Invoice number", the same as the past is not allowed.
    - e.g. "#{processInstanceId}"
    - e.g. "#{#format(processInstanceStartDatetime, 'yyyyMMdd')}-#{processInstanceId}"
    - e.g. "#{#sformat('%06d', processInstanceSequenceNumber)}"
    - If omitted, the number part of the latest invoice will be incremented.
- PayPal-supported language and locale
    - e.g. "ja-JP", "en-US", "fr-FR", "de-DE", "zh-CN"
    - https://developer.paypal.com/docs/api/reference/locale-codes/
- Currencie Codes
    - e.g. "JPY", "USD", "EUR", "GBP", "CNY",,,
    - https://developer.paypal.com/docs/api/reference/currency-codes/
- Items Information in TSV
    - Up to 8 columns (Name/Qty/Price/Descr/TaxName/TaxRate/DiscountAmount/DiscountRate)
    - The first three columns are required. (Name/Qty/Price)
    - Comma-separated formats are also available for numeric column settings.
    - Different rates, different tax names. (10% "consumption", 8% "consumption-reduced")
    - In case of mixed tax rates, recognizable names for the applied tax rate are better
- For generating TSV text, commercial tools, "converter" or "reorder" addon are effective
    - https://support.questetra.com/addons/converter-table-to-tsv-string/
    - https://support.questetra.com/addons/tsv-string-reorder-columns/
- If you need to round the numeric data, you can also use the EL formatter.
    - Round to the nearest integer: '#{#sformat("%.0f", #q_numExample)}'
    - Largest integer less than or equal to: '#{#sformat("%.0f", (#q_numExample - 0.5))}'
    - Round off to 2 decimal places: '#{#sformat("%.2f", #q_numExample)}'
    - Truncate to 2 decimal places: '#{#sformat("%.2f", (#q_numExample - 0.005))}'
    - https://questetra.zendesk.com/hc/en-us/articles/360024292872-R2272
- Date configs support reference of date type data. The format is "YYYY-MM-DD".
- Up to 10 CC notification addresses other than the recipient. (Additional_recipients)
- The maximum size of the Invoicert's logo image (URL specified) is 250x90px.
- Simultaneous generation of multiple invoices not supported. (One primary_recipient)
- "Invoice Template" in the PayPal Invoicing are not reflected.
- The specification of "Ship To" is not supported.
- Invoicer's Address Phone or Fax not supported.
    - To add via this addon, consider using "B2: Additional Information" (400 chs).
    - Or, use "F: Note" (4000 chs) at the bottom of the invoice (also in notification).
- 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')
    - addon developer: invoicer/address/{address_line_1,,,postal_code,country_code}
- PayPal API Reference /v2/invoicing/invoices (There is some wrong code)
    - https://developer.paypal.com/docs/invoicing/basic-integration/#2-create-draft-invoice
    - https://developer.paypal.com/docs/api/invoicing/v2/#invoices_create

APPENDIX-ja
- "請求書番号" は過去との重複が発生しないような発番設定にする必要があります。
    - e.g. "#{processInstanceId}"
    - e.g. "#{#format(processInstanceStartDatetime, 'yyyyMMdd')}-#{processInstanceId}"
    - e.g. "#{#sformat('%06d', processInstanceSequenceNumber)}"
    - 無指定の場合、最新の請求書番号の数字部分が自動追加されます。
- PayPal通知メールにおける言語の指定にはロケールコードを利用してください。
    - e.g. "ja-JP", "en-US", "fr-FR", "de-DE", "zh-CN"
    - https://developer.paypal.com/docs/api/reference/locale-codes/
- 通貨の指定には通貨コードを利用してください。
    - e.g. "JPY", "USD", "EUR", "GBP", "CNY",,,
    - https://developer.paypal.com/docs/api/reference/currency-codes/
- 商品明細はTSVテキストにて指定します。
    - 最大8列のTSVが設定可能です。(商品名/数量/単価/詳細説明/税名/税率/割引額/割引率)
    - 先頭3列は必須列です。(商品名/数量/単価)
    - 数値型列の設定はカンマ桁区切り書式でも構いません。
    - 異なる税率には異なる税名称をつける必要があります(10%: "消費税", 8%: "消費税[軽]")
    - 税率混在ケースでは、適用税率が認知できる "商品名" や "詳細説明" が期待されます。
- TSVテキスト生成には、市販ツール活用の他、"コンバータ/並び替え" 等の自動処理も有効です。
    - https://support.questetra.com/ja/addons/converter-table-to-tsv-string/
    - https://support.questetra.com/ja/addons/tsv-string-reorder-columns/
- 数値型データの端数処理(Rounding)が必要な場合、EL式のフォーマッタも利用できます。
    - 小数点以下が四捨五入された文字列: '#{#sformat("%.0f", #q_numExample)}'
    - 小数点以下が切り捨てられた文字列: '#{#sformat("%.0f", ( #q_numExample - 0.5 ) )}'
    - 小数第3位が四捨五入された文字列: '#{#sformat("%.2f", #q_numExample)}'
    - 小数第3位が切り捨てられた文字列: '#{#sformat("%.2f", ( #q_numExample - 0.005 ) )}'
    - https://questetra.zendesk.com/hc/ja/articles/360024292872-R2272
- 日付設定には日付型データの参照も可能です。フォーマットは "YYYY-MM-DD" です。
- 受取人以外のCC通知アドレスは10件まで指定できます。(additional_recipients)
- 請求人のロゴ画像(URL指定)は、最大 250x90px です。
- このアドオンは複数枚請求書の同時生成には対応していません。(primary_recipient は1人)
- 請求書ツール内 "テンプレート" の設定内容は反映されません。
- "配送先" の指定には対応していません。
- 請求人の"住所" "電話番号" "ファックス" 等には対応していません。
    - 請求書内に住所等を付記したい場合、"請求人の追加情報"(B2)〔最大400字〕を活用ください
    - もしくは請求書下部の "備考"(F)〔最大4000字〕を活用ください(通知メールにも記載されます)
- テストのために(Live モードではなく)Sandbox モードで API 通信させたい場合
    - Sandbox 用の CLIENT-ID と SECRET をセット
    - スクリプト内のアクセスURLを "api.sandbox.paypal.com" に ('postUri1' と 'postUri2')
- PayPal API Reference /v2/invoicing/invoices (誤植Codeアリ注意)
    - https://developer.paypal.com/docs/invoicing/basic-integration/#2-create-draft-invoice
    - https://developer.paypal.com/docs/api/invoicing/v2/#invoices_create
*/


//////// 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 strTaxsetting     = configs.get( "StrConfA2" ) + "";
  let boolTaxinclusive  = false;
  if( strTaxsetting   === "true" ){
      boolTaxinclusive  = true;
  }
  engine.log( " AutomatedTask Config: Tax Inclusive: " + boolTaxinclusive );
const strCurrencycode   = configs.get( "StrConfA3" ) + ""; // required
  if( strCurrencycode === "" ){
    throw new Error( "\n AutomatedTask ConfigError:" +
                     " Config {A3 Currency Code} not specified \n" );
  }
const strInvnumInvdue   = configs.get( "StrConfA4" ) + "";
  let strInvnumber      = "";
  let strInvdue         = "";
  const arrInvnumInvdue = strInvnumInvdue.split("\n");
  if( arrInvnumInvdue.length >= 1 ){
    strInvnumber        = arrInvnumInvdue[0];
  }
  if( arrInvnumInvdue.length >= 2 ){
    strInvdue           = arrInvnumInvdue[1];
  }
  engine.log( " AutomatedTask Config: Invoice Number: " + strInvnumber );
  engine.log( " AutomatedTask Config: Invoice Due: " + strInvdue );
  const strInvdate        = configs.get( "StrConfA5" ) + "";
  engine.log( " AutomatedTask Config: Invoice Date: " + strInvdate );
const strInvoicerEmail  = configs.get( "StrConfB1" ) + ""; // required
const strInvoicerinfo   = configs.get( "StrConfB2" ) + "";
  let strInvoicerBizname = "";
  let strInvoicerName1   = "";
  let strInvoicerName2   = "";
  let strInvoicerAddit   = "";
  const arrInvoicerinfo = strInvoicerinfo.split("\n");
  if( arrInvoicerinfo.length >= 1 ){
    strInvoicerBizname  = arrInvoicerinfo[0];
  }
  if( arrInvoicerinfo.length >= 2 ){
    strInvoicerName1    = arrInvoicerinfo[1];
  }
  if( arrInvoicerinfo.length >= 3 ){
    strInvoicerName2    = arrInvoicerinfo[2];
  }
  if( arrInvoicerinfo.length >= 4 ){
    strInvoicerAddit    = arrInvoicerinfo[3];
  }
const strInvoicerProfile = configs.get( "StrConfB3" ) + "";
  let strInvoicerLogo   = "";
  let strInvoicerWeb    = "";
  let strInvoicerTaxid  = "";
  const arrInvoicerProfile = strInvoicerProfile.split("\n");
  if( arrInvoicerProfile.length >= 1 ){
    strInvoicerLogo     = arrInvoicerProfile[0];
  }
  if( arrInvoicerProfile.length >= 2 ){
    strInvoicerWeb      = arrInvoicerProfile[1];
  }
  if( arrInvoicerProfile.length >= 3 ){
    strInvoicerTaxid    = arrInvoicerProfile[2];
  }
const strRecipientEmail = configs.get( "StrConfC1" ) + ""; // required
  if( strRecipientEmail === "" ){
    throw new Error( "\n AutomatedTask ConfigError:" +
                     " Config {C1 Recipient Email} not specified \n" );
  }
  engine.log( " AutomatedTask Config: Recipient Email: " + strRecipientEmail );
const strRecipientLang  = configs.get( "StrConfC2" ) + "";
  engine.log( " AutomatedTask Config: Recipient Lang: " + strRecipientLang );
const strRecipientinfo  = configs.get( "StrConfC3" ) + "";
  let strRecipientBizname = "";
  let strRecipientName1   = "";
  let strRecipientName2   = "";
  const arrRecipientinfo  = strRecipientinfo.split("\n");
  if( arrRecipientinfo.length >= 1 ){
    strRecipientBizname = arrRecipientinfo[0];
  }
  if( arrRecipientinfo.length >= 2 ){
    strRecipientName1   = arrRecipientinfo[1];
  }
  if( arrRecipientinfo.length >= 3 ){
    strRecipientName2   = arrRecipientinfo[2];
  }
  engine.log( " AutomatedTask Config: Recipient BizName: " + strRecipientBizname );
  engine.log( " AutomatedTask Config: Recipient Name1: " + strRecipientName1 );
  engine.log( " AutomatedTask Config: Recipient Name2: " + strRecipientName2 );
const strAdditionalRecipients = configs.get( "StrConfD" ) + "";
  const arrAdditionalRecipients = strAdditionalRecipients.split("\n");
  if( strAdditionalRecipients === "" ){
    engine.log( " AutomatedTask Config: # of Additional Recipients: 0" );
  }else{
    engine.log( " AutomatedTask Config: # of Additional Recipients: " + arrAdditionalRecipients.length );
  }
const strTsvItems             = configs.get( "StrConfE" ) + "";
  if( strTsvItems   === "" ){
    throw new Error( "\n AutomatedTask ConfigError:" +
                     " Config {E TSV for Items} not specified \n" );
  }
  const arrTsvItems           = strTsvItems.split("\n");
  engine.log( " AutomatedTask Config: # of Items: " + arrTsvItems.length );
const strNoteToRecipient      = configs.get( "StrConfF" ) + "";
const strTermsConditions      = configs.get( "StrConfG" ) + "";
const strMemo                 = configs.get( "StrConfH" ) + "";
const strPocketInvoiceid      = configs.getObject( "SelectConfI" );
const strPocketInvoiceurl     = configs.getObject( "SelectConfJ" );
const strPocketInvoiceStatus  = configs.getObject( "SelectConfK" );


//// == 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 ); // with "Authorization: Basic "
    request1 = request1.formParam( "grant_type", "client_credentials" );
engine.log( " AutomatedTask ApiRequest1 Prepared" );
// RFC 6749 - OAuth 2.0 - Client Credentials Grant
// - https://tools.ietf.org/html/rfc6749#section-4.4.2

// 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: Create Invoice Draft in PayPal.com
let request2Obj = {};
    request2Obj.detail = {}; // "detail" object required in PayPal API
    if( strInvnumber !== "" ){
      request2Obj.detail.invoice_number = strInvnumber;
    }
    if( strInvdate !== "" ){
      request2Obj.detail.invoice_date = strInvdate;
    }
    request2Obj.detail.currency_code  = strCurrencycode; // required in PayPal API
    if( strNoteToRecipient !== "" ){
      request2Obj.detail.note         = strNoteToRecipient;
    }
    if( strTermsConditions !== "" ){
      request2Obj.detail.terms_and_conditions = strTermsConditions; /// API-Doc may be wrong
    }
    if( strMemo !== "" ){
      request2Obj.detail.memo           = strMemo;
    }
    if( strInvdue !== "" ){
      request2Obj.detail.payment_term   = {};
      request2Obj.detail.payment_term.due_date = strInvdue;
    }
    request2Obj.invoicer = {}; // required in this addon
    request2Obj.invoicer.email_address   = strInvoicerEmail;
    if( strInvoicerBizname !== "" ){
      request2Obj.invoicer.business_name = strInvoicerBizname;     /// No info in API-Doc
    }
    request2Obj.invoicer.name = {};
    request2Obj.invoicer.name.given_name = strInvoicerName1;
    request2Obj.invoicer.name.surname    = strInvoicerName2;
    if( strInvoicerLogo !== "" ){
      request2Obj.invoicer.logo_url = strInvoicerLogo;
    }
    if( strInvoicerWeb !== "" ){
      request2Obj.invoicer.website  = strInvoicerWeb;
    }
    if( strInvoicerTaxid !== "" ){
      request2Obj.invoicer.tax_id   = strInvoicerTaxid;
    }
    if( strInvoicerAddit !== "" ){
      request2Obj.invoicer.additional_notes = strInvoicerAddit;
    }
    request2Obj.primary_recipients = []; // required (length must be 1 in this addon)
    request2Obj.primary_recipients[0] = {};
    request2Obj.primary_recipients[0].billing_info = {};
    request2Obj.primary_recipients[0].billing_info.email_address = strRecipientEmail;
    if( strRecipientLang !== "" ){
      request2Obj.primary_recipients[0].billing_info.language = strRecipientLang;
    }
    if( strRecipientBizname !== "" ){
      request2Obj.primary_recipients[0].billing_info.business_name = strRecipientBizname;
    }
    request2Obj.primary_recipients[0].billing_info.name = {};
    if( strRecipientName1 !== "" ){
      request2Obj.primary_recipients[0].billing_info.name.given_name = strRecipientName1;
    }
    if( strRecipientName2 !== "" ){
      request2Obj.primary_recipients[0].billing_info.name.surname = strRecipientName2;
    }
    if( strAdditionalRecipients !== "" ){
      request2Obj.additional_recipients = [];
      for( let i = 0; i < arrAdditionalRecipients.length; i++ ){
        request2Obj.additional_recipients[i] = arrAdditionalRecipients[i];
      }
    }
    request2Obj.items = []; // "items" array required in PayPal API
    for( let i = 0; i < arrTsvItems.length; i++ ){
      let arrColumns = arrTsvItems[i].split("\t");
      request2Obj.items[i] = {};
      request2Obj.items[i].name = arrColumns[0];     // required
      request2Obj.items[i].quantity = arrColumns[1]; // required
      request2Obj.items[i].unit_amount = {};
      request2Obj.items[i].unit_amount.currency_code = strCurrencycode;
      request2Obj.items[i].unit_amount.value = arrColumns[2]; // required

      if( arrColumns.length >= 4 ){
        request2Obj.items[i].description = arrColumns[3];
      }
      if( arrColumns.length >= 6 ){
        if( arrColumns[4] !== "" && arrColumns[5] !== "" ){
          request2Obj.items[i].tax = {};
          request2Obj.items[i].tax.name = arrColumns[4];
          request2Obj.items[i].tax.percent = arrColumns[5];
        }
      }
      if( arrColumns.length >= 7 ){
        if( arrColumns[6] !== "" ){
          request2Obj.items[i].discount = {};
          request2Obj.items[i].discount.amount = {};
          request2Obj.items[i].discount.amount.currency_code = strCurrencycode;
          request2Obj.items[i].discount.amount.value = arrColumns[6];
        }
      }
      if( arrColumns.length >= 8 ){
        if( arrColumns[6] !== "" && arrColumns[7] !== "" ){
          throw new Error( "\n AutomatedTask UnexpectedTsvError:" +
                           " Cannot discount by both Amount and Percent: " + i + "\n" );
        }
        if( arrColumns[7] !== "" ){
          request2Obj.items[i].discount = {};
          request2Obj.items[i].discount.percent = arrColumns[7];
        }
      }
    }
    request2Obj.configuration = {};
    request2Obj.configuration.tax_inclusive = boolTaxinclusive;
let postUri2 = "https://api.paypal.com/v2/invoicing/invoices";
let request2 = httpClient.begin(); // HttpRequestWrapper
    request2 = request2.bearer( strBearerToken );
    request2 = request2.body( JSON.stringify( request2Obj ), "application/json" );
engine.log( " AutomatedTask ApiRequest2 Prepared" );

// try request2
const response2 = request2.post( postUri2 ); // HttpResponseWrapper
engine.log( " AutomatedTask ApiRequest2 Start: " + postUri2 );
const response2Code = response2.getStatusCode() + ""; // (primitive string)
const response2Body = response2.getResponseAsString() + "";
engine.log( " AutomatedTask ApiResponse2 Status: " + response2Code );
if( response2Code !== "201"){
  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
{
  "rel":"self",
  "href":"https://api.paypal.com/v2/invoicing/invoices/INV2-XXXX-8H2Z-XXXX-YGSU",
  "method":"GET"
}
***/
const response2Obj = JSON.parse( response2Body );


// prepare request3: Show invoice details
let getUri3  = response2Obj.href;
let request3 = httpClient.begin(); // HttpRequestWrapper
    request3 = request3.bearer( strBearerToken );
engine.log( " AutomatedTask ApiRequest3 Prepared" );

// try request3
const response3 = request3.get( getUri3 ); // HttpResponseWrapper
engine.log( " AutomatedTask ApiRequest3 Start: " + getUri3 );
const response3Code = response3.getStatusCode() + ""; // (primitive string)
const response3Body = response3.getResponseAsString() + "";
engine.log( " AutomatedTask ApiResponse3 Status: " + response3Code );
if( response3Code !== "200"){
  throw new Error( "\n AutomatedTask UnexpectedResponseError: " +
                    response3Code + "\n" + response3Body + "\n" );
}

// parse response3
/*** engine.log( response3Body ); // debug
response sample
{
  "id":"INV2-XXXX-ZDDH-XXXX-RAJ4",
  "status":"DRAFT",
  "detail":{
    "currency_code":"JPY",
    "note":"“Questetra BPM Suite” は、....",
    "memo":"BPM 1361963",
    "additional_data":"6000835 京都市中京区高宮町204御池ビル4階",
    "category_code":"SHIPPABLE",
    "invoice_number":"20210118-1361963",
    "invoice_date":"2021-01-17",
    "payment_term":{
      "term_type":"DUE_ON_DATE_SPECIFIED",
      "due_date":"2021-04-30"
    },
    "viewed_by_recipient":false,
    "group_draft":false,
    "metadata":{
      "create_time":"2021-01-18T07:00:03Z",
      "last_update_time":"2021-01-18T07:00:03Z",
      "created_by_flow":"REGULAR_SINGLE",
      "recipient_view_url":"https://www.paypal.com/invoice/p/#XXXXZDDHXXXXRAJ4",
      "invoicer_view_url":"https://www.paypal.com/invoice/details/INV2-XXXX-ZDDH-XXXX-RAJ4",
      "caller_type":"API_V2_INVOICE"
    },
    "archived":false
  },
  "invoicer":{ ◆ },
  "primary_recipients":[{ ◆ }],
  "additional_recipients":[ "example1@example.net", "example2@example.com" ],
  "items":[{ ◆ },{ ◆ },{ ◆ }],
  "configuration":{
    "tax_calculated_after_discount":false,
    "tax_inclusive":false,
    "allow_tip":false,
    "template_id":"TEMP-137273103M373943N"
  },
  "amount":{ ◆ },
  "due_amount":{ ◆ },
}
***/
const response3Obj   = JSON.parse( response3Body );
let strInvoiceid     = response3Obj.id;
let strInvoiceurl    = response3Obj.detail.metadata.invoicer_view_url;
let strInvoiceStatus = response3Obj.status;


//// == Data Updating / ワークフローデータへの代入 ==
if( strPocketInvoiceid !== null ){
  engine.setData( strPocketInvoiceid,     strInvoiceid );
}
if( strPocketInvoiceurl !== null ){
  engine.setData( strPocketInvoiceurl,    strInvoiceurl );
}
if( strPocketInvoiceStatus !== null ){
  engine.setData( strPocketInvoiceStatus, strInvoiceStatus );
}

} //////// END "main()" /////////////////////////////////////////////////////////////////

Download

2021-01-22 (C) Questetra, Inc. (MIT License)
https://support.questetra.com/addons/paypal-invoice-create-draft/
The Addon-import feature is available with Professional edition.

Notes

  • An invoice will be automatically generated when a process reaches this step in the workflow.
  • This addon (Automated Step) requires the CLIENT-ID and SECRET. (Get on your dashboard)
  • Terminology in this addon 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.
  • Consider a pre-mail system such as “You will receive an invoice from paypal.com”.
    • e.g. “PayPal Invoice ID:123, From: ‘Questetra, Inc. service-jp@paypal.com'”
  • Recipients with a PayPal account can log in and pay the invoice with PayPal.
    • Alternatively, recipients can pay as a guest with a debit card or credit card.
    • The invoice status in PayPal will be PAID.

Capture

Creates a draft invoice on the payment platform PayPal. To move the invoice from a draft to payable state, the SEND action is required. Billing automation and paperless operations are realized.
An invoice will be automatically generated when a process reaches this step in the workflow.

Appendix

  • “Invoice number”, the same as the past is not allowed.
    • e.g. “#{processInstanceId}”
    • e.g. “#{#format(processInstanceStartDatetime, ‘yyyyMMdd’)}-#{processInstanceId}”
    • e.g. “#{#sformat(‘%06d’, processInstanceSequenceNumber)}”
    • If omitted, the number part of the latest invoice will be incremented.
  • PayPal-supported language and locale
  • Currencie Codes
  • Items Information in TSV
    • Up to 8 columns (Name/Qty/Price/Descr/TaxName/TaxRate/DiscountAmount/DiscountRate)
    • The first three columns are required. (Name/Qty/Price)
    • Comma-separated formats are also available for numeric column settings.
    • Different rates, different tax names. (10% “consumption”, 8% “consumption-reduced”)
    • In case of mixed tax rates, recognizable names for the applied tax rate are better
  • For generating TSV text, commercial tools, “converter” or “reorder” addon are effective
  • If you need to round the numeric data, you can also use the EL formatter.
    • Round to the nearest integer: ‘#{#sformat(“%.0f”, #q_numExample)}’
    • Largest integer less than or equal to: ‘#{#sformat(“%.0f”, (#q_numExample – 0.5))}’
    • Round off to 2 decimal places: ‘#{#sformat(“%.2f”, #q_numExample)}’
    • Truncate to 2 decimal places: ‘#{#sformat(“%.2f”, (#q_numExample – 0.005))}’
    • https://questetra.zendesk.com/hc/en-us/articles/360024292872-R2272
  • Date configs support reference of date type data. The format is “YYYY-MM-DD”.
  • Up to 10 CC notification addresses other than the recipient. (Additional_recipients)
  • The maximum size of the Invoicert’s logo image (URL specified) is 250x90px.
  • Simultaneous generation of multiple invoices not supported. (One primary_recipient)
  • “Invoice Template” in the PayPal Invoicing are not reflected.
  • The specification of “Ship To” is not supported.
  • Invoicer’s Address Phone or Fax not supported.
    • To add via this addon, consider using “B2: Additional Information” (400 chs).
    • Or, use “F: Note” (4000 chs) at the bottom of the invoice (also in notification).
  • 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’)
    • addon developer: invoicer/address/{address_line_1,,,postal_code,country_code}
  • PayPal API Reference /v2/invoicing/invoices (There is some wrong code)

See also

PayPal: Invoice, Send
PayPal: Invoice, Check Detail

5 thoughts on “PayPal #Invoice: Create Draft”

  1. Pingback: PayPal Invoicing Create (USD) – Questetra Support

  2. Pingback: PayPal Invoicing Create (JPY) – Questetra Support

  3. Pingback: PayPal: Invoice, Check Detail – Questetra Support

  4. Pingback: Stripe: Customer, Destination Charge – Questetra Support

  5. Pingback: PayPal Billing Process – Questetra Support

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from Questetra Support

Subscribe now to keep reading and get access to the full archive.

Continue reading

Scroll to Top