Stripe: 請求書ドラフトに項目を追加 (Stripe: Add Item to Draft Invoice)
この工程は、Stripe 上のドラフト状態の請求書に請求項目を追加します。
Configs:共通設定
  • 工程名
  • メモ
Configs
  • C1: API シークレットキーを設定した認証設定 *
  • C2: ドラフト状態の請求書の ID *
  • C3-A: 商品価格 ID(指定しない場合、商品名と単価を指定してください)
  • C3-B1: 商品名#{EL}
  • C3-B2: 単価(通貨の最小単位で指定。USD の場合はセント)
  • C4: 数量 *

Notes

  • Stripe の API シークレットキーを取得するには https://dashboard.stripe.com/apikeys を参照してください
  • 追加する項目の詳細は、C3-A: 商品価格 ID か、あるいは C3-B1: 商品名C3-B2: 単価 の組み合わせで指定してください
    • 商品価格 ID は、Stripe の商品オブジェクトに設定された価格オブジェクトの ID のことで、price_ から始まります
    • 単価は通貨の最小単位で設定してください
      • 例えば、10 USD を請求するには、1000(1000 セント)と設定します
      • 詳細は Stripe のドキュメントを参照してください

Capture

See also

Script (click to open)
  • 下記のスクリプトを記述した XML ファイルをダウンロードできます
    • stripe-invoice-item-add.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 invoiceId = retrieveInvoiceId();
    const itemPropertyMap = retrieveItemPropertyMap();

    //// == Calculating / 演算 ==
    const customerId = getCustomerFromInvoice(auth, invoiceId);
    attachInvoiceItem(auth, customerId, invoiceId, itemPropertyMap); // customerId の指定が必須
}

/**
  * config から請求書ドラフトの ID を読み出す。空文字列の場合はエラー
  * @return {String} invoiceId 請求書ドラフトの ID
  */
function retrieveInvoiceId() {
    const invoiceId = engine.findData(configs.getObject('conf_InvoiceId'));
    if (invoiceId === null) {
        throw 'Invoice ID is blank.';
    }
    return invoiceId;
}

/**
  * config からラインアイテムのプロパティを読み出し、Map に格納して返す
  * @return {Map<String, String>} itemPropertyMap ラインアイテムのプロパティの Map
  */
function retrieveItemPropertyMap() {
    const itemPropertyMap = new Map();
    const priceId = retrievePriceId();
    const itemName = configs.get('conf_ItemName');
    const unitAmountDef = configs.getObject('conf_UnitAmount');
    if (priceId !== null && priceId !== '') { // 価格 ID が指定されている場合
        if (itemName !== null && itemName !== '') {
            throw 'Price ID and item name cannot be set at the same time.';
        }
        if (unitAmountDef !== null) {
            throw 'Price ID and unit amount cannot be set at the same time.';
        }
        itemPropertyMap.set('price', priceId);
    } else { // 価格 ID が指定されていない場合
        checkItemName(itemName);
        const unitAmount = findAndParseUnitAmount(unitAmountDef);
        itemPropertyMap.set('description', itemName);
        itemPropertyMap.set('unit_amount', unitAmount);
    }
    itemPropertyMap.set('quantity', retrieveQuantity());
    return itemPropertyMap;
}

/**
  * config から価格 ID を読み出す。データ項目が設定されているのに値が空の場合はエラー
  * @return {String} priceId 価格 ID
  */
function retrievePriceId() {
    const dataDef = configs.getObject('conf_PriceId');
    if (dataDef === null) { // 固定値の場合
        return configs.get('conf_PriceId');
    }
    if (dataDef.matchDataType('SELECT_SINGLE')) { // 選択型データ項目の場合
        const select = engine.findData(dataDef);
        if (select === null || select.size() === 0) { // 未選択
          throw 'Price ID is not selected.';
        }
        return select.get(0).getValue();
    }
    // 文字型データ項目の場合
    const priceId = engine.findData(dataDef);
    if (priceId === null) {
        throw 'Price ID is blank.';
    }
    return priceId;
}

/**
  * 商品名をチェックし、以下の場合はエラー
  * - 空文字列の場合
  * - 長さが maxLength を超える場合
  * @param {String} itemName 商品名
  */
