Stripe: 請求書で課金 (Stripe: Charge by Invoice)
この工程は、Stripe 上の確定済み請求書にもとづき、顧客に課金します。デフォルトの支払方法への課金が失敗した場合は、顧客に紐づけられている予備の支払方法(カード)に対して課金します。すべての課金に失敗した場合、エラーになります。
Configs:共通設定
  • 工程名
  • メモ
Configs
  • C1: API シークレットキーを設定した認証設定 *
  • C2: 確定済み請求書の ID *
  • C3: 課金完了時、顧客に領収書をメール送付する

Notes

Capture

See also

Script (click to open)
  • 下記のスクリプトを記述した XML ファイルをダウンロードできます
    • stripe-invoice-charge.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 sendReceipt = configs.getObject('conf_SendReceipt');

    //// == Calculating / 演算 ==
    const {customerId, paymentIntentId} = checkInvoice(auth, invoiceId);
    const {customerEmail, defaultPaymentMethodId} = getCustomer(auth, customerId);
    updatePaymentIntent(auth, paymentIntentId, sendReceipt, customerEmail);
    chargeByInvoice(auth, invoiceId, defaultPaymentMethodId, 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;
}

/**
  * 請求書のステータスをチェックし、顧客 ID と支払オブジェクトの ID を返す
  * - ステータスが open, uncollectible 以外の場合はエラー
  * @param {String} oauth 認証設定
  * @param {String} invoiceId 請求書の ID
  * @return {Object} returnObj
  * @return {String} returnObj.customerId 顧客 ID
  * @return {String} returnObj.paymentIntentId 支払オブジェクトの ID
  */
function checkInvoice(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 retrieve invoice. status: ${status}`;
    }
    const invoiceObj = JSON.parse(responseStr);
    switch (invoiceObj.status) {
        case 'draft':
            throw 'The invoice is still draft. It needs to be finalized first.';
        case 'paid':
            throw 'The invoice is already paid.';
        case 'void':
            throw 'The invoice is void.';
        default: // open or uncollectible
            // do nothing
    }
    const customerId = invoiceObj.customer;
    const paymentIntentId = invoiceObj.payment_intent;
    return {customerId, paymentIntentId};
}

/**
  * 顧客オブジェクトを取得し、メールアドレスとデフォルトの支払方法の ID を返す
  * - 顧客が削除済みの場合、エラー
  * @param {String} oauth 認証設定
  * @param {String} customerId 顧客 ID
  * @return {Object} returnObj
  * @return {String} returnObj.customerEmail 顧客のメールアドレス。未設定の場合は null
  * @return {String} returnObj.defaultPaymentMethodId 顧客のデフォルトの支払方法の ID。未設定の場合は null
  */
function getCustomer(auth, customerId) {
    const apiUri = `https://api.stripe.com/v1/customers/${customerId}`;
    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 retrieve customer. status: ${status}`;
    }
    const customerObj = JSON.parse(responseStr);
    if (customerObj.deleted) {
        throw 'The customer is deleted.';
    }
    // 顧客のメールアドレス以外に「請求先メールアドレス」もダッシュボードからは設定できるが、API では取得不可
    const customerEmail = customerObj.email;
    let defaultPaymentMethodId = customerObj.invoice_settings.default_payment_method;
    if (defaultPaymentMethodId === null) {
        // 従来の登録方法で Source オブジェクトとして登録されている場合は default_source に設定される
        defaultPaymentMethodId = customerObj.default_source;
    }
    return {customerEmail, defaultPaymentMethodId};
}

/**
  * 支払オブジェクトを更新し、領収書送付先メールアドレスを設定する
  * @param {String} oauth 認証設定
  * @param {String} paymentIntentId 支払オブジェクトの ID
  * @param {boolean} sendReceipt 領収書をメール送付するかどうか
  * @param {String} customerEmail 顧客のメールアドレス
  */
function updatePaymentIntent(auth, paymentIntentId, sendReceipt, customerEmail) {
    let receiptEmail = '';
    if (sendReceipt) {
        if (customerEmail === null) {
            throw "The customer's email is not set. Unable to send the receipt.";
        }
        receiptEmail = customerEmail;
    }
    const apiUri = `https://api.stripe.com/v1/payment_intents/${paymentIntentId}`;
    const response = httpClient.begin()
        .authSetting(auth) // with "Authorization: Bearer XX"
        .header('Stripe-Version', STRIPE_API_VERSION)
        .formParam('receipt_email', receiptEmail)
        .post(apiUri);
    const status = response.getStatusCode();
    const responseStr = response.getResponseAsString();
    if (status !== 200) {
        engine.log(responseStr);
        throw `Failed to update payment intent. status: ${status}`;
    }
}

