@lomray/microservice-payment-stripe
Version:
Stripe payment microservice based on NodeJS & inverted json.
203 lines (199 loc) • 11.6 kB
JavaScript
;
var tslib = require('tslib');
var microserviceNodejsLib = require('@lomray/microservice-nodejs-lib');
var remote = require('../../config/remote.js');
var stripePaymentMethods = require('../../constants/stripe-payment-methods.js');
var taxBehaviour = require('../../constants/tax-behaviour.js');
var transactionRole = require('../../constants/transaction-role.js');
var getPercentFromAmount = require('../../helpers/get-percent-from-amount.js');
/**
* Stripe calculation service
*/
class Calculation {
/**
* Returns receiver payment amount
* @description NOTES: How much end user will get after fees from transaction
* 1. Stable unit - stable amount that payment provider charges
* 2. Payment percent - payment provider fee percent for single transaction
* Fees calculation:
* 1. User pays fee
* totalAmount = 106$, receiverReceiver = 100, taxFee = 3, platformFee = 3
* 2. Receiver pays fees
* totalAmount = 100$, receiverReceiver = 94, taxFee = 3, platformFee = 3
*/
static getPaymentIntentFees({ entityUnitCost, additionalFeesPercent, feesPayer = transactionRole.SENDER, applicationPaymentPercent = 0, extraReceiverRevenuePercent = 0, shouldEstimateTax = false, withStripeFee = true, }) {
var _a, _b, _c, _d, _e, _f;
return tslib.__awaiter(this, void 0, void 0, function* () {
const { taxes } = yield remote();
// Calculate additional fees
const receiverAdditionalFee = getPercentFromAmount(entityUnitCost, additionalFeesPercent === null || additionalFeesPercent === void 0 ? void 0 : additionalFeesPercent.receiver);
const senderAdditionalFee = getPercentFromAmount(entityUnitCost, additionalFeesPercent === null || additionalFeesPercent === void 0 ? void 0 : additionalFeesPercent.sender);
// Additional receiver revenue from application percent
const extraReceiverUnitRevenue = getPercentFromAmount(entityUnitCost, extraReceiverRevenuePercent);
const sharedFees = Object.assign({ extraReceiverUnitRevenue,
senderAdditionalFee,
receiverAdditionalFee }, (shouldEstimateTax ? { estimatedTaxPercent: taxes === null || taxes === void 0 ? void 0 : taxes.defaultPercent } : {}));
// How much percent from total amount will receive end user
const platformUnitFee = getPercentFromAmount(entityUnitCost, applicationPaymentPercent);
if (feesPayer === transactionRole.SENDER) {
let userTempUnitAmount = entityUnitCost + platformUnitFee + senderAdditionalFee;
let userUnitAmount;
let estimatedTaxUnit = 0;
if (shouldEstimateTax) {
userTempUnitAmount += (_a = taxes === null || taxes === void 0 ? void 0 : taxes.stableUnit) !== null && _a !== void 0 ? _a : 0;
estimatedTaxUnit = getPercentFromAmount(userTempUnitAmount, (_b = taxes === null || taxes === void 0 ? void 0 : taxes.defaultPercent) !== null && _b !== void 0 ? _b : 0);
}
userTempUnitAmount += estimatedTaxUnit;
if (withStripeFee) {
// If tax will be calculated on top set of transaction - do not include Stripe fee
userUnitAmount = (_c = (yield Calculation.getStripeFeeAndProcessingAmount({
amountUnit: userTempUnitAmount,
feesPayer: transactionRole.SENDER,
}))) === null || _c === void 0 ? void 0 : _c.processingAmountUnit;
}
else {
userUnitAmount = userTempUnitAmount;
}
return Object.assign(Object.assign(Object.assign({}, sharedFees), (shouldEstimateTax ? { estimatedTaxUnit, taxFeeUnit: taxes === null || taxes === void 0 ? void 0 : taxes.stableUnit } : {})), { platformUnitFee,
userUnitAmount, stripeUnitFee: Math.round(userUnitAmount - userTempUnitAmount), receiverUnitRevenue: Math.round(entityUnitCost - receiverAdditionalFee + extraReceiverUnitRevenue) });
}
let stripeUnitFee = 0;
if (withStripeFee) {
stripeUnitFee = (_d = (yield Calculation.getStripeFeeAndProcessingAmount({
amountUnit: entityUnitCost,
feesPayer: transactionRole.RECEIVER,
}))) === null || _d === void 0 ? void 0 : _d.stripeFeeUnit;
}
let userUnitAmount = Math.round(entityUnitCost + senderAdditionalFee);
let estimatedTaxUnit = 0;
if (shouldEstimateTax) {
userUnitAmount += (_e = taxes === null || taxes === void 0 ? void 0 : taxes.stableUnit) !== null && _e !== void 0 ? _e : 0;
estimatedTaxUnit = getPercentFromAmount(userUnitAmount, (_f = taxes === null || taxes === void 0 ? void 0 : taxes.defaultPercent) !== null && _f !== void 0 ? _f : 0);
}
userUnitAmount += estimatedTaxUnit;
const receiverUnitRevenue = Math.round(entityUnitCost -
stripeUnitFee -
platformUnitFee -
receiverAdditionalFee +
extraReceiverUnitRevenue);
return Object.assign(Object.assign(Object.assign({}, sharedFees), (shouldEstimateTax ? { estimatedTaxUnit, taxFeeUnit: taxes === null || taxes === void 0 ? void 0 : taxes.stableUnit } : {})), { platformUnitFee,
stripeUnitFee,
userUnitAmount,
receiverUnitRevenue });
});
}
/**
* Returns Stripe fee
*/
static getStripeFeeAndProcessingAmount({ amountUnit, feesPayer, }) {
return tslib.__awaiter(this, void 0, void 0, function* () {
const { fees } = yield remote();
const { paymentPercent, stablePaymentUnit } = fees;
if (feesPayer === transactionRole.SENDER) {
const processingAmountUnit = Math.round((amountUnit + stablePaymentUnit) / (1 - paymentPercent / 100));
return { processingAmountUnit, stripeFeeUnit: processingAmountUnit - amountUnit };
}
const stripeFeeUnit = getPercentFromAmount(amountUnit, paymentPercent) + stablePaymentUnit;
return { processingAmountUnit: amountUnit - stripeFeeUnit, stripeFeeUnit };
});
}
/**
* Returns payment intent tax
*/
static getPaymentIntentTax(sdk, { processingTransactionAmountUnit, entityId, paymentMethodId, feesPayer, }) {
return tslib.__awaiter(this, void 0, void 0, function* () {
const { taxes } = yield remote();
const { stableUnit, autoCalculateFeeUnit } = taxes;
const tax = yield this.computePaymentIntentTax(sdk, {
entityId,
amountUnit:
// If sender cover fees, Stripe Tax calculate fee should be included into the precessing amount
feesPayer === transactionRole.SENDER
? processingTransactionAmountUnit + stableUnit
: processingTransactionAmountUnit,
paymentMethodId,
});
return { tax, createTaxTransactionFeeUnit: stableUnit, autoCalculateFeeUnit };
});
}
/**
* Compute transaction tax
* @description NOTE: Customer SHOULD HAVE address details - at least postal code
* One this api call cost is $0.05 - DO NOT ALLOW user call this method in depth
*/
static computePaymentIntentTax(sdk, { amountUnit, paymentMethodId, entityId, behaviour = taxBehaviour.EXCLUSIVE, shouldIgnoreNotCollecting = false, }) {
var _a, _b, _c, _d, _e;
return tslib.__awaiter(this, void 0, void 0, function* () {
// Get payment method data
const paymentMethod = yield sdk.paymentMethods.retrieve(paymentMethodId, {
expand: [stripePaymentMethods.CARD],
});
/* eslint-disable camelcase */
const { postal_code, country } = ((_a = paymentMethod === null || paymentMethod === void 0 ? void 0 : paymentMethod.billing_details) === null || _a === void 0 ? void 0 : _a.address) || {};
if (!postal_code || !country) {
throw new microserviceNodejsLib.BaseException({
status: 500,
message: 'For tax calculation, a payment method must include, at a minimum, the postal code and country information.',
});
}
const tax = yield sdk.tax.calculations.create({
currency: 'usd',
line_items: [
{
amount: amountUnit,
reference: entityId,
tax_behavior: behaviour,
quantity: 1,
},
],
customer_details: {
address: {
country,
postal_code,
},
address_source: 'billing',
},
expand: ['line_items.data.tax_breakdown'],
});
/* eslint-enable camelcase */
// @TODO: Fix. This property exist in response, but sdk type - not
if (
// @ts-ignore
((_b = tax === null || tax === void 0 ? void 0 : tax.tax_breakdown) === null || _b === void 0 ? void 0 : _b.some((breakdown) => (breakdown === null || breakdown === void 0 ? void 0 : breakdown.taxability_reason) === 'not_collecting')) &&
!shouldIgnoreNotCollecting) {
// @TODO: CHECK how to prevent register user tax from not collecting registration
throw new microserviceNodejsLib.BaseException({
status: 500,
message: 'Failed to compute tax. One or more tax breakdown is not collecting.',
payload: tax === null || tax === void 0 ? void 0 : tax.tax_breakdown,
});
}
const totalAmountUnit = (_d = (_c = tax === null || tax === void 0 ? void 0 : tax.line_items) === null || _c === void 0 ? void 0 : _c.data) === null || _d === void 0 ? void 0 : _d.reduce((total, { amount_tax: amountTax }) => total + amountTax, 0);
const totalTaxPercent = (_e = tax === null || tax === void 0 ? void 0 : tax.tax_breakdown) === null || _e === void 0 ? void 0 : _e.reduce((total, { tax_rate_details: details }) => total + (Number(details === null || details === void 0 ? void 0 : details.percentage_decimal) || 0), 0);
if (!tax.id ||
!tax.expires_at ||
typeof totalAmountUnit !== 'number' ||
typeof totalTaxPercent !== 'number') {
throw new microserviceNodejsLib.BaseException({
status: 500,
message: 'Failed to compute tax. Tax is invalid.',
payload: {
taxId: tax.id,
expireAt: tax.expires_at,
amountUnit: totalAmountUnit,
},
});
}
return {
id: tax.id,
totalAmountUnit,
totalTaxPercent,
behaviour,
transactionAmountWithTaxUnit: tax.amount_total,
createdAt: new Date(tax.tax_date),
expiresAt: new Date(tax.expires_at),
};
});
}
}
module.exports = Calculation;