function checkItemName(itemName) {
    if (itemName === null || itemName === '') {
        throw 'Item name is blank. It is required when Price ID is not set.';
    }
    const maxLength = 250;
    if (itemName.length > maxLength) {
        throw `Item name must be at most ${maxLength} characters.`;
    }
}

/**
  * スクリプトエンジンから商品単価を取得し、String にパースして返す
  * 商品単価の上限値を超える場合はエラー
  * @param {DataDefinitionView} dataDef
  * @return {String} unitAmount 商品単価(非負整数)
  */
function findAndParseUnitAmount(dataDef) {
    if (dataDef === null) {
        throw 'Unit amount is not set. It is required when Price ID is not set.';
    }
    const longValue = findAndParseDecimal(dataDef, 'Unit amount');
    const maxAmount = 99999999;
    if (longValue > maxAmount) {
        throw `Unit amount must be smaller than ${maxAmount + 1}.`;
    }
    return longValue.toString();
}

/**
  * config から数量を取得する
  * @return {String} quantity 数量(非負整数)
  */
function retrieveQuantity() {
    const dataDef = configs.getObject('conf_Quantity');
    if (dataDef !== null) { // 数値型データ項目で指定
        const longValue = findAndParseDecimal(dataDef, 'Quantity');
        return longValue.toString();
    }
    // 固定値で指定
    const string = configs.get('conf_Quantity');
    const regex = /^(0|[1-9]\d*)$/;
    if (!regex.test(string)) {
        throw 'Quantity must be a non-negative integer.';
    }
    return string;
}

/**
  * スクリプトエンジンから数値型データ項目の値を読み出し、long にパースして返す
  * 以下の場合はエラー
  * - 値が空
  * - 小数点以下が0でない
  * - 負の数
  * @param {DataDefinitionView} dataDef
  * @param {String} label エラー出力用ラベル
  * @return {long} longValue 変換後の数値
  */
function findAndParseDecimal(dataDef, label) {
    const bigDecimal = engine.findData(dataDef);
    if (bigDecimal === null) {
        throw `${label} is blank.`;
    }
    let longValue;
    try {
        longValue = bigDecimal.longValueExact();
    } catch (e) {
        throw `${label} must be integer.`;
    }
    if (longValue < 0) {
        throw `${label} must not be negative.`;
    }
    return longValue;
}

/**
  * 請求書オブジェクトを取得し、請求書に設定されている顧客の ID を返す
  * @param {String} oauth 認証設定
  * @param {String} invoiceId 請求書の ID
  * @return {String} customerId 顧客 ID
  */
function getCustomerFromInvoice(auth, invoiceId) {
    const apiUri = `https://api.stripe.com/v1/invoices/${encodeURIComponent(invoiceId)}`;
    const response = httpClient.begin()
        .authSetting(auth) // with "Authorization: Bearer XX"
        .header('Stripe-Version', STRIPE_API_VERSION)
        .get(apiUri);
    const status = response.getStatusCode();
    const responseStr = response.getResponseAsString();
    if (status !== 200) {
        engine.log(responseStr);
        throw `Failed to get invoice. status: ${status}`;
    }
    const invoiceObj = JSON.parse(responseStr);
    return invoiceObj.customer; // 顧客はかならず設定されている
}

/**
  * 請求書ドラフトにラインアイテムを追加する
  * @param {String} oauth 認証設定
  * @param {String} customerId 顧客 ID
  * @param {String} invoiceId 請求書ドラフトの ID
  * @param {Map<String, String>} itemPropertyMap ラインアイテムのプロパティの Map
  */
function attachInvoiceItem(auth, customerId, invoiceId, itemPropertyMap) {
    const apiUri = 'https://api.stripe.com/v1/invoiceitems';
    let request = httpClient.begin()
        .authSetting(auth) // with "Authorization: Bearer XX"
        .header('Stripe-Version', STRIPE_API_VERSION)
        .formParam('customer', customerId) // Stripe API の仕様上、必須
        .formParam('invoice', invoiceId);
    itemPropertyMap.forEach((value, key) => {
        request = request.formParam(key, value);
    });
    const response = request.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人のブロガーが「いいね」をつけました。