Stripe: Create Draft Invoice
This item creates a draft invoice on Stripe. Draft invoices must be finalized separately before you can invoice or charge the customer.
Configs: Common
  • Step Name
  • Note
Configs
  • C1: Authorization Setting in which API Secret Key is set *
  • C2-deprecated: Customer ID#{EL}
  • C2: Customer ID *
  • C3: Description (Displayed in the invoice as ‘memo’)#{EL}
  • C4: Currency (If not selected, defaults to that of the customer)
  • C5: List of item names, unit amounts, and quantities
  • C6: Data item to save ID of the invoice
  • C7: Data item to save URL of the invoice detail page

Notes

  • If C2-deprecated is set, you must set C2: Customer ID
  • To get Stripe’s API Secret Key, visit https://dashboard.stripe.com/apikeys (Stripe login required)
  • The currency of the invoice is determined by the customer’s setting or the Stripe account setting
  • 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


const STRIPE_API_VERSION = '2022-08-01';

main();

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

    //// == Calculating / 演算 ==
    const invoiceId = createInvoice(auth, customerId, description, currency);
    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 customerIdV1 = configs.get('conf_CustomerId');
    if (customerIdV1 !== null && customerIdV1 !== '') {
        return customerIdV1;
    }
    let customerId = configs.get('conf_CustomerId_V2'); // 固定値の場合
    const customerIdDef = configs.getObject('conf_CustomerId_V2');
    if (customerIdDef !== null) {
        if (customerIdDef.matchDataType('SELECT_SINGLE')) { // 選択型データ項目の場合
            const select = engine.findData(customerIdDef);
            if (select === null || select.size() === 0) { // 未選択
              throw 'Customer ID is not selected.';
            }
            customerId = select.get(0).getValue();
        } else { // 文字型データ項目の場合
            customerId = engine.findData(customerIdDef);
        }
    }
    if (customerId === null || 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 説明
  * @param {String} currency 通貨
  * @return {String} invoiceId 作成された請求書ドラフトの ID
  */
function createInvoice(auth, customerId, description, currency) {
    const apiUri = 'https://api.stripe.com/v1/invoices';
    const request = httpClient.begin()
        .authSetting(auth) // with "Authorization: Bearer XX"
        .header('Stripe-Version', STRIPE_API_VERSION)
        .formParam('customer', customerId)
        .formParam('description', description)
        .formParam('auto_advance', 'false'); // ドラフトが自動で確定されないよう false に
    if (currency !== '') {
        request.formParam('currency', currency);
    }
    const response = request.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"
        .header('Stripe-Version', STRIPE_API_VERSION)
        .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: