Stripe: Charge by Invoice
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.
Configs: Common
  • Step Name
  • Note
Configs
  • C1: Authorization Setting in which API Secret Key is set *
  • C2: Finalized Invoice ID *
  • 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


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"
        .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"
        .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"
        .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"
        .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"
        .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 bloggers like this: