2022-10-03
(C) Questetra, Inc. (MIT License)
2
https://support.questetra.com/bpmn-icons/service-task-stripe-product-create/
https://support.questetra.com/ja/bpmn-icons/service-task-stripe-product-create/
This item creates a product object on Stripe, with a price object attached to it.
この工程は、Stripe 上に商品オブジェクトを作成します。商品に設定した価格は商品価格オブジェクトとして保存されます。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAvJJREFUWEfF
l19I01EUxz/XKZqWNbVS6cH+UfSUGQlCD0EQRCkoFDkDUzENi6IgkB4sKEgjDStFnVlzalC9+BBR
IEFBEeVbFP3R/lhRpLRpKHO78dtv021u+pszfnv87Zzv+dxzzj33XoHOP6E1fvleGe9KJF/CDiSZ
CDIAo8d/BMkggn4BfVE27rX0ir9atOcEKC6WqdEOTkuoAOK0iALjAponY7jY0SF+zOYzK0CJSVYI
uAQkaAwcaDYm4VS7VTSH8g8JUFokryOpnGdgfzdBk7lTHAmmFRSgxCS7BBxYkOAeEQnd7VZRGKg5
A2BBVz4z2oxM+AF4at60kCsP1JJQ6dsTUwBKtxscvI+g4bRyjzljWOfdHVMAZSZZL+G4VpVI7AQ0
tFnFCUXDDaAMGWciv8PY55HEV3zHDTaSlWHlBigzySIJlvmoxsbBxHj4ngIOtllFpxug1CTNQMls
MjnbIS0d7t6etmpshe9DcKEmfACg3WwVpSpAoXyFIDNQZuMm2J2rfk1Ng6XL4NlTSE4Blwt6LOB0
QtEhkBKGvsCdHli9FnLzoe8h9L8MASfpN3eJLd4MDPscLFMeTTcgOhocDjXNi5fAr5+wYqUa+NOA
ClF9FiYnVdtHD2DnLpiYgNhYqKmGr5+DQoyYrSLJCyCDmdQ1gtEIdpv6r7LKbgscroKr9ZC/Dzpa
VIBr9VBeBYYoiDKotkLA/V7/svnGMVuFYuLugaAAx05C+ipIWa4KShe8eA7ZOdB4GQr2TwMMfoSM
NfDuLazfALY/YLdD9y148zp4GXwBgpag9goYk2BkGJ48hj15MDoK8QnQUAtZ26DrJrRaVMBRO9Sd
h7wC2JylAp87A9+G5ipBiCbU0ttbs6HiqFqCkA0XTCigCefchqFgFsWrO2TggxZcP5vpbRjJIAo7
rMfBbxDpPoo941i/w0gB0P04ViB0vZB4m0nXK5kXQtdL6X/JRLjXcp9M6Pcw8ULo+jTznXK6PU7n
O2q1+v0D2nRCMMki7aoAAAAASUVORK5CYII=
{
const sourceStr = 'あいうえおかきくけこ';
const string = sourceStr.repeat(Math.floor(length / sourceStr.length))
+ sourceStr.slice(0, length % sourceStr.length);
return string;
}
/**
* 設定の準備 - サブ通貨を設定しない場合
* @param name
* @param description
* @param currency
* @param unitAmount
* @return returnObj
* @return returnObj.productIdDef
* @return returnObj.productUrlDef
* @return returnObj.priceIdDef
* @return returnObj.priceUrlDef
*/
const prepareConfigs = (name, description, currency, unitAmount) => {
configs.put('conf_Auth', 'Stripe');
configs.put('conf_Name', name);
configs.put('conf_Description', description);
configs.put('conf_Currency', currency);
// 単価を保存した数値型データ項目を準備し、設定
const unitAmountDef = engine.createDataDefinition('単価', 1, 'q_unitAmount', 'DECIMAL');
configs.putObject('conf_UnitAmount', unitAmountDef);
if (unitAmount !== null) {
engine.setData(unitAmountDef, java.math.BigDecimal.valueOf(unitAmount));
}
// 商品オブジェクトの ID を保存する文字型データ項目(単一行)を準備し、設定
const productIdDef = engine.createDataDefinition('商品 ID', 2, 'q_productId', 'STRING_TEXTFIELD');
engine.setData(productIdDef, '事前文字列');
configs.putObject('conf_ProductId', productIdDef);
// 商品オブジェクトの URL を保存する文字型データ項目(単一行)を準備し、設定
const productUrlDef = engine.createDataDefinition('商品 URL', 3, 'q_productUrl', 'STRING_TEXTFIELD');
engine.setData(productUrlDef, '事前文字列');
configs.putObject('conf_ProductUrl', productUrlDef);
// 価格オブジェクトの ID を保存する文字型データ項目(単一行)を準備し、設定
const priceIdDef = engine.createDataDefinition('価格 ID', 4, 'q_priceId', 'STRING_TEXTFIELD');
engine.setData(priceIdDef, '事前文字列');
configs.putObject('conf_PriceId', priceIdDef);
// 価格オブジェクトの URL を保存する文字型データ項目(単一行)を準備し、設定
const priceUrlDef = engine.createDataDefinition('価格 URL', 5, 'q_priceUrl', 'STRING_TEXTFIELD');
engine.setData(priceUrlDef, '事前文字列');
configs.putObject('conf_PriceUrl', priceUrlDef);
return {productIdDef, productUrlDef, priceIdDef, priceUrlDef};
}
/**
* 商品名が空
*/
test('Product Name is blank', () => {
prepareConfigs('', '商品の説明', 'jpy', 100);
expect(execute).toThrow('Product Name is blank.');
});
/**
* 商品名が長すぎる
*/
test('Product Name is too long', () => {
const name = createString(NAME_MAX_LENGTH + 1);
prepareConfigs(name, '商品の説明', 'jpy', 100);
expect(execute).toThrow(`Product Name must be at most ${NAME_MAX_LENGTH} characters.`);
});
/**
* 商品説明が長すぎる
*/
test('Product Description is too long', () => {
const description = createString(DESCRIPTION_MAX_LENGTH + 1);
prepareConfigs('テスト商品 1', description, 'jpy', 100);
expect(execute).toThrow(`Product Description must be at most ${DESCRIPTION_MAX_LENGTH} characters.`);
});
/**
* 単価が空
*/
test('Unit amount is blank', () => {
prepareConfigs('テスト商品 1', '商品の説明', 'jpy', null);
expect(execute).toThrow('Unit amount is blank.');
});
/**
* 単価が整数でない
*/
test('Unit amount is not integer', () => {
prepareConfigs('テスト商品 1', '商品の説明', 'jpy', 100.01);
expect(execute).toThrow('Unit amount must be integer.');
});
/**
* 単価が負の整数
*/
test('Unit amount is negative', () => {
prepareConfigs('テスト商品 1', '商品の説明', 'jpy', -100);
expect(execute).toThrow('Unit amount must not be negative.');
});
/**
* 単価が上限値を超える
*/
test('Unit amount exceeds the limit', () => {
prepareConfigs('テスト商品 1', '商品の説明', 'jpy', MAX_UNIT_AMOUNT + 1);
expect(execute).toThrow(`Unit amount must be smaller than ${MAX_UNIT_AMOUNT + 1}.`);
});
/**
* サブ通貨を設定する
* @param i
* @param currency
* @param unitAmount
*/
const setSubCurrency = (i, currency, unitAmount) => {
configs.put(`conf_Opt${i}_Currency`, currency);
// 単価を保存した数値型データ項目を準備し、設定
const unitAmountDef = engine.createDataDefinition(`サブ通貨 ${i} の単価`, 5 + i, `q_unitAmountForOpt${i}`, 'DECIMAL');
configs.putObject(`conf_Opt${i}_UnitAmount`, unitAmountDef);
if (unitAmount !== null) {
engine.setData(unitAmountDef, java.math.BigDecimal.valueOf(unitAmount));
}
}
/**
* サブ通貨が選択されていないのに単価が設定されている
*/
test('Unit amount for a sub-currency is set while the sub-currency is not selected', () => {
prepareConfigs('テスト商品 1', '商品の説明', 'jpy', 100);
setSubCurrency(1, '', 200);
expect(execute).toThrow('Unit amount for sub-currency 1 is set while sub-currency 1 is not selected.');
});
/**
* サブ通貨の指定がメイン通貨と重複
*/
test('Sub-currency is the same as the main currency', () => {
prepareConfigs('テスト商品 1', '商品の説明', 'jpy', 100);
setSubCurrency(2, 'jpy', 200);
expect(execute).toThrow('Currency JPY is set multiple times.');
});
/**
* サブ通貨同士で指定が重複
*/
test('Sub-currency is the same as another sub-currency', () => {
prepareConfigs('テスト商品 1', '商品の説明', 'jpy', 100);
setSubCurrency(1, 'usd', 200);
setSubCurrency(3, 'usd', 250);
expect(execute).toThrow('Currency USD is set multiple times.');
});
/**
* サブ通貨の単価が設定されていない
*/
test('Unit amount for a sub-currency is not set', () => {
prepareConfigs('テスト商品 1', '商品の説明', 'jpy', 100);
configs.put('conf_Opt1_Currency', 'usd');
expect(execute).toThrow('Unit amount for sub-currency 1 is not set.');
});
/**
* サブ通貨の単価が不正
*/
test('Unit amount for a sub-currency is invalid', () => {
prepareConfigs('テスト商品 1', '商品の説明', 'jpy', 100);
setSubCurrency(4, 'usd', 200.5);
expect(execute).toThrow('Unit amount for sub-currency 4 must be integer.');
});
/**
* 商品オブジェクトを作成する API リクエストのテスト
* @param {Object} request
* @param request.url
* @param request.method
* @param request.headers
* @param request.contentType
* @param request.body
* @param name
* @param description
* @param currency
* @param unitAmount
* @param currencyOptions
*/
const assertRequest = ({url, method, headers, contentType, body}, name, description, currency, unitAmount, currencyOptions) => {
expect(url).toEqual('https://api.stripe.com/v1/products');
expect(method).toEqual('POST');
expect(headers.get('Stripe-Version')).toEqual(STRIPE_VERSION);
expect(contentType).startsWith('application/x-www-form-urlencoded');
let expectedBody = `name=${encodeURIComponent(name)}`
+ `&${encodeURIComponent('default_price_data[currency]')}=${currency}`
+ `&${encodeURIComponent('default_price_data[unit_amount]')}=${unitAmount}`;
if (description !== '') {
expectedBody += `&description=${encodeURIComponent(description)}`;
}
currencyOptions.forEach((subUnitAmount, subCurrency) => {
const encodedParamName = encodeURIComponent(`default_price_data[currency_options][${subCurrency}][unit_amount]`);
expectedBody += `&${encodedParamName}=${subUnitAmount}`;
});
expectedBody = expectedBody.replace(/%20/g, '+'); // HttpRequestWrapper#formParam() はスペースを + に置き換える
expect(body).toEqual(expectedBody);
};
const NO_CURRENCY_OPTIONS = new Map();
/**
* 商品オブジェクトを作成する HTTP リクエストで失敗
*/
test('Fail to create product', () => {
prepareConfigs('テスト商品 1', '商品の説明', 'jpy', 100);
httpClient.setRequestHandler((request) => {
assertRequest(request, 'テスト商品 1', '商品の説明', 'jpy', 100, NO_CURRENCY_OPTIONS);
return httpClient.createHttpResponse(400, 'application/json', '{}');
});
expect(execute).toThrow('Failed to create product. status: 400');
});
/**
* 成功 - JPY, 単価は整数, 商品説明あり, サブ通貨なし
*/
test('Success - JPY, Unit amount is integer, With description, No sub-currencies', () => {
const {productIdDef, productUrlDef, priceIdDef, priceUrlDef} = prepareConfigs('テスト商品 1', '商品 1 の説明', 'jpy', 100);
const productObj = {
'id': 'prod_10001',
'default_price': 'price_00001'
};
httpClient.setRequestHandler((request) => {
assertRequest(request, 'テスト商品 1', '商品 1 の説明', 'jpy', 100, NO_CURRENCY_OPTIONS);
return httpClient.createHttpResponse(200, 'application/json', JSON.stringify(productObj));
});
execute();
// 文字型データ項目の値をチェック
expect(engine.findData(productIdDef)).toEqual('prod_10001');
expect(engine.findData(productUrlDef)).toEqual('https://dashboard.stripe.com/products/prod_10001');
expect(engine.findData(priceIdDef)).toEqual('price_00001');
expect(engine.findData(priceUrlDef)).toEqual('https://dashboard.stripe.com/prices/price_00001');
});
/**
* 成功 - USD, 単価の小数点以下が 0, 商品説明なし, サブ通貨を 1 つ指定
*/
test('Success - USD, Unit amount can be parsed to integer, Without description, With a sub-currency', () => {
const {productIdDef, productUrlDef, priceIdDef, priceUrlDef} = prepareConfigs('テスト商品 2', '', 'usd', 500.00);
setSubCurrency(1, 'cad', 400.00);
const currencyOptions = new Map();
currencyOptions.set('cad', 400);
const productObj = {
'id': 'prod_10002',
'default_price': 'price_00002'
};
httpClient.setRequestHandler((request) => {
assertRequest(request, 'テスト商品 2', '', 'usd', 500, currencyOptions);
return httpClient.createHttpResponse(200, 'application/json', JSON.stringify(productObj));
});
execute();
// 文字型データ項目の値をチェック
expect(engine.findData(productIdDef)).toEqual('prod_10002');
expect(engine.findData(productUrlDef)).toEqual('https://dashboard.stripe.com/products/prod_10002');
expect(engine.findData(priceIdDef)).toEqual('price_00002');
expect(engine.findData(priceUrlDef)).toEqual('https://dashboard.stripe.com/prices/price_00002');
});
/**
* 成功 - KRW, 商品名と商品説明の長さが最大, 単価が最大値, サブ通貨を 2 つ指定
*/
test('Success - KRW, String configs with longest length, Unit amount at its maximum, With two sub-currencies', () => {
const name = createString(NAME_MAX_LENGTH);
const description = createString(DESCRIPTION_MAX_LENGTH);
const {productIdDef, productUrlDef, priceIdDef, priceUrlDef} = prepareConfigs(name, description, 'krw', MAX_UNIT_AMOUNT);
setSubCurrency(2, 'hkd', MAX_UNIT_AMOUNT);
setSubCurrency(4, 'jpy', MAX_UNIT_AMOUNT - 1);
const currencyOptions = new Map();
currencyOptions.set('hkd', MAX_UNIT_AMOUNT);
currencyOptions.set('jpy', MAX_UNIT_AMOUNT - 1);
const productObj = {
'id': 'prod_10003',
'default_price': 'price_00003'
};
httpClient.setRequestHandler((request) => {
assertRequest(request, name, description, 'krw', MAX_UNIT_AMOUNT, currencyOptions);
return httpClient.createHttpResponse(200, 'application/json', JSON.stringify(productObj));
});
execute();
// 文字型データ項目の値をチェック
expect(engine.findData(productIdDef)).toEqual('prod_10003');
expect(engine.findData(productUrlDef)).toEqual('https://dashboard.stripe.com/products/prod_10003');
expect(engine.findData(priceIdDef)).toEqual('price_00003');
expect(engine.findData(priceUrlDef)).toEqual('https://dashboard.stripe.com/prices/price_00003');
});
/**
* 成功 - 価格オブジェクトのID と URL を保存しない, サブ通貨が選択されているがデータ項目の値が空
*/
test('Success - Price ID and URL not saved, sub-currency is ignored as its unit amount is blank', () => {
const {productIdDef, productUrlDef, priceIdDef, priceUrlDef} = prepareConfigs('テスト商品 4', '商品 4 の説明', 'jpy', 100);
configs.put('conf_PriceId', '');
configs.put('conf_PriceUrl', '');
setSubCurrency(3, 'cny', null);
const productObj = {
'id': 'prod_20001',
'default_price': 'price_00004'
};
httpClient.setRequestHandler((request) => {
assertRequest(request, 'テスト商品 4', '商品 4 の説明', 'jpy', 100, NO_CURRENCY_OPTIONS);
return httpClient.createHttpResponse(200, 'application/json', JSON.stringify(productObj));
});
execute();
// 文字型データ項目の値をチェック
expect(engine.findData(productIdDef)).toEqual('prod_20001');
expect(engine.findData(productUrlDef)).toEqual('https://dashboard.stripe.com/products/prod_20001');
expect(engine.findData(priceIdDef)).toEqual('事前文字列');
expect(engine.findData(priceUrlDef)).toEqual('事前文字列');
});
/**
* 成功 - 商品オブジェクトのID と URL を保存しない, サブ通貨を 4 つすべてに設定
*/
test('Success - Product ID and URL not saved, all sub-currencies are set', () => {
const {productIdDef, productUrlDef, priceIdDef, priceUrlDef} = prepareConfigs('テスト商品 5', '商品 5 の説明', 'aud', 700);
configs.put('conf_ProductId', '');
configs.put('conf_ProductUrl', '');
setSubCurrency(1, 'usd', 100);
setSubCurrency(2, 'cad', 200);
setSubCurrency(3, 'gbp', 300);
setSubCurrency(4, 'eur', 400);
const currencyOptions = new Map();
currencyOptions.set('usd', 100);
currencyOptions.set('cad', 200);
currencyOptions.set('gbp', 300);
currencyOptions.set('eur', 400);
const productObj = {
'id': 'prod_20002',
'default_price': 'price_00005'
};
httpClient.setRequestHandler((request) => {
assertRequest(request, 'テスト商品 5', '商品 5 の説明', 'aud', 700, currencyOptions);
return httpClient.createHttpResponse(200, 'application/json', JSON.stringify(productObj));
});
execute();
// 文字型データ項目の値をチェック
expect(engine.findData(productIdDef)).toEqual('事前文字列');
expect(engine.findData(productUrlDef)).toEqual('事前文字列');
expect(engine.findData(priceIdDef)).toEqual('price_00005');
expect(engine.findData(priceUrlDef)).toEqual('https://dashboard.stripe.com/prices/price_00005');
});
]]>