feat: Add support to update account holder (#11499)
This commit is contained in:
@@ -1,6 +1,10 @@
|
|||||||
import { BigNumberInput } from "../totals"
|
import { BigNumberInput } from "../totals"
|
||||||
import { PaymentCollectionStatus } from "./common"
|
import { PaymentCollectionStatus } from "./common"
|
||||||
import { PaymentCustomerDTO, PaymentProviderContext } from "./provider"
|
import {
|
||||||
|
PaymentAccountHolderDTO,
|
||||||
|
PaymentCustomerDTO,
|
||||||
|
PaymentProviderContext,
|
||||||
|
} from "./provider"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The payment collection to be created.
|
* The payment collection to be created.
|
||||||
@@ -275,6 +279,33 @@ export interface CreateAccountHolderDTO {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UpdateAccountHolderDTO {
|
||||||
|
/**
|
||||||
|
* The ID of the account holder.
|
||||||
|
*/
|
||||||
|
id: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The provider's ID.
|
||||||
|
*/
|
||||||
|
provider_id: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Necessary context data for the associated payment provider.
|
||||||
|
*/
|
||||||
|
context: PaymentProviderContext & {
|
||||||
|
/**
|
||||||
|
* The account holder information from Medusa.
|
||||||
|
*/
|
||||||
|
account_holder: PaymentAccountHolderDTO
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds custom data in key-value pairs.
|
||||||
|
*/
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The details of the webhook event payload.
|
* The details of the webhook event payload.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -193,6 +193,18 @@ export type CreateAccountHolderInput = PaymentProviderInput & {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type UpdateAccountHolderInput = PaymentProviderInput & {
|
||||||
|
/**
|
||||||
|
* The context of creating the account holder.
|
||||||
|
*/
|
||||||
|
context: PaymentProviderContext & {
|
||||||
|
/**
|
||||||
|
* The account holder's details.
|
||||||
|
*/
|
||||||
|
account_holder: PaymentAccountHolderDTO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @interface
|
* @interface
|
||||||
*
|
*
|
||||||
@@ -323,6 +335,8 @@ export type CreateAccountHolderOutput = PaymentProviderOutput & {
|
|||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type UpdateAccountHolderOutput = PaymentProviderOutput
|
||||||
|
|
||||||
export type DeleteAccountHolderOutput = PaymentProviderOutput
|
export type DeleteAccountHolderOutput = PaymentProviderOutput
|
||||||
|
|
||||||
export type ListPaymentMethodsOutput = (PaymentProviderOutput & {
|
export type ListPaymentMethodsOutput = (PaymentProviderOutput & {
|
||||||
@@ -466,6 +480,50 @@ export interface IPaymentProvider {
|
|||||||
data: CreateAccountHolderInput
|
data: CreateAccountHolderInput
|
||||||
): Promise<CreateAccountHolderOutput>
|
): Promise<CreateAccountHolderOutput>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is used when updating an account holder in Medusa, allowing you to update
|
||||||
|
* the equivalent account in the third-party service.
|
||||||
|
*
|
||||||
|
* The returned data will be stored in the account holder created in Medusa. For example,
|
||||||
|
* the returned `id` property will be stored in the account holder's `external_id` property.
|
||||||
|
*
|
||||||
|
* @param data - Input data including the details of the account holder to update.
|
||||||
|
* @returns The result of updating the account holder. If an error occurs, throw it.
|
||||||
|
*
|
||||||
|
* @version 2.6.0
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* import { MedusaError } from "@medusajs/framework/utils"
|
||||||
|
*
|
||||||
|
* class MyPaymentProviderService extends AbstractPaymentProvider<
|
||||||
|
* Options
|
||||||
|
* > {
|
||||||
|
* async updateAccountHolder({ context, data }: UpdateAccountHolderInput) {
|
||||||
|
* const { account_holder, customer } = context
|
||||||
|
*
|
||||||
|
* if (!account_holder?.data?.id) {
|
||||||
|
* throw new MedusaError(
|
||||||
|
* MedusaError.Types.INVALID_DATA,
|
||||||
|
* "Missing account holder ID."
|
||||||
|
* )
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // assuming you have a client that updates the account holder
|
||||||
|
* const providerAccountHolder = await this.client.updateAccountHolder({
|
||||||
|
* id: account_holder.data.id,
|
||||||
|
* ...data
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* return {
|
||||||
|
* id: providerAccountHolder.id,
|
||||||
|
* data: providerAccountHolder as unknown as Record<string, unknown>
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
updateAccountHolder?(
|
||||||
|
data: UpdateAccountHolderInput
|
||||||
|
): Promise<UpdateAccountHolderOutput>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is used when an account holder is deleted in Medusa, allowing you
|
* This method is used when an account holder is deleted in Medusa, allowing you
|
||||||
* to also delete the equivalent account holder in the third-party service.
|
* to also delete the equivalent account holder in the third-party service.
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import {
|
|||||||
CreateAccountHolderDTO,
|
CreateAccountHolderDTO,
|
||||||
UpsertPaymentCollectionDTO,
|
UpsertPaymentCollectionDTO,
|
||||||
CreatePaymentMethodDTO,
|
CreatePaymentMethodDTO,
|
||||||
|
UpdateAccountHolderDTO,
|
||||||
} from "./mutations"
|
} from "./mutations"
|
||||||
import { WebhookActionResult } from "./provider"
|
import { WebhookActionResult } from "./provider"
|
||||||
|
|
||||||
@@ -788,6 +789,37 @@ export interface IPaymentModuleService extends IModuleService {
|
|||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<AccountHolderDTO>
|
): Promise<AccountHolderDTO>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method updates(if supported by provider) the account holder in the payment provider.
|
||||||
|
*
|
||||||
|
* @param {UpdateAccountHolderDTO} data - The details of the account holder.
|
||||||
|
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
|
||||||
|
* @returns {Promise<Record<string, unknown>>} The account holder's details in the payment provider, typically just the ID.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const accountHolder =
|
||||||
|
* await paymentModuleService.updateAccountHolder(
|
||||||
|
* {
|
||||||
|
* provider_id: "stripe",
|
||||||
|
* context: {
|
||||||
|
* account_holder: {
|
||||||
|
* data: {
|
||||||
|
* id: "acc_holder_123",
|
||||||
|
* },
|
||||||
|
* },
|
||||||
|
* customer: {
|
||||||
|
* id: "cus_123",
|
||||||
|
* company_name: "new_name",
|
||||||
|
* },
|
||||||
|
* },
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
updateAccountHolder(
|
||||||
|
input: UpdateAccountHolderDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<AccountHolderDTO>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method deletes the account holder in the payment provider.
|
* This method deletes the account holder in the payment provider.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ import {
|
|||||||
WebhookActionResult,
|
WebhookActionResult,
|
||||||
CreateAccountHolderOutput,
|
CreateAccountHolderOutput,
|
||||||
InitiatePaymentOutput,
|
InitiatePaymentOutput,
|
||||||
|
UpdateAccountHolderDTO,
|
||||||
|
UpdateAccountHolderOutput,
|
||||||
} from "@medusajs/framework/types"
|
} from "@medusajs/framework/types"
|
||||||
import {
|
import {
|
||||||
BigNumber,
|
BigNumber,
|
||||||
@@ -1027,6 +1029,45 @@ export default class PaymentModuleService
|
|||||||
return await this.baseRepository_.serialize(accountHolder)
|
return await this.baseRepository_.serialize(accountHolder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@InjectManager()
|
||||||
|
async updateAccountHolder(
|
||||||
|
input: UpdateAccountHolderDTO,
|
||||||
|
@MedusaContext() sharedContext?: Context
|
||||||
|
): Promise<AccountHolderDTO> {
|
||||||
|
if (!input.context?.account_holder) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
"Missing account holder data while updating account holder."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let accountHolder: InferEntityType<typeof AccountHolder> | undefined
|
||||||
|
let providerAccountHolder: UpdateAccountHolderOutput | undefined
|
||||||
|
|
||||||
|
providerAccountHolder =
|
||||||
|
await this.paymentProviderService_.updateAccountHolder(
|
||||||
|
input.provider_id,
|
||||||
|
{
|
||||||
|
context: input.context,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// The data field can be empty when either the method is not supported or an account holder wasn't updated
|
||||||
|
// We still want to do the update as we might only be updating the metadata
|
||||||
|
accountHolder = await this.accountHolderService_.update(
|
||||||
|
{
|
||||||
|
id: input.id,
|
||||||
|
...(providerAccountHolder?.data
|
||||||
|
? { data: providerAccountHolder.data }
|
||||||
|
: {}),
|
||||||
|
metadata: input.metadata,
|
||||||
|
},
|
||||||
|
sharedContext
|
||||||
|
)
|
||||||
|
|
||||||
|
return await this.baseRepository_.serialize(accountHolder)
|
||||||
|
}
|
||||||
|
|
||||||
@InjectManager()
|
@InjectManager()
|
||||||
async deleteAccountHolder(
|
async deleteAccountHolder(
|
||||||
id: string,
|
id: string,
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ import {
|
|||||||
RefundPaymentOutput,
|
RefundPaymentOutput,
|
||||||
SavePaymentMethodInput,
|
SavePaymentMethodInput,
|
||||||
SavePaymentMethodOutput,
|
SavePaymentMethodOutput,
|
||||||
|
UpdateAccountHolderInput,
|
||||||
|
UpdateAccountHolderOutput,
|
||||||
UpdatePaymentInput,
|
UpdatePaymentInput,
|
||||||
UpdatePaymentOutput,
|
UpdatePaymentOutput,
|
||||||
WebhookActionResult,
|
WebhookActionResult,
|
||||||
@@ -149,6 +151,21 @@ Please make sure that the provider is registered in the container and it is conf
|
|||||||
return await provider.createAccountHolder(input)
|
return await provider.createAccountHolder(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateAccountHolder(
|
||||||
|
providerId: string,
|
||||||
|
input: UpdateAccountHolderInput
|
||||||
|
): Promise<UpdateAccountHolderOutput> {
|
||||||
|
const provider = this.retrieveProvider(providerId)
|
||||||
|
if (!provider.updateAccountHolder) {
|
||||||
|
this.#logger.warn(
|
||||||
|
`Provider ${providerId} does not support updating account holders`
|
||||||
|
)
|
||||||
|
return {} as unknown as UpdateAccountHolderOutput
|
||||||
|
}
|
||||||
|
|
||||||
|
return await provider.updateAccountHolder(input)
|
||||||
|
}
|
||||||
|
|
||||||
async deleteAccountHolder(
|
async deleteAccountHolder(
|
||||||
providerId: string,
|
providerId: string,
|
||||||
input: DeleteAccountHolderInput
|
input: DeleteAccountHolderInput
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import {
|
|||||||
RetrievePaymentOutput,
|
RetrievePaymentOutput,
|
||||||
SavePaymentMethodInput,
|
SavePaymentMethodInput,
|
||||||
SavePaymentMethodOutput,
|
SavePaymentMethodOutput,
|
||||||
|
UpdateAccountHolderInput,
|
||||||
|
UpdateAccountHolderOutput,
|
||||||
UpdatePaymentInput,
|
UpdatePaymentInput,
|
||||||
UpdatePaymentOutput,
|
UpdatePaymentOutput,
|
||||||
WebhookActionResult,
|
WebhookActionResult,
|
||||||
@@ -378,6 +380,66 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateAccountHolder({
|
||||||
|
context,
|
||||||
|
}: UpdateAccountHolderInput): Promise<UpdateAccountHolderOutput> {
|
||||||
|
const { account_holder, customer, idempotency_key } = context
|
||||||
|
|
||||||
|
if (!account_holder?.data?.id) {
|
||||||
|
throw this.buildError(
|
||||||
|
"No account holder in context",
|
||||||
|
new Error("No account holder provided while updating account holder")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no customer context was provided, we simply don't update anything within the provider
|
||||||
|
if (!customer) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountHolderId = account_holder.data.id as string
|
||||||
|
|
||||||
|
const shipping = customer.billing_address
|
||||||
|
? ({
|
||||||
|
address: {
|
||||||
|
city: customer.billing_address.city,
|
||||||
|
country: customer.billing_address.country_code,
|
||||||
|
line1: customer.billing_address.address_1,
|
||||||
|
line2: customer.billing_address.address_2,
|
||||||
|
postal_code: customer.billing_address.postal_code,
|
||||||
|
state: customer.billing_address.province,
|
||||||
|
},
|
||||||
|
} as Stripe.CustomerCreateParams.Shipping)
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stripeCustomer = await this.stripe_.customers.update(
|
||||||
|
accountHolderId,
|
||||||
|
{
|
||||||
|
email: customer.email,
|
||||||
|
name:
|
||||||
|
customer.company_name ||
|
||||||
|
`${customer.first_name ?? ""} ${customer.last_name ?? ""}`.trim() ||
|
||||||
|
undefined,
|
||||||
|
phone: customer.phone as string | undefined,
|
||||||
|
...shipping,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
idempotencyKey: idempotency_key,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: stripeCustomer as unknown as Record<string, unknown>,
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw this.buildError(
|
||||||
|
"An error occurred in updateAccountHolder when updating a Stripe customer",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async deleteAccountHolder({
|
async deleteAccountHolder({
|
||||||
context,
|
context,
|
||||||
}: DeleteAccountHolderInput): Promise<DeleteAccountHolderOutput> {
|
}: DeleteAccountHolderInput): Promise<DeleteAccountHolderOutput> {
|
||||||
|
|||||||
Reference in New Issue
Block a user