Stripe: 請求書ドラフト作成 (Stripe: Create Draft Invoice)
この工程は、Stripe 上に請求書のドラフト(下書き)を作成します。顧客に請求書の送付やの課金を行うには、 別途、ドラフト状態の請求書を確定させる必要があります。
Configs:共通設定
  • 工程名
  • メモ
Configs
  • C1: API シークレットキーを設定した認証設定 *
  • C2-deprecated: 顧客 ID#{EL}
  • C2: 顧客 ID *
  • C3: 説明(請求書に「メモ」として表示されます)#{EL}
  • C4: 通貨(未選択の場合、顧客の通貨設定に従います)
  • C5: 商品名、単価、数量の一覧
  • C6: 請求書の ID を保存するデータ項目
  • C7: 請求書詳細ページの URL を保存するデータ項目

Notes

  • 廃止予定(C2-deprecated)が設定されている場合、C2: 顧客 ID を設定してください
  • Stripe の API シークレットキーを取得するには https://dashboard.stripe.com/apikeys を参照してください(要 Stripe ログイン)
  • 請求書の通貨は、顧客の設定や Stripe アカウントの設定によって決まります
  • 単価は通貨の最小単位で設定してください
    • 例えば、10 USD を請求するには、1000(1000 セント)と設定します
    • 詳細は Stripe のドキュメントを参照してください

Capture

See also

Script (click to open)
  • 下記のスクリプトを記述した XML ファイルをダウンロードできます
    • stripe-invoice-create.xml (C) Questetra, Inc. (MIT License)
    • Professional をご利用であればファイルの内容を改変することでオリジナルのアドオンとして活用できます


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人のブロガーが「いいね」をつけました。