Stripe: Create Draft Invoice
This item creates a draft invoice on Stripe. The created invoice remains a draft until you finalize it, which allows you to send the invoice or charge the customer.
Configs: Common
  • Step Name
  • Note
Configs
  • C1: Authorization Setting in which API Secret Key is set *
  • C2: Customer ID *#{EL}
  • C3: Description (Displayed in the invoice as ‘memo’)#{EL}
  • C4: List of item names, unit amounts, and quantities
  • C5: Data item to save ID of the invoice
  • C6: Data item to save URL of the invoice detail page

Notes

  • To get Stripe’s API Secret Key, visit https://dashboard.stripe.com/apikeys.
  • The currency of the invoice is determined by the customer’s setting or the Stripe account setting. For more details, see the Stripe Support Page.
  • Unit amounts must be provided in the currency’s smallest unit. For example, to charge 10 USD, provide 1000 (that is, 1000 cents). For more details, see the Stripe Documentation.

Capture

See also

Script (click to open)
  • An XML file that contains the code below is available to download
    • stripe-invoice-create.xml (C) Questetra, Inc. (MIT License)
    • If you are using Professional, you can modify the contents of this file and use it as your own add-on


main();

function main(){
    //// == Config Retrieving / 工程コンフィグの参照 ==
    const auth = configs.get('conf_Auth');
    const customerId = retrieveCustomerId();
    const description = configs.get('conf_Description');
    const items = retrieveItems();
    const invoiceIdDef = configs.getObject('conf_InvoiceId');
    const invoiceUrlDef = configs.getObject('conf_InvoiceUrl');

    //// == Calculating / 演算 ==
    const invoiceId = createInvoice(auth, customerId, description);
    items.forEach(item => {
        attachInvoiceItem(auth, customerId, invoiceId, item);
    });

    //// == Data Updating / ワークフローデータへの代入 ==
    if (invoiceIdDef !== null) { // STRING
        engine.setData(invoiceIdDef, invoiceId);
    }
    if (invoiceUrlDef !== null) { // STRING
        engine.setData(invoiceUrlDef, `https://dashboard.stripe.com/invoices/${invoiceId}`);
    }
}

/**
  * config から顧客 ID を読み出す。空文字列の場合はエラー
  * @return {String} customerId 顧客 ID
  */
function retrieveCustomerId() {
    const customerId = configs.get('conf_CustomerId');
    if (customerId === '') {
        throw 'Customer ID is blank.';
    }
    return customerId;
}

/**
  * config からラインアイテムの情報を読み出す
  * 入力値が不正な場合や、請求額合計が 8 桁(Stripe の上限値)を超える場合はエラー
  * @return {List<Object>} items
  * @return {String} items[].description アイテム名
  * @return {String} items[].unitAmount 単価
  * @return {String} items[].quantity 数量
  */
function retrieveItems() {
    const itemsDef = configs.getObject('conf_LineItems');
    if (itemsDef === null) {
        return [];
    }
    const table = engine.findData(itemsDef); // ScriptListArray

    // テーブルのサイズをチェック
    if (table === null) {
        return [];
    }
    if (table.size() > httpClient.getRequestingLimit() - 1) { // 請求書ドラフトの作成で 1 回、各アイテムの添付でリクエスト 1 回
        throw 'Too many line items. Number of necessary HTTP requests exceeds the limit.';
    }

    // サブデータ項目の数と型をチェック
    const subDataDefs = itemsDef.getSubDataDefinitions(); // List<SubDataDefinitionView>
    if (subDataDefs.size() < 3) {
        throw 'The line items must include item description, unit amount, and quantity.';
    }
    if (!subDataDefs.get(0).matchDataType('STRING')) {
        throw 'Line item name (1st column) must be STRING.';
    }
    if (!subDataDefs.get(1).matchDataType('DECIMAL')) {
        throw 'Line item unit amount (2nd column) must be DECIMAL.';
    }
    if (!subDataDefs.get(2).matchDataType('DECIMAL')) {
        throw 'Line item quantity (3rd column) must be DECIMAL.';
    }

    // オブジェクトの配列に整形
    const items = [];
    let totalAmount = 0;
    for (let i = 0; i < table.size(); i++) {
        const row = table.getRow(i);
        const description = row.getObject(0);
        if (description === null) {
            throw `Line item ${i+1} is invalid. Item name must not be blank.`;
        }
        const unitAmount = parseBigDecimalToLong(row.getObject(1), 'Unit amount', i);
        const quantity = parseBigDecimalToLong(row.getObject(2), 'Quantity', i);
        totalAmount += unitAmount * quantity;
        // 請求額は Stripe の制限で最大 8 桁まで。IDR のみ 12 桁までだが、ここでは一律で 8 桁までとする
        if (totalAmount > 99999999) {
            throw 'The total amount of line items must be less than 100000000.';
        }
        const item = {description, unitAmount, quantity};
        items.push(item);
    }
    return items;
}

