Skip to content

Commit

Permalink
chore(payment-stripe): smallest unit (medusajs#7748)
Browse files Browse the repository at this point in the history
  • Loading branch information
carlos-r-l-rodrigues authored Jun 17, 2024
1 parent 263d9d0 commit 70a72ce
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 10 deletions.
30 changes: 20 additions & 10 deletions packages/modules/providers/payment-stripe/src/core/stripe-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { EOL } from "os"
import Stripe from "stripe"

import {
CreatePaymentProviderSession,
MedusaContainer,
PaymentProviderError,
PaymentProviderSessionResponse,
Expand All @@ -12,22 +13,23 @@ import {
} from "@medusajs/types"
import {
AbstractPaymentProvider,
BigNumber,
MedusaError,
PaymentActions,
PaymentSessionStatus,
isDefined,
isPaymentProviderError,
} from "@medusajs/utils"

import { CreatePaymentProviderSession } from "@medusajs/types"
import {
ErrorCodes,
ErrorIntentStatus,
PaymentIntentOptions,
StripeCredentials,
StripeOptions,
} from "../types"
import {
getAmountFromSmallestUnit,
getSmallestUnit,
} from "../utils/get-smallest-unit"

abstract class StripeBase extends AbstractPaymentProvider<StripeCredentials> {
protected readonly options_: StripeOptions
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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) {
Expand All @@ -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)
Expand All @@ -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

Expand Down Expand Up @@ -317,29 +323,33 @@ 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":
return {
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":
return {
action: PaymentActions.FAILED,
data: {
resource_id: intent.metadata.resource_id,
amount: intent.amount,
amount: getAmountFromSmallestUnit(intent.amount, currency),
},
}
default:
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 70a72ce

Please sign in to comment.