Stripe: Charge by Invoice

Stripe: Charge by Invoice

Stripe: 請求書で課金

This item charges the customer based on a finalized invoice on Stripe. If the default payment method fails, this item tries to charge other payment methods (card) attached to the customer. An error occurs if no charge succeeded.

Auto Step icon
Basic Configs
Step Name
Note
Configs for this Auto Step
conf_Auth
C1: Authorization Setting in which API Secret Key is set *
conf_InvoiceId
C2: Finalized Invoice ID *
conf_SendReceipt
C3: Email the receipt to the customer when the charge completed

Notes

Capture

See also

Script (click to open)
  • An XML file that contains the code below is available to download
    • stripe-invoice-charge.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 auto step


const STRIPE_API_VERSION = '2022-08-01';

function main(){
    //// == Config Retrieving / 工程コンフィグの参照 ==
    const auth = configs.getObject('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 new Error('Invoice ID is blank.');
    }
    return invoiceId;
}

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

/**
  * 顧客オブジェクトを取得し、メールアドレスとデフォルトの支払方法の ID を返す
  * - 顧客が削除済みの場合、エラー
  * @param {AuthSettingWrapper} auth  HTTP 認証設定 トークン直接指定
  * @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 new Error(`Failed to retrieve customer. status: ${status}`);
    }
    const customerObj = JSON.parse(responseStr);
    if (customerObj.deleted) {
        throw new Error('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 {AuthSettingWrapper} auth  HTTP 認証設定 トークン直接指定
  * @param {String} paymentIntentId 支払オブジェクトの ID
  * @param {boolean} sendReceipt 領収書をメール送付するかどうか
  * @param {String} customerEmail 顧客のメールアドレス
  */
function updatePaymentIntent(auth, paymentIntentId, sendReceipt, customerEmail) {
    let receiptEmail = '';
    if (sendReceipt) {
        if (customerEmail === null) {
            throw new Error("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 new Error(`Failed to update payment intent. status: ${status}`);
    }
}

/**
  * 請求書で課金する。課金を試みる支払方法の順序は
  * 1. 顧客のデフォルトの支払方法
  * 2. 顧客の予備の支払方法(カード)
  * @param {AuthSettingWrapper} auth  HTTP 認証設定 トークン直接指定
  * @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 new Error('No payment method succeeded.');
}

/**
  * 指定した支払方法への課金を試みる
  * @param {AuthSettingWrapper} auth  HTTP 認証設定 トークン直接指定
  * @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 {AuthSettingWrapper} auth  HTTP 認証設定 トークン直接指定
  * @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 new Error(`Failed to get the customer's payment methods. status: ${status}`);
    }
    const responseObj = JSON.parse(responseStr);
    const paymentMethods = responseObj.data;
    if (paymentMethods.length === 0) {
        throw new Error('The customer has no card-type payment methods.');
    }
    return paymentMethods.map(paymentMethod => paymentMethod.id);
}

    
Scroll to Top

Discover more from Questetra Support

Subscribe now to keep reading and get access to the full archive.

Continue reading