UNPKG

@lomray/microservice-payment-stripe

Version:

Stripe payment microservice based on NodeJS & inverted json.

203 lines (199 loc) 11.6 kB
'use strict'; 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;