/**
  * BigDecimal の数値を long にパースして返す
  * 小数点以下が0でない場合、負の数の場合はエラー
  * @param {BigDecimal} bigDecimal 数値
  * @param {String} label エラー出力用ラベル
  * @param {Number} i エラー出力用インデックス
  * @return {long} longValue 変換後の数値
  */
function parseBigDecimalToLong(bigDecimal, label, i) {
    let longValue;
    try {
        longValue = bigDecimal.longValueExact();
    } catch (e) {
        throw `Line item ${i+1} is invalid. ${label} must be integer.`;
    }
    if (longValue < 0) {
        throw `Line item ${i+1} is invalid. ${label} must not be negative.`;
    }
    return longValue;
}

/**
  * 請求書のドラフトを作成する
  * @param {String} oauth 認証設定
  * @param {String} customerId 顧客 ID
  * @param {String} description 説明
  * @return {String} invoiceId 作成された請求書ドラフトの ID
  */
function createInvoice(auth, customerId, description) {
    const apiUri = 'https://api.stripe.com/v1/invoices';
    const response = httpClient.begin()
        .authSetting(auth) // with "Authorization: Bearer XX"
        .formParam('customer', customerId)
        .formParam('description', description)
        .formParam('auto_advance', 'false') // ドラフトが自動で確定されないよう false に
        .formParam('pending_invoice_items_behavior', 'exclude') // 保留中の invoice item が追加されないよう false に
        .post(apiUri);
    const status = response.getStatusCode();
    const responseStr = response.getResponseAsString();
    if (status !== 200) {
        engine.log(responseStr);
        throw `Failed to create draft invoice. status: ${status}`;
    }
    return JSON.parse(responseStr).id;
}

/**
  * 請求書ドラフトにラインアイテムを追加する
  * @param {String} oauth 認証設定
  * @param {String} customerId 顧客 ID
  * @param {String} invoiceId 請求書ドラフトの ID
  * @param {Object} item
  * @param {String} item.description アイテム名
  * @param {long} item.unitAmount 単価
  * @param {long} item.quantity 数量
  */
function attachInvoiceItem(auth, customerId, invoiceId, {description, unitAmount, quantity}) {
    const apiUri = 'https://api.stripe.com/v1/invoiceitems';
    const response = httpClient.begin()
        .authSetting(auth) // with "Authorization: Bearer XX"
        .formParam('customer', customerId)
        .formParam('invoice', invoiceId)
        .formParam('description', description)
        .formParam('unit_amount', unitAmount.toString())
        .formParam('quantity', quantity.toString())
        .post(apiUri);
    const status = response.getStatusCode();
    const responseStr = response.getResponseAsString();
    if (status !== 200) {
        engine.log(responseStr);
        throw `Failed to attach an invoice item. status: ${status}`;
    }
}

    
%d bloggers like this: