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