feat: Add support for managing account holder in payment module (#11015)
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
import { ModuleJoinerConfig } from "@medusajs/framework/types"
|
||||
import { LINKS, Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export const CustomerAccountHolder: ModuleJoinerConfig = {
|
||||
serviceName: LINKS.CustomerAccountHolder,
|
||||
isLink: true,
|
||||
databaseConfig: {
|
||||
tableName: "customer_account_holder",
|
||||
idPrefix: "custacchldr",
|
||||
},
|
||||
alias: [
|
||||
{
|
||||
name: ["customer_account_holder", "customer_account_holders"],
|
||||
entity: "LinkCustomerAccountHolder",
|
||||
},
|
||||
],
|
||||
primaryKeys: ["id", "customer_id", "account_holder_id"],
|
||||
relationships: [
|
||||
{
|
||||
serviceName: Modules.CUSTOMER,
|
||||
entity: "Customer",
|
||||
primaryKey: "id",
|
||||
foreignKey: "customer_id",
|
||||
alias: "customer",
|
||||
args: {
|
||||
methodSuffix: "Customers",
|
||||
},
|
||||
},
|
||||
{
|
||||
serviceName: Modules.PAYMENT,
|
||||
entity: "AccountHolder",
|
||||
primaryKey: "id",
|
||||
foreignKey: "account_holder_id",
|
||||
alias: "account_holder",
|
||||
args: {
|
||||
methodSuffix: "AccountHolders",
|
||||
},
|
||||
},
|
||||
],
|
||||
extends: [
|
||||
{
|
||||
serviceName: Modules.CUSTOMER,
|
||||
entity: "Customer",
|
||||
fieldAlias: {
|
||||
account_holder: "account_holder_link.account_holder",
|
||||
},
|
||||
relationship: {
|
||||
serviceName: LINKS.CustomerAccountHolder,
|
||||
primaryKey: "customer_id",
|
||||
foreignKey: "id",
|
||||
alias: "account_holder_link",
|
||||
},
|
||||
},
|
||||
{
|
||||
serviceName: Modules.PAYMENT,
|
||||
entity: "AccountHolder",
|
||||
fieldAlias: {
|
||||
customer: "customer_link.customer",
|
||||
},
|
||||
relationship: {
|
||||
serviceName: LINKS.CustomerAccountHolder,
|
||||
primaryKey: "account_holder_id",
|
||||
foreignKey: "id",
|
||||
alias: "customer_link",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -16,3 +16,4 @@ export * from "./region-payment-provider"
|
||||
export * from "./sales-channel-location"
|
||||
export * from "./shipping-option-price-set"
|
||||
export * from "./product-shipping-profile"
|
||||
export * from "./customer-account-holder"
|
||||
|
||||
@@ -23,12 +23,13 @@ moduleIntegrationTestRunner<IPaymentModuleService>({
|
||||
service: PaymentModuleService,
|
||||
}).linkable
|
||||
|
||||
expect(Object.keys(linkable)).toHaveLength(5)
|
||||
expect(Object.keys(linkable)).toHaveLength(6)
|
||||
expect(Object.keys(linkable)).toEqual([
|
||||
"paymentCollection",
|
||||
"paymentSession",
|
||||
"payment",
|
||||
"refundReason",
|
||||
"accountHolder",
|
||||
"paymentProvider",
|
||||
])
|
||||
|
||||
@@ -73,6 +74,15 @@ moduleIntegrationTestRunner<IPaymentModuleService>({
|
||||
field: "refundReason",
|
||||
},
|
||||
},
|
||||
accountHolder: {
|
||||
id: {
|
||||
linkable: "account_holder_id",
|
||||
entity: "AccountHolder",
|
||||
primaryKey: "id",
|
||||
serviceName: "payment",
|
||||
field: "accountHolder",
|
||||
},
|
||||
},
|
||||
paymentProvider: {
|
||||
id: {
|
||||
linkable: "payment_provider_id",
|
||||
@@ -100,10 +110,7 @@ moduleIntegrationTestRunner<IPaymentModuleService>({
|
||||
currency_code: "usd",
|
||||
data: {},
|
||||
context: {
|
||||
extra: {},
|
||||
customer: {},
|
||||
billing_address: {},
|
||||
email: "test@test.test.com",
|
||||
customer: { id: "cus-id-1", email: "new@test.tsst" },
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -347,10 +354,10 @@ moduleIntegrationTestRunner<IPaymentModuleService>({
|
||||
currency_code: "usd",
|
||||
data: {},
|
||||
context: {
|
||||
extra: {},
|
||||
customer: {},
|
||||
billing_address: {},
|
||||
email: "test@test.test.com",
|
||||
customer: {
|
||||
id: "cus-id-1",
|
||||
email: "test@test.test.com",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -402,10 +409,7 @@ moduleIntegrationTestRunner<IPaymentModuleService>({
|
||||
currency_code: "usd",
|
||||
data: {},
|
||||
context: {
|
||||
extra: {},
|
||||
customer: {},
|
||||
billing_address: {},
|
||||
email: "test@test.test.com",
|
||||
customer: { id: "cus-id-1", email: "test@test.test.com" },
|
||||
},
|
||||
})
|
||||
.catch((e) => e)
|
||||
@@ -439,10 +443,7 @@ moduleIntegrationTestRunner<IPaymentModuleService>({
|
||||
currency_code: "usd",
|
||||
data: {},
|
||||
context: {
|
||||
extra: {},
|
||||
customer: {},
|
||||
billing_address: {},
|
||||
email: "test@test.test.com",
|
||||
customer: { id: "cus-id-1", email: "test@test.test.com" },
|
||||
},
|
||||
})
|
||||
.catch((e) => e)
|
||||
@@ -461,10 +462,7 @@ moduleIntegrationTestRunner<IPaymentModuleService>({
|
||||
currency_code: "usd",
|
||||
data: {},
|
||||
context: {
|
||||
extra: {},
|
||||
customer: {},
|
||||
billing_address: {},
|
||||
email: "test@test.test.com",
|
||||
customer: { id: "cus-id-1", email: "new@test.tsst" },
|
||||
},
|
||||
})
|
||||
|
||||
@@ -474,10 +472,7 @@ moduleIntegrationTestRunner<IPaymentModuleService>({
|
||||
currency_code: "eur",
|
||||
data: {},
|
||||
context: {
|
||||
extra: {},
|
||||
customer: {},
|
||||
billing_address: {},
|
||||
email: "new@test.tsst",
|
||||
customer: { id: "cus-id-1", email: "new@test.tsst" },
|
||||
},
|
||||
})
|
||||
|
||||
@@ -505,10 +500,7 @@ moduleIntegrationTestRunner<IPaymentModuleService>({
|
||||
currency_code: "usd",
|
||||
data: {},
|
||||
context: {
|
||||
extra: {},
|
||||
email: "test@test.com",
|
||||
billing_address: {},
|
||||
customer: {},
|
||||
customer: { id: "cus-id-1", email: "new@test.tsst" },
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
PaymentProvider,
|
||||
PaymentSession,
|
||||
RefundReason,
|
||||
AccountHolder,
|
||||
} from "@models"
|
||||
import { default as schema } from "./schema"
|
||||
|
||||
@@ -16,12 +17,14 @@ export const joinerConfig = defineJoinerConfig(Modules.PAYMENT, {
|
||||
PaymentProvider,
|
||||
PaymentSession,
|
||||
RefundReason,
|
||||
AccountHolder,
|
||||
],
|
||||
linkableKeys: {
|
||||
payment_id: Payment.name,
|
||||
payment_collection_id: PaymentCollection.name,
|
||||
payment_provider_id: PaymentProvider.name,
|
||||
refund_reason_id: RefundReason.name,
|
||||
account_holder_id: AccountHolder.name,
|
||||
},
|
||||
alias: [
|
||||
{
|
||||
|
||||
@@ -4,6 +4,132 @@
|
||||
],
|
||||
"name": "public",
|
||||
"tables": [
|
||||
{
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"provider_id": {
|
||||
"name": "provider_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"external_id": {
|
||||
"name": "external_id",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "text"
|
||||
},
|
||||
"data": {
|
||||
"name": "data",
|
||||
"type": "jsonb",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"default": "'{}'",
|
||||
"mappedType": "json"
|
||||
},
|
||||
"metadata": {
|
||||
"name": "metadata",
|
||||
"type": "jsonb",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "json"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"length": 6,
|
||||
"default": "now()",
|
||||
"mappedType": "datetime"
|
||||
},
|
||||
"deleted_at": {
|
||||
"name": "deleted_at",
|
||||
"type": "timestamptz",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"length": 6,
|
||||
"mappedType": "datetime"
|
||||
}
|
||||
},
|
||||
"name": "account_holder",
|
||||
"schema": "public",
|
||||
"indexes": [
|
||||
{
|
||||
"keyName": "IDX_account_holder_deleted_at",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_account_holder_deleted_at\" ON \"account_holder\" (deleted_at) WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "IDX_account_holder_provider_id_external_id_unique",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_account_holder_provider_id_external_id_unique\" ON \"account_holder\" (provider_id, external_id) WHERE deleted_at IS NULL"
|
||||
},
|
||||
{
|
||||
"keyName": "account_holder_pkey",
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"composite": false,
|
||||
"constraint": true,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {},
|
||||
"nativeEnums": {}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
"id": {
|
||||
@@ -172,6 +298,7 @@
|
||||
"keyName": "IDX_payment_collection_deleted_at",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_payment_collection_deleted_at\" ON \"payment_collection\" (deleted_at) WHERE deleted_at IS NULL"
|
||||
@@ -182,12 +309,14 @@
|
||||
"id"
|
||||
],
|
||||
"composite": false,
|
||||
"constraint": true,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {}
|
||||
"foreignKeys": {},
|
||||
"nativeEnums": {}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
@@ -294,6 +423,7 @@
|
||||
"keyName": "IDX_payment_method_token_deleted_at",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_payment_method_token_deleted_at\" ON \"payment_method_token\" (deleted_at) WHERE deleted_at IS NULL"
|
||||
@@ -304,12 +434,14 @@
|
||||
"id"
|
||||
],
|
||||
"composite": false,
|
||||
"constraint": true,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {}
|
||||
"foreignKeys": {},
|
||||
"nativeEnums": {}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
@@ -372,6 +504,7 @@
|
||||
"keyName": "IDX_payment_provider_deleted_at",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_payment_provider_deleted_at\" ON \"payment_provider\" (deleted_at) WHERE deleted_at IS NULL"
|
||||
@@ -382,12 +515,14 @@
|
||||
"id"
|
||||
],
|
||||
"composite": false,
|
||||
"constraint": true,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {}
|
||||
"foreignKeys": {},
|
||||
"nativeEnums": {}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
@@ -420,14 +555,15 @@
|
||||
"payment_provider_id"
|
||||
],
|
||||
"composite": true,
|
||||
"constraint": true,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {
|
||||
"payment_collection_payment_providers_payment_coll_aa276_foreign": {
|
||||
"constraintName": "payment_collection_payment_providers_payment_coll_aa276_foreign",
|
||||
"payment_collection_payment_providers_payment_col_aa276_foreign": {
|
||||
"constraintName": "payment_collection_payment_providers_payment_col_aa276_foreign",
|
||||
"columnNames": [
|
||||
"payment_collection_id"
|
||||
],
|
||||
@@ -439,8 +575,8 @@
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
},
|
||||
"payment_collection_payment_providers_payment_provider_id_foreign": {
|
||||
"constraintName": "payment_collection_payment_providers_payment_provider_id_foreign",
|
||||
"payment_collection_payment_providers_payment_pro_2d555_foreign": {
|
||||
"constraintName": "payment_collection_payment_providers_payment_pro_2d555_foreign",
|
||||
"columnNames": [
|
||||
"payment_provider_id"
|
||||
],
|
||||
@@ -452,7 +588,8 @@
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nativeEnums": {}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
@@ -606,6 +743,7 @@
|
||||
"keyName": "IDX_payment_session_payment_collection_id",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_payment_session_payment_collection_id\" ON \"payment_session\" (payment_collection_id) WHERE deleted_at IS NULL"
|
||||
@@ -614,6 +752,7 @@
|
||||
"keyName": "IDX_payment_session_deleted_at",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_payment_session_deleted_at\" ON \"payment_session\" (deleted_at) WHERE deleted_at IS NULL"
|
||||
@@ -624,6 +763,7 @@
|
||||
"id"
|
||||
],
|
||||
"composite": false,
|
||||
"constraint": true,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
@@ -643,7 +783,8 @@
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nativeEnums": {}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
@@ -788,6 +929,7 @@
|
||||
"keyName": "IDX_payment_payment_collection_id",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_payment_payment_collection_id\" ON \"payment\" (payment_collection_id) WHERE deleted_at IS NULL"
|
||||
@@ -796,6 +938,7 @@
|
||||
"keyName": "IDX_payment_payment_session_id_unique",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_payment_payment_session_id_unique\" ON \"payment\" (payment_session_id) WHERE deleted_at IS NULL"
|
||||
@@ -804,6 +947,7 @@
|
||||
"keyName": "IDX_payment_deleted_at",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_payment_deleted_at\" ON \"payment\" (deleted_at) WHERE deleted_at IS NULL"
|
||||
@@ -812,6 +956,7 @@
|
||||
"keyName": "IDX_payment_provider_id",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_payment_provider_id\" ON \"payment\" (provider_id) WHERE deleted_at IS NULL"
|
||||
@@ -820,6 +965,7 @@
|
||||
"keyName": "IDX_payment_payment_session_id",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_payment_payment_session_id\" ON \"payment\" (payment_session_id) WHERE deleted_at IS NULL"
|
||||
@@ -830,6 +976,7 @@
|
||||
"id"
|
||||
],
|
||||
"composite": false,
|
||||
"constraint": true,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
@@ -861,7 +1008,8 @@
|
||||
"referencedTableName": "public.payment_session",
|
||||
"updateRule": "cascade"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nativeEnums": {}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
@@ -959,6 +1107,7 @@
|
||||
"keyName": "IDX_capture_payment_id",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_capture_payment_id\" ON \"capture\" (payment_id) WHERE deleted_at IS NULL"
|
||||
@@ -967,6 +1116,7 @@
|
||||
"keyName": "IDX_capture_deleted_at",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_capture_deleted_at\" ON \"capture\" (deleted_at) WHERE deleted_at IS NULL"
|
||||
@@ -977,6 +1127,7 @@
|
||||
"id"
|
||||
],
|
||||
"composite": false,
|
||||
"constraint": true,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
@@ -996,7 +1147,8 @@
|
||||
"deleteRule": "cascade",
|
||||
"updateRule": "cascade"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nativeEnums": {}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
@@ -1076,6 +1228,7 @@
|
||||
"keyName": "IDX_refund_reason_deleted_at",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_refund_reason_deleted_at\" ON \"refund_reason\" (deleted_at) WHERE deleted_at IS NULL"
|
||||
@@ -1086,12 +1239,14 @@
|
||||
"id"
|
||||
],
|
||||
"composite": false,
|
||||
"constraint": true,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
],
|
||||
"checks": [],
|
||||
"foreignKeys": {}
|
||||
"foreignKeys": {},
|
||||
"nativeEnums": {}
|
||||
},
|
||||
{
|
||||
"columns": {
|
||||
@@ -1207,6 +1362,7 @@
|
||||
"keyName": "IDX_refund_payment_id",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_refund_payment_id\" ON \"refund\" (payment_id) WHERE deleted_at IS NULL"
|
||||
@@ -1215,6 +1371,7 @@
|
||||
"keyName": "IDX_refund_refund_reason_id",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_refund_refund_reason_id\" ON \"refund\" (refund_reason_id) WHERE deleted_at IS NULL"
|
||||
@@ -1223,6 +1380,7 @@
|
||||
"keyName": "IDX_refund_deleted_at",
|
||||
"columnNames": [],
|
||||
"composite": false,
|
||||
"constraint": false,
|
||||
"primary": false,
|
||||
"unique": false,
|
||||
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_refund_deleted_at\" ON \"refund\" (deleted_at) WHERE deleted_at IS NULL"
|
||||
@@ -1233,6 +1391,7 @@
|
||||
"id"
|
||||
],
|
||||
"composite": false,
|
||||
"constraint": true,
|
||||
"primary": true,
|
||||
"unique": true
|
||||
}
|
||||
@@ -1265,7 +1424,9 @@
|
||||
"deleteRule": "set null",
|
||||
"updateRule": "cascade"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nativeEnums": {}
|
||||
}
|
||||
]
|
||||
],
|
||||
"nativeEnums": {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Migration } from '@mikro-orm/migrations';
|
||||
|
||||
export class Migration20250123122334 extends Migration {
|
||||
|
||||
override async up(): Promise<void> {
|
||||
this.addSql(`create table if not exists "account_holder" ("id" text not null, "provider_id" text not null, "external_id" text not null, "email" text null, "data" jsonb not null default '{}', "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "account_holder_pkey" primary key ("id"));`);
|
||||
this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_account_holder_deleted_at" ON "account_holder" (deleted_at) WHERE deleted_at IS NULL;`);
|
||||
this.addSql(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_account_holder_provider_id_external_id_unique" ON "account_holder" (provider_id, external_id) WHERE deleted_at IS NULL;`);
|
||||
|
||||
this.addSql(`alter table if exists "payment_collection_payment_providers" drop constraint if exists "payment_collection_payment_providers_payment_coll_aa276_foreign";`);
|
||||
this.addSql(`alter table if exists "payment_collection_payment_providers" drop constraint if exists "payment_collection_payment_providers_payment_provider_id_foreign";`);
|
||||
|
||||
this.addSql(`alter table if exists "payment_collection_payment_providers" add constraint "payment_collection_payment_providers_payment_col_aa276_foreign" foreign key ("payment_collection_id") references "payment_collection" ("id") on update cascade on delete cascade;`);
|
||||
this.addSql(`alter table if exists "payment_collection_payment_providers" add constraint "payment_collection_payment_providers_payment_pro_2d555_foreign" foreign key ("payment_provider_id") references "payment_provider" ("id") on update cascade on delete cascade;`);
|
||||
}
|
||||
|
||||
override async down(): Promise<void> {
|
||||
this.addSql(`drop table if exists "account_holder" cascade;`);
|
||||
|
||||
this.addSql(`alter table if exists "payment_collection_payment_providers" drop constraint if exists "payment_collection_payment_providers_payment_col_aa276_foreign";`);
|
||||
this.addSql(`alter table if exists "payment_collection_payment_providers" drop constraint if exists "payment_collection_payment_providers_payment_pro_2d555_foreign";`);
|
||||
|
||||
this.addSql(`alter table if exists "payment_collection_payment_providers" add constraint "payment_collection_payment_providers_payment_coll_aa276_foreign" foreign key ("payment_collection_id") references "payment_collection" ("id") on update cascade on delete cascade;`);
|
||||
this.addSql(`alter table if exists "payment_collection_payment_providers" add constraint "payment_collection_payment_providers_payment_provider_id_foreign" foreign key ("payment_provider_id") references "payment_provider" ("id") on update cascade on delete cascade;`);
|
||||
}
|
||||
|
||||
}
|
||||
19
packages/modules/payment/src/models/account-holder.ts
Normal file
19
packages/modules/payment/src/models/account-holder.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
const AccountHolder = model
|
||||
.define("AccountHolder", {
|
||||
id: model.id({ prefix: "acchld" }).primaryKey(),
|
||||
provider_id: model.text(),
|
||||
external_id: model.text(),
|
||||
email: model.text().nullable(),
|
||||
data: model.json().default({}),
|
||||
metadata: model.json().nullable(),
|
||||
})
|
||||
.indexes([
|
||||
{
|
||||
on: ["provider_id", "external_id"],
|
||||
unique: true,
|
||||
},
|
||||
])
|
||||
|
||||
export default AccountHolder
|
||||
@@ -6,3 +6,4 @@ export { default as PaymentProvider } from "./payment-provider"
|
||||
export { default as PaymentSession } from "./payment-session"
|
||||
export { default as Refund } from "./refund"
|
||||
export { default as RefundReason } from "./refund-reason"
|
||||
export { default as AccountHolder } from "./account-holder"
|
||||
|
||||
@@ -1,8 +1,25 @@
|
||||
import crypto from "crypto"
|
||||
|
||||
import {
|
||||
CreatePaymentProviderSession,
|
||||
PaymentProviderError,
|
||||
PaymentProviderSessionResponse,
|
||||
AuthorizePaymentInput,
|
||||
AuthorizePaymentOutput,
|
||||
CancelPaymentInput,
|
||||
CancelPaymentOutput,
|
||||
CapturePaymentInput,
|
||||
CapturePaymentOutput,
|
||||
DeletePaymentInput,
|
||||
DeletePaymentOutput,
|
||||
GetPaymentStatusInput,
|
||||
GetPaymentStatusOutput,
|
||||
InitiatePaymentInput,
|
||||
InitiatePaymentOutput,
|
||||
ProviderWebhookPayload,
|
||||
RefundPaymentInput,
|
||||
RefundPaymentOutput,
|
||||
RetrievePaymentInput,
|
||||
RetrievePaymentOutput,
|
||||
UpdatePaymentInput,
|
||||
UpdatePaymentOutput,
|
||||
WebhookActionResult,
|
||||
} from "@medusajs/framework/types"
|
||||
import {
|
||||
@@ -23,53 +40,49 @@ export class SystemProviderService extends AbstractPaymentProvider {
|
||||
}
|
||||
|
||||
async initiatePayment(
|
||||
context: CreatePaymentProviderSession
|
||||
): Promise<PaymentProviderSessionResponse> {
|
||||
return { data: {} }
|
||||
input: InitiatePaymentInput
|
||||
): Promise<InitiatePaymentOutput> {
|
||||
return { data: {}, id: crypto.randomUUID() }
|
||||
}
|
||||
|
||||
async getPaymentStatus(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<PaymentSessionStatus> {
|
||||
input: GetPaymentStatusInput
|
||||
): Promise<GetPaymentStatusOutput> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
|
||||
async retrievePayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | PaymentProviderError> {
|
||||
input: RetrievePaymentInput
|
||||
): Promise<RetrievePaymentOutput> {
|
||||
return {}
|
||||
}
|
||||
|
||||
async authorizePayment(_): Promise<
|
||||
| PaymentProviderError
|
||||
| {
|
||||
status: PaymentSessionStatus
|
||||
data: PaymentProviderSessionResponse["data"]
|
||||
}
|
||||
> {
|
||||
async authorizePayment(
|
||||
input: AuthorizePaymentInput
|
||||
): Promise<AuthorizePaymentOutput> {
|
||||
return { data: {}, status: PaymentSessionStatus.AUTHORIZED }
|
||||
}
|
||||
|
||||
async updatePayment(
|
||||
_
|
||||
): Promise<PaymentProviderError | PaymentProviderSessionResponse> {
|
||||
return { data: {} } as PaymentProviderSessionResponse
|
||||
async updatePayment(input: UpdatePaymentInput): Promise<UpdatePaymentOutput> {
|
||||
return { data: {} }
|
||||
}
|
||||
|
||||
async deletePayment(_): Promise<Record<string, unknown>> {
|
||||
return {}
|
||||
async deletePayment(input: DeletePaymentInput): Promise<DeletePaymentOutput> {
|
||||
return { data: {} }
|
||||
}
|
||||
|
||||
async capturePayment(_): Promise<Record<string, unknown>> {
|
||||
return {}
|
||||
async capturePayment(
|
||||
input: CapturePaymentInput
|
||||
): Promise<CapturePaymentOutput> {
|
||||
return { data: {} }
|
||||
}
|
||||
|
||||
async refundPayment(_): Promise<Record<string, unknown>> {
|
||||
return {}
|
||||
async refundPayment(input: RefundPaymentInput): Promise<RefundPaymentOutput> {
|
||||
return { data: {} }
|
||||
}
|
||||
|
||||
async cancelPayment(_): Promise<Record<string, unknown>> {
|
||||
return {}
|
||||
async cancelPayment(input: CancelPaymentInput): Promise<CancelPaymentOutput> {
|
||||
return { data: {} }
|
||||
}
|
||||
|
||||
async getWebhookActionAndData(
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
CreatePaymentMethodDTO,
|
||||
CreatePaymentSessionDTO,
|
||||
CreateRefundDTO,
|
||||
AccountHolderDTO,
|
||||
DAL,
|
||||
FilterablePaymentCollectionProps,
|
||||
FilterablePaymentMethodProps,
|
||||
@@ -31,13 +32,17 @@ import {
|
||||
UpdatePaymentCollectionDTO,
|
||||
UpdatePaymentDTO,
|
||||
UpdatePaymentSessionDTO,
|
||||
CreateAccountHolderDTO,
|
||||
UpsertPaymentCollectionDTO,
|
||||
WebhookActionResult,
|
||||
CreateAccountHolderOutput,
|
||||
InitiatePaymentOutput,
|
||||
} from "@medusajs/framework/types"
|
||||
import {
|
||||
BigNumber,
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
isPresent,
|
||||
isString,
|
||||
MathBN,
|
||||
MedusaContext,
|
||||
@@ -48,6 +53,7 @@ import {
|
||||
promiseAll,
|
||||
} from "@medusajs/framework/utils"
|
||||
import {
|
||||
AccountHolder,
|
||||
Capture,
|
||||
Payment,
|
||||
PaymentCollection,
|
||||
@@ -66,6 +72,7 @@ type InjectedDependencies = {
|
||||
refundService: ModulesSdkTypes.IMedusaInternalService<any>
|
||||
paymentSessionService: ModulesSdkTypes.IMedusaInternalService<any>
|
||||
paymentCollectionService: ModulesSdkTypes.IMedusaInternalService<any>
|
||||
accountHolderService: ModulesSdkTypes.IMedusaInternalService<any>
|
||||
paymentProviderService: PaymentProviderService
|
||||
}
|
||||
|
||||
@@ -76,6 +83,7 @@ const generateMethodForModels = {
|
||||
Capture,
|
||||
Refund,
|
||||
RefundReason,
|
||||
AccountHolder,
|
||||
}
|
||||
|
||||
export default class PaymentModuleService
|
||||
@@ -86,6 +94,7 @@ export default class PaymentModuleService
|
||||
Capture: { dto: CaptureDTO }
|
||||
Refund: { dto: RefundDTO }
|
||||
RefundReason: { dto: RefundReasonDTO }
|
||||
AccountHolder: { dto: AccountHolderDTO }
|
||||
}>(generateMethodForModels)
|
||||
implements IPaymentModuleService
|
||||
{
|
||||
@@ -107,6 +116,9 @@ export default class PaymentModuleService
|
||||
typeof PaymentCollection
|
||||
>
|
||||
protected paymentProviderService_: PaymentProviderService
|
||||
protected accountHolderService_: ModulesSdkTypes.IMedusaInternalService<
|
||||
typeof AccountHolder
|
||||
>
|
||||
|
||||
constructor(
|
||||
{
|
||||
@@ -117,6 +129,7 @@ export default class PaymentModuleService
|
||||
paymentSessionService,
|
||||
paymentProviderService,
|
||||
paymentCollectionService,
|
||||
accountHolderService,
|
||||
}: InjectedDependencies,
|
||||
protected readonly moduleDeclaration: InternalModuleDeclaration
|
||||
) {
|
||||
@@ -131,6 +144,7 @@ export default class PaymentModuleService
|
||||
this.paymentSessionService_ = paymentSessionService
|
||||
this.paymentProviderService_ = paymentProviderService
|
||||
this.paymentCollectionService_ = paymentCollectionService
|
||||
this.accountHolderService_ = accountHolderService
|
||||
}
|
||||
|
||||
__joinerConfig(): ModuleJoinerConfig {
|
||||
@@ -316,7 +330,7 @@ export default class PaymentModuleService
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentSessionDTO> {
|
||||
let paymentSession: InferEntityType<typeof PaymentSession> | undefined
|
||||
let providerPaymentSession: Record<string, unknown> | undefined
|
||||
let providerPaymentSession: InitiatePaymentOutput | undefined
|
||||
|
||||
try {
|
||||
paymentSession = await this.createPaymentSession_(
|
||||
@@ -328,7 +342,8 @@ export default class PaymentModuleService
|
||||
providerPaymentSession = await this.paymentProviderService_.createSession(
|
||||
input.provider_id,
|
||||
{
|
||||
context: { ...input.context, session_id: paymentSession!.id },
|
||||
context: input.context,
|
||||
data: { ...input.data, session_id: paymentSession!.id },
|
||||
amount: input.amount,
|
||||
currency_code: input.currency_code,
|
||||
}
|
||||
@@ -338,15 +353,14 @@ export default class PaymentModuleService
|
||||
await this.paymentSessionService_.update(
|
||||
{
|
||||
id: paymentSession!.id,
|
||||
data: { ...input.data, ...providerPaymentSession },
|
||||
data: { ...input.data, ...providerPaymentSession.data },
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
)[0]
|
||||
} catch (error) {
|
||||
if (providerPaymentSession) {
|
||||
await this.paymentProviderService_.deleteSession({
|
||||
provider_id: input.provider_id,
|
||||
await this.paymentProviderService_.deleteSession(input.provider_id, {
|
||||
data: input.data,
|
||||
})
|
||||
}
|
||||
@@ -420,8 +434,7 @@ export default class PaymentModuleService
|
||||
sharedContext
|
||||
)
|
||||
|
||||
await this.paymentProviderService_.deleteSession({
|
||||
provider_id: session.provider_id,
|
||||
await this.paymentProviderService_.deleteSession(session.provider_id, {
|
||||
data: session.data,
|
||||
})
|
||||
|
||||
@@ -460,11 +473,11 @@ export default class PaymentModuleService
|
||||
}
|
||||
|
||||
let { data, status } = await this.paymentProviderService_.authorizePayment(
|
||||
session.provider_id,
|
||||
{
|
||||
provider_id: session.provider_id,
|
||||
data: session.data,
|
||||
},
|
||||
context
|
||||
context,
|
||||
}
|
||||
)
|
||||
|
||||
if (
|
||||
@@ -486,8 +499,7 @@ export default class PaymentModuleService
|
||||
sharedContext
|
||||
)
|
||||
} catch (error) {
|
||||
await this.paymentProviderService_.cancelPayment({
|
||||
provider_id: session.provider_id,
|
||||
await this.paymentProviderService_.cancelPayment(session.provider_id, {
|
||||
data,
|
||||
})
|
||||
|
||||
@@ -509,7 +521,7 @@ export default class PaymentModuleService
|
||||
@InjectTransactionManager()
|
||||
async authorizePaymentSession_(
|
||||
session: InferEntityType<typeof PaymentSession>,
|
||||
data: Record<string, unknown>,
|
||||
data: Record<string, unknown> | undefined,
|
||||
status: PaymentSessionStatus,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<InferEntityType<typeof Payment>> {
|
||||
@@ -711,15 +723,17 @@ export default class PaymentModuleService
|
||||
isFullyCaptured: boolean,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
const paymentData = await this.paymentProviderService_.capturePayment({
|
||||
data: payment.data!,
|
||||
provider_id: payment.provider_id,
|
||||
})
|
||||
const paymentData = await this.paymentProviderService_.capturePayment(
|
||||
payment.provider_id,
|
||||
{
|
||||
data: payment.data!,
|
||||
}
|
||||
)
|
||||
|
||||
await this.paymentService_.update(
|
||||
{
|
||||
id: payment.id,
|
||||
data: paymentData,
|
||||
data: paymentData.data,
|
||||
captured_at: isFullyCaptured ? new Date() : undefined,
|
||||
},
|
||||
sharedContext
|
||||
@@ -817,15 +831,15 @@ export default class PaymentModuleService
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
const paymentData = await this.paymentProviderService_.refundPayment(
|
||||
payment.provider_id,
|
||||
{
|
||||
data: payment.data!,
|
||||
provider_id: payment.provider_id,
|
||||
},
|
||||
refund.raw_amount as BigNumberInput
|
||||
amount: refund.raw_amount as BigNumberInput,
|
||||
}
|
||||
)
|
||||
|
||||
await this.paymentService_.update(
|
||||
{ id: payment.id, data: paymentData },
|
||||
{ id: payment.id, data: paymentData.data },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
@@ -843,9 +857,8 @@ export default class PaymentModuleService
|
||||
sharedContext
|
||||
)
|
||||
|
||||
await this.paymentProviderService_.cancelPayment({
|
||||
await this.paymentProviderService_.cancelPayment(payment.provider_id, {
|
||||
data: payment.data!,
|
||||
provider_id: payment.provider_id,
|
||||
})
|
||||
|
||||
await this.paymentService_.update(
|
||||
@@ -909,6 +922,82 @@ export default class PaymentModuleService
|
||||
]
|
||||
}
|
||||
|
||||
@InjectManager()
|
||||
async createAccountHolder(
|
||||
input: CreateAccountHolderDTO,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<AccountHolderDTO> {
|
||||
if (input.context?.account_holder) {
|
||||
return input.context.account_holder as AccountHolderDTO
|
||||
}
|
||||
|
||||
let accountHolder: InferEntityType<typeof AccountHolder> | undefined
|
||||
let providerAccountHolder: CreateAccountHolderOutput | undefined
|
||||
|
||||
try {
|
||||
providerAccountHolder =
|
||||
await this.paymentProviderService_.createAccountHolder(
|
||||
input.provider_id,
|
||||
{ context: input.context }
|
||||
)
|
||||
|
||||
// This can be empty when either the method is not supported or an account holder wasn't created
|
||||
if (isPresent(providerAccountHolder)) {
|
||||
accountHolder = await this.accountHolderService_.create(
|
||||
{
|
||||
external_id: providerAccountHolder.id,
|
||||
email: input.context.customer?.email,
|
||||
data: providerAccountHolder.data,
|
||||
provider_id: input.provider_id,
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
if (providerAccountHolder) {
|
||||
await this.paymentProviderService_.deleteAccountHolder(
|
||||
input.provider_id,
|
||||
{
|
||||
context: {
|
||||
account_holder: providerAccountHolder as {
|
||||
data: Record<string, unknown>
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (accountHolder) {
|
||||
await this.accountHolderService_.delete(accountHolder.id, sharedContext)
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
return await this.baseRepository_.serialize(accountHolder)
|
||||
}
|
||||
|
||||
@InjectManager()
|
||||
async deleteAccountHolder(
|
||||
id: string,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<void> {
|
||||
const accountHolder = await this.accountHolderService_.retrieve(
|
||||
id,
|
||||
{ select: ["id", "provider_id", "external_id", "email", "data"] },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
await this.accountHolderService_.delete(id, sharedContext)
|
||||
|
||||
await this.paymentProviderService_.deleteAccountHolder(
|
||||
accountHolder.provider_id,
|
||||
{
|
||||
context: { account_holder: accountHolder },
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@InjectManager()
|
||||
async listPaymentMethods(
|
||||
filters: FilterablePaymentMethodProps,
|
||||
@@ -917,12 +1006,12 @@ export default class PaymentModuleService
|
||||
): Promise<PaymentMethodDTO[]> {
|
||||
const res = await this.paymentProviderService_.listPaymentMethods(
|
||||
filters.provider_id,
|
||||
filters.context
|
||||
{ context: filters.context }
|
||||
)
|
||||
|
||||
return res.map((item) => ({
|
||||
id: item.id,
|
||||
data: item.data,
|
||||
data: item.data!,
|
||||
provider_id: filters.provider_id,
|
||||
}))
|
||||
}
|
||||
@@ -936,12 +1025,12 @@ export default class PaymentModuleService
|
||||
const paymentMethods =
|
||||
await this.paymentProviderService_.listPaymentMethods(
|
||||
filters.provider_id,
|
||||
filters.context
|
||||
{ context: filters.context }
|
||||
)
|
||||
|
||||
const normalizedResponse = paymentMethods.map((item) => ({
|
||||
id: item.id,
|
||||
data: item.data,
|
||||
data: item.data!,
|
||||
provider_id: filters.provider_id,
|
||||
}))
|
||||
|
||||
@@ -950,9 +1039,9 @@ export default class PaymentModuleService
|
||||
|
||||
// @ts-ignore
|
||||
createPaymentMethods(
|
||||
data: CreatePaymentCollectionDTO,
|
||||
data: CreatePaymentMethodDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO>
|
||||
): Promise<PaymentMethodDTO>
|
||||
|
||||
createPaymentMethods(
|
||||
data: CreatePaymentMethodDTO[],
|
||||
@@ -975,7 +1064,7 @@ export default class PaymentModuleService
|
||||
const normalizedResponse = result.map((item, i) => {
|
||||
return {
|
||||
id: item.id,
|
||||
data: item.data,
|
||||
data: item.data!,
|
||||
provider_id: input[i].provider_id,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,25 +1,36 @@
|
||||
import {
|
||||
BigNumberInput,
|
||||
CreatePaymentProviderSession,
|
||||
AuthorizePaymentInput,
|
||||
AuthorizePaymentOutput,
|
||||
CancelPaymentInput,
|
||||
CancelPaymentOutput,
|
||||
CapturePaymentInput,
|
||||
CapturePaymentOutput,
|
||||
CreateAccountHolderInput,
|
||||
CreateAccountHolderOutput,
|
||||
DAL,
|
||||
DeleteAccountHolderInput,
|
||||
DeleteAccountHolderOutput,
|
||||
DeletePaymentInput,
|
||||
DeletePaymentOutput,
|
||||
GetPaymentStatusInput,
|
||||
GetPaymentStatusOutput,
|
||||
InitiatePaymentInput,
|
||||
InitiatePaymentOutput,
|
||||
IPaymentProvider,
|
||||
ListPaymentMethodsInput,
|
||||
ListPaymentMethodsOutput,
|
||||
Logger,
|
||||
PaymentMethodResponse,
|
||||
PaymentProviderAuthorizeResponse,
|
||||
PaymentProviderContext,
|
||||
PaymentProviderDataInput,
|
||||
PaymentProviderError,
|
||||
PaymentProviderSessionResponse,
|
||||
PaymentSessionStatus,
|
||||
ProviderWebhookPayload,
|
||||
SavePaymentMethod,
|
||||
SavePaymentMethodResponse,
|
||||
UpdatePaymentProviderSession,
|
||||
RefundPaymentInput,
|
||||
RefundPaymentOutput,
|
||||
SavePaymentMethodInput,
|
||||
SavePaymentMethodOutput,
|
||||
UpdatePaymentInput,
|
||||
UpdatePaymentOutput,
|
||||
WebhookActionResult,
|
||||
} from "@medusajs/framework/types"
|
||||
import { MedusaError, ModulesSdkUtils } from "@medusajs/framework/utils"
|
||||
import { ModulesSdkUtils } from "@medusajs/framework/utils"
|
||||
import { PaymentProvider } from "@models"
|
||||
import { EOL } from "os"
|
||||
|
||||
type InjectedDependencies = {
|
||||
logger?: Logger
|
||||
@@ -59,135 +70,128 @@ Please make sure that the provider is registered in the container and it is conf
|
||||
|
||||
async createSession(
|
||||
providerId: string,
|
||||
sessionInput: CreatePaymentProviderSession
|
||||
): Promise<PaymentProviderSessionResponse["data"]> {
|
||||
sessionInput: InitiatePaymentInput
|
||||
): Promise<InitiatePaymentOutput> {
|
||||
const provider = this.retrieveProvider(providerId)
|
||||
|
||||
const paymentResponse = await provider.initiatePayment(sessionInput)
|
||||
|
||||
if (isPaymentProviderError(paymentResponse)) {
|
||||
this.throwPaymentProviderError(paymentResponse)
|
||||
}
|
||||
|
||||
return (paymentResponse as PaymentProviderSessionResponse).data
|
||||
return await provider.initiatePayment(sessionInput)
|
||||
}
|
||||
|
||||
async updateSession(
|
||||
providerId: string,
|
||||
sessionInput: UpdatePaymentProviderSession
|
||||
): Promise<PaymentProviderSessionResponse["data"]> {
|
||||
sessionInput: UpdatePaymentInput
|
||||
): Promise<UpdatePaymentOutput> {
|
||||
const provider = this.retrieveProvider(providerId)
|
||||
|
||||
const paymentResponse = await provider.updatePayment(sessionInput)
|
||||
|
||||
if (isPaymentProviderError(paymentResponse)) {
|
||||
this.throwPaymentProviderError(paymentResponse)
|
||||
}
|
||||
|
||||
return (paymentResponse as PaymentProviderSessionResponse)?.data
|
||||
return await provider.updatePayment(sessionInput)
|
||||
}
|
||||
|
||||
async deleteSession(input: PaymentProviderDataInput): Promise<void> {
|
||||
const provider = this.retrieveProvider(input.provider_id)
|
||||
|
||||
const error = await provider.deletePayment(input.data)
|
||||
if (isPaymentProviderError(error)) {
|
||||
this.throwPaymentProviderError(error)
|
||||
}
|
||||
async deleteSession(
|
||||
providerId: string,
|
||||
input: DeletePaymentInput
|
||||
): Promise<DeletePaymentOutput> {
|
||||
const provider = this.retrieveProvider(providerId)
|
||||
return await provider.deletePayment(input)
|
||||
}
|
||||
|
||||
async authorizePayment(
|
||||
input: PaymentProviderDataInput,
|
||||
context: Record<string, unknown>
|
||||
): Promise<{ data: Record<string, unknown>; status: PaymentSessionStatus }> {
|
||||
const provider = this.retrieveProvider(input.provider_id)
|
||||
|
||||
const res = await provider.authorizePayment(input.data, context)
|
||||
if (isPaymentProviderError(res)) {
|
||||
this.throwPaymentProviderError(res)
|
||||
}
|
||||
|
||||
const { data, status } = res as PaymentProviderAuthorizeResponse
|
||||
return { data, status }
|
||||
providerId: string,
|
||||
input: AuthorizePaymentInput
|
||||
): Promise<AuthorizePaymentOutput> {
|
||||
const provider = this.retrieveProvider(providerId)
|
||||
return await provider.authorizePayment(input)
|
||||
}
|
||||
|
||||
async getStatus(
|
||||
input: PaymentProviderDataInput
|
||||
): Promise<PaymentSessionStatus> {
|
||||
const provider = this.retrieveProvider(input.provider_id)
|
||||
return await provider.getPaymentStatus(input.data)
|
||||
providerId: string,
|
||||
input: GetPaymentStatusInput
|
||||
): Promise<GetPaymentStatusOutput> {
|
||||
const provider = this.retrieveProvider(providerId)
|
||||
return await provider.getPaymentStatus(input)
|
||||
}
|
||||
|
||||
async capturePayment(
|
||||
input: PaymentProviderDataInput
|
||||
): Promise<Record<string, unknown>> {
|
||||
const provider = this.retrieveProvider(input.provider_id)
|
||||
|
||||
const res = await provider.capturePayment(input.data)
|
||||
if (isPaymentProviderError(res)) {
|
||||
this.throwPaymentProviderError(res)
|
||||
}
|
||||
|
||||
return res as Record<string, unknown>
|
||||
providerId: string,
|
||||
input: CapturePaymentInput
|
||||
): Promise<CapturePaymentOutput> {
|
||||
const provider = this.retrieveProvider(providerId)
|
||||
return await provider.capturePayment(input)
|
||||
}
|
||||
|
||||
async cancelPayment(input: PaymentProviderDataInput): Promise<void> {
|
||||
const provider = this.retrieveProvider(input.provider_id)
|
||||
|
||||
const error = await provider.cancelPayment(input.data)
|
||||
if (isPaymentProviderError(error)) {
|
||||
this.throwPaymentProviderError(error)
|
||||
}
|
||||
async cancelPayment(
|
||||
providerId: string,
|
||||
input: CancelPaymentInput
|
||||
): Promise<CancelPaymentOutput> {
|
||||
const provider = this.retrieveProvider(providerId)
|
||||
return await provider.cancelPayment(input)
|
||||
}
|
||||
|
||||
async refundPayment(
|
||||
input: PaymentProviderDataInput,
|
||||
amount: BigNumberInput
|
||||
): Promise<Record<string, unknown>> {
|
||||
const provider = this.retrieveProvider(input.provider_id)
|
||||
providerId: string,
|
||||
input: RefundPaymentInput
|
||||
): Promise<RefundPaymentOutput> {
|
||||
const provider = this.retrieveProvider(providerId)
|
||||
return await provider.refundPayment(input)
|
||||
}
|
||||
|
||||
const res = await provider.refundPayment(input.data, amount)
|
||||
if (isPaymentProviderError(res)) {
|
||||
this.throwPaymentProviderError(res)
|
||||
async createAccountHolder(
|
||||
providerId: string,
|
||||
input: CreateAccountHolderInput
|
||||
): Promise<CreateAccountHolderOutput> {
|
||||
const provider = this.retrieveProvider(providerId)
|
||||
if (!provider.createAccountHolder) {
|
||||
this.#logger.warn(
|
||||
`Provider ${providerId} does not support creating account holders`
|
||||
)
|
||||
return {} as unknown as CreateAccountHolderOutput
|
||||
}
|
||||
|
||||
return res as Record<string, unknown>
|
||||
return await provider.createAccountHolder(input)
|
||||
}
|
||||
|
||||
async deleteAccountHolder(
|
||||
providerId: string,
|
||||
input: DeleteAccountHolderInput
|
||||
): Promise<DeleteAccountHolderOutput> {
|
||||
const provider = this.retrieveProvider(providerId)
|
||||
if (!provider.deleteAccountHolder) {
|
||||
this.#logger.warn(
|
||||
`Provider ${providerId} does not support deleting account holders`
|
||||
)
|
||||
return {}
|
||||
}
|
||||
|
||||
return await provider.deleteAccountHolder(input)
|
||||
}
|
||||
|
||||
async listPaymentMethods(
|
||||
providerId: string,
|
||||
context: PaymentProviderContext
|
||||
): Promise<PaymentMethodResponse[]> {
|
||||
input: ListPaymentMethodsInput
|
||||
): Promise<ListPaymentMethodsOutput> {
|
||||
const provider = this.retrieveProvider(providerId)
|
||||
if (!provider.listPaymentMethods) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
this.#logger.warn(
|
||||
`Provider ${providerId} does not support listing payment methods`
|
||||
)
|
||||
return []
|
||||
}
|
||||
|
||||
return await provider.listPaymentMethods(context)
|
||||
return await provider.listPaymentMethods(input)
|
||||
}
|
||||
|
||||
async savePaymentMethod(
|
||||
providerId: string,
|
||||
input: SavePaymentMethod
|
||||
): Promise<SavePaymentMethodResponse> {
|
||||
input: SavePaymentMethodInput
|
||||
): Promise<SavePaymentMethodOutput> {
|
||||
const provider = this.retrieveProvider(providerId)
|
||||
if (!provider.savePaymentMethod) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
this.#logger.warn(
|
||||
`Provider ${providerId} does not support saving payment methods`
|
||||
)
|
||||
return {} as unknown as SavePaymentMethodOutput
|
||||
}
|
||||
|
||||
const res = await provider.savePaymentMethod(input)
|
||||
|
||||
if (isPaymentProviderError(res)) {
|
||||
this.throwPaymentProviderError(res)
|
||||
}
|
||||
|
||||
return res as SavePaymentMethodResponse
|
||||
return await provider.savePaymentMethod(input)
|
||||
}
|
||||
|
||||
async getWebhookActionAndData(
|
||||
@@ -198,22 +202,4 @@ Please make sure that the provider is registered in the container and it is conf
|
||||
|
||||
return await provider.getWebhookActionAndData(data)
|
||||
}
|
||||
|
||||
private throwPaymentProviderError(errObj: PaymentProviderError) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`${errObj.error}${errObj.detail ? `:${EOL}${errObj.detail}` : ""}`,
|
||||
errObj.code
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function isPaymentProviderError(obj: any): obj is PaymentProviderError {
|
||||
return (
|
||||
obj &&
|
||||
typeof obj === "object" &&
|
||||
"error" in obj &&
|
||||
"code" in obj &&
|
||||
"detail" in obj
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,33 @@
|
||||
import Stripe from "stripe"
|
||||
|
||||
import {
|
||||
CreatePaymentProviderSession,
|
||||
PaymentMethodResponse,
|
||||
PaymentProviderContext,
|
||||
PaymentProviderError,
|
||||
PaymentProviderSessionResponse,
|
||||
AuthorizePaymentInput,
|
||||
AuthorizePaymentOutput,
|
||||
CancelPaymentInput,
|
||||
CancelPaymentOutput,
|
||||
CapturePaymentInput,
|
||||
CapturePaymentOutput,
|
||||
CreateAccountHolderInput,
|
||||
CreateAccountHolderOutput,
|
||||
DeleteAccountHolderInput,
|
||||
DeleteAccountHolderOutput,
|
||||
DeletePaymentInput,
|
||||
DeletePaymentOutput,
|
||||
GetPaymentStatusInput,
|
||||
GetPaymentStatusOutput,
|
||||
InitiatePaymentInput,
|
||||
InitiatePaymentOutput,
|
||||
ListPaymentMethodsInput,
|
||||
ListPaymentMethodsOutput,
|
||||
ProviderWebhookPayload,
|
||||
SavePaymentMethod,
|
||||
SavePaymentMethodResponse,
|
||||
UpdatePaymentProviderSession,
|
||||
RefundPaymentInput,
|
||||
RefundPaymentOutput,
|
||||
RetrievePaymentInput,
|
||||
RetrievePaymentOutput,
|
||||
SavePaymentMethodInput,
|
||||
SavePaymentMethodOutput,
|
||||
UpdatePaymentInput,
|
||||
UpdatePaymentOutput,
|
||||
WebhookActionResult,
|
||||
} from "@medusajs/framework/types"
|
||||
import {
|
||||
@@ -95,62 +113,55 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
||||
return res
|
||||
}
|
||||
|
||||
async getPaymentStatus(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<PaymentSessionStatus> {
|
||||
const id = paymentSessionData.id as string
|
||||
async getPaymentStatus({
|
||||
data,
|
||||
}: GetPaymentStatusInput): Promise<GetPaymentStatusOutput> {
|
||||
const id = data?.id as string
|
||||
if (!id) {
|
||||
throw this.buildError(
|
||||
"No payment intent ID provided while getting payment status",
|
||||
new Error("No payment intent ID provided")
|
||||
)
|
||||
}
|
||||
|
||||
const paymentIntent = await this.stripe_.paymentIntents.retrieve(id)
|
||||
|
||||
switch (paymentIntent.status) {
|
||||
case "requires_payment_method":
|
||||
case "requires_confirmation":
|
||||
case "processing":
|
||||
return PaymentSessionStatus.PENDING
|
||||
return { status: PaymentSessionStatus.PENDING }
|
||||
case "requires_action":
|
||||
return PaymentSessionStatus.REQUIRES_MORE
|
||||
return { status: PaymentSessionStatus.REQUIRES_MORE }
|
||||
case "canceled":
|
||||
return PaymentSessionStatus.CANCELED
|
||||
return { status: PaymentSessionStatus.CANCELED }
|
||||
case "requires_capture":
|
||||
return PaymentSessionStatus.AUTHORIZED
|
||||
return { status: PaymentSessionStatus.AUTHORIZED }
|
||||
case "succeeded":
|
||||
return PaymentSessionStatus.CAPTURED
|
||||
return { status: PaymentSessionStatus.CAPTURED }
|
||||
default:
|
||||
return PaymentSessionStatus.PENDING
|
||||
return { status: PaymentSessionStatus.PENDING }
|
||||
}
|
||||
}
|
||||
|
||||
async initiatePayment(
|
||||
input: CreatePaymentProviderSession
|
||||
): Promise<PaymentProviderError | PaymentProviderSessionResponse> {
|
||||
const { email, extra, session_id, customer } = input.context
|
||||
const { currency_code, amount } = input
|
||||
|
||||
const additionalParameters = this.normalizePaymentIntentParameters(extra)
|
||||
async initiatePayment({
|
||||
currency_code,
|
||||
amount,
|
||||
data,
|
||||
context,
|
||||
}: InitiatePaymentInput): Promise<InitiatePaymentOutput> {
|
||||
const additionalParameters = this.normalizePaymentIntentParameters(data)
|
||||
|
||||
const intentRequest: Stripe.PaymentIntentCreateParams = {
|
||||
amount: getSmallestUnit(amount, currency_code),
|
||||
currency: currency_code,
|
||||
metadata: { session_id: session_id! },
|
||||
metadata: { session_id: data?.session_id as string },
|
||||
...additionalParameters,
|
||||
}
|
||||
|
||||
if (customer?.metadata?.stripe_id) {
|
||||
intentRequest.customer = customer.metadata.stripe_id as string
|
||||
} else {
|
||||
let stripeCustomer
|
||||
try {
|
||||
stripeCustomer = await this.stripe_.customers.create({
|
||||
email,
|
||||
})
|
||||
} catch (e) {
|
||||
return this.buildError(
|
||||
"An error occurred in initiatePayment when creating a Stripe customer",
|
||||
e
|
||||
)
|
||||
}
|
||||
|
||||
intentRequest.customer = stripeCustomer.id
|
||||
}
|
||||
intentRequest.customer = context?.account_holder?.data?.id as
|
||||
| string
|
||||
| undefined
|
||||
|
||||
let sessionData
|
||||
try {
|
||||
@@ -158,158 +169,215 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
||||
intentRequest
|
||||
)) as unknown as Record<string, unknown>
|
||||
} catch (e) {
|
||||
return this.buildError(
|
||||
throw this.buildError(
|
||||
"An error occurred in InitiatePayment during the creation of the stripe payment intent",
|
||||
e
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
id: sessionData.id,
|
||||
data: sessionData,
|
||||
// TODO: REVISIT
|
||||
// update_requests: customer?.metadata?.stripe_id
|
||||
// ? undefined
|
||||
// : {
|
||||
// customer_metadata: {
|
||||
// stripe_id: intentRequest.customer,
|
||||
// },
|
||||
// },
|
||||
}
|
||||
}
|
||||
|
||||
async authorizePayment(
|
||||
paymentSessionData: Record<string, unknown>,
|
||||
context: Record<string, unknown>
|
||||
): Promise<
|
||||
| PaymentProviderError
|
||||
| {
|
||||
status: PaymentSessionStatus
|
||||
data: PaymentProviderSessionResponse["data"]
|
||||
}
|
||||
> {
|
||||
const status = await this.getPaymentStatus(paymentSessionData)
|
||||
return { data: paymentSessionData, status }
|
||||
input: AuthorizePaymentInput
|
||||
): Promise<AuthorizePaymentOutput> {
|
||||
const statusResponse = await this.getPaymentStatus(input)
|
||||
return statusResponse
|
||||
}
|
||||
|
||||
async cancelPayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]> {
|
||||
async cancelPayment({
|
||||
data,
|
||||
}: CancelPaymentInput): Promise<CancelPaymentOutput> {
|
||||
try {
|
||||
const id = paymentSessionData.id as string
|
||||
const id = data?.id as string
|
||||
|
||||
if (!id) {
|
||||
return paymentSessionData
|
||||
return { data: data }
|
||||
}
|
||||
|
||||
return (await this.stripe_.paymentIntents.cancel(
|
||||
id
|
||||
)) as unknown as PaymentProviderSessionResponse["data"]
|
||||
const res = await this.stripe_.paymentIntents.cancel(id)
|
||||
return { data: res as unknown as Record<string, unknown> }
|
||||
} catch (error) {
|
||||
if (error.payment_intent?.status === ErrorIntentStatus.CANCELED) {
|
||||
return error.payment_intent
|
||||
return { data: error.payment_intent }
|
||||
}
|
||||
|
||||
return this.buildError("An error occurred in cancelPayment", error)
|
||||
throw this.buildError("An error occurred in cancelPayment", error)
|
||||
}
|
||||
}
|
||||
|
||||
async capturePayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]> {
|
||||
const id = paymentSessionData.id as string
|
||||
async capturePayment({
|
||||
data,
|
||||
}: CapturePaymentInput): Promise<CapturePaymentOutput> {
|
||||
const id = data?.id as string
|
||||
|
||||
try {
|
||||
const intent = await this.stripe_.paymentIntents.capture(id)
|
||||
return intent as unknown as PaymentProviderSessionResponse["data"]
|
||||
return { data: intent as unknown as Record<string, unknown> }
|
||||
} catch (error) {
|
||||
if (error.code === ErrorCodes.PAYMENT_INTENT_UNEXPECTED_STATE) {
|
||||
if (error.payment_intent?.status === ErrorIntentStatus.SUCCEEDED) {
|
||||
return error.payment_intent
|
||||
return { data: error.payment_intent }
|
||||
}
|
||||
}
|
||||
|
||||
return this.buildError("An error occurred in capturePayment", error)
|
||||
throw this.buildError("An error occurred in capturePayment", error)
|
||||
}
|
||||
}
|
||||
|
||||
async deletePayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]> {
|
||||
return await this.cancelPayment(paymentSessionData)
|
||||
async deletePayment(input: DeletePaymentInput): Promise<DeletePaymentOutput> {
|
||||
return await this.cancelPayment(input)
|
||||
}
|
||||
|
||||
async refundPayment(
|
||||
paymentSessionData: Record<string, unknown>,
|
||||
refundAmount: number
|
||||
): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]> {
|
||||
const id = paymentSessionData.id as string
|
||||
async refundPayment({
|
||||
amount,
|
||||
data,
|
||||
}: RefundPaymentInput): Promise<RefundPaymentOutput> {
|
||||
const id = data?.id as string
|
||||
if (!id) {
|
||||
throw this.buildError(
|
||||
"No payment intent ID provided while refunding payment",
|
||||
new Error("No payment intent ID provided")
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
const { currency } = paymentSessionData
|
||||
const currencyCode = data?.currency as string
|
||||
await this.stripe_.refunds.create({
|
||||
amount: getSmallestUnit(refundAmount, currency as string),
|
||||
amount: getSmallestUnit(amount, currencyCode),
|
||||
payment_intent: id as string,
|
||||
})
|
||||
} catch (e) {
|
||||
return this.buildError("An error occurred in refundPayment", e)
|
||||
throw this.buildError("An error occurred in refundPayment", e)
|
||||
}
|
||||
|
||||
return paymentSessionData
|
||||
return { data }
|
||||
}
|
||||
|
||||
async retrievePayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]> {
|
||||
async retrievePayment({
|
||||
data,
|
||||
}: RetrievePaymentInput): Promise<RetrievePaymentOutput> {
|
||||
try {
|
||||
const id = paymentSessionData.id as string
|
||||
const id = data?.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"]
|
||||
return { data: intent as unknown as Record<string, unknown> }
|
||||
} catch (e) {
|
||||
return this.buildError("An error occurred in retrievePayment", e)
|
||||
throw this.buildError("An error occurred in retrievePayment", e)
|
||||
}
|
||||
}
|
||||
|
||||
async updatePayment(
|
||||
input: UpdatePaymentProviderSession
|
||||
): Promise<PaymentProviderError | PaymentProviderSessionResponse> {
|
||||
const { context, data, currency_code, amount } = input
|
||||
|
||||
async updatePayment({
|
||||
data,
|
||||
currency_code,
|
||||
amount,
|
||||
}: UpdatePaymentInput): Promise<UpdatePaymentOutput> {
|
||||
const amountNumeric = getSmallestUnit(amount, currency_code)
|
||||
if (isPresent(amount) && data?.amount === amountNumeric) {
|
||||
return { data }
|
||||
}
|
||||
|
||||
const stripeId = context.customer?.metadata?.stripe_id
|
||||
try {
|
||||
const id = data?.id as string
|
||||
const sessionData = (await this.stripe_.paymentIntents.update(id, {
|
||||
amount: amountNumeric,
|
||||
})) as unknown as Record<string, unknown>
|
||||
|
||||
if (stripeId !== data.customer) {
|
||||
return await this.initiatePayment(input)
|
||||
} else {
|
||||
if (isPresent(amount) && data.amount === amountNumeric) {
|
||||
return { data }
|
||||
}
|
||||
|
||||
try {
|
||||
const id = data.id as string
|
||||
const sessionData = (await this.stripe_.paymentIntents.update(id, {
|
||||
amount: amountNumeric,
|
||||
})) as unknown as PaymentProviderSessionResponse["data"]
|
||||
|
||||
return { data: sessionData }
|
||||
} catch (e) {
|
||||
return this.buildError("An error occurred in updatePayment", e)
|
||||
}
|
||||
return { data: sessionData }
|
||||
} catch (e) {
|
||||
throw this.buildError("An error occurred in updatePayment", e)
|
||||
}
|
||||
}
|
||||
|
||||
async listPaymentMethods(
|
||||
context: PaymentProviderContext
|
||||
): Promise<PaymentMethodResponse[]> {
|
||||
const customerId = context.customer?.metadata?.stripe_id
|
||||
if (!customerId) {
|
||||
async createAccountHolder({
|
||||
context,
|
||||
}: CreateAccountHolderInput): Promise<CreateAccountHolderOutput> {
|
||||
const { account_holder, customer } = context
|
||||
|
||||
if (account_holder?.data?.id) {
|
||||
return { id: account_holder.data.id as string }
|
||||
}
|
||||
|
||||
if (!customer) {
|
||||
throw this.buildError(
|
||||
"No customer in context",
|
||||
new Error("No customer provided while creating account holder")
|
||||
)
|
||||
}
|
||||
|
||||
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.create({
|
||||
email: customer.email,
|
||||
name:
|
||||
customer.company_name ||
|
||||
`${customer.first_name ?? ""} ${customer.last_name ?? ""}`.trim() ||
|
||||
undefined,
|
||||
phone: customer.phone as string | undefined,
|
||||
...shipping,
|
||||
})
|
||||
|
||||
return {
|
||||
id: stripeCustomer.id,
|
||||
data: stripeCustomer as unknown as Record<string, unknown>,
|
||||
}
|
||||
} catch (e) {
|
||||
throw this.buildError(
|
||||
"An error occurred in createAccountHolder when creating a Stripe customer",
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async deleteAccountHolder({
|
||||
context,
|
||||
}: DeleteAccountHolderInput): Promise<DeleteAccountHolderOutput> {
|
||||
const { account_holder } = context
|
||||
const accountHolderId = account_holder?.data?.id as string | undefined
|
||||
if (!accountHolderId) {
|
||||
throw this.buildError(
|
||||
"No account holder in context",
|
||||
new Error("No account holder provided while deleting account holder")
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
await this.stripe_.customers.del(accountHolderId)
|
||||
return {}
|
||||
} catch (e) {
|
||||
throw this.buildError("An error occurred in deleteAccountHolder", e)
|
||||
}
|
||||
}
|
||||
|
||||
async listPaymentMethods({
|
||||
context,
|
||||
}: ListPaymentMethodsInput): Promise<ListPaymentMethodsOutput> {
|
||||
const accountHolderId = context?.account_holder?.data?.id as
|
||||
| string
|
||||
| undefined
|
||||
if (!accountHolderId) {
|
||||
return []
|
||||
}
|
||||
|
||||
const paymentMethods = await this.stripe_.customers.listPaymentMethods(
|
||||
customerId as string,
|
||||
accountHolderId,
|
||||
// In order to keep the interface simple, we just list the maximum payment methods, which should be enough in almost all cases.
|
||||
// We can always extend the interface to allow additional filtering, if necessary.
|
||||
{ limit: 100 }
|
||||
@@ -321,21 +389,23 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
||||
}))
|
||||
}
|
||||
|
||||
async savePaymentMethod(
|
||||
input: SavePaymentMethod
|
||||
): Promise<PaymentProviderError | SavePaymentMethodResponse> {
|
||||
const { context, data } = input
|
||||
const customer = context?.customer
|
||||
async savePaymentMethod({
|
||||
context,
|
||||
data,
|
||||
}: SavePaymentMethodInput): Promise<SavePaymentMethodOutput> {
|
||||
const accountHolderId = context?.account_holder?.data?.id as
|
||||
| string
|
||||
| undefined
|
||||
|
||||
if (!customer?.metadata?.stripe_id) {
|
||||
return this.buildError(
|
||||
if (!accountHolderId) {
|
||||
throw this.buildError(
|
||||
"Account holder not set while saving a payment method",
|
||||
new Error("Missing account holder")
|
||||
)
|
||||
}
|
||||
|
||||
const resp = await this.stripe_.setupIntents.create({
|
||||
customer: customer.metadata.stripe_id as string,
|
||||
customer: accountHolderId,
|
||||
...data,
|
||||
})
|
||||
|
||||
@@ -397,18 +467,15 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
||||
this.options_.webhookSecret
|
||||
)
|
||||
}
|
||||
protected buildError(message: string, error: Error): PaymentProviderError {
|
||||
protected buildError(message: string, error: Error): Error {
|
||||
const errorDetails =
|
||||
"raw" in error ? (error.raw as Stripe.StripeRawError) : error
|
||||
|
||||
return {
|
||||
error: `${message}: ${error.message}`,
|
||||
code: "code" in errorDetails ? errorDetails.code : "unknown",
|
||||
detail:
|
||||
"detail" in errorDetails
|
||||
? `${error.message}: ${errorDetails.detail}`
|
||||
: error.message,
|
||||
}
|
||||
return new Error(
|
||||
`${message}: ${error.message}. ${
|
||||
"detail" in errorDetails ? errorDetails.detail : ""
|
||||
}`.trim()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user