feat(medusa,medusa-react): PaymentCollection support (#2659)
* chore: medusa react, order edit complete fix and single payment session
This commit is contained in:
1 parent
42d9c7222b
commit
15c667fbd3
54 files changed
+2001
-261
No files matched your search
@@ -13,7 +13,11 @@ export default (): RequestHandler => {
|
||||
if (err) {
|
||||
return next(err)
|
||||
}
|
||||
req.user = user
|
||||
|
||||
if (user) {
|
||||
req.user = user
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
)(req, res, next)
|
||||
|
||||
@@ -46,6 +46,9 @@ export default () => {
|
||||
case MedusaError.Types.UNAUTHORIZED:
|
||||
statusCode = 401
|
||||
break
|
||||
case MedusaError.Types.PAYMENT_AUTHORIZATION_ERROR:
|
||||
statusCode = 422
|
||||
break
|
||||
case MedusaError.Types.DUPLICATE_ERROR:
|
||||
statusCode = 422
|
||||
errObj.code = INVALID_REQUEST_ERROR
|
||||
|
||||
@@ -3,6 +3,10 @@ import passport from "passport"
|
||||
|
||||
export default (): RequestHandler => {
|
||||
return (req: Request, res: Response, next: NextFunction): void => {
|
||||
if (req.user) {
|
||||
return next()
|
||||
}
|
||||
|
||||
passport.authenticate(["store-jwt", "bearer"], { session: false })(
|
||||
req,
|
||||
res,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { FindParams } from "../../../../types/common"
|
||||
|
||||
/**
|
||||
* @oas [get] /payment-collections/{id}
|
||||
* operationId: "GetPaymentCollectonsPaymentCollection"
|
||||
* operationId: "GetPaymentCollectionsPaymentCollection"
|
||||
* summary: "Retrieve an PaymentCollection"
|
||||
* description: "Retrieves a PaymentCollection."
|
||||
* x-authenticated: true
|
||||
|
||||
@@ -8,7 +8,7 @@ import OrderEditingFeatureFlag from "../../../../loaders/feature-flags/order-edi
|
||||
import { isFeatureFlagEnabled } from "../../../middlewares/feature-flag-enabled"
|
||||
|
||||
import { GetPaymentCollectionsParams } from "./get-payment-collection"
|
||||
import { AdminUpdatePaymentCollectionRequest } from "./update-payment-collection"
|
||||
import { AdminUpdatePaymentCollectionsReq } from "./update-payment-collection"
|
||||
import { PaymentCollection } from "../../../../models"
|
||||
|
||||
const route = Router()
|
||||
@@ -32,7 +32,7 @@ export default (app, container) => {
|
||||
|
||||
route.post(
|
||||
"/:id",
|
||||
transformBody(AdminUpdatePaymentCollectionRequest),
|
||||
transformBody(AdminUpdatePaymentCollectionsReq),
|
||||
middlewares.wrap(require("./update-payment-collection").default)
|
||||
)
|
||||
|
||||
@@ -68,7 +68,7 @@ export const defaulPaymentCollectionRelations = [
|
||||
"payments",
|
||||
]
|
||||
|
||||
export type AdminPaymentCollectionRes = {
|
||||
export type AdminPaymentCollectionsRes = {
|
||||
payment_collection: PaymentCollection
|
||||
}
|
||||
export type AdminPaymentCollectionDeleteRes = {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { PaymentCollectionService } from "../../../../services"
|
||||
|
||||
/**
|
||||
* @oas [post] /payment-collections/{id}/authorize
|
||||
* operationId: "MarkAuthorizedPaymentCollectionsPaymentCollection"
|
||||
* operationId: "PostPaymentCollectionsPaymentCollectionAuthorize"
|
||||
* summary: "Set the status of PaymentCollection as Authorized"
|
||||
* description: "Sets the status of PaymentCollection as Authorized."
|
||||
* x-authenticated: true
|
||||
|
||||
+2
-2
@@ -75,7 +75,7 @@ import { PaymentCollectionService } from "../../../../services"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
const data = req.validatedBody as AdminUpdatePaymentCollectionRequest
|
||||
const data = req.validatedBody as AdminUpdatePaymentCollectionsReq
|
||||
|
||||
const paymentCollectionService: PaymentCollectionService = req.scope.resolve(
|
||||
"paymentCollectionService"
|
||||
@@ -93,7 +93,7 @@ export default async (req, res) => {
|
||||
res.status(200).json({ payment_collection: paymentCollection })
|
||||
}
|
||||
|
||||
export class AdminUpdatePaymentCollectionRequest {
|
||||
export class AdminUpdatePaymentCollectionsReq {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string
|
||||
|
||||
@@ -45,6 +45,11 @@ describe("GET /store/order-edits/:id/complete", () => {
|
||||
`/store/order-edits/${orderEditId}/complete`,
|
||||
{
|
||||
flags: [OrderEditingFeatureFlag],
|
||||
clientSession: {
|
||||
jwt: {
|
||||
user: IdMap.getId("lebron"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -77,7 +77,7 @@ export default async (req: Request, res: Response) => {
|
||||
paymentProviderService.withTransaction(manager)
|
||||
|
||||
const orderEdit = await orderEditServiceTx.retrieve(id, {
|
||||
relations: ["payment_collection"],
|
||||
relations: ["payment_collection", "payment_collection.payments"],
|
||||
})
|
||||
|
||||
if (orderEdit.status === OrderEditStatus.CONFIRMED) {
|
||||
|
||||
+33
-13
@@ -1,13 +1,24 @@
|
||||
import { IsArray, IsString } from "class-validator"
|
||||
import { PaymentCollectionService } from "../../../../services"
|
||||
|
||||
/**
|
||||
* @oas [post] /payment-collections/{id}/authorize
|
||||
* operationId: "PostPaymentCollectionsAuthorize"
|
||||
* summary: "Authorize a Payment Collections"
|
||||
* description: "Authorizes a Payment Collections."
|
||||
* x-authenticated: true
|
||||
* @oas [post] /payment-collections/{id}/sessions/batch/authorize
|
||||
* operationId: "PostPaymentCollectionsSessionsBatchAuthorize"
|
||||
* summary: "Authorize Payment Sessions of a Payment Collection"
|
||||
* description: "Authorizes Payment Sessions of a Payment Collection."
|
||||
* x-authenticated: false
|
||||
* parameters:
|
||||
* - (path) id=* {string} The ID of the Payment Collections.
|
||||
* requestBody:
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* session_ids:
|
||||
* description: "List of Payment Session IDs to authorize."
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* x-codeSamples:
|
||||
* - lang: JavaScript
|
||||
* label: JS Client
|
||||
@@ -22,8 +33,7 @@ import { PaymentCollectionService } from "../../../../services"
|
||||
* - lang: Shell
|
||||
* label: cURL
|
||||
* source: |
|
||||
* curl --location --request POST 'https://medusa-url.com/store/payment-collections/{id}/authorize' \
|
||||
* --header 'Authorization: Bearer {api_token}'
|
||||
* curl --location --request POST 'https://medusa-url.com/store/payment-collections/{id}/sessions/batch/authorize'
|
||||
* security:
|
||||
* - api_token: []
|
||||
* - cookie_auth: []
|
||||
@@ -35,7 +45,6 @@ import { PaymentCollectionService } from "../../../../services"
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* payment_collection:
|
||||
* $ref: "#/components/schemas/payment_collection"
|
||||
@@ -53,15 +62,26 @@ import { PaymentCollectionService } from "../../../../services"
|
||||
* $ref: "#/components/responses/500_error"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { payment_id } = req.params
|
||||
const { id } = req.params
|
||||
const data =
|
||||
req.validatedBody as StorePostPaymentCollectionsBatchSessionsAuthorizeReq
|
||||
|
||||
const paymentCollectionService: PaymentCollectionService = req.scope.resolve(
|
||||
"paymentCollectionService"
|
||||
)
|
||||
|
||||
const payment_collection = await paymentCollectionService.authorize(
|
||||
payment_id
|
||||
)
|
||||
const payment_collection =
|
||||
await paymentCollectionService.authorizePaymentSessions(
|
||||
id,
|
||||
data.session_ids,
|
||||
req.request_context
|
||||
)
|
||||
|
||||
res.status(200).json({ payment_collection })
|
||||
res.status(207).json({ payment_collection })
|
||||
}
|
||||
|
||||
export class StorePostPaymentCollectionsBatchSessionsAuthorizeReq {
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
session_ids: string[]
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { PaymentSessionStatus } from "../../../../models"
|
||||
import { PaymentCollectionService } from "../../../../services"
|
||||
|
||||
/**
|
||||
* @oas [post] /payment-collections/{id}/sessions/{session_id}/authorize
|
||||
* operationId: "PostPaymentCollectionsSessionsSessionAuthorize"
|
||||
* summary: "Authorize a Payment Session of a Payment Collection"
|
||||
* description: "Authorizes a Payment Session of a Payment Collection."
|
||||
* x-authenticated: false
|
||||
* parameters:
|
||||
* - (path) id=* {string} The ID of the Payment Collections.
|
||||
* - (path) session_id=* {string} The ID of the Payment Session.
|
||||
* x-codeSamples:
|
||||
* - lang: JavaScript
|
||||
* label: JS Client
|
||||
* source: |
|
||||
* import Medusa from "@medusajs/medusa-js"
|
||||
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
* // must be previously logged in or use api token
|
||||
* medusa.paymentCollections.authorize(payment_id, session_id)
|
||||
* .then(({ payment_collection }) => {
|
||||
* console.log(payment_collection.id);
|
||||
* });
|
||||
* - lang: Shell
|
||||
* label: cURL
|
||||
* source: |
|
||||
* curl --location --request POST 'https://medusa-url.com/store/payment-collections/{id}/sessions/{session_id}/authorize'
|
||||
* security:
|
||||
* - api_token: []
|
||||
* - cookie_auth: []
|
||||
* tags:
|
||||
* - Payment
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* payment_session:
|
||||
* $ref: "#/components/schemas/payment_session"
|
||||
* "400":
|
||||
* $ref: "#/components/responses/400_error"
|
||||
* "401":
|
||||
* $ref: "#/components/responses/unauthorized"
|
||||
* "404":
|
||||
* $ref: "#/components/responses/not_found_error"
|
||||
* "409":
|
||||
* $ref: "#/components/responses/invalid_state_error"
|
||||
* "422":
|
||||
* $ref: "#/components/responses/invalid_request_error"
|
||||
* "500":
|
||||
* $ref: "#/components/responses/500_error"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { id, session_id } = req.params
|
||||
|
||||
const paymentCollectionService: PaymentCollectionService = req.scope.resolve(
|
||||
"paymentCollectionService"
|
||||
)
|
||||
|
||||
const payment_collection =
|
||||
await paymentCollectionService.authorizePaymentSessions(
|
||||
id,
|
||||
[session_id],
|
||||
req.request_context
|
||||
)
|
||||
|
||||
const session = payment_collection.payment_sessions.find(
|
||||
({ id }) => id === session_id
|
||||
)
|
||||
|
||||
if (session?.status !== PaymentSessionStatus.AUTHORIZED) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.PAYMENT_AUTHORIZATION_ERROR,
|
||||
`Failed to authorize Payment Session id "${id}"`
|
||||
)
|
||||
}
|
||||
|
||||
res.status(200).json({ payment_session: session })
|
||||
}
|
||||
@@ -3,10 +3,10 @@ import { FindParams } from "../../../../types/common"
|
||||
|
||||
/**
|
||||
* @oas [get] /payment-collections/{id}
|
||||
* operationId: "GetPaymentCollectonsPaymentCollection"
|
||||
* operationId: "GetPaymentCollectionsPaymentCollection"
|
||||
* summary: "Retrieve an PaymentCollection"
|
||||
* description: "Retrieves a PaymentCollection."
|
||||
* x-authenticated: true
|
||||
* x-authenticated: false
|
||||
* parameters:
|
||||
* - (path) id=* {string} The ID of the PaymentCollection.
|
||||
* - (query) expand {string} Comma separated list of relations to include in the results.
|
||||
@@ -25,8 +25,7 @@ import { FindParams } from "../../../../types/common"
|
||||
* - lang: Shell
|
||||
* label: cURL
|
||||
* source: |
|
||||
* curl --location --request GET 'https://medusa-url.com/store/payment-collections/{id}' \
|
||||
* --header 'Authorization: Bearer {api_token}'
|
||||
* curl --location --request GET 'https://medusa-url.com/store/payment-collections/{id}'
|
||||
* security:
|
||||
* - api_token: []
|
||||
* - cookie_auth: []
|
||||
|
||||
@@ -8,10 +8,11 @@ import middlewares, {
|
||||
import OrderEditingFeatureFlag from "../../../../loaders/feature-flags/order-editing"
|
||||
import { isFeatureFlagEnabled } from "../../../middlewares/feature-flag-enabled"
|
||||
|
||||
import { StoreManagePaymentCollectionSessionRequest } from "./manage-payment-sessions"
|
||||
import { StoreRefreshPaymentCollectionSessionRequest } from "./refresh-payment-session"
|
||||
import { StorePostPaymentCollectionsBatchSessionsReq } from "./manage-batch-payment-sessions"
|
||||
import { GetPaymentCollectionsParams } from "./get-payment-collection"
|
||||
import { PaymentCollection, PaymentSession } from "../../../../models"
|
||||
import { StorePaymentCollectionSessionsReq } from "./manage-payment-session"
|
||||
import { StorePostPaymentCollectionsBatchSessionsAuthorizeReq } from "./authorize-batch-payment-sessions"
|
||||
|
||||
const route = Router()
|
||||
|
||||
@@ -33,22 +34,33 @@ export default (app, container) => {
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:id/authorize",
|
||||
middlewares.wrap(require("./authorize-payment-collection").default)
|
||||
"/:id/sessions/batch",
|
||||
transformBody(StorePostPaymentCollectionsBatchSessionsReq),
|
||||
middlewares.wrap(require("./manage-batch-payment-sessions").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:id/sessions/batch/authorize",
|
||||
transformBody(StorePostPaymentCollectionsBatchSessionsAuthorizeReq),
|
||||
middlewares.wrap(require("./authorize-batch-payment-sessions").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:id/sessions",
|
||||
transformBody(StoreManagePaymentCollectionSessionRequest),
|
||||
middlewares.wrap(require("./manage-payment-sessions").default)
|
||||
transformBody(StorePaymentCollectionSessionsReq),
|
||||
middlewares.wrap(require("./manage-payment-session").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:id/sessions/:session_id/refresh",
|
||||
transformBody(StoreRefreshPaymentCollectionSessionRequest),
|
||||
"/:id/sessions/:session_id",
|
||||
middlewares.wrap(require("./refresh-payment-session").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:id/sessions/:session_id/authorize",
|
||||
middlewares.wrap(require("./authorize-payment-session").default)
|
||||
)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
@@ -66,14 +78,16 @@ export const defaultPaymentCollectionFields = [
|
||||
|
||||
export const defaulPaymentCollectionRelations = ["region", "payment_sessions"]
|
||||
|
||||
export type StorePaymentCollectionRes = {
|
||||
export type StorePaymentCollectionsRes = {
|
||||
payment_collection: PaymentCollection
|
||||
}
|
||||
|
||||
export type StorePaymentCollectionSessionRes = {
|
||||
export type StorePaymentCollectionsSessionRes = {
|
||||
payment_session: PaymentSession
|
||||
}
|
||||
|
||||
export * from "./get-payment-collection"
|
||||
export * from "./manage-payment-sessions"
|
||||
export * from "./manage-payment-session"
|
||||
export * from "./manage-batch-payment-sessions"
|
||||
export * from "./refresh-payment-session"
|
||||
export * from "./authorize-batch-payment-sessions"
|
||||
+20
-34
@@ -5,34 +5,29 @@ import { EntityManager } from "typeorm"
|
||||
import { PaymentCollectionService } from "../../../../services"
|
||||
|
||||
/**
|
||||
* @oas [post] /payment-collections/{id}/sessions
|
||||
* operationId: "PostPaymentCollectionsSessions"
|
||||
* summary: "Manage Payment Sessions from Payment Collections"
|
||||
* description: "Manages Payment Sessions from Payment Collections."
|
||||
* x-authenticated: true
|
||||
* @oas [post] /payment-collections/{id}/sessions/batch
|
||||
* operationId: "PostPaymentCollectionsPaymentCollectionSessionsBatch"
|
||||
* summary: "Manage Multiple Payment Sessions from Payment Collections"
|
||||
* description: "Manages Multiple Payment Sessions from Payment Collections."
|
||||
* x-authenticated: false
|
||||
* parameters:
|
||||
* - (path) id=* {string} The ID of the Payment Collections.
|
||||
* requestBody:
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* sessions:
|
||||
* description: "An array or a single entry of payment sessions related to the Payment Collection. If the session_id is not provided the existing sessions not present will be deleted and the provided ones will be created."
|
||||
* description: "An array of payment sessions related to the Payment Collection. If the session_id is not provided, existing sessions not present will be deleted and the provided ones will be created."
|
||||
* type: array
|
||||
* items:
|
||||
* required:
|
||||
* - provider_id
|
||||
* - customer_id
|
||||
* - amount
|
||||
* properties:
|
||||
* provider_id:
|
||||
* type: string
|
||||
* description: The ID of the Payment Provider.
|
||||
* customer_id:
|
||||
* type: string
|
||||
* description: "The ID of the Customer."
|
||||
* amount:
|
||||
* type: integer
|
||||
* description: "The amount ."
|
||||
@@ -50,15 +45,13 @@ import { PaymentCollectionService } from "../../../../services"
|
||||
* // Total amount = 10000
|
||||
*
|
||||
* // Adding two new sessions
|
||||
* medusa.paymentCollections.manageSessions(payment_id, [
|
||||
* medusa.paymentCollections.managePaymentSessionsBatch(payment_id, [
|
||||
* {
|
||||
* provider_id: "stripe",
|
||||
* customer_id: "cus_123",
|
||||
* amount: 5000,
|
||||
* },
|
||||
* {
|
||||
* provider_id: "manual",
|
||||
* customer_id: "cus_123",
|
||||
* amount: 5000,
|
||||
* },
|
||||
* ])
|
||||
@@ -67,20 +60,20 @@ import { PaymentCollectionService } from "../../../../services"
|
||||
* });
|
||||
*
|
||||
* // Updating one session and removing the other
|
||||
* medusa.paymentCollections.manageSessions(payment_id, {
|
||||
* medusa.paymentCollections.managePaymentSessionsBatch(payment_id, [
|
||||
* {
|
||||
* provider_id: "stripe",
|
||||
* customer_id: "cus_123",
|
||||
* amount: 10000,
|
||||
* session_id: "ps_123456"
|
||||
* })
|
||||
* },
|
||||
* ])
|
||||
* .then(({ payment_collection }) => {
|
||||
* console.log(payment_collection.id);
|
||||
* });
|
||||
* - lang: Shell
|
||||
* label: cURL
|
||||
* source: |
|
||||
* curl --location --request POST 'https://medusa-url.com/store/payment-collections/{id}/sessions' \
|
||||
* --header 'Authorization: Bearer {api_token}'
|
||||
* curl --location --request POST 'https://medusa-url.com/store/payment-collections/{id}/sessions/batch'
|
||||
* security:
|
||||
* - api_token: []
|
||||
* - cookie_auth: []
|
||||
@@ -92,7 +85,6 @@ import { PaymentCollectionService } from "../../../../services"
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* payment_collection:
|
||||
* $ref: "#/components/schemas/payment_collection"
|
||||
@@ -110,9 +102,11 @@ import { PaymentCollectionService } from "../../../../services"
|
||||
* $ref: "#/components/responses/500_error"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const data = req.validatedBody as StoreManagePaymentCollectionSessionRequest
|
||||
const data = req.validatedBody as StorePostPaymentCollectionsBatchSessionsReq
|
||||
const { id } = req.params
|
||||
|
||||
const customerId = req.user?.customer_id
|
||||
|
||||
const paymentCollectionService: PaymentCollectionService = req.scope.resolve(
|
||||
"paymentCollectionService"
|
||||
)
|
||||
@@ -122,20 +116,17 @@ export default async (req, res) => {
|
||||
async (transactionManager) => {
|
||||
return await paymentCollectionService
|
||||
.withTransaction(transactionManager)
|
||||
.setPaymentSessions(id, data.sessions)
|
||||
.setPaymentSessionsBatch(id, data.sessions, customerId)
|
||||
}
|
||||
)
|
||||
|
||||
res.status(200).json({ payment_collection: paymentCollection })
|
||||
}
|
||||
|
||||
export class PaymentCollectionSessionInputRequest {
|
||||
export class StorePostPaymentCollectionsSessionsReq {
|
||||
@IsString()
|
||||
provider_id: string
|
||||
|
||||
@IsString()
|
||||
customer_id: string
|
||||
|
||||
@IsInt()
|
||||
@IsNotEmpty()
|
||||
amount: number
|
||||
@@ -145,12 +136,7 @@ export class PaymentCollectionSessionInputRequest {
|
||||
session_id?: string
|
||||
}
|
||||
|
||||
export class StoreManagePaymentCollectionSessionRequest {
|
||||
@IsType([
|
||||
PaymentCollectionSessionInputRequest,
|
||||
[PaymentCollectionSessionInputRequest],
|
||||
])
|
||||
sessions:
|
||||
| PaymentCollectionSessionInputRequest
|
||||
| PaymentCollectionSessionInputRequest[]
|
||||
export class StorePostPaymentCollectionsBatchSessionsReq {
|
||||
@IsType([[StorePostPaymentCollectionsSessionsReq]])
|
||||
sessions: StorePostPaymentCollectionsSessionsReq[]
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { IsString } from "class-validator"
|
||||
|
||||
import { EntityManager } from "typeorm"
|
||||
import { PaymentCollectionService } from "../../../../services"
|
||||
|
||||
/**
|
||||
* @oas [post] /payment-collections/{id}/sessions
|
||||
* operationId: "PostPaymentCollectionsSessions"
|
||||
* summary: "Manage Payment Sessions from Payment Collections"
|
||||
* description: "Manages Payment Sessions from Payment Collections."
|
||||
* x-authenticated: false
|
||||
* parameters:
|
||||
* - (path) id=* {string} The ID of the Payment Collection.
|
||||
* requestBody:
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* required:
|
||||
* - provider_id
|
||||
* properties:
|
||||
* provider_id:
|
||||
* type: string
|
||||
* description: The ID of the Payment Provider.
|
||||
* x-codeSamples:
|
||||
* - lang: JavaScript
|
||||
* label: JS Client
|
||||
* source: |
|
||||
* import Medusa from "@medusajs/medusa-js"
|
||||
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
* // must be previously logged in or use api token
|
||||
*
|
||||
* // Total amount = 10000
|
||||
*
|
||||
* // Adding a payment session
|
||||
* medusa.paymentCollections.managePaymentSession(payment_id, { provider_id: "stripe" })
|
||||
* .then(({ payment_collection }) => {
|
||||
* console.log(payment_collection.id);
|
||||
* });
|
||||
*
|
||||
* - lang: Shell
|
||||
* label: cURL
|
||||
* source: |
|
||||
* curl --location --request POST 'https://medusa-url.com/store/payment-collections/{id}/sessions'
|
||||
* security:
|
||||
* - api_token: []
|
||||
* - cookie_auth: []
|
||||
* tags:
|
||||
* - Payment
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* payment_collection:
|
||||
* $ref: "#/components/schemas/payment_collection"
|
||||
* "400":
|
||||
* $ref: "#/components/responses/400_error"
|
||||
* "401":
|
||||
* $ref: "#/components/responses/unauthorized"
|
||||
* "404":
|
||||
* $ref: "#/components/responses/not_found_error"
|
||||
* "409":
|
||||
* $ref: "#/components/responses/invalid_state_error"
|
||||
* "422":
|
||||
* $ref: "#/components/responses/invalid_request_error"
|
||||
* "500":
|
||||
* $ref: "#/components/responses/500_error"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const data = req.validatedBody as StorePaymentCollectionSessionsReq
|
||||
const { id } = req.params
|
||||
|
||||
const customerId = req.user?.customer_id
|
||||
|
||||
const paymentCollectionService: PaymentCollectionService = req.scope.resolve(
|
||||
"paymentCollectionService"
|
||||
)
|
||||
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
|
||||
const paymentCollection = await manager.transaction(
|
||||
async (transactionManager) => {
|
||||
return await paymentCollectionService
|
||||
.withTransaction(transactionManager)
|
||||
.setPaymentSession(id, data, customerId)
|
||||
}
|
||||
)
|
||||
|
||||
res.status(200).json({ payment_collection: paymentCollection })
|
||||
}
|
||||
|
||||
export class StorePaymentCollectionSessionsReq {
|
||||
@IsString()
|
||||
provider_id: string
|
||||
}
|
||||
+8
-14
@@ -4,10 +4,11 @@ import { EntityManager } from "typeorm"
|
||||
import { PaymentCollectionService } from "../../../../services"
|
||||
|
||||
/**
|
||||
* @oas [post] /payment-collections/{id}/sessions/{session_id}/refresh
|
||||
* @oas [post] /payment-collections/{id}/sessions/{session_id}
|
||||
* operationId: PostPaymentCollectionsPaymentCollectionPaymentSessionsSession
|
||||
* summary: Refresh a Payment Session
|
||||
* description: "Refreshes a Payment Session to ensure that it is in sync with the Payment Collection."
|
||||
* x-authenticated: false
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the PaymentCollection.
|
||||
* - (path) session_id=* {string} The id of the Payment Session to be refreshed.
|
||||
@@ -33,13 +34,13 @@ import { PaymentCollectionService } from "../../../../services"
|
||||
* import Medusa from "@medusajs/medusa-js"
|
||||
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
* medusa.paymentCollections.refreshPaymentSession(payment_collection_id, session_id, payload)
|
||||
* .then(({ payment_collection }) => {
|
||||
* console.log(payment_collection.id);
|
||||
* .then(({ payment_session }) => {
|
||||
* console.log(payment_session.id);
|
||||
* });
|
||||
* - lang: Shell
|
||||
* label: cURL
|
||||
* source: |
|
||||
* curl --location --request POST 'https://medusa-url.com/store/payment-collections/{id}/sessions/{session_id}/refresh'
|
||||
* curl --location --request POST 'https://medusa-url.com/store/payment-collections/{id}/sessions/{session_id}'
|
||||
* tags:
|
||||
* - PaymentCollection
|
||||
* responses:
|
||||
@@ -64,29 +65,22 @@ import { PaymentCollectionService } from "../../../../services"
|
||||
* $ref: "#/components/responses/500_error"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const data = req.validatedBody as StoreRefreshPaymentCollectionSessionRequest
|
||||
const { id, session_id } = req.params
|
||||
|
||||
const paymentCollectionService: PaymentCollectionService = req.scope.resolve(
|
||||
"paymentCollectionService"
|
||||
)
|
||||
|
||||
const customerId = req.user?.customer_id
|
||||
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
const paymentSession = await manager.transaction(
|
||||
async (transactionManager) => {
|
||||
return await paymentCollectionService
|
||||
.withTransaction(transactionManager)
|
||||
.refreshPaymentSession(id, session_id, data)
|
||||
.refreshPaymentSession(id, session_id, customerId)
|
||||
}
|
||||
)
|
||||
|
||||
res.status(200).json({ payment_session: paymentSession })
|
||||
}
|
||||
|
||||
export class StoreRefreshPaymentCollectionSessionRequest {
|
||||
@IsString()
|
||||
provider_id: string
|
||||
|
||||
@IsString()
|
||||
customer_id: string
|
||||
}
|
||||
@@ -29,6 +29,7 @@ export const CustomerServiceMock = {
|
||||
password_hash: "1234",
|
||||
})
|
||||
}
|
||||
return Promise.resolve()
|
||||
}),
|
||||
retrieveByEmail: jest.fn().mockImplementation((email) => {
|
||||
if (email === "lebron@james.com") {
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
PaymentProviderServiceMock,
|
||||
} from "../__mocks__/payment-provider"
|
||||
import { CustomerServiceMock } from "../__mocks__/customer"
|
||||
import { PaymentCollectionSessionInput } from "../../types/payment-collection"
|
||||
import { PaymentCollectionsSessionsBatchInput } from "../../types/payment-collection"
|
||||
|
||||
describe("PaymentCollectionService", () => {
|
||||
afterEach(() => {
|
||||
@@ -376,20 +376,18 @@ describe("PaymentCollectionService", () => {
|
||||
expect(entity).rejects.toThrow(Error)
|
||||
})
|
||||
|
||||
describe("Manage Payment Sessions", () => {
|
||||
describe("Manage Single Payment Session", () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("should throw error if payment collection doesn't have the correct status", async () => {
|
||||
const inp: PaymentCollectionSessionInput = {
|
||||
amount: 100,
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
customer_id: "customer1",
|
||||
}
|
||||
const ret = paymentCollectionService.setPaymentSessions(
|
||||
const ret = paymentCollectionService.setPaymentSession(
|
||||
IdMap.getId("payment-collection-id2"),
|
||||
inp
|
||||
{
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
},
|
||||
"customer1"
|
||||
)
|
||||
|
||||
expect(ret).rejects.toThrowError(
|
||||
@@ -400,15 +398,120 @@ describe("PaymentCollectionService", () => {
|
||||
expect(PaymentProviderServiceMock.createSessionNew).toBeCalledTimes(0)
|
||||
})
|
||||
|
||||
it("should throw error if amount is different than requested", async () => {
|
||||
const inp: PaymentCollectionSessionInput = {
|
||||
amount: 101,
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
customer_id: "customer1",
|
||||
}
|
||||
const ret = paymentCollectionService.setPaymentSessions(
|
||||
it("should ignore session if provider doesn't belong to the region", async () => {
|
||||
const multiRet = paymentCollectionService.setPaymentSession(
|
||||
IdMap.getId("payment-collection-id1"),
|
||||
inp
|
||||
{
|
||||
provider_id: IdMap.getId("region1_invalid_provider"),
|
||||
},
|
||||
"customer1"
|
||||
)
|
||||
|
||||
expect(multiRet).rejects.toThrow(`Payment provider not found`)
|
||||
expect(PaymentProviderServiceMock.createSessionNew).toBeCalledTimes(0)
|
||||
})
|
||||
|
||||
it("should add a new session", async () => {
|
||||
await paymentCollectionService.setPaymentSession(
|
||||
IdMap.getId("payment-collection-id1"),
|
||||
{
|
||||
provider_id: IdMap.getId("region1_provider2"),
|
||||
},
|
||||
"lebron"
|
||||
)
|
||||
|
||||
expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes(
|
||||
1
|
||||
)
|
||||
expect(CustomerServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("should update an existing one", async () => {
|
||||
await paymentCollectionService.setPaymentSession(
|
||||
IdMap.getId("payment-collection-id1"),
|
||||
{
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
},
|
||||
"lebron"
|
||||
)
|
||||
|
||||
expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes(
|
||||
0
|
||||
)
|
||||
expect(PaymentProviderServiceMock.updateSessionNew).toHaveBeenCalledTimes(
|
||||
1
|
||||
)
|
||||
expect(CustomerServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("should add a new session and delete existing one", async () => {
|
||||
const inp: PaymentCollectionsSessionsBatchInput[] = [
|
||||
{
|
||||
amount: 100,
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
},
|
||||
]
|
||||
await paymentCollectionService.setPaymentSessionsBatch(
|
||||
IdMap.getId("payment-collection-session"),
|
||||
inp,
|
||||
IdMap.getId("lebron")
|
||||
)
|
||||
|
||||
expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes(
|
||||
1
|
||||
)
|
||||
expect(PaymentProviderServiceMock.updateSessionNew).toHaveBeenCalledTimes(
|
||||
0
|
||||
)
|
||||
expect(paymentCollectionRepository.deleteMultiple).toHaveBeenCalledTimes(
|
||||
1
|
||||
)
|
||||
|
||||
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Manage Multiple Payment Sessions", () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("should throw error if payment collection doesn't have the correct status", async () => {
|
||||
const inp: PaymentCollectionsSessionsBatchInput[] = [
|
||||
{
|
||||
amount: 100,
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
},
|
||||
]
|
||||
const ret = paymentCollectionService.setPaymentSessionsBatch(
|
||||
IdMap.getId("payment-collection-id2"),
|
||||
inp,
|
||||
"customer1"
|
||||
)
|
||||
|
||||
expect(ret).rejects.toThrowError(
|
||||
new Error(
|
||||
`Cannot set payment sessions for a payment collection with status ${PaymentCollectionStatus.AUTHORIZED}`
|
||||
)
|
||||
)
|
||||
|
||||
expect(PaymentProviderServiceMock.createSessionNew).toBeCalledTimes(0)
|
||||
})
|
||||
|
||||
it("should throw error if amount is different than requested", async () => {
|
||||
const inp: PaymentCollectionsSessionsBatchInput[] = [
|
||||
{
|
||||
amount: 101,
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
},
|
||||
]
|
||||
|
||||
const ret = paymentCollectionService.setPaymentSessionsBatch(
|
||||
IdMap.getId("payment-collection-id1"),
|
||||
inp,
|
||||
"customer1"
|
||||
)
|
||||
|
||||
expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes(
|
||||
@@ -418,21 +521,20 @@ describe("PaymentCollectionService", () => {
|
||||
`The sum of sessions is not equal to 100 on Payment Collection`
|
||||
)
|
||||
|
||||
const multInp: PaymentCollectionSessionInput[] = [
|
||||
const multInp: PaymentCollectionsSessionsBatchInput[] = [
|
||||
{
|
||||
amount: 51,
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
customer_id: "customer1",
|
||||
},
|
||||
{
|
||||
amount: 50,
|
||||
provider_id: IdMap.getId("region1_provider2"),
|
||||
customer_id: "customer1",
|
||||
},
|
||||
]
|
||||
const multiRet = paymentCollectionService.setPaymentSessions(
|
||||
const multiRet = paymentCollectionService.setPaymentSessionsBatch(
|
||||
IdMap.getId("payment-collection-id1"),
|
||||
multInp
|
||||
multInp,
|
||||
"customer1"
|
||||
)
|
||||
|
||||
expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes(
|
||||
@@ -444,21 +546,20 @@ describe("PaymentCollectionService", () => {
|
||||
})
|
||||
|
||||
it("should ignore sessions where provider doesn't belong to the region", async () => {
|
||||
const multInp: PaymentCollectionSessionInput[] = [
|
||||
const multInp: PaymentCollectionsSessionsBatchInput[] = [
|
||||
{
|
||||
amount: 50,
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
customer_id: "customer1",
|
||||
},
|
||||
{
|
||||
amount: 50,
|
||||
provider_id: IdMap.getId("region1_invalid_provider"),
|
||||
customer_id: "customer1",
|
||||
},
|
||||
]
|
||||
const multiRet = paymentCollectionService.setPaymentSessions(
|
||||
const multiRet = paymentCollectionService.setPaymentSessionsBatch(
|
||||
IdMap.getId("payment-collection-id1"),
|
||||
multInp
|
||||
multInp,
|
||||
"customer1"
|
||||
)
|
||||
|
||||
expect(multiRet).rejects.toThrow(
|
||||
@@ -468,22 +569,21 @@ describe("PaymentCollectionService", () => {
|
||||
})
|
||||
|
||||
it("should add a new session and update existing one", async () => {
|
||||
const inp: PaymentCollectionSessionInput[] = [
|
||||
const inp: PaymentCollectionsSessionsBatchInput[] = [
|
||||
{
|
||||
session_id: IdMap.getId("payCol_session1"),
|
||||
amount: 50,
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
customer_id: IdMap.getId("lebron"),
|
||||
},
|
||||
{
|
||||
amount: 50,
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
customer_id: IdMap.getId("lebron"),
|
||||
},
|
||||
]
|
||||
await paymentCollectionService.setPaymentSessions(
|
||||
await paymentCollectionService.setPaymentSessionsBatch(
|
||||
IdMap.getId("payment-collection-session"),
|
||||
inp
|
||||
inp,
|
||||
"lebron"
|
||||
)
|
||||
|
||||
expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes(
|
||||
@@ -497,16 +597,16 @@ describe("PaymentCollectionService", () => {
|
||||
})
|
||||
|
||||
it("should add a new session and delete existing one", async () => {
|
||||
const inp: PaymentCollectionSessionInput[] = [
|
||||
const inp: PaymentCollectionsSessionsBatchInput[] = [
|
||||
{
|
||||
amount: 100,
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
customer_id: IdMap.getId("lebron"),
|
||||
},
|
||||
]
|
||||
await paymentCollectionService.setPaymentSessions(
|
||||
await paymentCollectionService.setPaymentSessionsBatch(
|
||||
IdMap.getId("payment-collection-session"),
|
||||
inp
|
||||
inp,
|
||||
IdMap.getId("lebron")
|
||||
)
|
||||
|
||||
expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes(
|
||||
@@ -526,10 +626,7 @@ describe("PaymentCollectionService", () => {
|
||||
await paymentCollectionService.refreshPaymentSession(
|
||||
IdMap.getId("payment-collection-session"),
|
||||
IdMap.getId("payCol_session1"),
|
||||
{
|
||||
customer_id: "customer1",
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
}
|
||||
"customer1"
|
||||
)
|
||||
|
||||
expect(
|
||||
@@ -545,10 +642,7 @@ describe("PaymentCollectionService", () => {
|
||||
const sess = paymentCollectionService.refreshPaymentSession(
|
||||
IdMap.getId("payment-collection-session"),
|
||||
IdMap.getId("payCol_session-not-found"),
|
||||
{
|
||||
customer_id: "customer1",
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
}
|
||||
"customer1"
|
||||
)
|
||||
|
||||
expect(sess).rejects.toThrow(
|
||||
@@ -567,19 +661,22 @@ describe("PaymentCollectionService", () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("should mark as paid if amount is 0", async () => {
|
||||
await paymentCollectionService.authorize(
|
||||
IdMap.getId("payment-collection-zero")
|
||||
it("should mark as authorized if amount is 0", async () => {
|
||||
const auth = await paymentCollectionService.authorizePaymentSessions(
|
||||
IdMap.getId("payment-collection-zero"),
|
||||
[]
|
||||
)
|
||||
|
||||
expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes(
|
||||
0
|
||||
)
|
||||
expect(auth.status).toBe(PaymentCollectionStatus.AUTHORIZED)
|
||||
})
|
||||
|
||||
it("should reject payment collection without payment sessions", async () => {
|
||||
const ret = paymentCollectionService.authorize(
|
||||
IdMap.getId("payment-collection-no-session")
|
||||
const ret = paymentCollectionService.authorizePaymentSessions(
|
||||
IdMap.getId("payment-collection-no-session"),
|
||||
[]
|
||||
)
|
||||
|
||||
expect(ret).rejects.toThrowError(
|
||||
@@ -590,8 +687,9 @@ describe("PaymentCollectionService", () => {
|
||||
})
|
||||
|
||||
it("should call authorizePayments for all sessions", async () => {
|
||||
await paymentCollectionService.authorize(
|
||||
IdMap.getId("payment-collection-not-authorized")
|
||||
await paymentCollectionService.authorizePaymentSessions(
|
||||
IdMap.getId("payment-collection-not-authorized"),
|
||||
[IdMap.getId("payCol_session1"), IdMap.getId("payCol_session2")]
|
||||
)
|
||||
|
||||
expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes(
|
||||
@@ -604,8 +702,9 @@ describe("PaymentCollectionService", () => {
|
||||
})
|
||||
|
||||
it("should skip authorized sessions - partially authorized", async () => {
|
||||
await paymentCollectionService.authorize(
|
||||
IdMap.getId("payment-collection-partial")
|
||||
await paymentCollectionService.authorizePaymentSessions(
|
||||
IdMap.getId("payment-collection-partial"),
|
||||
[IdMap.getId("payCol_session1"), IdMap.getId("payCol_session2")]
|
||||
)
|
||||
|
||||
expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes(
|
||||
@@ -618,8 +717,9 @@ describe("PaymentCollectionService", () => {
|
||||
})
|
||||
|
||||
it("should skip authorized sessions - fully authorized", async () => {
|
||||
await paymentCollectionService.authorize(
|
||||
IdMap.getId("payment-collection-fully")
|
||||
await paymentCollectionService.authorizePaymentSessions(
|
||||
IdMap.getId("payment-collection-fully"),
|
||||
[IdMap.getId("payCol_session1"), IdMap.getId("payCol_session2")]
|
||||
)
|
||||
|
||||
expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes(
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { DeepPartial, EntityManager, Equal } from "typeorm"
|
||||
import { DeepPartial, EntityManager } from "typeorm"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
|
||||
import { FindConfig } from "../types/common"
|
||||
import { buildQuery, isDefined, setMetadata } from "../utils"
|
||||
import { PaymentCollectionRepository } from "../repositories/payment-collection"
|
||||
import {
|
||||
Customer,
|
||||
PaymentCollection,
|
||||
PaymentCollectionStatus,
|
||||
PaymentSession,
|
||||
@@ -16,12 +15,12 @@ import {
|
||||
CustomerService,
|
||||
EventBusService,
|
||||
PaymentProviderService,
|
||||
PaymentService,
|
||||
} from "./index"
|
||||
|
||||
import {
|
||||
CreatePaymentCollectionInput,
|
||||
PaymentCollectionSessionInput,
|
||||
PaymentCollectionsSessionsBatchInput,
|
||||
PaymentCollectionsSessionsInput,
|
||||
PaymentProviderDataInput,
|
||||
} from "../types/payment-collection"
|
||||
|
||||
@@ -66,6 +65,12 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
this.customerService_ = customerService
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a payment collection by id.
|
||||
* @param paymentCollectionId - the id of the payment collection
|
||||
* @param config - the config to retrieve the payment collection
|
||||
* @return the payment collection.
|
||||
*/
|
||||
async retrieve(
|
||||
paymentCollectionId: string,
|
||||
config: FindConfig<PaymentCollection> = {}
|
||||
@@ -75,9 +80,11 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
this.paymentCollectionRepository_
|
||||
)
|
||||
|
||||
const query = buildQuery({ id: paymentCollectionId }, config)
|
||||
|
||||
const paymentCollection = await paymentCollectionRepository.find(query)
|
||||
let paymentCollection: PaymentCollection[] = []
|
||||
if (paymentCollectionId) {
|
||||
const query = buildQuery({ id: paymentCollectionId }, config)
|
||||
paymentCollection = await paymentCollectionRepository.find(query)
|
||||
}
|
||||
|
||||
if (!paymentCollection.length) {
|
||||
throw new MedusaError(
|
||||
@@ -89,6 +96,11 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
return paymentCollection[0]
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new payment collection.
|
||||
* @param data - info to create the payment collection
|
||||
* @return the payment collection created.
|
||||
*/
|
||||
async create(data: CreatePaymentCollectionInput): Promise<PaymentCollection> {
|
||||
return await this.atomicPhase_(async (manager) => {
|
||||
const paymentCollectionRepository = manager.getCustomRepository(
|
||||
@@ -118,6 +130,12 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a payment collection.
|
||||
* @param paymentCollectionId - the id of the payment collection to update
|
||||
* @param data - info to be updated
|
||||
* @return the payment collection updated.
|
||||
*/
|
||||
async update(
|
||||
paymentCollectionId: string,
|
||||
data: DeepPartial<PaymentCollection>
|
||||
@@ -147,6 +165,11 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a payment collection.
|
||||
* @param paymentCollectionId - the id of the payment collection to be removed
|
||||
* @return the payment collection removed.
|
||||
*/
|
||||
async delete(
|
||||
paymentCollectionId: string
|
||||
): Promise<PaymentCollection | undefined> {
|
||||
@@ -187,18 +210,24 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
|
||||
private isValidTotalAmount(
|
||||
total: number,
|
||||
sessionsInput: PaymentCollectionSessionInput[]
|
||||
sessionsInput: PaymentCollectionsSessionsBatchInput[]
|
||||
): boolean {
|
||||
const sum = sessionsInput.reduce((cur, sess) => cur + sess.amount, 0)
|
||||
return total === sum
|
||||
}
|
||||
|
||||
async setPaymentSessions(
|
||||
/**
|
||||
* Manages multiple payment sessions of a payment collection.
|
||||
* @param paymentCollectionId - the id of the payment collection
|
||||
* @param sessionsInput - array containing payment session info
|
||||
* @param customerId - the id of the customer
|
||||
* @return the payment collection and its payment sessions.
|
||||
*/
|
||||
async setPaymentSessionsBatch(
|
||||
paymentCollectionId: string,
|
||||
sessions: PaymentCollectionSessionInput[] | PaymentCollectionSessionInput
|
||||
sessionsInput: PaymentCollectionsSessionsBatchInput[],
|
||||
customerId: string
|
||||
): Promise<PaymentCollection> {
|
||||
let sessionsInput = Array.isArray(sessions) ? sessions : [sessions]
|
||||
|
||||
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const paymentCollectionRepository = manager.getCustomRepository(
|
||||
this.paymentCollectionRepository_
|
||||
@@ -228,20 +257,19 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
)
|
||||
}
|
||||
|
||||
let customer: Customer | undefined = undefined
|
||||
const customer = !isDefined(customerId)
|
||||
? null
|
||||
: await this.customerService_
|
||||
.withTransaction(manager)
|
||||
.retrieve(customerId, {
|
||||
select: ["id", "email", "metadata"],
|
||||
})
|
||||
.catch(() => null)
|
||||
|
||||
const selectedSessionIds: string[] = []
|
||||
const paymentSessions: PaymentSession[] = []
|
||||
|
||||
for (const session of sessionsInput) {
|
||||
if (!customer) {
|
||||
customer = await this.customerService_
|
||||
.withTransaction(manager)
|
||||
.retrieve(session.customer_id, {
|
||||
select: ["id", "email", "metadata"],
|
||||
})
|
||||
}
|
||||
|
||||
const existingSession = payCol.payment_sessions?.find(
|
||||
(sess) => session.session_id === sess?.id
|
||||
)
|
||||
@@ -252,9 +280,6 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
amount: session.amount,
|
||||
provider_id: session.provider_id,
|
||||
customer,
|
||||
metadata: {
|
||||
resource_id: payCol.id,
|
||||
},
|
||||
}
|
||||
|
||||
if (existingSession) {
|
||||
@@ -275,12 +300,22 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
}
|
||||
|
||||
if (payCol.payment_sessions?.length) {
|
||||
const removeIds: string[] = payCol.payment_sessions
|
||||
.map((sess) => sess.id)
|
||||
.filter((id) => !selectedSessionIds.includes(id))
|
||||
const removeSessions: PaymentSession[] = payCol.payment_sessions.filter(
|
||||
({ id }) => !selectedSessionIds.includes(id)
|
||||
)
|
||||
|
||||
if (removeIds.length) {
|
||||
await paymentCollectionRepository.deleteMultiple(removeIds)
|
||||
if (removeSessions.length) {
|
||||
await paymentCollectionRepository.deleteMultiple(
|
||||
removeSessions.map((sess) => sess.id)
|
||||
)
|
||||
|
||||
Promise.all(
|
||||
removeSessions.map(async (sess) =>
|
||||
this.paymentProviderService_
|
||||
.withTransaction(manager)
|
||||
.deleteSessionNew(sess)
|
||||
)
|
||||
).catch(() => void 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,10 +325,116 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages a single payment sessions of a payment collection.
|
||||
* @param paymentCollectionId - the id of the payment collection
|
||||
* @param sessionsInput - object containing payment session info
|
||||
* @param customerId - the id of the customer
|
||||
* @return the payment collection and its payment session.
|
||||
*/
|
||||
async setPaymentSession(
|
||||
paymentCollectionId: string,
|
||||
sessionInput: PaymentCollectionsSessionsInput,
|
||||
customerId: string
|
||||
): Promise<PaymentCollection> {
|
||||
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const paymentCollectionRepository = manager.getCustomRepository(
|
||||
this.paymentCollectionRepository_
|
||||
)
|
||||
|
||||
const payCol = await this.retrieve(paymentCollectionId, {
|
||||
relations: ["region", "region.payment_providers", "payment_sessions"],
|
||||
})
|
||||
|
||||
if (payCol.status !== PaymentCollectionStatus.NOT_PAID) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`Cannot set payment sessions for a payment collection with status ${payCol.status}`
|
||||
)
|
||||
}
|
||||
|
||||
const hasProvider = payCol?.region?.payment_providers
|
||||
.map((p) => p.id)
|
||||
.includes(sessionInput.provider_id)
|
||||
|
||||
if (!hasProvider) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Payment provider not found"
|
||||
)
|
||||
}
|
||||
|
||||
const customer = !isDefined(customerId)
|
||||
? null
|
||||
: await this.customerService_
|
||||
.withTransaction(manager)
|
||||
.retrieve(customerId, {
|
||||
select: ["id", "email", "metadata"],
|
||||
})
|
||||
.catch(() => null)
|
||||
|
||||
const paymentSessions: PaymentSession[] = []
|
||||
const inputData: PaymentProviderDataInput = {
|
||||
resource_id: payCol.id,
|
||||
currency_code: payCol.currency_code,
|
||||
amount: payCol.amount,
|
||||
provider_id: sessionInput.provider_id,
|
||||
customer,
|
||||
}
|
||||
|
||||
const existingSession = payCol.payment_sessions?.find(
|
||||
(sess) => sessionInput.provider_id === sess?.provider_id
|
||||
)
|
||||
|
||||
if (existingSession) {
|
||||
const paymentSession = await this.paymentProviderService_
|
||||
.withTransaction(manager)
|
||||
.updateSessionNew(existingSession, inputData)
|
||||
|
||||
paymentSessions.push(paymentSession)
|
||||
} else {
|
||||
const paymentSession = await this.paymentProviderService_
|
||||
.withTransaction(manager)
|
||||
.createSessionNew(inputData)
|
||||
|
||||
paymentSessions.push(paymentSession)
|
||||
|
||||
const removeSessions: PaymentSession[] = payCol.payment_sessions.filter(
|
||||
({ id }) => id != paymentSession.id
|
||||
)
|
||||
|
||||
if (removeSessions.length) {
|
||||
await paymentCollectionRepository.deleteMultiple(
|
||||
removeSessions.map((sess) => sess.id)
|
||||
)
|
||||
|
||||
Promise.all(
|
||||
removeSessions.map(async (sess) =>
|
||||
this.paymentProviderService_
|
||||
.withTransaction(manager)
|
||||
.deleteSessionNew(sess)
|
||||
)
|
||||
).catch(() => void 0)
|
||||
}
|
||||
}
|
||||
|
||||
payCol.payment_sessions = paymentSessions
|
||||
|
||||
return await paymentCollectionRepository.save(payCol)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes and recreate a payment session of a payment collection.
|
||||
* @param paymentCollectionId - the id of the payment collection
|
||||
* @param sessionId - the id of the payment session to be replaced
|
||||
* @param customerId - the id of the customer
|
||||
* @return the new payment session created.
|
||||
*/
|
||||
async refreshPaymentSession(
|
||||
paymentCollectionId: string,
|
||||
sessionId: string,
|
||||
sessionInput: Omit<PaymentCollectionSessionInput, "amount">
|
||||
customerId: string
|
||||
): Promise<PaymentSession> {
|
||||
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const paymentCollectionRepository = manager.getCustomRepository(
|
||||
@@ -330,11 +471,14 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
)
|
||||
}
|
||||
|
||||
const customer = await this.customerService_
|
||||
.withTransaction(manager)
|
||||
.retrieve(sessionInput.customer_id, {
|
||||
select: ["id", "email", "metadata"],
|
||||
})
|
||||
const customer = !isDefined(customerId)
|
||||
? null
|
||||
: await this.customerService_
|
||||
.withTransaction(manager)
|
||||
.retrieve(customerId, {
|
||||
select: ["id", "email", "metadata"],
|
||||
})
|
||||
.catch(() => null)
|
||||
|
||||
const inputData: PaymentProviderDataInput = {
|
||||
resource_id: payCol.id,
|
||||
@@ -365,6 +509,11 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a payment collection as authorized bypassing the payment flow.
|
||||
* @param paymentCollectionId - the id of the payment collection
|
||||
* @return the payment session authorized.
|
||||
*/
|
||||
async markAsAuthorized(
|
||||
paymentCollectionId: string
|
||||
): Promise<PaymentCollection> {
|
||||
@@ -387,8 +536,16 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
})
|
||||
}
|
||||
|
||||
async authorize(
|
||||
/**
|
||||
* Authorizes the payment sessions of a payment collection.
|
||||
* @param paymentCollectionId - the id of the payment collection
|
||||
* @param sessionIds - array of payment session ids to be authorized
|
||||
* @param context - additional data required by payment providers
|
||||
* @return the payment collection and its payment session.
|
||||
*/
|
||||
async authorizePaymentSessions(
|
||||
paymentCollectionId: string,
|
||||
sessionIds: string[],
|
||||
context: Record<string, unknown> = {}
|
||||
): Promise<PaymentCollection> {
|
||||
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||
@@ -404,9 +561,9 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
return payCol
|
||||
}
|
||||
|
||||
// If cart total is 0, we don't perform anything payment related
|
||||
if (payCol.amount <= 0) {
|
||||
payCol.authorized_amount = 0
|
||||
payCol.status = PaymentCollectionStatus.AUTHORIZED
|
||||
return await paymentCollectionRepository.save(payCol)
|
||||
}
|
||||
|
||||
@@ -426,6 +583,10 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!sessionIds.includes(session.id)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const auth = await this.paymentProviderService_
|
||||
.withTransaction(manager)
|
||||
.authorizePayment(session, context)
|
||||
|
||||
@@ -349,6 +349,15 @@ export default class PaymentProviderService extends TransactionBaseService {
|
||||
})
|
||||
}
|
||||
|
||||
async deleteSessionNew(paymentSession: PaymentSession): Promise<void> {
|
||||
return await this.atomicPhase_(async (transactionManager) => {
|
||||
const provider = this.retrieveProvider(paymentSession.provider_id)
|
||||
return await provider
|
||||
.withTransaction(transactionManager)
|
||||
.deletePayment(paymentSession)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a provider given an id
|
||||
* @param {string} providerId - the id of the provider to get
|
||||
|
||||
@@ -52,6 +52,12 @@ export default class PaymentService extends TransactionBaseService {
|
||||
this.eventBusService_ = eventBusService
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a payment by id.
|
||||
* @param paymentId - the id of the payment
|
||||
* @param config - the config to retrieve the payment
|
||||
* @return the payment.
|
||||
*/
|
||||
async retrieve(
|
||||
paymentId: string,
|
||||
config: FindConfig<Payment> = {}
|
||||
@@ -75,6 +81,11 @@ export default class PaymentService extends TransactionBaseService {
|
||||
return payment[0]
|
||||
}
|
||||
|
||||
/**
|
||||
* Created a new payment.
|
||||
* @param paymentInput - info to create the payment
|
||||
* @return the payment created.
|
||||
*/
|
||||
async create(paymentInput: PaymentDataInput): Promise<Payment> {
|
||||
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const { data, currency_code, amount, provider_id } = paymentInput
|
||||
@@ -100,6 +111,12 @@ export default class PaymentService extends TransactionBaseService {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a payment in order to link it to an order or a swap.
|
||||
* @param paymentId - the id of the payment
|
||||
* @param data - order_id or swap_id to link the payment
|
||||
* @return the payment updated.
|
||||
*/
|
||||
async update(
|
||||
paymentId: string,
|
||||
data: { order_id?: string; swap_id?: string }
|
||||
@@ -129,6 +146,11 @@ export default class PaymentService extends TransactionBaseService {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures a payment.
|
||||
* @param paymentOrId - the id or the class instance of the payment
|
||||
* @return the payment captured.
|
||||
*/
|
||||
async capture(paymentOrId: string | Payment): Promise<Payment> {
|
||||
const payment =
|
||||
typeof paymentOrId === "string"
|
||||
@@ -170,6 +192,14 @@ export default class PaymentService extends TransactionBaseService {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* refunds a payment.
|
||||
* @param paymentOrId - the id or the class instance of the payment
|
||||
* @param amount - the amount to be refunded from the payment
|
||||
* @param reason - the refund reason
|
||||
* @param note - additional note of the refund
|
||||
* @return the refund created.
|
||||
*/
|
||||
async refund(
|
||||
paymentOrId: string | Payment,
|
||||
amount: number,
|
||||
|
||||
@@ -15,22 +15,24 @@ export type CreatePaymentCollectionInput = {
|
||||
description?: string
|
||||
}
|
||||
|
||||
export type PaymentCollectionSessionInput = {
|
||||
export type PaymentCollectionsSessionsBatchInput = {
|
||||
provider_id: string
|
||||
amount: number
|
||||
session_id?: string
|
||||
customer_id: string
|
||||
}
|
||||
|
||||
export type PaymentCollectionsSessionsInput = {
|
||||
provider_id: string
|
||||
}
|
||||
|
||||
export type PaymentProviderDataInput = {
|
||||
resource_id: string
|
||||
customer: Partial<Customer>
|
||||
customer: Partial<Customer> | null
|
||||
currency_code: string
|
||||
provider_id: string
|
||||
amount: number
|
||||
cart_id?: string
|
||||
cart?: Cart
|
||||
metadata?: any
|
||||
}
|
||||
export const defaultPaymentCollectionRelations = [
|
||||
"region",
|
||||
|
||||
Reference in new issue
Block a user