はじめに
Node.jsでStripeを操作する際には、Stripe Node.js Libraryを利用する事になる。ただ、このライブラリのインターフェースはsnake_caseであり、JavaScriptのcameCaseと乖離があり利用しにくいと感じていた。
そこで今回は、インターフェース(入出力)をcameCaseで行えるようにするために、ライブラリに対しカスタムの拡張を行う実装をやってみたいと思う。
ソースコード全体は以下。
実際にやってみる
import Stripe from 'stripe';
import { strict as assert } from 'assert';
import snakecaseKeys from 'snakecase-keys';
import camelcaseKeys from 'camelcase-keys';
export default () => {
assert.ok(
process.env.STRIPE_KEY_SECRET,
'env STRIPE_KEY_SECRET must be required'
);
const stripe = Stripe(process.env.STRIPE_KEY_SECRET);
// IF(入出力)をcamelCaseにした関数を別途定義($〇〇 のように利用)
Object.keys(stripe)
.filter(
(category) => !category.startsWith('_') && !category.match(/^[A-Z].+/)
)
.forEach((category) => {
Object.keys(Object.getPrototypeOf(stripe[category]))
.filter((func) => typeof stripe[category][func] === 'function')
.forEach((func) => {
stripe[category][`$${func}`] = async (...args) => {
const snakecaseArgs = snakecaseKeys(args, { deep: true });
const result = await stripe[category][func](...snakecasedArgs);
return camelcaseKeys(result, { deep: true });
};
});
});
return stripe;
};
上記の実装でうまくいく理由を順を追ってみていく。
まず、やりたい事を整理すると、やりたいのは元の関数を上書きする事なので、まずは元の関数そのものを取得する必要があるが、それはCreate a customerの実装を見ればどこに関数があるかは検討を付けられ、stripe.〇〇.△△
の△△
が関数になる。というわけで、△△
だけを取り出せればいい。
まず、console.log(Object.keys(stripe))
の実行結果としては以下のようになる。
[
'VERSION', 'on', 'once',
'off', '_api', 'account',
'accountLinks', 'accounts', 'applePayDomains',
'applicationFees', 'balance', 'balanceTransactions',
'charges', 'countrySpecs', 'coupons',
'creditNotes', 'customers', 'disputes',
'ephemeralKeys', 'events', 'exchangeRates',
'fileLinks', 'files', 'invoiceItems',
'invoices', 'mandates', 'oauth',
'paymentIntents', 'paymentLinks', 'paymentMethods',
'payouts', 'plans', 'prices',
'products', 'promotionCodes', 'quotes',
'refunds', 'reviews', 'setupAttempts',
'setupIntents', 'shippingRates', 'sources',
'subscriptionItems', 'subscriptions', 'subscriptionSchedules',
'taxCodes', 'taxRates', 'tokens',
'topups', 'transfers', 'webhookEndpoints',
'apps', 'billingPortal', 'checkout',
'financialConnections', 'identity', 'issuing',
'radar', 'reporting', 'sigma',
'terminal', 'testHelpers', 'treasury',
'errors', 'webhooks', '_prevRequestMetrics',
'_enableTelemetry', 'StripeResource'
]
この内、_
で始めっているもの、大文字で始まっているものは不要なのでこの時点で除外する事にする。
Object.keys(stripe).filter(
(category) => !category.startsWith('_') && !category.match(/^[A-Z].+/)
);
続いて、残ったオブジェクトの中から関数を取り出すが、それにはObject.getPrototypeOf()を利用する。例えば、Customersが持つ関数一覧は以下のようにして取得できる。
Object.keys(stripe)
.filter(
(category) => !category.startsWith('_') && !category.match(/^[A-Z].+/)
)
.forEach((category) => {
if (category === 'customers')
console.log(Object.getPrototypeOf(stripe[category]));
});
{
create: [Function (anonymous)],
retrieve: [Function (anonymous)],
update: [Function (anonymous)],
list: [Function (anonymous)],
del: [Function (anonymous)],
createFundingInstructions: [Function (anonymous)],
deleteDiscount: [Function (anonymous)],
listPaymentMethods: [Function (anonymous)],
retrievePaymentMethod: [Function (anonymous)],
search: [Function (anonymous)],
retrieveCashBalance: [Function (anonymous)],
updateCashBalance: [Function (anonymous)],
createBalanceTransaction: [Function (anonymous)],
retrieveBalanceTransaction: [Function (anonymous)],
updateBalanceTransaction: [Function (anonymous)],
listBalanceTransactions: [Function (anonymous)],
retrieveCashBalanceTransaction: [Function (anonymous)],
listCashBalanceTransactions: [Function (anonymous)],
createSource: [Function (anonymous)],
retrieveSource: [Function (anonymous)],
updateSource: [Function (anonymous)],
listSources: [Function (anonymous)],
deleteSource: [Function (anonymous)],
verifySource: [Function (anonymous)],
createTaxId: [Function (anonymous)],
retrieveTaxId: [Function (anonymous)],
listTaxIds: [Function (anonymous)],
deleteTaxId: [Function (anonymous)]
}
つまり、他のカテゴリーの関数も取得するには、以下のように実装すればいいだろう。
Object.keys(stripe)
.filter(
(category) => !category.startsWith('_') && !category.match(/^[A-Z].+/)
)
.forEach((category) => {
Object.keys(Object.getPrototypeOf(stripe[category]))
.filter((func) => typeof stripe[category][func] === 'function')
.forEach((func) => {
// ここで各Stripeのオブジェクト(Customerなど)に対するcreate, retrieve,...などの関数が取得できる
});
});
ここまで行けば、後はケース変換のロジックを書けばいいだけ。
まとめ
JavaScriptだからこその黒魔術感はあるが、一応やりたい事は出来た。しかしなぜNode.jsのStripeの公式ライブラリの入出力(インターフェース)はsnake_caseなんだろうか・・・。
おまけのTips
Jestで環境変数を設定する
プロダクトコードでよく利用される方法と同じ方法が使える。具体的には、dotenvをすればよく、以下のようになる。
import * as dotenv from 'dotenv';
// 省略
dotenv.config();
// 省略
※ちなみに、直接process.env
を設定する方法もあるが、それについては別記事を参照ください。