chore(payment-stripe): smallest unit (#7748)

This commit is contained in:
Carlos R. L. Rodrigues
2024-06-17 13:37:38 -03:00
committed by GitHub
parent 263d9d0f22
commit 70a72ce2df
2 changed files with 95 additions and 10 deletions

View File

@@ -3,6 +3,7 @@ import { EOL } from "os"
import Stripe from "stripe"
import {
CreatePaymentProviderSession,
MedusaContainer,
PaymentProviderError,
PaymentProviderSessionResponse,
@@ -12,15 +13,12 @@ import {
} from "@medusajs/types"
import {
AbstractPaymentProvider,
BigNumber,
MedusaError,
PaymentActions,
PaymentSessionStatus,
isDefined,
isPaymentProviderError,
} from "@medusajs/utils"
import { CreatePaymentProviderSession } from "@medusajs/types"
import {
ErrorCodes,
ErrorIntentStatus,
@@ -28,6 +26,10 @@ import {
StripeCredentials,
StripeOptions,
} from "../types"
import {
getAmountFromSmallestUnit,
getSmallestUnit,
} from "../utils/get-smallest-unit"
abstract class StripeBase extends AbstractPaymentProvider<StripeCredentials> {
protected readonly options_: StripeOptions
@@ -116,7 +118,7 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeCredentials> {
const intentRequest: Stripe.PaymentIntentCreateParams = {
description,
amount: Math.round(new BigNumber(amount).numeric),
amount: getSmallestUnit(amount, currency_code),
currency: currency_code,
metadata: { resource_id: resource_id ?? "Medusa Payment" },
capture_method: this.options_.capture ? "automatic" : "manual",
@@ -232,8 +234,9 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeCredentials> {
const id = paymentSessionData.id as string
try {
const { currency } = paymentSessionData
await this.stripe_.refunds.create({
amount: Math.round(refundAmount),
amount: getSmallestUnit(refundAmount, currency as string),
payment_intent: id as string,
})
} catch (e) {
@@ -249,6 +252,9 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeCredentials> {
try {
const id = paymentSessionData.id as string
const intent = await this.stripe_.paymentIntents.retrieve(id)
intent.amount = getAmountFromSmallestUnit(intent.amount, intent.currency)
return intent as unknown as PaymentProviderSessionResponse["data"]
} catch (e) {
return this.buildError("An error occurred in retrievePayment", e)
@@ -258,9 +264,9 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeCredentials> {
async updatePayment(
input: UpdatePaymentProviderSession
): Promise<PaymentProviderError | PaymentProviderSessionResponse> {
const { context, data, amount } = input
const { context, data, currency_code, amount } = input
const amountNumeric = Math.round(new BigNumber(amount).numeric)
const amountNumeric = getSmallestUnit(amount, currency_code)
const stripeId = context.customer?.metadata?.stripe_id
@@ -317,13 +323,17 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeCredentials> {
const event = this.constructWebhookEvent(webhookData)
const intent = event.data.object as Stripe.PaymentIntent
const { currency } = intent
switch (event.type) {
case "payment_intent.amount_capturable_updated":
return {
action: PaymentActions.AUTHORIZED,
data: {
resource_id: intent.metadata.resource_id,
amount: intent.amount_capturable, // NOTE: revisit when implementing multicapture
amount: getAmountFromSmallestUnit(
intent.amount_capturable,
currency
), // NOTE: revisit when implementing multicapture
},
}
case "payment_intent.succeeded":
@@ -331,7 +341,7 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeCredentials> {
action: PaymentActions.SUCCESSFUL,
data: {
resource_id: intent.metadata.resource_id,
amount: intent.amount_received,
amount: getAmountFromSmallestUnit(intent.amount_received, currency),
},
}
case "payment_intent.payment_failed":
@@ -339,7 +349,7 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeCredentials> {
action: PaymentActions.FAILED,
data: {
resource_id: intent.metadata.resource_id,
amount: intent.amount,
amount: getAmountFromSmallestUnit(intent.amount, currency),
},
}
default:

View File

@@ -0,0 +1,75 @@
import { BigNumberInput } from "@medusajs/types"
import { BigNumber, MathBN } from "@medusajs/utils"
function getCurrencyMultiplier(currency) {
const currencyMultipliers = {
0: [
"BIF",
"CLP",
"DJF",
"GNF",
"JPY",
"KMF",
"KRW",
"MGA",
"PYG",
"RWF",
"UGX",
"VND",
"VUV",
"XAF",
"XOF",
"XPF",
],
3: ["BHD", "IQD", "JOD", "KWD", "OMR", "TND"],
}
currency = currency.toUpperCase()
let power = 2
for (const [key, value] of Object.entries(currencyMultipliers)) {
if (value.includes(currency)) {
power = parseInt(key, 10)
break
}
}
return Math.pow(10, power)
}
/**
* Converts an amount to the format required by Stripe based on currency.
* https://docs.stripe.com/currencies
* @param {BigNumberInput} amount - The amount to be converted.
* @param {string} currency - The currency code (e.g., 'USD', 'JOD').
* @returns {number} - The converted amount in the smallest currency unit.
*/
export function getSmallestUnit(
amount: BigNumberInput,
currency: string
): number {
const multiplier = getCurrencyMultiplier(currency)
const smallestAmount = new BigNumber(MathBN.mult(amount, multiplier))
let numeric = smallestAmount.numeric
// Check if the currency requires rounding to the nearest ten
if (multiplier === 1e3) {
numeric = Math.ceil(numeric / 10) * 10
}
return numeric
}
/**
* Converts an amount from the smallest currency unit to the standard unit based on currency.
* @param {BigNumberInput} amount - The amount in the smallest currency unit.
* @param {string} currency - The currency code (e.g., 'USD', 'JOD').
* @returns {number} - The converted amount in the standard currency unit.
*/
export function getAmountFromSmallestUnit(
amount: BigNumberInput,
currency: string
): number {
const multiplier = getCurrencyMultiplier(currency)
const standardAmount = new BigNumber(MathBN.div(amount, multiplier))
return standardAmount.numeric
}