/**
  * 請求書で課金する。課金を試みる支払方法の順序は
  * 1. 顧客のデフォルトの支払方法
  * 2. 顧客の予備の支払方法(カード)
  * @param {String} oauth 認証設定
  * @param {String} invoiceId 請求書の ID
  * @param {String} defaultPaymentMethodId 顧客のデフォルトの支払方法の ID
  * @param {String} customerId 顧客 ID (デフォルトの支払方法に失敗した場合に使用)
  */
function chargeByInvoice(auth, invoiceId, defaultPaymentMethodId, customerId) {
    let succeeded = false;
    if (defaultPaymentMethodId !== null) {
        succeeded = tryCharge(auth, invoiceId, defaultPaymentMethodId);
    }
    if (succeeded) {
        return;
    }
    const customerPaymentMethodIds = getCustomerPaymentMethodIds(auth, customerId);
    const otherPaymentMethodIds = customerPaymentMethodIds.filter(id => id !== defaultPaymentMethodId);
    const httpLimit = httpClient.getRequestingLimit();
    for (let i = 0; i < otherPaymentMethodIds.length; i++) {
        succeeded = tryCharge(auth, invoiceId, otherPaymentMethodIds[i]);
        if (succeeded) {
            return;
        }
    }
    throw 'No payment method succeeded.';
}

/**
  * 指定した支払方法への課金を試みる
  * @param {String} oauth 認証設定
  * @param {String} invoiceId 請求書の ID
  * @param {String} paymentMethodId 支払方法の ID
  * @return {boolean} succeeded 課金が成功したかどうか
  */
function tryCharge(auth, invoiceId, paymentMethodId) {
    const apiUri = `https://api.stripe.com/v1/invoices/${encodeURIComponent(invoiceId)}/pay`;
    const response = httpClient.begin()
        .authSetting(auth) // with "Authorization: Bearer XX"
        .header('Stripe-Version', STRIPE_API_VERSION)
        .formParam('payment_method', paymentMethodId)
        .post(apiUri);
    const status = response.getStatusCode();
    const responseStr = response.getResponseAsString();
    if (status !== 200) {
        let errorLog = `Failed to charge by the payment method: ${paymentMethodId}, status: ${status}`;
        const error = JSON.parse(responseStr).error;
        if (error !== undefined && error.message !== undefined) {
            errorLog += `, message: ${error.message}`;
        }
        engine.log(errorLog);
        return false;
    }
    return true;
}

/**
  * 顧客の支払方法(カード)の ID 一覧を取得する
  * @param {String} oauth 認証設定
  * @param {String} customerId 顧客 ID
  * @return {Array<String>} paymentMethodIds 支払方法(カード)の ID 一覧
  */
function getCustomerPaymentMethodIds(auth, customerId) {
    const apiUri = `https://api.stripe.com/v1/customers/${customerId}/payment_methods`;
    const response = httpClient.begin()
        .authSetting(auth) // with "Authorization: Bearer XX"
        .header('Stripe-Version', STRIPE_API_VERSION)
        .queryParam('type', 'card') // required
        .queryParam('limit', '100') // maximum limit
        .get(apiUri);
    const status = response.getStatusCode();
    const responseStr = response.getResponseAsString();
    if (status !== 200) {
        engine.log(responseStr);
        throw `Failed to get the customer's payment methods. status: ${status}`;
    }
    const responseObj = JSON.parse(responseStr);
    const paymentMethods = responseObj.data;
    if (paymentMethods.length === 0) {
        throw 'The customer has no card-type payment methods.';
    }
    return paymentMethods.map(paymentMethod => paymentMethod.id);
}

    
%d人のブロガーが「いいね」をつけました。