Merge branch 'develop' of github.com:medusajs/medusa into develop
This commit is contained in:
commit
0b0d50b476
28 files changed
+1961
-1274
No files matched your search
@@ -1,59 +1,67 @@
|
|||||||
import { AbstractPaymentService } from "@medusajs/medusa";
|
import { AbstractPaymentService } from "@medusajs/medusa"
|
||||||
|
|
||||||
class TestPayService extends AbstractPaymentService {
|
class TestPayService extends AbstractPaymentService {
|
||||||
static identifier = "test-pay";
|
static identifier = "test-pay"
|
||||||
|
|
||||||
constructor(_) {
|
constructor(_) {
|
||||||
super(_);
|
super(_)
|
||||||
}
|
}
|
||||||
|
|
||||||
async getStatus(paymentData) {
|
async getStatus(paymentData) {
|
||||||
return "authorized";
|
return "authorized"
|
||||||
}
|
}
|
||||||
|
|
||||||
async retrieveSavedMethods(customer) {
|
async retrieveSavedMethods(customer) {
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([])
|
||||||
}
|
}
|
||||||
|
|
||||||
async createPayment() {
|
async createPayment() {
|
||||||
return {};
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createPaymentNew() {
|
||||||
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
async retrievePayment(data) {
|
async retrievePayment(data) {
|
||||||
return {};
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPaymentData(sessionData) {
|
async getPaymentData(sessionData) {
|
||||||
return {};
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
async authorizePayment(sessionData, context = {}) {
|
async authorizePayment(sessionData, context = {}) {
|
||||||
return {};
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
async updatePaymentData(sessionData, update) {
|
async updatePaymentData(sessionData, update) {
|
||||||
return {};
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
async updatePayment(sessionData, cart) {
|
async updatePayment(sessionData, cart) {
|
||||||
return {};
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updatePaymentNew(sessionData, paymentInput) {
|
||||||
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
async deletePayment(payment) {
|
async deletePayment(payment) {
|
||||||
return {};
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
async capturePayment(payment) {
|
async capturePayment(payment) {
|
||||||
return {};
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
async refundPayment(payment, amountToRefund) {
|
async refundPayment(payment, amountToRefund) {
|
||||||
return {};
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
async cancelPayment(payment) {
|
async cancelPayment(payment) {
|
||||||
return {};
|
return {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TestPayService;
|
export default TestPayService
|
||||||
@@ -33,6 +33,10 @@ class TestPayService extends AbstractPaymentService {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createPaymentNew(inputData) {
|
||||||
|
return inputData
|
||||||
|
}
|
||||||
|
|
||||||
async retrievePayment(data) {
|
async retrievePayment(data) {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
@@ -59,6 +63,10 @@ class TestPayService extends AbstractPaymentService {
|
|||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updatePaymentNew(sessionData) {
|
||||||
|
return sessionData
|
||||||
|
}
|
||||||
|
|
||||||
async deletePayment(payment) {
|
async deletePayment(payment) {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,6 +93,15 @@ const bootstrapApp = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
res.header("Access-Control-Allow-Origin", req.headers.origin)
|
||||||
|
res.header("Access-Control-Allow-Methods", "*")
|
||||||
|
res.header(
|
||||||
|
"Access-Control-Allow-Headers",
|
||||||
|
"Origin, X-Requested-With, Content-Type, Accept"
|
||||||
|
)
|
||||||
|
next()
|
||||||
|
})
|
||||||
|
|
||||||
const dir = path.resolve(
|
const dir = path.resolve(
|
||||||
path.join(__dirname, "../../packages/medusa/src/loaders")
|
path.join(__dirname, "../../packages/medusa/src/loaders")
|
||||||
|
|||||||
@@ -201,6 +201,10 @@ class AdyenService extends BaseService {
|
|||||||
return { cart_id: cart.id }
|
return { cart_id: cart.id }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createPaymentNew(paymentInput) {
|
||||||
|
return { resource_id: paymentInput.resource_id }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves Adyen payment. This is not supported by adyen, so we simply
|
* Retrieves Adyen payment. This is not supported by adyen, so we simply
|
||||||
* return the current payment method data
|
* return the current payment method data
|
||||||
@@ -322,6 +326,10 @@ class AdyenService extends BaseService {
|
|||||||
return paymentData
|
return paymentData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updatePaymentNew(paymentData, details) {
|
||||||
|
return paymentData
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Additional details
|
* Additional details
|
||||||
* @param {object} paymentData - payment data
|
* @param {object} paymentData - payment data
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { MedusaError } from "medusa-core-utils"
|
|
||||||
|
|
||||||
export default async (req, res) => {
|
export default async (req, res) => {
|
||||||
const { klarna_order_id } = req.query
|
const { klarna_order_id } = req.query
|
||||||
|
|
||||||
|
function isPaymentCollection(id) {
|
||||||
|
return id && id.startsWith("paycol")
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const orderService = req.scope.resolve("orderService")
|
const orderService = req.scope.resolve("orderService")
|
||||||
const klarnaProviderService = req.scope.resolve("pp_klarna")
|
const klarnaProviderService = req.scope.resolve("pp_klarna")
|
||||||
@@ -11,10 +13,18 @@ export default async (req, res) => {
|
|||||||
klarna_order_id
|
klarna_order_id
|
||||||
)
|
)
|
||||||
|
|
||||||
const cartId = klarnaOrder.merchant_data
|
const resourceId = klarnaOrder.merchant_data
|
||||||
const order = await orderService.retrieveByCartId(cartId)
|
|
||||||
|
|
||||||
await klarnaProviderService.acknowledgeOrder(klarnaOrder.order_id, order.id)
|
if (isPaymentCollection(resourceId)) {
|
||||||
|
await klarnaProviderService.acknowledgeOrder(klarnaOrder.order_id)
|
||||||
|
} else {
|
||||||
|
const order = await orderService.retrieveByCartId(resourceId)
|
||||||
|
|
||||||
|
await klarnaProviderService.acknowledgeOrder(
|
||||||
|
klarnaOrder.order_id,
|
||||||
|
order.id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
res.sendStatus(200)
|
res.sendStatus(200)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -230,6 +230,84 @@ class KlarnaProviderService extends PaymentService {
|
|||||||
return order
|
return order
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validateKlarnaOrderUrls(property) {
|
||||||
|
const required = ["terms", "checkout", "confirmation"]
|
||||||
|
|
||||||
|
const isMissing = required.some((prop) => !this.options_[property]?.[prop])
|
||||||
|
|
||||||
|
if (isMissing) {
|
||||||
|
throw new Error(
|
||||||
|
`options.${property} is required to create a Klarna Order.\n` +
|
||||||
|
`medusa-config.js file has to contain ${property} { ${required.join(
|
||||||
|
", "
|
||||||
|
)}}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceStringWithPropertyValue(string, obj) {
|
||||||
|
const keys = Object.keys(obj)
|
||||||
|
for (const key of keys) {
|
||||||
|
if (string.includes(`{${key}}`)) {
|
||||||
|
string = string.replace(`{${key}}`, obj[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
|
||||||
|
async paymentInputToKlarnaOrder(paymentInput) {
|
||||||
|
if (paymentInput.cart) {
|
||||||
|
this.validateKlarnaOrderUrls("merchant_urls")
|
||||||
|
return this.cartToKlarnaOrder(paymentInput.cart)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.validateKlarnaOrderUrls("payment_collection_urls")
|
||||||
|
|
||||||
|
let order = {
|
||||||
|
// Custom id is stored, such that we can use it for hooks
|
||||||
|
merchant_data: paymentInput.resource_id,
|
||||||
|
locale: "en-US",
|
||||||
|
}
|
||||||
|
|
||||||
|
const { currency_code, amount } = paymentInput
|
||||||
|
|
||||||
|
order.order_lines = [
|
||||||
|
{
|
||||||
|
name: "Payment Collection",
|
||||||
|
quantity: 1,
|
||||||
|
unit_price: amount,
|
||||||
|
tax_rate: 0,
|
||||||
|
total_amount: amount,
|
||||||
|
total_tax_amount: 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
// Defaults to Sweden
|
||||||
|
order.purchase_country = "SE"
|
||||||
|
|
||||||
|
order.order_amount = amount
|
||||||
|
order.order_tax_amount = 0
|
||||||
|
order.purchase_currency = currency_code.toUpperCase()
|
||||||
|
|
||||||
|
order.merchant_urls = {
|
||||||
|
terms: this.replaceStringWithPropertyValue(
|
||||||
|
this.options_.payment_collection_urls.terms,
|
||||||
|
paymentInput
|
||||||
|
),
|
||||||
|
checkout: this.replaceStringWithPropertyValue(
|
||||||
|
this.options_.payment_collection_urls.checkout,
|
||||||
|
paymentInput
|
||||||
|
),
|
||||||
|
confirmation: this.replaceStringWithPropertyValue(
|
||||||
|
this.options_.payment_collection_urls.confirmation,
|
||||||
|
paymentInput
|
||||||
|
),
|
||||||
|
push: `${this.backendUrl_}/klarna/push?klarna_order_id={checkout.order.id}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
return order
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Status for Klarna order.
|
* Status for Klarna order.
|
||||||
* @param {Object} paymentData - payment method data from cart
|
* @param {Object} paymentData - payment method data from cart
|
||||||
@@ -251,7 +329,7 @@ class KlarnaProviderService extends PaymentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates Stripe PaymentIntent.
|
* Creates Klarna PaymentIntent.
|
||||||
* @param {string} cart - the cart to create a payment for
|
* @param {string} cart - the cart to create a payment for
|
||||||
* @param {number} amount - the amount to create a payment for
|
* @param {number} amount - the amount to create a payment for
|
||||||
* @returns {string} id of payment intent
|
* @returns {string} id of payment intent
|
||||||
@@ -271,6 +349,21 @@ class KlarnaProviderService extends PaymentService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createPaymentNew(paymentInput) {
|
||||||
|
try {
|
||||||
|
const order = await this.paymentInputToKlarnaOrder(paymentInput)
|
||||||
|
|
||||||
|
const klarnaPayment = await this.klarna_
|
||||||
|
.post(this.klarnaOrderUrl_, order)
|
||||||
|
.then(({ data }) => data)
|
||||||
|
|
||||||
|
return klarnaPayment
|
||||||
|
} catch (error) {
|
||||||
|
this.logger_.error(error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves Klarna Order.
|
* Retrieves Klarna Order.
|
||||||
* @param {string} cart - the cart to retrieve order for
|
* @param {string} cart - the cart to retrieve order for
|
||||||
@@ -338,18 +431,20 @@ class KlarnaProviderService extends PaymentService {
|
|||||||
* @param {string} klarnaOrderId - id of the order to acknowledge
|
* @param {string} klarnaOrderId - id of the order to acknowledge
|
||||||
* @returns {string} id of acknowledged order
|
* @returns {string} id of acknowledged order
|
||||||
*/
|
*/
|
||||||
async acknowledgeOrder(klarnaOrderId, orderId) {
|
async acknowledgeOrder(klarnaOrderId, orderId = null) {
|
||||||
try {
|
try {
|
||||||
await this.klarna_.post(
|
await this.klarna_.post(
|
||||||
`${this.klarnaOrderManagementUrl_}/${klarnaOrderId}/acknowledge`
|
`${this.klarnaOrderManagementUrl_}/${klarnaOrderId}/acknowledge`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (orderId !== null) {
|
||||||
await this.klarna_.patch(
|
await this.klarna_.patch(
|
||||||
`${this.klarnaOrderManagementUrl_}/${klarnaOrderId}/merchant-references`,
|
`${this.klarnaOrderManagementUrl_}/${klarnaOrderId}/merchant-references`,
|
||||||
{
|
{
|
||||||
merchant_reference1: orderId,
|
merchant_reference1: orderId,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return klarnaOrderId
|
return klarnaOrderId
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -408,6 +503,22 @@ class KlarnaProviderService extends PaymentService {
|
|||||||
return paymentData
|
return paymentData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updatePaymentNew(paymentData, paymentInput) {
|
||||||
|
if (paymentInput.amount !== paymentData.order_amount) {
|
||||||
|
const order = await this.paymentInputToKlarnaOrder(paymentInput)
|
||||||
|
return this.klarna_
|
||||||
|
.post(`${this.klarnaOrderUrl_}/${paymentData.order_id}`, order)
|
||||||
|
.then(({ data }) => data)
|
||||||
|
.catch(async (_) => {
|
||||||
|
return this.klarna_
|
||||||
|
.post(this.klarnaOrderUrl_, order)
|
||||||
|
.then(({ data }) => data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return paymentData
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Captures Klarna order.
|
* Captures Klarna order.
|
||||||
* @param {Object} paymentData - payment method data from cart
|
* @param {Object} paymentData - payment method data from cart
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ class ManualPaymentService extends PaymentService {
|
|||||||
return { status: "pending" }
|
return { status: "pending" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createPaymentNew() {
|
||||||
|
return { status: "pending" }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves payment
|
* Retrieves payment
|
||||||
* @param {object} data - the data of the payment to retrieve
|
* @param {object} data - the data of the payment to retrieve
|
||||||
@@ -52,6 +56,10 @@ class ManualPaymentService extends PaymentService {
|
|||||||
return sessionData.data
|
return sessionData.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updatePaymentNew(sessionData) {
|
||||||
|
return sessionData.data
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
.
|
.
|
||||||
* @param {object} sessionData - payment session data.
|
* @param {object} sessionData - payment session data.
|
||||||
|
|||||||
@@ -21,21 +21,11 @@ export default async (req, res) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
function isPaymentCollection(id) {
|
||||||
const body = req.body
|
return id && id.startsWith("paycol")
|
||||||
const authId = body.resource.id
|
|
||||||
const auth = await paypalService.retrieveAuthorization(authId)
|
|
||||||
|
|
||||||
const order = await paypalService.retrieveOrderFromAuth(auth)
|
|
||||||
|
|
||||||
const purchaseUnit = order.purchase_units[0]
|
|
||||||
const cartId = purchaseUnit.custom_id
|
|
||||||
|
|
||||||
if (!cartId) {
|
|
||||||
res.sendStatus(200)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function autorizeCart(req, cartId) {
|
||||||
const manager = req.scope.resolve("manager")
|
const manager = req.scope.resolve("manager")
|
||||||
const cartService = req.scope.resolve("cartService")
|
const cartService = req.scope.resolve("cartService")
|
||||||
const swapService = req.scope.resolve("swapService")
|
const swapService = req.scope.resolve("swapService")
|
||||||
@@ -78,6 +68,42 @@ export default async (req, res) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function autorizePaymentCollection(req, payColId) {
|
||||||
|
const manager = req.scope.resolve("manager")
|
||||||
|
const paymentCollectionService = req.scope.resolve(
|
||||||
|
"paymentCollectonService"
|
||||||
|
)
|
||||||
|
|
||||||
|
await manager.transaction(async (m) => {
|
||||||
|
const payCol = await paymentCollectionService
|
||||||
|
.withTransaction(m)
|
||||||
|
.retrieve(payColId)
|
||||||
|
})
|
||||||
|
// TODO: complete authorization
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const body = req.body
|
||||||
|
const authId = body.resource.id
|
||||||
|
const auth = await paypalService.retrieveAuthorization(authId)
|
||||||
|
|
||||||
|
const order = await paypalService.retrieveOrderFromAuth(auth)
|
||||||
|
|
||||||
|
const purchaseUnit = order.purchase_units[0]
|
||||||
|
const customId = purchaseUnit.custom_id
|
||||||
|
|
||||||
|
if (!customId) {
|
||||||
|
res.sendStatus(200)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPaymentCollection(customId)) {
|
||||||
|
await autorizePaymentCollection(req, customId)
|
||||||
|
} else {
|
||||||
|
await autorizeCart(req, customId)
|
||||||
|
}
|
||||||
|
|
||||||
res.sendStatus(200)
|
res.sendStatus(200)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -118,6 +118,34 @@ class PayPalProviderService extends PaymentService {
|
|||||||
return { id: res.result.id }
|
return { id: res.result.id }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createPaymentNew(paymentInput) {
|
||||||
|
const { resource_id, currency_code, amount } = paymentInput
|
||||||
|
|
||||||
|
const request = new PayPal.orders.OrdersCreateRequest()
|
||||||
|
request.requestBody({
|
||||||
|
intent: "AUTHORIZE",
|
||||||
|
application_context: {
|
||||||
|
shipping_preference: "NO_SHIPPING",
|
||||||
|
},
|
||||||
|
purchase_units: [
|
||||||
|
{
|
||||||
|
custom_id: resource_id,
|
||||||
|
amount: {
|
||||||
|
currency_code: currency_code.toUpperCase(),
|
||||||
|
value: roundToTwo(
|
||||||
|
humanizeAmount(amount, currency_code),
|
||||||
|
currency_code
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await this.paypal_.execute(request)
|
||||||
|
|
||||||
|
return { id: res.result.id }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a PayPal order.
|
* Retrieves a PayPal order.
|
||||||
* @param {object} data - the data stored with the payment
|
* @param {object} data - the data stored with the payment
|
||||||
@@ -216,6 +244,35 @@ class PayPalProviderService extends PaymentService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updatePaymentNew(sessionData, paymentInput) {
|
||||||
|
try {
|
||||||
|
const { currency_code, amount } = paymentInput
|
||||||
|
|
||||||
|
const request = new PayPal.orders.OrdersPatchRequest(sessionData.id)
|
||||||
|
request.requestBody([
|
||||||
|
{
|
||||||
|
op: "replace",
|
||||||
|
path: "/purchase_units/@reference_id=='default'",
|
||||||
|
value: {
|
||||||
|
amount: {
|
||||||
|
currency_code: currency_code.toUpperCase(),
|
||||||
|
value: roundToTwo(
|
||||||
|
humanizeAmount(amount, currency_code),
|
||||||
|
currency_code
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
await this.paypal_.execute(request)
|
||||||
|
|
||||||
|
return sessionData
|
||||||
|
} catch (error) {
|
||||||
|
return this.createPaymentNew(paymentInput)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Not suported
|
* Not suported
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -10,13 +10,15 @@ export default async (req, res) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const paymentIntent = event.data.object
|
function isPaymentCollection(id) {
|
||||||
|
return id && id.startsWith("paycol")
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCartPayments(event, req, res, cartId) {
|
||||||
const manager = req.scope.resolve("manager")
|
const manager = req.scope.resolve("manager")
|
||||||
const cartService = req.scope.resolve("cartService")
|
const cartService = req.scope.resolve("cartService")
|
||||||
const orderService = req.scope.resolve("orderService")
|
const orderService = req.scope.resolve("orderService")
|
||||||
|
|
||||||
const cartId = paymentIntent.metadata.cart_id
|
|
||||||
const order = await orderService
|
const order = await orderService
|
||||||
.retrieveByCartId(cartId)
|
.retrieveByCartId(cartId)
|
||||||
.catch(() => undefined)
|
.catch(() => undefined)
|
||||||
@@ -46,4 +48,15 @@ export default async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res.sendStatus(200)
|
res.sendStatus(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentIntent = event.data.object
|
||||||
|
const cartId = paymentIntent.metadata.cart_id
|
||||||
|
const resourceId = paymentIntent.metadata.resource_id
|
||||||
|
|
||||||
|
if (isPaymentCollection(resourceId)) {
|
||||||
|
// TODO: handle payment collection
|
||||||
|
} else {
|
||||||
|
await handleCartPayments(event, req, res, resourceId ?? cartId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
import Stripe from "stripe"
|
||||||
|
import { AbstractPaymentService, PaymentSessionData } from "@medusajs/medusa"
|
||||||
|
|
||||||
|
class StripeBase extends AbstractPaymentService {
|
||||||
|
static identifier = null
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
{
|
||||||
|
stripeProviderService,
|
||||||
|
customerService,
|
||||||
|
totalsService,
|
||||||
|
regionService,
|
||||||
|
manager,
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
paymentMethodTypes
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
stripeProviderService,
|
||||||
|
customerService,
|
||||||
|
totalsService,
|
||||||
|
regionService,
|
||||||
|
manager,
|
||||||
|
},
|
||||||
|
options
|
||||||
|
)
|
||||||
|
/** @private @const {string[]} */
|
||||||
|
this.paymentMethodTypes = paymentMethodTypes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required Stripe options:
|
||||||
|
* {
|
||||||
|
* api_key: "stripe_secret_key", REQUIRED
|
||||||
|
* webhook_secret: "stripe_webhook_secret", REQUIRED
|
||||||
|
* // Use this flag to capture payment immediately (default is false)
|
||||||
|
* capture: true
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
this.options_ = options
|
||||||
|
|
||||||
|
/** @private @const {Stripe} */
|
||||||
|
this.stripe_ = Stripe(options.api_key)
|
||||||
|
|
||||||
|
/** @private @const {CustomerService} */
|
||||||
|
this.stripeProviderService_ = stripeProviderService
|
||||||
|
|
||||||
|
/** @private @const {CustomerService} */
|
||||||
|
this.customerService_ = customerService
|
||||||
|
|
||||||
|
/** @private @const {RegionService} */
|
||||||
|
this.regionService_ = regionService
|
||||||
|
|
||||||
|
/** @private @const {TotalsService} */
|
||||||
|
this.totalsService_ = totalsService
|
||||||
|
|
||||||
|
/** @private @const {EntityManager} */
|
||||||
|
this.manager_ = manager
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches Stripe payment intent. Check its status and returns the
|
||||||
|
* corresponding Medusa status.
|
||||||
|
* @param {PaymentSessionData} paymentSessionData - payment method data from cart
|
||||||
|
* @return {Promise<PaymentSessionStatus>} the status of the payment intent
|
||||||
|
*/
|
||||||
|
async getStatus(paymentSessionData) {
|
||||||
|
return await this.stripeProviderService_.getStatus(paymentSessionData)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a customers saved payment methods if registered in Stripe.
|
||||||
|
* @param {object} customer - customer to fetch saved cards for
|
||||||
|
* @return {Promise<Data[]>} saved payments methods
|
||||||
|
*/
|
||||||
|
async retrieveSavedMethods(customer) {
|
||||||
|
return Promise.resolve([])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a Stripe customer
|
||||||
|
* @param {string} customerId - Stripe customer id
|
||||||
|
* @return {Promise<object>} Stripe customer
|
||||||
|
*/
|
||||||
|
async retrieveCustomer(customerId) {
|
||||||
|
return await this.stripeProviderService_.retrieveCustomer(customerId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Stripe customer using a Medusa customer.
|
||||||
|
* @param {object} customer - Customer data from Medusa
|
||||||
|
* @return {Promise<object>} Stripe customer
|
||||||
|
*/
|
||||||
|
async createCustomer(customer) {
|
||||||
|
return await this.stripeProviderService_
|
||||||
|
.withTransaction(this.manager_)
|
||||||
|
.createCustomer(customer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Stripe payment intent.
|
||||||
|
* If customer is not registered in Stripe, we do so.
|
||||||
|
* @param {Cart} cart - cart to create a payment for
|
||||||
|
* @return {Promise<PaymentSessionData>} Stripe payment intent
|
||||||
|
*/
|
||||||
|
async createPayment(cart) {
|
||||||
|
const intentRequest = {
|
||||||
|
payment_method_types: this.paymentMethodTypes,
|
||||||
|
capture_method: "automatic",
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.stripeProviderService_.createPayment(cart, intentRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
async createPaymentNew(paymentInput) {
|
||||||
|
const intentRequest = {
|
||||||
|
payment_method_types: this.paymentMethodTypes,
|
||||||
|
capture_method: "automatic",
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.stripeProviderService_.createPaymentNew(
|
||||||
|
paymentInput,
|
||||||
|
intentRequest
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves Stripe payment intent.
|
||||||
|
* @param {PaymentData} paymentData - the data of the payment to retrieve
|
||||||
|
* @return {Promise<Data>} Stripe payment intent
|
||||||
|
*/
|
||||||
|
async retrievePayment(paymentData) {
|
||||||
|
return await this.stripeProviderService_.retrievePayment(paymentData)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a Stripe payment intent and returns it.
|
||||||
|
* @param {PaymentSession} paymentSession - the data of the payment to retrieve
|
||||||
|
* @return {Promise<PaymentData>} Stripe payment intent
|
||||||
|
*/
|
||||||
|
async getPaymentData(paymentSession) {
|
||||||
|
return await this.stripeProviderService_.getPaymentData(paymentSession)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authorizes Stripe payment intent by simply returning
|
||||||
|
* the status for the payment intent in use.
|
||||||
|
* @param {PaymentSession} paymentSession - payment session data
|
||||||
|
* @param {object} context - properties relevant to current context
|
||||||
|
* @return {Promise<{data: PaymentSessionData; status: PaymentSessionStatus}>} result with data and status
|
||||||
|
*/
|
||||||
|
async authorizePayment(paymentSession, context = {}) {
|
||||||
|
return await this.stripeProviderService_.authorizePayment(
|
||||||
|
paymentSession,
|
||||||
|
context
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async updatePaymentData(paymentSessionData, data) {
|
||||||
|
return await this.stripeProviderService_.updatePaymentData(
|
||||||
|
paymentSessionData,
|
||||||
|
data
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates Stripe payment intent.
|
||||||
|
* @param {PaymentSessionData} paymentSessionData - payment session data.
|
||||||
|
* @param {Cart} cart
|
||||||
|
* @return {Promise<PaymentSessionData>} Stripe payment intent
|
||||||
|
*/
|
||||||
|
async updatePayment(paymentSessionData, cart) {
|
||||||
|
return await this.stripeProviderService_.updatePayment(
|
||||||
|
paymentSessionData,
|
||||||
|
cart
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async updatePaymentNew(paymentSessionData, paymentInput) {
|
||||||
|
return await this.stripeProviderService_.updatePaymentNew(
|
||||||
|
paymentSessionData,
|
||||||
|
paymentInput
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async deletePayment(paymentSession) {
|
||||||
|
return await this.stripeProviderService_.deletePayment(paymentSession)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates customer of Stripe payment intent.
|
||||||
|
* @param {string} paymentIntentId - id of payment intent to update
|
||||||
|
* @param {string} customerId - id of new Stripe customer
|
||||||
|
* @return {object} Stripe payment intent
|
||||||
|
*/
|
||||||
|
async updatePaymentIntentCustomer(paymentIntentId, customerId) {
|
||||||
|
return await this.stripeProviderService_.updatePaymentIntentCustomer(
|
||||||
|
paymentIntentId,
|
||||||
|
customerId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Captures payment for Stripe payment intent.
|
||||||
|
* @param {Payment} payment - payment method data from cart
|
||||||
|
* @return {Promise<PaymentData>} Stripe payment intent
|
||||||
|
*/
|
||||||
|
async capturePayment(payment) {
|
||||||
|
return await this.stripeProviderService_.capturePayment(payment)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refunds payment for Stripe payment intent.
|
||||||
|
* @param {Payment} payment - payment method data from cart
|
||||||
|
* @param {number} refundAmount - amount to refund
|
||||||
|
* @return {Promise<PaymentData>} refunded payment intent
|
||||||
|
*/
|
||||||
|
async refundPayment(payment, refundAmount) {
|
||||||
|
return await this.stripeProviderService_.refundPayment(
|
||||||
|
payment,
|
||||||
|
refundAmount
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels payment for Stripe payment intent.
|
||||||
|
* @param {Payment} payment - payment method data from cart
|
||||||
|
* @return {Promise<PaymentData>} canceled payment intent
|
||||||
|
*/
|
||||||
|
async cancelPayment(payment) {
|
||||||
|
return await this.stripeProviderService_.cancelPayment(payment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StripeBase
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import Stripe from "stripe"
|
import StripeBase from "../helpers/stripe-base"
|
||||||
import { AbstractPaymentService, PaymentSessionData } from "@medusajs/medusa"
|
|
||||||
|
|
||||||
class BancontactProviderService extends AbstractPaymentService {
|
class BancontactProviderService extends StripeBase {
|
||||||
static identifier = "stripe-bancontact"
|
static identifier = "stripe-bancontact"
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -22,245 +21,9 @@ class BancontactProviderService extends AbstractPaymentService {
|
|||||||
regionService,
|
regionService,
|
||||||
manager,
|
manager,
|
||||||
},
|
},
|
||||||
options
|
options,
|
||||||
|
["bancontact"]
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* Required Stripe options:
|
|
||||||
* {
|
|
||||||
* api_key: "stripe_secret_key", REQUIRED
|
|
||||||
* webhook_secret: "stripe_webhook_secret", REQUIRED
|
|
||||||
* // Use this flag to capture payment immediately (default is false)
|
|
||||||
* capture: true
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
this.options_ = options
|
|
||||||
|
|
||||||
/** @private @const {Stripe} */
|
|
||||||
this.stripe_ = Stripe(options.api_key)
|
|
||||||
|
|
||||||
/** @private @const {CustomerService} */
|
|
||||||
this.stripeProviderService_ = stripeProviderService
|
|
||||||
|
|
||||||
/** @private @const {CustomerService} */
|
|
||||||
this.customerService_ = customerService
|
|
||||||
|
|
||||||
/** @private @const {RegionService} */
|
|
||||||
this.regionService_ = regionService
|
|
||||||
|
|
||||||
/** @private @const {TotalsService} */
|
|
||||||
this.totalsService_ = totalsService
|
|
||||||
|
|
||||||
/** @private @const {EntityManager} */
|
|
||||||
this.manager_ = manager
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches Stripe payment intent. Check its status and returns the
|
|
||||||
* corresponding Medusa status.
|
|
||||||
* @param {PaymentSessionData} paymentSessionData - payment method data from cart
|
|
||||||
* @return {Promise<PaymentSessionStatus>} the status of the payment intent
|
|
||||||
*/
|
|
||||||
async getStatus(paymentSessionData) {
|
|
||||||
return await this.stripeProviderService_.getStatus(paymentSessionData)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a customers saved payment methods if registered in Stripe.
|
|
||||||
* @param {object} customer - customer to fetch saved cards for
|
|
||||||
* @return {Promise<Data[]>} saved payments methods
|
|
||||||
*/
|
|
||||||
async retrieveSavedMethods(customer) {
|
|
||||||
return Promise.resolve([])
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a Stripe customer
|
|
||||||
* @param {string} customerId - Stripe customer id
|
|
||||||
* @return {Promise<object>} Stripe customer
|
|
||||||
*/
|
|
||||||
async retrieveCustomer(customerId) {
|
|
||||||
return await this.stripeProviderService_.retrieveCustomer(customerId)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a Stripe customer using a Medusa customer.
|
|
||||||
* @param {object} customer - Customer data from Medusa
|
|
||||||
* @return {Promise<object>} Stripe customer
|
|
||||||
*/
|
|
||||||
async createCustomer(customer) {
|
|
||||||
return await this.stripeProviderService_
|
|
||||||
.withTransaction(this.manager_)
|
|
||||||
.createCustomer(customer)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a Stripe payment intent.
|
|
||||||
* If customer is not registered in Stripe, we do so.
|
|
||||||
* @param {Cart} cart - cart to create a payment for
|
|
||||||
* @return {Promise<PaymentSessionData>} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async createPayment(cart) {
|
|
||||||
const { customer_id, region_id, email } = cart
|
|
||||||
const region = await this.regionService_
|
|
||||||
.withTransaction(this.manager_)
|
|
||||||
.retrieve(region_id)
|
|
||||||
const { currency_code } = region
|
|
||||||
|
|
||||||
const amount = await this.totalsService_
|
|
||||||
.withTransaction(this.manager_)
|
|
||||||
.getTotal(cart)
|
|
||||||
|
|
||||||
const intentRequest = {
|
|
||||||
amount: Math.round(amount),
|
|
||||||
description:
|
|
||||||
cart?.context?.payment_description ?? this.options?.payment_description,
|
|
||||||
currency: currency_code,
|
|
||||||
payment_method_types: ["bancontact"],
|
|
||||||
capture_method: "automatic",
|
|
||||||
metadata: { cart_id: `${cart.id}` },
|
|
||||||
}
|
|
||||||
|
|
||||||
if (customer_id) {
|
|
||||||
const customer = await this.customerService_
|
|
||||||
.withTransaction(this.manager_)
|
|
||||||
.retrieve(customer_id)
|
|
||||||
|
|
||||||
if (customer.metadata?.stripe_id) {
|
|
||||||
intentRequest.customer = customer.metadata.stripe_id
|
|
||||||
} else {
|
|
||||||
const stripeCustomer = await this.createCustomer({
|
|
||||||
email,
|
|
||||||
id: customer_id,
|
|
||||||
})
|
|
||||||
|
|
||||||
intentRequest.customer = stripeCustomer.id
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const stripeCustomer = await this.createCustomer({
|
|
||||||
email,
|
|
||||||
})
|
|
||||||
|
|
||||||
intentRequest.customer = stripeCustomer.id
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.stripe_.paymentIntents.create(intentRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves Stripe payment intent.
|
|
||||||
* @param {PaymentData} paymentData - the data of the payment to retrieve
|
|
||||||
* @return {Promise<Data>} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async retrievePayment(paymentData) {
|
|
||||||
return await this.stripeProviderService_.retrievePayment(paymentData)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a Stripe payment intent and returns it.
|
|
||||||
* @param {PaymentSession} paymentSession - the data of the payment to retrieve
|
|
||||||
* @return {Promise<PaymentData>} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async getPaymentData(paymentSession) {
|
|
||||||
return await this.stripeProviderService_.getPaymentData(paymentSession)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authorizes Stripe payment intent by simply returning
|
|
||||||
* the status for the payment intent in use.
|
|
||||||
* @param {PaymentSession} paymentSession - payment session data
|
|
||||||
* @param {object} context - properties relevant to current context
|
|
||||||
* @return {Promise<{data: PaymentSessionData; status: PaymentSessionStatus}>} result with data and status
|
|
||||||
*/
|
|
||||||
async authorizePayment(paymentSession, context = {}) {
|
|
||||||
return await this.stripeProviderService_.authorizePayment(
|
|
||||||
paymentSession,
|
|
||||||
context
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async updatePaymentData(paymentSessionData, data) {
|
|
||||||
return await this.stripeProviderService_.updatePaymentData(
|
|
||||||
paymentSessionData,
|
|
||||||
data
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates Stripe payment intent.
|
|
||||||
* @param {PaymentSessionData} paymentSessionData - payment session data.
|
|
||||||
* @param {Cart} cart
|
|
||||||
* @return {Promise<PaymentSessionData>} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async updatePayment(paymentSessionData, cart) {
|
|
||||||
try {
|
|
||||||
const stripeId = cart.customer?.metadata?.stripe_id || undefined
|
|
||||||
|
|
||||||
if (stripeId !== paymentSessionData.customer) {
|
|
||||||
return this.createPayment(cart)
|
|
||||||
} else {
|
|
||||||
if (
|
|
||||||
cart.total &&
|
|
||||||
paymentSessionData.amount === Math.round(cart.total)
|
|
||||||
) {
|
|
||||||
return sessionData
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.stripe_.paymentIntents.update(paymentSessionData.id, {
|
|
||||||
amount: Math.round(cart.total),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async deletePayment(paymentSession) {
|
|
||||||
return await this.stripeProviderService_.deletePayment(paymentSession)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates customer of Stripe payment intent.
|
|
||||||
* @param {string} paymentIntentId - id of payment intent to update
|
|
||||||
* @param {string} customerId - id of new Stripe customer
|
|
||||||
* @return {object} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async updatePaymentIntentCustomer(paymentIntentId, customerId) {
|
|
||||||
return await this.stripeProviderService_.updatePaymentIntentCustomer(
|
|
||||||
paymentIntentId,
|
|
||||||
customerId
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Captures payment for Stripe payment intent.
|
|
||||||
* @param {Payment} payment - payment method data from cart
|
|
||||||
* @return {Promise<PaymentData>} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async capturePayment(payment) {
|
|
||||||
return await this.stripeProviderService_.capturePayment(payment)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refunds payment for Stripe payment intent.
|
|
||||||
* @param {Payment} payment - payment method data from cart
|
|
||||||
* @param {number} refundAmount - amount to refund
|
|
||||||
* @return {Promise<PaymentData>} refunded payment intent
|
|
||||||
*/
|
|
||||||
async refundPayment(payment, refundAmount) {
|
|
||||||
return await this.stripeProviderService_.refundPayment(
|
|
||||||
payment,
|
|
||||||
refundAmount
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancels payment for Stripe payment intent.
|
|
||||||
* @param {Payment} payment - payment method data from cart
|
|
||||||
* @return {Promise<PaymentData>} canceled payment intent
|
|
||||||
*/
|
|
||||||
async cancelPayment(payment) {
|
|
||||||
return await this.stripeProviderService_.cancelPayment(payment)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import Stripe from "stripe"
|
import StripeBase from "../helpers/stripe-base"
|
||||||
import { AbstractPaymentService, PaymentSessionStatus } from "@medusajs/medusa"
|
|
||||||
|
|
||||||
class BlikProviderService extends AbstractPaymentService {
|
class BlikProviderService extends StripeBase {
|
||||||
static identifier = "stripe-blik"
|
static identifier = "stripe-blik"
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -22,242 +21,9 @@ class BlikProviderService extends AbstractPaymentService {
|
|||||||
regionService,
|
regionService,
|
||||||
manager,
|
manager,
|
||||||
},
|
},
|
||||||
options
|
options,
|
||||||
|
["blik"]
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* Required Stripe options:
|
|
||||||
* {
|
|
||||||
* api_key: "stripe_secret_key", REQUIRED
|
|
||||||
* webhook_secret: "stripe_webhook_secret", REQUIRED
|
|
||||||
* // Use this flag to capture payment immediately (default is false)
|
|
||||||
* capture: true
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
this.options_ = options
|
|
||||||
|
|
||||||
/** @private @const {Stripe} */
|
|
||||||
this.stripe_ = Stripe(options.api_key)
|
|
||||||
|
|
||||||
/** @private @const {CustomerService} */
|
|
||||||
this.stripeProviderService_ = stripeProviderService
|
|
||||||
|
|
||||||
/** @private @const {CustomerService} */
|
|
||||||
this.customerService_ = customerService
|
|
||||||
|
|
||||||
/** @private @const {RegionService} */
|
|
||||||
this.regionService_ = regionService
|
|
||||||
|
|
||||||
/** @private @const {TotalsService} */
|
|
||||||
this.totalsService_ = totalsService
|
|
||||||
|
|
||||||
this.manager_ = manager
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches Stripe payment intent. Check its status and returns the
|
|
||||||
* corresponding Medusa status.
|
|
||||||
* @param {PaymentSessionData} paymentSessionData - payment method data from cart
|
|
||||||
* @return {Promise<PaymentSessionStatus>} the status of the payment intent
|
|
||||||
*/
|
|
||||||
async getStatus(paymentSessionData) {
|
|
||||||
return await this.stripeProviderService_.getStatus(paymentSessionData)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a customers saved payment methods if registered in Stripe.
|
|
||||||
* @param {object} customer - customer to fetch saved cards for
|
|
||||||
* @return {Promise<Data[]>} saved payments methods
|
|
||||||
*/
|
|
||||||
async retrieveSavedMethods(customer) {
|
|
||||||
return Promise.resolve([])
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a Stripe customer
|
|
||||||
* @param {string} customerId - Stripe customer id
|
|
||||||
* @return {Promise<object>} Stripe customer
|
|
||||||
*/
|
|
||||||
async retrieveCustomer(customerId) {
|
|
||||||
return await this.stripeProviderService_.retrieveCustomer(customerId)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a Stripe customer using a Medusa customer.
|
|
||||||
* @param {object} customer - Customer data from Medusa
|
|
||||||
* @return {Promise<object>} Stripe customer
|
|
||||||
*/
|
|
||||||
async createCustomer(customer) {
|
|
||||||
return await this.stripeProviderService_
|
|
||||||
.withTransaction(this.manager_)
|
|
||||||
.createCustomer(customer)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a Stripe payment intent.
|
|
||||||
* If customer is not registered in Stripe, we do so.
|
|
||||||
* @param {Cart} cart - cart to create a payment for
|
|
||||||
* @returns {PaymentSessionData} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async createPayment(cart) {
|
|
||||||
const { customer_id, region_id, email } = cart
|
|
||||||
const region = await this.regionService_
|
|
||||||
.withTransaction(this.manager_)
|
|
||||||
.retrieve(region_id)
|
|
||||||
const { currency_code } = region
|
|
||||||
|
|
||||||
const amount = await this.totalsService_
|
|
||||||
.withTransaction(this.manager_)
|
|
||||||
.getTotal(cart)
|
|
||||||
|
|
||||||
const intentRequest = {
|
|
||||||
amount: Math.round(amount),
|
|
||||||
currency: currency_code,
|
|
||||||
payment_method_types: ["blik"],
|
|
||||||
capture_method: "automatic",
|
|
||||||
metadata: { cart_id: `${cart.id}` },
|
|
||||||
}
|
|
||||||
|
|
||||||
if (customer_id) {
|
|
||||||
const customer = await this.customerService_
|
|
||||||
.withTransaction(this.manager_)
|
|
||||||
.retrieve(customer_id)
|
|
||||||
|
|
||||||
if (customer.metadata?.stripe_id) {
|
|
||||||
intentRequest.customer = customer.metadata.stripe_id
|
|
||||||
} else {
|
|
||||||
const stripeCustomer = await this.createCustomer({
|
|
||||||
email,
|
|
||||||
id: customer_id,
|
|
||||||
})
|
|
||||||
|
|
||||||
intentRequest.customer = stripeCustomer.id
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const stripeCustomer = await this.createCustomer({
|
|
||||||
email,
|
|
||||||
})
|
|
||||||
|
|
||||||
intentRequest.customer = stripeCustomer.id
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.stripe_.paymentIntents.create(intentRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves Stripe payment intent.
|
|
||||||
* @param {PaymentData} paymentData - the data of the payment to retrieve
|
|
||||||
* @return {Promise<Data>} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async retrievePayment(paymentData) {
|
|
||||||
return await this.stripeProviderService_.retrievePayment(paymentData)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a Stripe payment intent and returns it.
|
|
||||||
* @param {PaymentSession} paymentSession - the data of the payment to retrieve
|
|
||||||
* @return {Promise<PaymentData>} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async getPaymentData(paymentSession) {
|
|
||||||
return await this.stripeProviderService_.getPaymentData(paymentSession)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authorizes Stripe payment intent by simply returning
|
|
||||||
* the status for the payment intent in use.
|
|
||||||
* @param {PaymentSession} paymentSession - payment session data
|
|
||||||
* @param {object} context - properties relevant to current context
|
|
||||||
* @return {Promise<{data: PaymentSessionData; status: PaymentSessionStatus}>} result with data and status
|
|
||||||
*/
|
|
||||||
async authorizePayment(paymentSession, context = {}) {
|
|
||||||
return await this.stripeProviderService_.authorizePayment(
|
|
||||||
paymentSession,
|
|
||||||
context
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async updatePaymentData(paymentSessionData, data) {
|
|
||||||
return await this.stripeProviderService_.updatePaymentData(
|
|
||||||
paymentSessionData,
|
|
||||||
data
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates Stripe payment intent.
|
|
||||||
* @param {PaymentSessionData} paymentSessionData - payment session data.
|
|
||||||
* @param {Cart} cart
|
|
||||||
* @return {Promise<PaymentSessionData>} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async updatePayment(paymentSessionData, cart) {
|
|
||||||
try {
|
|
||||||
const stripeId = cart.customer?.metadata?.stripe_id || undefined
|
|
||||||
|
|
||||||
if (stripeId !== paymentSessionData.customer) {
|
|
||||||
return this.createPayment(cart)
|
|
||||||
} else {
|
|
||||||
if (
|
|
||||||
cart.total &&
|
|
||||||
paymentSessionData.amount === Math.round(cart.total)
|
|
||||||
) {
|
|
||||||
return sessionData
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.stripe_.paymentIntents.update(paymentSessionData.id, {
|
|
||||||
amount: Math.round(cart.total),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async deletePayment(paymentSession) {
|
|
||||||
return await this.stripeProviderService_.deletePayment(paymentSession)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates customer of Stripe payment intent.
|
|
||||||
* @param {string} paymentIntentId - id of payment intent to update
|
|
||||||
* @param {string} customerId - id of new Stripe customer
|
|
||||||
* @return {object} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async updatePaymentIntentCustomer(paymentIntentId, customerId) {
|
|
||||||
return await this.stripeProviderService_.updatePaymentIntentCustomer(
|
|
||||||
paymentIntentId,
|
|
||||||
customerId
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Captures payment for Stripe payment intent.
|
|
||||||
* @param {Payment} payment - payment method data from cart
|
|
||||||
* @return {Promise<PaymentData>} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async capturePayment(payment) {
|
|
||||||
return await this.stripeProviderService_.capturePayment(payment)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refunds payment for Stripe payment intent.
|
|
||||||
* @param {Payment} payment - payment method data from cart
|
|
||||||
* @param {number} refundAmount - amount to refund
|
|
||||||
* @return {Promise<PaymentData>} refunded payment intent
|
|
||||||
*/
|
|
||||||
async refundPayment(payment, refundAmount) {
|
|
||||||
return await this.stripeProviderService_.refundPayment(
|
|
||||||
payment,
|
|
||||||
refundAmount
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancels payment for Stripe payment intent.
|
|
||||||
* @param {Payment} payment - payment method data from cart
|
|
||||||
* @return {Promise<PaymentData>} canceled payment intent
|
|
||||||
*/
|
|
||||||
async cancelPayment(payment) {
|
|
||||||
return await this.stripeProviderService_.cancelPayment(payment)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import Stripe from "stripe"
|
import StripeBase from "../helpers/stripe-base"
|
||||||
import { AbstractPaymentService, PaymentSessionStatus } from "@medusajs/medusa"
|
|
||||||
|
|
||||||
class GiropayProviderService extends AbstractPaymentService {
|
class GiropayProviderService extends StripeBase {
|
||||||
static identifier = "stripe-giropay"
|
static identifier = "stripe-giropay"
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -22,245 +21,9 @@ class GiropayProviderService extends AbstractPaymentService {
|
|||||||
regionService,
|
regionService,
|
||||||
manager,
|
manager,
|
||||||
},
|
},
|
||||||
options
|
options,
|
||||||
|
["giropay"]
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* Required Stripe options:
|
|
||||||
* {
|
|
||||||
* api_key: "stripe_secret_key", REQUIRED
|
|
||||||
* webhook_secret: "stripe_webhook_secret", REQUIRED
|
|
||||||
* // Use this flag to capture payment immediately (default is false)
|
|
||||||
* capture: true
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
this.options_ = options
|
|
||||||
|
|
||||||
/** @private @const {Stripe} */
|
|
||||||
this.stripe_ = Stripe(options.api_key)
|
|
||||||
|
|
||||||
/** @private @const {CustomerService} */
|
|
||||||
this.stripeProviderService_ = stripeProviderService
|
|
||||||
|
|
||||||
/** @private @const {CustomerService} */
|
|
||||||
this.customerService_ = customerService
|
|
||||||
|
|
||||||
/** @private @const {RegionService} */
|
|
||||||
this.regionService_ = regionService
|
|
||||||
|
|
||||||
/** @private @const {TotalsService} */
|
|
||||||
this.totalsService_ = totalsService
|
|
||||||
|
|
||||||
/** @private @const {EntityManager} */
|
|
||||||
this.manager_ = manager
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches Stripe payment intent. Check its status and returns the
|
|
||||||
* corresponding Medusa status.
|
|
||||||
* @param {PaymentSessionData} paymentSessionData - payment method data from cart
|
|
||||||
* @return {Promise<PaymentSessionStatus>} the status of the payment intent
|
|
||||||
*/
|
|
||||||
async getStatus(paymentSessionData) {
|
|
||||||
return await this.stripeProviderService_.getStatus(paymentSessionData)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a customers saved payment methods if registered in Stripe.
|
|
||||||
* @param {Customer} customer - customer to fetch saved cards for
|
|
||||||
* @return {Promise<Data[]>} saved payments methods
|
|
||||||
*/
|
|
||||||
async retrieveSavedMethods(customer) {
|
|
||||||
return Promise.resolve([])
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a Stripe customer
|
|
||||||
* @param {string} customerId - Stripe customer id
|
|
||||||
* @return {Promise<object>} Stripe customer
|
|
||||||
*/
|
|
||||||
async retrieveCustomer(customerId) {
|
|
||||||
return await this.stripeProviderService_.retrieveCustomer(customerId)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a Stripe customer using a Medusa customer.
|
|
||||||
* @param {object} customer - Customer data from Medusa
|
|
||||||
* @return {Promise<object>} Stripe customer
|
|
||||||
*/
|
|
||||||
async createCustomer(customer) {
|
|
||||||
return await this.stripeProviderService_
|
|
||||||
.withTransaction(this.manager_)
|
|
||||||
.createCustomer(customer)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a Stripe payment intent.
|
|
||||||
* If customer is not registered in Stripe, we do so.
|
|
||||||
* @param {Cart} cart - cart to create a payment for
|
|
||||||
* @return {Promise<PaymentSessionData>} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async createPayment(cart) {
|
|
||||||
const { customer_id, region_id, email } = cart
|
|
||||||
const region = await this.regionService_
|
|
||||||
.withTransaction(this.manager_)
|
|
||||||
.retrieve(region_id)
|
|
||||||
const { currency_code } = region
|
|
||||||
|
|
||||||
const amount = await this.totalsService_
|
|
||||||
.withTransaction(this.manager_)
|
|
||||||
.getTotal(cart)
|
|
||||||
|
|
||||||
const intentRequest = {
|
|
||||||
amount: Math.round(amount),
|
|
||||||
description:
|
|
||||||
cart?.context?.payment_description ?? this.options?.payment_description,
|
|
||||||
currency: currency_code,
|
|
||||||
payment_method_types: ["giropay"],
|
|
||||||
capture_method: "automatic",
|
|
||||||
metadata: { cart_id: `${cart.id}` },
|
|
||||||
}
|
|
||||||
|
|
||||||
if (customer_id) {
|
|
||||||
const customer = await this.customerService_
|
|
||||||
.withTransaction(this.manager_)
|
|
||||||
.retrieve(customer_id)
|
|
||||||
|
|
||||||
if (customer.metadata?.stripe_id) {
|
|
||||||
intentRequest.customer = customer.metadata.stripe_id
|
|
||||||
} else {
|
|
||||||
const stripeCustomer = await this.createCustomer({
|
|
||||||
email,
|
|
||||||
id: customer_id,
|
|
||||||
})
|
|
||||||
|
|
||||||
intentRequest.customer = stripeCustomer.id
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const stripeCustomer = await this.createCustomer({
|
|
||||||
email,
|
|
||||||
})
|
|
||||||
|
|
||||||
intentRequest.customer = stripeCustomer.id
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.stripe_.paymentIntents.create(intentRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves Stripe payment intent.
|
|
||||||
* @param {PaymentData} paymentData - the data of the payment to retrieve
|
|
||||||
* @return {Promise<Data>} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async retrievePayment(paymentData) {
|
|
||||||
return await this.stripeProviderService_.retrievePayment(paymentData)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a Stripe payment intent and returns it.
|
|
||||||
* @param {PaymentSession} paymentSession - the data of the payment to retrieve
|
|
||||||
* @return {Promise<PaymentData>} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async getPaymentData(paymentSession) {
|
|
||||||
return await this.stripeProviderService_.getPaymentData(paymentSession)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authorizes Stripe payment intent by simply returning
|
|
||||||
* the status for the payment intent in use.
|
|
||||||
* @param {PaymentSession} paymentSession - payment session data
|
|
||||||
* @param {Data} context - properties relevant to current context
|
|
||||||
* @return {Promise<{data: PaymentSessionData; status: PaymentSessionStatus}>} result with data and status
|
|
||||||
*/
|
|
||||||
async authorizePayment(paymentSession, context = {}) {
|
|
||||||
return await this.stripeProviderService_.authorizePayment(
|
|
||||||
paymentSession,
|
|
||||||
context
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async updatePaymentData(paymentSessionData, data) {
|
|
||||||
return await this.stripeProviderService_.updatePaymentData(
|
|
||||||
paymentSessionData,
|
|
||||||
data
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates Stripe payment intent.
|
|
||||||
* @param {PaymentSessionData} paymentSessionData - payment session data.
|
|
||||||
* @param {Cart} cart
|
|
||||||
* @return {Promise<PaymentSessionData>} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async updatePayment(paymentSessionData, cart) {
|
|
||||||
try {
|
|
||||||
const stripeId = cart.customer?.metadata?.stripe_id || undefined
|
|
||||||
|
|
||||||
if (stripeId !== paymentSessionData.customer) {
|
|
||||||
return this.createPayment(cart)
|
|
||||||
} else {
|
|
||||||
if (
|
|
||||||
cart.total &&
|
|
||||||
paymentSessionData.amount === Math.round(cart.total)
|
|
||||||
) {
|
|
||||||
return paymentSessionData
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.stripe_.paymentIntents.update(paymentSessionData.id, {
|
|
||||||
amount: Math.round(cart.total),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async deletePayment(paymentSession) {
|
|
||||||
return await this.stripeProviderService_.deletePayment(paymentSession)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates customer of Stripe payment intent.
|
|
||||||
* @param {string} paymentIntentId - id of payment intent to update
|
|
||||||
* @param {string} customerId - id of new Stripe customer
|
|
||||||
* @return {object} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async updatePaymentIntentCustomer(paymentIntentId, customerId) {
|
|
||||||
return await this.stripeProviderService_.updatePaymentIntentCustomer(
|
|
||||||
paymentIntentId,
|
|
||||||
customerId
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Captures payment for Stripe payment intent.
|
|
||||||
* @param {Payment} payment - payment method data from cart
|
|
||||||
* @return {Promise<PaymentData>} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async capturePayment(payment) {
|
|
||||||
return await this.stripeProviderService_.capturePayment(payment)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refunds payment for Stripe payment intent.
|
|
||||||
* @param {Payment} payment - payment method data from cart
|
|
||||||
* @param {number} refundAmount - amount to refund
|
|
||||||
* @return {Promise<PaymentData>} refunded payment intent
|
|
||||||
*/
|
|
||||||
async refundPayment(payment, refundAmount) {
|
|
||||||
return await this.stripeProviderService_.refundPayment(
|
|
||||||
payment,
|
|
||||||
refundAmount
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancels payment for Stripe payment intent.
|
|
||||||
* @param {Payment} payment - payment method data from cart
|
|
||||||
* @return {Promise<PaymentData>} canceled payment intent
|
|
||||||
*/
|
|
||||||
async cancelPayment(payment) {
|
|
||||||
return await this.stripeProviderService_.cancelPayment(payment)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import Stripe from "stripe"
|
import StripeBase from "../helpers/stripe-base"
|
||||||
import { PaymentService } from "medusa-interfaces"
|
|
||||||
|
|
||||||
class IdealProviderService extends PaymentService {
|
class IdealProviderService extends StripeBase {
|
||||||
static identifier = "stripe-ideal"
|
static identifier = "stripe-ideal"
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -22,242 +21,9 @@ class IdealProviderService extends PaymentService {
|
|||||||
regionService,
|
regionService,
|
||||||
manager,
|
manager,
|
||||||
},
|
},
|
||||||
options
|
options,
|
||||||
|
["ideal"]
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* Required Stripe options:
|
|
||||||
* {
|
|
||||||
* api_key: "stripe_secret_key", REQUIRED
|
|
||||||
* webhook_secret: "stripe_webhook_secret", REQUIRED
|
|
||||||
* // Use this flag to capture payment immediately (default is false)
|
|
||||||
* capture: true
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
this.options_ = options
|
|
||||||
|
|
||||||
/** @private @const {Stripe} */
|
|
||||||
this.stripe_ = Stripe(options.api_key)
|
|
||||||
|
|
||||||
/** @private @const {CustomerService} */
|
|
||||||
this.stripeProviderService_ = stripeProviderService
|
|
||||||
|
|
||||||
/** @private @const {CustomerService} */
|
|
||||||
this.customerService_ = customerService
|
|
||||||
|
|
||||||
/** @private @const {RegionService} */
|
|
||||||
this.regionService_ = regionService
|
|
||||||
|
|
||||||
/** @private @const {TotalsService} */
|
|
||||||
this.totalsService_ = totalsService
|
|
||||||
|
|
||||||
this.manager_ = manager
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches Stripe payment intent. Check its status and returns the
|
|
||||||
* corresponding Medusa status.
|
|
||||||
* @param {PaymentSessionData} paymentSessionData - payment method data from cart
|
|
||||||
* @return {Promise<PaymentSessionStatus>} the status of the payment intent
|
|
||||||
*/
|
|
||||||
async getStatus(paymentData) {
|
|
||||||
return await this.stripeProviderService_.getStatus(paymentData)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a customers saved payment methods if registered in Stripe.
|
|
||||||
* @param {object} customer - customer to fetch saved cards for
|
|
||||||
* @return {Promise<Data[]>} saved payments methods
|
|
||||||
*/
|
|
||||||
async retrieveSavedMethods(customer) {
|
|
||||||
return Promise.resolve([])
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a Stripe customer
|
|
||||||
* @param {string} customerId - Stripe customer id
|
|
||||||
* @return {Promise<object>} Stripe customer
|
|
||||||
*/
|
|
||||||
async retrieveCustomer(customerId) {
|
|
||||||
return await this.stripeProviderService_.retrieveCustomer(customerId)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a Stripe customer using a Medusa customer.
|
|
||||||
* @param {object} customer - Customer data from Medusa
|
|
||||||
* @return {Promise<object>} Stripe customer
|
|
||||||
*/
|
|
||||||
async createCustomer(customer) {
|
|
||||||
return await this.stripeProviderService_
|
|
||||||
.withTransaction(this.manager_)
|
|
||||||
.createCustomer(customer)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a Stripe payment intent.
|
|
||||||
* If customer is not registered in Stripe, we do so.
|
|
||||||
* @param {Cart} cart - cart to create a payment for
|
|
||||||
* @return {Promise<PaymentSessionData>} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async createPayment(cart) {
|
|
||||||
const { customer_id, region_id, email } = cart
|
|
||||||
const region = await this.regionService_
|
|
||||||
.withTransaction(this.manager_)
|
|
||||||
.retrieve(region_id)
|
|
||||||
const { currency_code } = region
|
|
||||||
|
|
||||||
const amount = await this.totalsService_
|
|
||||||
.withTransaction(this.manager_)
|
|
||||||
.getTotal(cart)
|
|
||||||
|
|
||||||
const intentRequest = {
|
|
||||||
amount: Math.round(amount),
|
|
||||||
description:
|
|
||||||
cart?.context?.payment_description ??
|
|
||||||
this.options_?.payment_description,
|
|
||||||
currency: currency_code,
|
|
||||||
payment_method_types: ["ideal"],
|
|
||||||
capture_method: "automatic",
|
|
||||||
metadata: { cart_id: `${cart.id}` },
|
|
||||||
}
|
|
||||||
|
|
||||||
if (customer_id) {
|
|
||||||
const customer = await this.customerService_
|
|
||||||
.withTransaction(this.manager_)
|
|
||||||
.retrieve(customer_id)
|
|
||||||
|
|
||||||
if (customer.metadata?.stripe_id) {
|
|
||||||
intentRequest.customer = customer.metadata.stripe_id
|
|
||||||
} else {
|
|
||||||
const stripeCustomer = await this.createCustomer({
|
|
||||||
email,
|
|
||||||
id: customer_id,
|
|
||||||
})
|
|
||||||
|
|
||||||
intentRequest.customer = stripeCustomer.id
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const stripeCustomer = await this.createCustomer({
|
|
||||||
email,
|
|
||||||
})
|
|
||||||
|
|
||||||
intentRequest.customer = stripeCustomer.id
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.stripe_.paymentIntents.create(intentRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves Stripe payment intent.
|
|
||||||
* @param {PaymentData} paymentData - the data of the payment to retrieve
|
|
||||||
* @return {Promise<Data>} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async retrievePayment(paymentData) {
|
|
||||||
return await this.stripeProviderService_.retrievePayment(paymentData)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a Stripe payment intent and returns it.
|
|
||||||
* @param {PaymentSession} paymentSession - the data of the payment to retrieve
|
|
||||||
* @return {Promise<PaymentData>} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async getPaymentData(paymentSession) {
|
|
||||||
return await this.stripeProviderService_.getPaymentData(paymentSession)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authorizes Stripe payment intent by simply returning
|
|
||||||
* the status for the payment intent in use.
|
|
||||||
* @param {PaymentSession} paymentSession - payment session data
|
|
||||||
* @param {object} context - properties relevant to current context
|
|
||||||
* @return {Promise<{data: PaymentSessionData; status: PaymentSessionStatus}>} result with data and status
|
|
||||||
*/
|
|
||||||
async authorizePayment(paymentSession, context = {}) {
|
|
||||||
return await this.stripeProviderService_.authorizePayment(
|
|
||||||
paymentSession,
|
|
||||||
context
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async updatePaymentData(paymentSessionData, data) {
|
|
||||||
return await this.stripeProviderService_.updatePaymentData(
|
|
||||||
paymentSessionData,
|
|
||||||
data
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates Stripe payment intent.
|
|
||||||
* @param {PaymentSessionData} paymentSessionData - payment session data.
|
|
||||||
* @param {Cart} cart
|
|
||||||
* @return {Promise<PaymentSessionData>} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async updatePayment(sessionData, cart) {
|
|
||||||
try {
|
|
||||||
const stripeId = cart.customer?.metadata?.stripe_id || undefined
|
|
||||||
|
|
||||||
if (stripeId !== sessionData.customer) {
|
|
||||||
return this.createPayment(cart)
|
|
||||||
} else {
|
|
||||||
if (cart.total && sessionData.amount === Math.round(cart.total)) {
|
|
||||||
return sessionData
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.stripe_.paymentIntents.update(sessionData.id, {
|
|
||||||
amount: Math.round(cart.total),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async deletePayment(paymentSession) {
|
|
||||||
return await this.stripeProviderService_.deletePayment(paymentSession)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates customer of Stripe payment intent.
|
|
||||||
* @param {string} paymentIntentId - id of payment intent to update
|
|
||||||
* @param {string} customerId - id of new Stripe customer
|
|
||||||
* @return {object} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async updatePaymentIntentCustomer(paymentIntentId, customerId) {
|
|
||||||
return await this.stripeProviderService_.updatePaymentIntentCustomer(
|
|
||||||
paymentIntentId,
|
|
||||||
customerId
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Captures payment for Stripe payment intent.
|
|
||||||
* @param {Payment} payment - payment method data from cart
|
|
||||||
* @return {Promise<PaymentData>} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async capturePayment(payment) {
|
|
||||||
return await this.stripeProviderService_.capturePayment(payment)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refunds payment for Stripe payment intent.
|
|
||||||
* @param {Payment} payment - payment method data from cart
|
|
||||||
* @param {number} refundAmount - amount to refund
|
|
||||||
* @return {Promise<PaymentData>} refunded payment intent
|
|
||||||
*/
|
|
||||||
async refundPayment(payment, refundAmount) {
|
|
||||||
return await this.stripeProviderService_.refundPayment(
|
|
||||||
payment,
|
|
||||||
refundAmount
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancels payment for Stripe payment intent.
|
|
||||||
* @param {Payment} payment - payment method data from cart
|
|
||||||
* @return {Promise<PaymentData>} canceled payment intent
|
|
||||||
*/
|
|
||||||
async cancelPayment(payment) {
|
|
||||||
return await this.stripeProviderService_.cancelPayment(payment)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ class StripeProviderService extends AbstractPaymentService {
|
|||||||
* @param {Cart} cart - cart to create a payment for
|
* @param {Cart} cart - cart to create a payment for
|
||||||
* @return {Promise<PaymentSessionData>} Stripe payment intent
|
* @return {Promise<PaymentSessionData>} Stripe payment intent
|
||||||
*/
|
*/
|
||||||
async createPayment(cart) {
|
async createPayment(cart, intentRequestData = {}) {
|
||||||
const { customer_id, region_id, email } = cart
|
const { customer_id, region_id, email } = cart
|
||||||
const { currency_code } = await this.regionService_
|
const { currency_code } = await this.regionService_
|
||||||
.withTransaction(this.manager_)
|
.withTransaction(this.manager_)
|
||||||
@@ -143,9 +143,10 @@ class StripeProviderService extends AbstractPaymentService {
|
|||||||
this.options_?.payment_description,
|
this.options_?.payment_description,
|
||||||
amount: Math.round(amount),
|
amount: Math.round(amount),
|
||||||
currency: currency_code,
|
currency: currency_code,
|
||||||
|
metadata: { cart_id: `${cart.id}` },
|
||||||
setup_future_usage: "on_session",
|
setup_future_usage: "on_session",
|
||||||
capture_method: this.options_.capture ? "automatic" : "manual",
|
capture_method: this.options_.capture ? "automatic" : "manual",
|
||||||
metadata: { cart_id: `${cart.id}` },
|
...intentRequestData,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.options_?.automatic_payment_methods) {
|
if (this.options_?.automatic_payment_methods) {
|
||||||
@@ -178,6 +179,44 @@ class StripeProviderService extends AbstractPaymentService {
|
|||||||
return await this.stripe_.paymentIntents.create(intentRequest)
|
return await this.stripe_.paymentIntents.create(intentRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createPaymentNew(paymentInput, intentRequestData = {}) {
|
||||||
|
const { customer, currency_code, amount, resource_id, cart } = paymentInput
|
||||||
|
const { id: customer_id, email } = customer
|
||||||
|
|
||||||
|
let intentRequest = {
|
||||||
|
description:
|
||||||
|
cart?.context?.payment_description ??
|
||||||
|
this.options_?.payment_description,
|
||||||
|
amount: Math.round(amount),
|
||||||
|
currency: currency_code,
|
||||||
|
metadata: { resource_id },
|
||||||
|
setup_future_usage: "on_session",
|
||||||
|
capture_method: this.options_.capture ? "automatic" : "manual",
|
||||||
|
...intentRequestData,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customer_id) {
|
||||||
|
if (customer.metadata?.stripe_id) {
|
||||||
|
intentRequest.customer = customer.metadata.stripe_id
|
||||||
|
} else {
|
||||||
|
const stripeCustomer = await this.createCustomer({
|
||||||
|
email,
|
||||||
|
id: customer_id,
|
||||||
|
})
|
||||||
|
|
||||||
|
intentRequest.customer = stripeCustomer.id
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const stripeCustomer = await this.createCustomer({
|
||||||
|
email,
|
||||||
|
})
|
||||||
|
|
||||||
|
intentRequest.customer = stripeCustomer.id
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.stripe_.paymentIntents.create(intentRequest)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves Stripe payment intent.
|
* Retrieves Stripe payment intent.
|
||||||
* @param {PaymentData} paymentData - the data of the payment to retrieve
|
* @param {PaymentData} paymentData - the data of the payment to retrieve
|
||||||
@@ -213,7 +252,6 @@ class StripeProviderService extends AbstractPaymentService {
|
|||||||
*/
|
*/
|
||||||
async authorizePayment(paymentSession, context = {}) {
|
async authorizePayment(paymentSession, context = {}) {
|
||||||
const stat = await this.getStatus(paymentSession.data)
|
const stat = await this.getStatus(paymentSession.data)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return { data: paymentSession.data, status: stat }
|
return { data: paymentSession.data, status: stat }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -242,7 +280,7 @@ class StripeProviderService extends AbstractPaymentService {
|
|||||||
const stripeId = cart.customer?.metadata?.stripe_id || undefined
|
const stripeId = cart.customer?.metadata?.stripe_id || undefined
|
||||||
|
|
||||||
if (stripeId !== sessionData.customer) {
|
if (stripeId !== sessionData.customer) {
|
||||||
return this.createPayment(cart)
|
return await this.createPayment(cart)
|
||||||
} else {
|
} else {
|
||||||
if (cart.total && sessionData.amount === Math.round(cart.total)) {
|
if (cart.total && sessionData.amount === Math.round(cart.total)) {
|
||||||
return sessionData
|
return sessionData
|
||||||
@@ -257,6 +295,26 @@ class StripeProviderService extends AbstractPaymentService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updatePaymentNew(paymentSessionData, paymentInput) {
|
||||||
|
try {
|
||||||
|
const stripeId = paymentInput.customer?.metadata?.stripe_id
|
||||||
|
|
||||||
|
if (stripeId !== paymentInput.customer_id) {
|
||||||
|
return await this.createPaymentNew(paymentInput)
|
||||||
|
} else {
|
||||||
|
if (paymentSessionData.amount === Math.round(paymentInput.amount)) {
|
||||||
|
return sessionData
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.stripe_.paymentIntents.update(paymentSessionData.id, {
|
||||||
|
amount: Math.round(paymentInput.amount),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async deletePayment(payment) {
|
async deletePayment(payment) {
|
||||||
try {
|
try {
|
||||||
const { id } = payment.data
|
const { id } = payment.data
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import Stripe from "stripe"
|
import StripeBase from "../helpers/stripe-base"
|
||||||
import { AbstractPaymentService, PaymentSessionStatus } from "@medusajs/medusa"
|
|
||||||
|
|
||||||
class Przelewy24ProviderService extends AbstractPaymentService {
|
class Przelewy24ProviderService extends StripeBase {
|
||||||
static identifier = "stripe-przelewy24"
|
static identifier = "stripe-przelewy24"
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -22,239 +21,9 @@ class Przelewy24ProviderService extends AbstractPaymentService {
|
|||||||
regionService,
|
regionService,
|
||||||
manager,
|
manager,
|
||||||
},
|
},
|
||||||
options
|
options,
|
||||||
|
["p24"]
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* Required Stripe options:
|
|
||||||
* {
|
|
||||||
* api_key: "stripe_secret_key", REQUIRED
|
|
||||||
* webhook_secret: "stripe_webhook_secret", REQUIRED
|
|
||||||
* // Use this flag to capture payment immediately (default is false)
|
|
||||||
* capture: true
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
this.options_ = options
|
|
||||||
|
|
||||||
/** @private @const {Stripe} */
|
|
||||||
this.stripe_ = Stripe(options.api_key)
|
|
||||||
|
|
||||||
/** @private @const {CustomerService} */
|
|
||||||
this.stripeProviderService_ = stripeProviderService
|
|
||||||
|
|
||||||
/** @private @const {CustomerService} */
|
|
||||||
this.customerService_ = customerService
|
|
||||||
|
|
||||||
/** @private @const {RegionService} */
|
|
||||||
this.regionService_ = regionService
|
|
||||||
|
|
||||||
/** @private @const {TotalsService} */
|
|
||||||
this.totalsService_ = totalsService
|
|
||||||
|
|
||||||
this.manager_ = manager
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches Stripe payment intent. Check its status and returns the
|
|
||||||
* corresponding Medusa status.
|
|
||||||
* @param {PaymentSessionData} paymentSessionData - payment method data from cart
|
|
||||||
* @return {Promise<PaymentSessionStatus>} the status of the payment intent
|
|
||||||
*/
|
|
||||||
async getStatus(paymentData) {
|
|
||||||
return await this.stripeProviderService_.getStatus(paymentData)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a customers saved payment methods if registered in Stripe.
|
|
||||||
* @param {object} customer - customer to fetch saved cards for
|
|
||||||
* @return {Promise<Data[]>} saved payments methods
|
|
||||||
*/
|
|
||||||
async retrieveSavedMethods(customer) {
|
|
||||||
return Promise.resolve([])
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a Stripe customer
|
|
||||||
* @param {string} customerId - Stripe customer id
|
|
||||||
* @return {Promise<object>} Stripe customer
|
|
||||||
*/
|
|
||||||
async retrieveCustomer(customerId) {
|
|
||||||
return await this.stripeProviderService_.retrieveCustomer(customerId)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a Stripe customer using a Medusa customer.
|
|
||||||
* @param {object} customer - Customer data from Medusa
|
|
||||||
* @return {Promise<object>} Stripe customer
|
|
||||||
*/
|
|
||||||
async createCustomer(customer) {
|
|
||||||
return await this.stripeProviderService_
|
|
||||||
.withTransaction(this.manager_)
|
|
||||||
.createCustomer(customer)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a Stripe payment intent.
|
|
||||||
* If customer is not registered in Stripe, we do so.
|
|
||||||
* @param {object} cart - cart to create a payment for
|
|
||||||
* @returns {object} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async createPayment(cart) {
|
|
||||||
const { customer_id, region_id, email } = cart
|
|
||||||
const region = await this.regionService_
|
|
||||||
.withTransaction(this.manager_)
|
|
||||||
.retrieve(region_id)
|
|
||||||
const { currency_code } = region
|
|
||||||
|
|
||||||
const amount = await this.totalsService_
|
|
||||||
.withTransaction(this.manager_)
|
|
||||||
.getTotal(cart)
|
|
||||||
|
|
||||||
const intentRequest = {
|
|
||||||
amount: Math.round(amount),
|
|
||||||
currency: currency_code,
|
|
||||||
payment_method_types: ["p24"],
|
|
||||||
capture_method: "automatic",
|
|
||||||
metadata: { cart_id: `${cart.id}` },
|
|
||||||
}
|
|
||||||
|
|
||||||
if (customer_id) {
|
|
||||||
const customer = await this.customerService_
|
|
||||||
.withTransaction(this.manager_)
|
|
||||||
.retrieve(customer_id)
|
|
||||||
|
|
||||||
if (customer.metadata?.stripe_id) {
|
|
||||||
intentRequest.customer = customer.metadata.stripe_id
|
|
||||||
} else {
|
|
||||||
const stripeCustomer = await this.createCustomer({
|
|
||||||
email,
|
|
||||||
id: customer_id,
|
|
||||||
})
|
|
||||||
|
|
||||||
intentRequest.customer = stripeCustomer.id
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const stripeCustomer = await this.createCustomer({
|
|
||||||
email,
|
|
||||||
})
|
|
||||||
|
|
||||||
intentRequest.customer = stripeCustomer.id
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.stripe_.paymentIntents.create(intentRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves Stripe payment intent.
|
|
||||||
* @param {PaymentData} paymentData - the data of the payment to retrieve
|
|
||||||
* @return {Promise<Data>} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async retrievePayment(paymentData) {
|
|
||||||
return await this.stripeProviderService_.retrievePayment(paymentData)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a Stripe payment intent and returns it.
|
|
||||||
* @param {PaymentSession} paymentSession - the data of the payment to retrieve
|
|
||||||
* @return {Promise<PaymentData>} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async getPaymentData(paymentSession) {
|
|
||||||
return await this.stripeProviderService_.getPaymentData(paymentSession)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authorizes Stripe payment intent by simply returning
|
|
||||||
* the status for the payment intent in use.
|
|
||||||
* @param {PaymentSession} paymentSession - payment session data
|
|
||||||
* @param {object} context - properties relevant to current context
|
|
||||||
* @return {Promise<{data: PaymentSessionData; status: PaymentSessionStatus}>} result with data and status
|
|
||||||
*/
|
|
||||||
async authorizePayment(paymentSession, context = {}) {
|
|
||||||
return await this.stripeProviderService_.authorizePayment(
|
|
||||||
paymentSession,
|
|
||||||
context
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async updatePaymentData(paymentSessionData, data) {
|
|
||||||
return await this.stripeProviderService_.updatePaymentData(
|
|
||||||
paymentSessionData,
|
|
||||||
data
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates Stripe payment intent.
|
|
||||||
* @param {PaymentSessionData} paymentSessionData - payment session data.
|
|
||||||
* @param {Cart} cart
|
|
||||||
* @return {Promise<PaymentSessionData>} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async updatePayment(sessionData, cart) {
|
|
||||||
try {
|
|
||||||
const stripeId = cart.customer?.metadata?.stripe_id || undefined
|
|
||||||
|
|
||||||
if (stripeId !== sessionData.customer) {
|
|
||||||
return this.createPayment(cart)
|
|
||||||
} else {
|
|
||||||
if (cart.total && sessionData.amount === Math.round(cart.total)) {
|
|
||||||
return sessionData
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.stripe_.paymentIntents.update(sessionData.id, {
|
|
||||||
amount: Math.round(cart.total),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async deletePayment(paymentSession) {
|
|
||||||
return await this.stripeProviderService_.deletePayment(paymentSession)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates customer of Stripe payment intent.
|
|
||||||
* @param {string} paymentIntentId - id of payment intent to update
|
|
||||||
* @param {string} customerId - id of new Stripe customer
|
|
||||||
* @return {object} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async updatePaymentIntentCustomer(paymentIntentId, customerId) {
|
|
||||||
return await this.stripeProviderService_.updatePaymentIntentCustomer(
|
|
||||||
paymentIntentId,
|
|
||||||
customerId
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Captures payment for Stripe payment intent.
|
|
||||||
* @param {Payment} payment - payment method data from cart
|
|
||||||
* @return {Promise<PaymentData>} Stripe payment intent
|
|
||||||
*/
|
|
||||||
async capturePayment(payment) {
|
|
||||||
return await this.stripeProviderService_.capturePayment(payment)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refunds payment for Stripe payment intent.
|
|
||||||
* @param {Payment} payment - payment method data from cart
|
|
||||||
* @param {number} refundAmount - amount to refund
|
|
||||||
* @return {Promise<PaymentData>} refunded payment intent
|
|
||||||
*/
|
|
||||||
async refundPayment(payment, refundAmount) {
|
|
||||||
return await this.stripeProviderService_.refundPayment(
|
|
||||||
payment,
|
|
||||||
refundAmount
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancels payment for Stripe payment intent.
|
|
||||||
* @param {Payment} payment - payment method data from cart
|
|
||||||
* @return {Promise<PaymentData>} canceled payment intent
|
|
||||||
*/
|
|
||||||
async cancelPayment(payment) {
|
|
||||||
return await this.stripeProviderService_.cancelPayment(payment)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
PaymentSessionStatus,
|
PaymentSessionStatus,
|
||||||
} from "../models"
|
} from "../models"
|
||||||
import { PaymentService } from "medusa-interfaces"
|
import { PaymentService } from "medusa-interfaces"
|
||||||
|
import { PaymentProviderDataInput } from "../types/payment-collection"
|
||||||
|
|
||||||
export type Data = Record<string, unknown>
|
export type Data = Record<string, unknown>
|
||||||
export type PaymentData = Data
|
export type PaymentData = Data
|
||||||
@@ -76,6 +77,9 @@ export abstract class AbstractPaymentService
|
|||||||
): Promise<PaymentSessionData>
|
): Promise<PaymentSessionData>
|
||||||
|
|
||||||
public abstract createPayment(cart: Cart): Promise<PaymentSessionData>
|
public abstract createPayment(cart: Cart): Promise<PaymentSessionData>
|
||||||
|
public abstract createPaymentNew(
|
||||||
|
paymentInput: PaymentProviderDataInput
|
||||||
|
): Promise<PaymentSessionData>
|
||||||
|
|
||||||
public abstract retrievePayment(paymentData: PaymentData): Promise<Data>
|
public abstract retrievePayment(paymentData: PaymentData): Promise<Data>
|
||||||
|
|
||||||
@@ -84,6 +88,11 @@ export abstract class AbstractPaymentService
|
|||||||
cart: Cart
|
cart: Cart
|
||||||
): Promise<PaymentSessionData>
|
): Promise<PaymentSessionData>
|
||||||
|
|
||||||
|
public abstract updatePaymentNew(
|
||||||
|
paymentSessionData: PaymentSessionData,
|
||||||
|
paymentInput: PaymentProviderDataInput
|
||||||
|
): Promise<PaymentSessionData>
|
||||||
|
|
||||||
public abstract authorizePayment(
|
public abstract authorizePayment(
|
||||||
paymentSession: PaymentSession,
|
paymentSession: PaymentSession,
|
||||||
context: Data
|
context: Data
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export class paymentCollection1664880666982 implements MigrationInterface {
|
|||||||
description text NULL,
|
description text NULL,
|
||||||
amount integer NOT NULL,
|
amount integer NOT NULL,
|
||||||
authorized_amount integer NULL,
|
authorized_amount integer NULL,
|
||||||
|
captured_amount integer NULL,
|
||||||
refunded_amount integer NULL,
|
refunded_amount integer NULL,
|
||||||
region_id character varying NOT NULL,
|
region_id character varying NOT NULL,
|
||||||
currency_code character varying NOT NULL,
|
currency_code character varying NOT NULL,
|
||||||
@@ -71,6 +72,12 @@ export class paymentCollection1664880666982 implements MigrationInterface {
|
|||||||
ALTER TABLE "order_edit" ADD CONSTRAINT "FK_order_edit_payment_collection_id" FOREIGN KEY ("payment_collection_id") REFERENCES "payment_collection"("id") ON DELETE NO ACTION ON UPDATE NO ACTION;
|
ALTER TABLE "order_edit" ADD CONSTRAINT "FK_order_edit_payment_collection_id" FOREIGN KEY ("payment_collection_id") REFERENCES "payment_collection"("id") ON DELETE NO ACTION ON UPDATE NO ACTION;
|
||||||
|
|
||||||
ALTER TABLE payment_session ADD COLUMN payment_authorized_at timestamp WITH time zone NULL;
|
ALTER TABLE payment_session ADD COLUMN payment_authorized_at timestamp WITH time zone NULL;
|
||||||
|
ALTER TABLE payment_session ADD COLUMN amount integer NULL;
|
||||||
|
ALTER TABLE payment_session ALTER COLUMN cart_id DROP NOT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE refund ADD COLUMN payment_id character varying NULL;
|
||||||
|
CREATE INDEX "IDX_refund_payment_id" ON "refund" ("payment_id");
|
||||||
|
ALTER TABLE "refund" ADD CONSTRAINT "FK_refund_payment_id" FOREIGN KEY ("payment_id") REFERENCES "payment"("id") ON DELETE NO ACTION ON UPDATE NO ACTION;
|
||||||
`)
|
`)
|
||||||
|
|
||||||
// Add missing indexes
|
// Add missing indexes
|
||||||
@@ -85,6 +92,12 @@ export class paymentCollection1664880666982 implements MigrationInterface {
|
|||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(`
|
await queryRunner.query(`
|
||||||
|
DROP INDEX "IDX_order_edit_payment_collection_id";
|
||||||
|
ALTER TABLE order_edit DROP CONSTRAINT "FK_order_edit_payment_collection_id";
|
||||||
|
|
||||||
|
DROP INDEX "IDX_refund_payment_id";
|
||||||
|
ALTER TABLE refund DROP CONSTRAINT "FK_refund_payment_id";
|
||||||
|
|
||||||
ALTER TABLE payment_collection DROP CONSTRAINT "FK_payment_collection_region_id";
|
ALTER TABLE payment_collection DROP CONSTRAINT "FK_payment_collection_region_id";
|
||||||
ALTER TABLE payment_collection_sessions DROP CONSTRAINT "FK_payment_collection_sessions_payment_collection_id";
|
ALTER TABLE payment_collection_sessions DROP CONSTRAINT "FK_payment_collection_sessions_payment_collection_id";
|
||||||
ALTER TABLE payment_collection_sessions DROP CONSTRAINT "FK_payment_collection_sessions_payment_session_id";
|
ALTER TABLE payment_collection_sessions DROP CONSTRAINT "FK_payment_collection_sessions_payment_session_id";
|
||||||
@@ -92,6 +105,9 @@ export class paymentCollection1664880666982 implements MigrationInterface {
|
|||||||
ALTER TABLE payment_collection_payments DROP CONSTRAINT "FK_payment_collection_payments_payment_id";
|
ALTER TABLE payment_collection_payments DROP CONSTRAINT "FK_payment_collection_payments_payment_id";
|
||||||
ALTER TABLE order_edit DROP COLUMN payment_collection_id;
|
ALTER TABLE order_edit DROP COLUMN payment_collection_id;
|
||||||
ALTER TABLE payment_session DROP COLUMN payment_authorized_at;
|
ALTER TABLE payment_session DROP COLUMN payment_authorized_at;
|
||||||
|
ALTER TABLE payment_session DROP COLUMN amount;
|
||||||
|
ALTER TABLE payment_session ALTER COLUMN cart_id SET NOT NULL;
|
||||||
|
ALTER TABLE refund DROP COLUMN payment_id;
|
||||||
|
|
||||||
DROP TABLE payment_collection;
|
DROP TABLE payment_collection;
|
||||||
DROP TABLE payment_collection_sessions;
|
DROP TABLE payment_collection_sessions;
|
||||||
@@ -99,10 +115,6 @@ export class paymentCollection1664880666982 implements MigrationInterface {
|
|||||||
|
|
||||||
DROP TYPE "PAYMENT_COLLECTION_TYPE_ENUM";
|
DROP TYPE "PAYMENT_COLLECTION_TYPE_ENUM";
|
||||||
DROP TYPE "PAYMENT_COLLECTION_STATUS_ENUM";
|
DROP TYPE "PAYMENT_COLLECTION_STATUS_ENUM";
|
||||||
|
|
||||||
DROP INDEX "IDX_order_edit_payment_collection_id";
|
|
||||||
ALTER TABLE order_edit DROP CONSTRAINT "FK_order_edit_payment_collection_id";
|
|
||||||
|
|
||||||
`)
|
`)
|
||||||
|
|
||||||
await queryRunner.query(`
|
await queryRunner.query(`
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ export class PaymentCollection extends SoftDeletableEntity {
|
|||||||
@Column({ type: "int", nullable: true })
|
@Column({ type: "int", nullable: true })
|
||||||
authorized_amount: number
|
authorized_amount: number
|
||||||
|
|
||||||
|
@Column({ type: "int", nullable: true })
|
||||||
|
captured_amount: number
|
||||||
|
|
||||||
@Column({ type: "int", nullable: true })
|
@Column({ type: "int", nullable: true })
|
||||||
refunded_amount: number
|
refunded_amount: number
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export enum PaymentSessionStatus {
|
|||||||
@Entity()
|
@Entity()
|
||||||
export class PaymentSession extends BaseEntity {
|
export class PaymentSession extends BaseEntity {
|
||||||
@Index()
|
@Index()
|
||||||
@Column()
|
@Column({ nullable: true })
|
||||||
cart_id: string
|
cart_id: string
|
||||||
|
|
||||||
@ManyToOne(() => Cart, (cart) => cart.payment_sessions)
|
@ManyToOne(() => Cart, (cart) => cart.payment_sessions)
|
||||||
@@ -51,6 +51,11 @@ export class PaymentSession extends BaseEntity {
|
|||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
idempotency_key: string
|
idempotency_key: string
|
||||||
|
|
||||||
|
@FeatureFlagDecorators(OrderEditingFeatureFlag.key, [
|
||||||
|
Column({ type: "integer", nullable: true }),
|
||||||
|
])
|
||||||
|
amount: number
|
||||||
|
|
||||||
@FeatureFlagDecorators(OrderEditingFeatureFlag.key, [
|
@FeatureFlagDecorators(OrderEditingFeatureFlag.key, [
|
||||||
Column({ type: resolveDbType("timestamptz"), nullable: true }),
|
Column({ type: resolveDbType("timestamptz"), nullable: true }),
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -5,12 +5,16 @@ import {
|
|||||||
Index,
|
Index,
|
||||||
JoinColumn,
|
JoinColumn,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
|
OneToOne,
|
||||||
} from "typeorm"
|
} from "typeorm"
|
||||||
|
|
||||||
import { BaseEntity } from "../interfaces/models/base-entity"
|
import { BaseEntity } from "../interfaces/models/base-entity"
|
||||||
import { DbAwareColumn } from "../utils/db-aware-column"
|
import { DbAwareColumn } from "../utils/db-aware-column"
|
||||||
import { Order } from "./order"
|
import { Order } from "./order"
|
||||||
import { generateEntityId } from "../utils/generate-entity-id"
|
import { generateEntityId } from "../utils/generate-entity-id"
|
||||||
|
import { Payment } from "./payment"
|
||||||
|
import { FeatureFlagDecorators } from "../utils/feature-flag-decorators"
|
||||||
|
import OrderEditingFeatureFlag from "../loaders/feature-flags/order-editing"
|
||||||
|
|
||||||
export enum RefundReason {
|
export enum RefundReason {
|
||||||
DISCOUNT = "discount",
|
DISCOUNT = "discount",
|
||||||
@@ -23,13 +27,25 @@ export enum RefundReason {
|
|||||||
@Entity()
|
@Entity()
|
||||||
export class Refund extends BaseEntity {
|
export class Refund extends BaseEntity {
|
||||||
@Index()
|
@Index()
|
||||||
@Column()
|
@Column({ nullable: true })
|
||||||
order_id: string
|
order_id: string
|
||||||
|
|
||||||
|
@FeatureFlagDecorators(OrderEditingFeatureFlag.key, [
|
||||||
|
Index(),
|
||||||
|
Column({ nullable: true }),
|
||||||
|
])
|
||||||
|
payment_id: string
|
||||||
|
|
||||||
@ManyToOne(() => Order, (order) => order.payments)
|
@ManyToOne(() => Order, (order) => order.payments)
|
||||||
@JoinColumn({ name: "order_id" })
|
@JoinColumn({ name: "order_id" })
|
||||||
order: Order
|
order: Order
|
||||||
|
|
||||||
|
@FeatureFlagDecorators(OrderEditingFeatureFlag.key, [
|
||||||
|
OneToOne(() => Payment, { nullable: true }),
|
||||||
|
JoinColumn({ name: "payment_id" }),
|
||||||
|
])
|
||||||
|
payment: Payment
|
||||||
|
|
||||||
@Column({ type: "int" })
|
@Column({ type: "int" })
|
||||||
amount: number
|
amount: number
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,72 @@
|
|||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
import { PaymentCollection } from "./../models/payment-collection"
|
import { PaymentCollection } from "./../models/payment-collection"
|
||||||
import { EntityRepository, Repository } from "typeorm"
|
import { EntityRepository, Repository } from "typeorm"
|
||||||
|
import { FindConfig } from "../types/common"
|
||||||
|
import { PaymentSession } from "../models"
|
||||||
|
|
||||||
@EntityRepository(PaymentCollection)
|
@EntityRepository(PaymentCollection)
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
export class PaymentCollectionRepository extends Repository<PaymentCollection> {}
|
export class PaymentCollectionRepository extends Repository<PaymentCollection> {
|
||||||
|
async getPaymentCollectionIdBySessionId(
|
||||||
|
sessionId: string,
|
||||||
|
config: FindConfig<PaymentCollection> = {}
|
||||||
|
): Promise<PaymentCollection> {
|
||||||
|
const paymentCollection = await this.find({
|
||||||
|
join: {
|
||||||
|
alias: "payment_col",
|
||||||
|
innerJoin: { payment_sessions: "payment_col.payment_sessions" },
|
||||||
|
},
|
||||||
|
where: (qb) => {
|
||||||
|
qb.where(
|
||||||
|
"payment_col_payment_sessions.payment_session_id = :sessionId",
|
||||||
|
{ sessionId }
|
||||||
|
)
|
||||||
|
},
|
||||||
|
relations: config.relations,
|
||||||
|
select: config.select,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!paymentCollection.length) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`Payment collection related to Payment Session id ${sessionId} was not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return paymentCollection[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPaymentCollectionIdByPaymentId(
|
||||||
|
paymentId: string,
|
||||||
|
config: FindConfig<PaymentCollection> = {}
|
||||||
|
): Promise<PaymentCollection> {
|
||||||
|
const paymentCollection = await this.find({
|
||||||
|
join: {
|
||||||
|
alias: "payment_col",
|
||||||
|
innerJoin: { payments: "payment_col.payments" },
|
||||||
|
},
|
||||||
|
where: (qb) => {
|
||||||
|
qb.where("payment_col_payments.payment_id = :paymentId", { paymentId })
|
||||||
|
},
|
||||||
|
relations: config.relations,
|
||||||
|
select: config.select,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!paymentCollection.length) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`Payment collection related to Payment id ${paymentId} was not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return paymentCollection[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteMultiple(ids: string[]): Promise<void> {
|
||||||
|
await this.createQueryBuilder()
|
||||||
|
.delete()
|
||||||
|
.from(PaymentSession)
|
||||||
|
.where("id IN (:...ids)", { ids })
|
||||||
|
.execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
export const DefaultProviderMock = {
|
export const DefaultProviderMock = {
|
||||||
getStatus: jest.fn().mockImplementation(data => {
|
getStatus: jest.fn().mockImplementation((data) => {
|
||||||
if (data.money_id === "success") {
|
if (data.money_id === "success") {
|
||||||
return Promise.resolve("authorized")
|
return Promise.resolve("authorized")
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,7 @@ export const DefaultProviderMock = {
|
|||||||
|
|
||||||
return Promise.resolve("initial")
|
return Promise.resolve("initial")
|
||||||
}),
|
}),
|
||||||
retrievePayment: jest.fn().mockImplementation(data => {
|
retrievePayment: jest.fn().mockImplementation((data) => {
|
||||||
return Promise.resolve(data)
|
return Promise.resolve(data)
|
||||||
}),
|
}),
|
||||||
list: jest.fn().mockImplementation(() => {
|
list: jest.fn().mockImplementation(() => {
|
||||||
@@ -19,15 +19,26 @@ export const DefaultProviderMock = {
|
|||||||
capturePayment: jest.fn().mockReturnValue(Promise.resolve()),
|
capturePayment: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
refundPayment: jest.fn().mockReturnValue(Promise.resolve()),
|
refundPayment: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
cancelPayment: jest.fn().mockReturnValue(Promise.resolve({})),
|
cancelPayment: jest.fn().mockReturnValue(Promise.resolve({})),
|
||||||
|
deletePayment: jest.fn().mockReturnValue(Promise.resolve({})),
|
||||||
|
authorizePayment: jest.fn().mockReturnValue(Promise.resolve({})),
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PaymentProviderServiceMock = {
|
export const PaymentProviderServiceMock = {
|
||||||
|
withTransaction: function () {
|
||||||
|
return this
|
||||||
|
},
|
||||||
updateSession: jest.fn().mockImplementation((session, cart) => {
|
updateSession: jest.fn().mockImplementation((session, cart) => {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
...session.data,
|
...session.data,
|
||||||
id: `${session.data.id}_updated`,
|
id: `${session.data.id}_updated`,
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
updateSessionNew: jest.fn().mockImplementation((session, sessionInput) => {
|
||||||
|
return Promise.resolve({
|
||||||
|
...session,
|
||||||
|
id: `${session.id}_updated`,
|
||||||
|
})
|
||||||
|
}),
|
||||||
list: jest.fn().mockImplementation(() => {
|
list: jest.fn().mockImplementation(() => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}),
|
}),
|
||||||
@@ -40,12 +51,31 @@ export const PaymentProviderServiceMock = {
|
|||||||
cartId: cart._id,
|
cartId: cart._id,
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
retrieveProvider: jest.fn().mockImplementation(providerId => {
|
createSessionNew: jest.fn().mockImplementation((sessionInput) => {
|
||||||
|
return Promise.resolve({
|
||||||
|
id: `${sessionInput.providerId}_session`,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
retrieveProvider: jest.fn().mockImplementation((providerId) => {
|
||||||
if (providerId === "default_provider") {
|
if (providerId === "default_provider") {
|
||||||
return DefaultProviderMock
|
return DefaultProviderMock
|
||||||
}
|
}
|
||||||
throw new Error("Provider Not Found")
|
throw new Error("Provider Not Found")
|
||||||
}),
|
}),
|
||||||
|
refreshSessionNew: jest.fn().mockImplementation((session, inputData) => {
|
||||||
|
DefaultProviderMock.deletePayment()
|
||||||
|
PaymentProviderServiceMock.createSessionNew(inputData)
|
||||||
|
return Promise.resolve({
|
||||||
|
...session,
|
||||||
|
id: `${session.id}_refreshed`,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
authorizePayment: jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(Promise.resolve({ status: "authorized" })),
|
||||||
|
createPaymentNew: jest.fn().mockImplementation((session, inputData) => {
|
||||||
|
Promise.resolve(inputData)
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
const mock = jest.fn().mockImplementation(() => {
|
const mock = jest.fn().mockImplementation(() => {
|
||||||
|
|||||||
@@ -1,7 +1,22 @@
|
|||||||
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
||||||
import { EventBusService, PaymentCollectionService } from "../index"
|
import {
|
||||||
import { PaymentCollectionStatus, PaymentCollectionType } from "../../models"
|
CustomerService,
|
||||||
|
EventBusService,
|
||||||
|
PaymentCollectionService,
|
||||||
|
PaymentProviderService,
|
||||||
|
} from "../index"
|
||||||
|
import {
|
||||||
|
PaymentCollectionStatus,
|
||||||
|
PaymentCollectionType,
|
||||||
|
PaymentCollection,
|
||||||
|
} from "../../models"
|
||||||
import { EventBusServiceMock } from "../__mocks__/event-bus"
|
import { EventBusServiceMock } from "../__mocks__/event-bus"
|
||||||
|
import {
|
||||||
|
DefaultProviderMock,
|
||||||
|
PaymentProviderServiceMock,
|
||||||
|
} from "../__mocks__/payment-provider"
|
||||||
|
import { CustomerServiceMock } from "../__mocks__/customer"
|
||||||
|
import { PaymentCollectionSessionInput } from "../../types/payment-collection"
|
||||||
|
|
||||||
describe("PaymentCollectionService", () => {
|
describe("PaymentCollectionService", () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -11,6 +26,23 @@ describe("PaymentCollectionService", () => {
|
|||||||
const paymentCollectionSample = {
|
const paymentCollectionSample = {
|
||||||
id: IdMap.getId("payment-collection-id1"),
|
id: IdMap.getId("payment-collection-id1"),
|
||||||
region_id: IdMap.getId("region1"),
|
region_id: IdMap.getId("region1"),
|
||||||
|
region: {
|
||||||
|
payment_providers: [
|
||||||
|
{
|
||||||
|
id: IdMap.getId("region1_provider1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: IdMap.getId("region1_provider2"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
payment_sessions: [
|
||||||
|
{
|
||||||
|
id: IdMap.getId("payCol_session1"),
|
||||||
|
provider_id: IdMap.getId("region1_provider1"),
|
||||||
|
amount: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
amount: 100,
|
amount: 100,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -19,6 +51,28 @@ describe("PaymentCollectionService", () => {
|
|||||||
status: PaymentCollectionStatus.NOT_PAID,
|
status: PaymentCollectionStatus.NOT_PAID,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const paymentCollectionWithSessions = {
|
||||||
|
id: IdMap.getId("payment-collection-session"),
|
||||||
|
region_id: IdMap.getId("region1"),
|
||||||
|
region: {
|
||||||
|
payment_providers: [
|
||||||
|
{
|
||||||
|
id: IdMap.getId("region1_provider1"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
payment_sessions: [
|
||||||
|
{
|
||||||
|
id: IdMap.getId("payCol_session1"),
|
||||||
|
provider_id: IdMap.getId("region1_provider1"),
|
||||||
|
amount: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
amount: 100,
|
||||||
|
created_at: new Date(),
|
||||||
|
status: PaymentCollectionStatus.NOT_PAID,
|
||||||
|
} as PaymentCollection
|
||||||
|
|
||||||
const paymentCollectionAuthorizedSample = {
|
const paymentCollectionAuthorizedSample = {
|
||||||
id: IdMap.getId("payment-collection-id2"),
|
id: IdMap.getId("payment-collection-id2"),
|
||||||
region_id: IdMap.getId("region1"),
|
region_id: IdMap.getId("region1"),
|
||||||
@@ -26,16 +80,123 @@ describe("PaymentCollectionService", () => {
|
|||||||
status: PaymentCollectionStatus.AUTHORIZED,
|
status: PaymentCollectionStatus.AUTHORIZED,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const zeroSample = {
|
||||||
|
id: IdMap.getId("payment-collection-zero"),
|
||||||
|
region_id: IdMap.getId("region1"),
|
||||||
|
amount: 0,
|
||||||
|
status: PaymentCollectionStatus.NOT_PAID,
|
||||||
|
}
|
||||||
|
|
||||||
|
const noSessionSample = {
|
||||||
|
id: IdMap.getId("payment-collection-zero"),
|
||||||
|
region_id: IdMap.getId("region1"),
|
||||||
|
amount: 10000,
|
||||||
|
status: PaymentCollectionStatus.NOT_PAID,
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullyAuthorizedSample = {
|
||||||
|
id: IdMap.getId("payment-collection-fully"),
|
||||||
|
region_id: IdMap.getId("region1"),
|
||||||
|
amount: 35000,
|
||||||
|
authorized_amount: 35000,
|
||||||
|
region: {
|
||||||
|
payment_providers: [
|
||||||
|
{
|
||||||
|
id: IdMap.getId("region1_provider1"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
payment_sessions: [
|
||||||
|
{
|
||||||
|
id: IdMap.getId("payCol_session1"),
|
||||||
|
payment_authorized_at: Date.now(),
|
||||||
|
provider_id: IdMap.getId("region1_provider1"),
|
||||||
|
amount: 35000,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
payments: [
|
||||||
|
{
|
||||||
|
id: IdMap.getId("payment-123"),
|
||||||
|
amount: 35000,
|
||||||
|
captured_amount: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
status: PaymentCollectionStatus.AUTHORIZED,
|
||||||
|
} as unknown as PaymentCollection
|
||||||
|
|
||||||
|
const partiallyAuthorizedSample = {
|
||||||
|
id: IdMap.getId("payment-collection-partial"),
|
||||||
|
region_id: IdMap.getId("region1"),
|
||||||
|
amount: 70000,
|
||||||
|
authorized_amount: 35000,
|
||||||
|
region: {
|
||||||
|
payment_providers: [
|
||||||
|
{
|
||||||
|
id: IdMap.getId("region1_provider1"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
payment_sessions: [
|
||||||
|
{
|
||||||
|
id: IdMap.getId("payCol_session1"),
|
||||||
|
provider_id: IdMap.getId("region1_provider1"),
|
||||||
|
amount: 35000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: IdMap.getId("payCol_session2"),
|
||||||
|
payment_authorized_at: Date.now(),
|
||||||
|
provider_id: IdMap.getId("region1_provider1"),
|
||||||
|
amount: 35000,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
payments: [],
|
||||||
|
status: PaymentCollectionStatus.PARTIALLY_AUTHORIZED,
|
||||||
|
}
|
||||||
|
|
||||||
|
const notAuthorizedSample = {
|
||||||
|
id: IdMap.getId("payment-collection-not-authorized"),
|
||||||
|
region_id: IdMap.getId("region1"),
|
||||||
|
amount: 70000,
|
||||||
|
region: {
|
||||||
|
payment_providers: [
|
||||||
|
{
|
||||||
|
id: IdMap.getId("region1_provider1"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
payment_sessions: [
|
||||||
|
{
|
||||||
|
id: IdMap.getId("payCol_session1"),
|
||||||
|
provider_id: IdMap.getId("region1_provider1"),
|
||||||
|
amount: 35000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: IdMap.getId("payCol_session2"),
|
||||||
|
provider_id: IdMap.getId("region1_provider1"),
|
||||||
|
amount: 35000,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
payments: [],
|
||||||
|
status: PaymentCollectionStatus.PARTIALLY_AUTHORIZED,
|
||||||
|
} as unknown as PaymentCollection
|
||||||
|
|
||||||
const paymentCollectionRepository = MockRepository({
|
const paymentCollectionRepository = MockRepository({
|
||||||
findOne: (query) => {
|
find: (query) => {
|
||||||
const map = {
|
const map = {
|
||||||
[IdMap.getId("payment-collection-id1")]: paymentCollectionSample,
|
[IdMap.getId("payment-collection-id1")]: paymentCollectionSample,
|
||||||
[IdMap.getId("payment-collection-id2")]:
|
[IdMap.getId("payment-collection-id2")]:
|
||||||
paymentCollectionAuthorizedSample,
|
paymentCollectionAuthorizedSample,
|
||||||
|
[IdMap.getId("payment-collection-session")]:
|
||||||
|
paymentCollectionWithSessions,
|
||||||
|
[IdMap.getId("payment-collection-zero")]: zeroSample,
|
||||||
|
[IdMap.getId("payment-collection-no-session")]: noSessionSample,
|
||||||
|
[IdMap.getId("payment-collection-fully")]: fullyAuthorizedSample,
|
||||||
|
[IdMap.getId("payment-collection-partial")]: partiallyAuthorizedSample,
|
||||||
|
[IdMap.getId("payment-collection-not-authorized")]: notAuthorizedSample,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (map[query?.where?.id]) {
|
if (map[query?.where?.id]) {
|
||||||
return { ...map[query?.where?.id] }
|
return [{ ...map[query?.where?.id] }]
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
},
|
},
|
||||||
@@ -45,20 +206,38 @@ describe("PaymentCollectionService", () => {
|
|||||||
...data,
|
...data,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
save: (data) => {
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
paymentCollectionRepository.deleteMultiple = jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(() => {
|
||||||
|
return Promise.resolve()
|
||||||
|
})
|
||||||
|
|
||||||
|
paymentCollectionRepository.getPaymentCollectionIdBySessionId = jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(async () => {
|
||||||
|
return paymentCollectionWithSessions
|
||||||
})
|
})
|
||||||
|
|
||||||
const paymentCollectionService = new PaymentCollectionService({
|
const paymentCollectionService = new PaymentCollectionService({
|
||||||
manager: MockManager,
|
manager: MockManager,
|
||||||
paymentCollectionRepository,
|
paymentCollectionRepository,
|
||||||
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
eventBusService: EventBusServiceMock as unknown as EventBusService,
|
||||||
|
paymentProviderService:
|
||||||
|
PaymentProviderServiceMock as unknown as PaymentProviderService,
|
||||||
|
customerService: CustomerServiceMock as unknown as CustomerService,
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should retrieve a payment collection", async () => {
|
it("should retrieve a payment collection", async () => {
|
||||||
await paymentCollectionService.retrieve(
|
await paymentCollectionService.retrieve(
|
||||||
IdMap.getId("payment-collection-id1")
|
IdMap.getId("payment-collection-id1")
|
||||||
)
|
)
|
||||||
expect(paymentCollectionRepository.findOne).toHaveBeenCalledTimes(1)
|
expect(paymentCollectionRepository.find).toHaveBeenCalledTimes(1)
|
||||||
expect(paymentCollectionRepository.findOne).toHaveBeenCalledWith({
|
expect(paymentCollectionRepository.find).toHaveBeenCalledWith({
|
||||||
where: { id: IdMap.getId("payment-collection-id1") },
|
where: { id: IdMap.getId("payment-collection-id1") },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -68,8 +247,8 @@ describe("PaymentCollectionService", () => {
|
|||||||
IdMap.getId("payment-collection-non-existing-id")
|
IdMap.getId("payment-collection-non-existing-id")
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(paymentCollectionRepository.findOne).toHaveBeenCalledTimes(1)
|
|
||||||
expect(payCol).rejects.toThrow(Error)
|
expect(payCol).rejects.toThrow(Error)
|
||||||
|
expect(paymentCollectionRepository.find).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should create a payment collection", async () => {
|
it("should create a payment collection", async () => {
|
||||||
@@ -155,9 +334,10 @@ describe("PaymentCollectionService", () => {
|
|||||||
IdMap.getId("payment-collection-non-existing"),
|
IdMap.getId("payment-collection-non-existing"),
|
||||||
submittedChanges
|
submittedChanges
|
||||||
)
|
)
|
||||||
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(0)
|
|
||||||
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(0)
|
|
||||||
expect(payCol).rejects.toThrow(Error)
|
expect(payCol).rejects.toThrow(Error)
|
||||||
|
expect(paymentCollectionRepository.save).toBeCalledTimes(0)
|
||||||
|
expect(EventBusServiceMock.emit).toBeCalledTimes(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should delete a payment collection", async () => {
|
it("should delete a payment collection", async () => {
|
||||||
@@ -197,4 +377,312 @@ describe("PaymentCollectionService", () => {
|
|||||||
|
|
||||||
expect(entity).rejects.toThrow(Error)
|
expect(entity).rejects.toThrow(Error)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("Manage Payment Sessions", () => {
|
||||||
|
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(
|
||||||
|
IdMap.getId("payment-collection-id2"),
|
||||||
|
inp
|
||||||
|
)
|
||||||
|
|
||||||
|
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: PaymentCollectionSessionInput = {
|
||||||
|
amount: 101,
|
||||||
|
provider_id: IdMap.getId("region1_provider1"),
|
||||||
|
customer_id: "customer1",
|
||||||
|
}
|
||||||
|
const ret = paymentCollectionService.setPaymentSessions(
|
||||||
|
IdMap.getId("payment-collection-id1"),
|
||||||
|
inp
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes(
|
||||||
|
0
|
||||||
|
)
|
||||||
|
expect(ret).rejects.toThrow(
|
||||||
|
`The sum of sessions is not equal to 100 on Payment Collection`
|
||||||
|
)
|
||||||
|
|
||||||
|
const multInp: PaymentCollectionSessionInput[] = [
|
||||||
|
{
|
||||||
|
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(
|
||||||
|
IdMap.getId("payment-collection-id1"),
|
||||||
|
multInp
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes(
|
||||||
|
0
|
||||||
|
)
|
||||||
|
expect(multiRet).rejects.toThrow(
|
||||||
|
`The sum of sessions is not equal to 100 on Payment Collection`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should ignore sessions where provider doesn't belong to the region", async () => {
|
||||||
|
const multInp: PaymentCollectionSessionInput[] = [
|
||||||
|
{
|
||||||
|
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(
|
||||||
|
IdMap.getId("payment-collection-id1"),
|
||||||
|
multInp
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(multiRet).rejects.toThrow(
|
||||||
|
`The sum of sessions is not equal to 100 on Payment Collection`
|
||||||
|
)
|
||||||
|
expect(PaymentProviderServiceMock.createSessionNew).toBeCalledTimes(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should add a new session and update existing one", async () => {
|
||||||
|
const inp: PaymentCollectionSessionInput[] = [
|
||||||
|
{
|
||||||
|
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(
|
||||||
|
IdMap.getId("payment-collection-session"),
|
||||||
|
inp
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes(
|
||||||
|
1
|
||||||
|
)
|
||||||
|
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: PaymentCollectionSessionInput[] = [
|
||||||
|
{
|
||||||
|
amount: 100,
|
||||||
|
provider_id: IdMap.getId("region1_provider1"),
|
||||||
|
customer_id: IdMap.getId("lebron"),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
await paymentCollectionService.setPaymentSessions(
|
||||||
|
IdMap.getId("payment-collection-session"),
|
||||||
|
inp
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes(
|
||||||
|
1
|
||||||
|
)
|
||||||
|
expect(PaymentProviderServiceMock.updateSessionNew).toHaveBeenCalledTimes(
|
||||||
|
0
|
||||||
|
)
|
||||||
|
expect(paymentCollectionRepository.deleteMultiple).toHaveBeenCalledTimes(
|
||||||
|
1
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(paymentCollectionRepository.save).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should refresh a payment session", async () => {
|
||||||
|
await paymentCollectionService.refreshPaymentSession(
|
||||||
|
IdMap.getId("payment-collection-session"),
|
||||||
|
IdMap.getId("payCol_session1"),
|
||||||
|
{
|
||||||
|
customer_id: "customer1",
|
||||||
|
amount: 100,
|
||||||
|
provider_id: IdMap.getId("region1_provider1"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
PaymentProviderServiceMock.refreshSessionNew
|
||||||
|
).toHaveBeenCalledTimes(1)
|
||||||
|
expect(DefaultProviderMock.deletePayment).toHaveBeenCalledTimes(1)
|
||||||
|
expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes(
|
||||||
|
1
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should fail to refresh a payment session if the amount is different", async () => {
|
||||||
|
const sess = paymentCollectionService.refreshPaymentSession(
|
||||||
|
IdMap.getId("payment-collection-session"),
|
||||||
|
IdMap.getId("payCol_session1"),
|
||||||
|
{
|
||||||
|
customer_id: "customer1",
|
||||||
|
amount: 80,
|
||||||
|
provider_id: IdMap.getId("region1_provider1"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(sess).rejects.toThrow(
|
||||||
|
"The amount has to be the same as the existing payment session"
|
||||||
|
)
|
||||||
|
expect(PaymentProviderServiceMock.refreshSessionNew).toBeCalledTimes(0)
|
||||||
|
expect(DefaultProviderMock.deletePayment).toBeCalledTimes(0)
|
||||||
|
expect(PaymentProviderServiceMock.createSessionNew).toBeCalledTimes(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Authorize Payments", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should mark as paid if amount is 0", async () => {
|
||||||
|
await paymentCollectionService.authorize(
|
||||||
|
IdMap.getId("payment-collection-zero")
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes(
|
||||||
|
0
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should reject payment collection without payment sessions", async () => {
|
||||||
|
const ret = paymentCollectionService.authorize(
|
||||||
|
IdMap.getId("payment-collection-no-session")
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(ret).rejects.toThrowError(
|
||||||
|
new Error(
|
||||||
|
"You cannot complete a Payment Collection without a payment session."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should call authorizePayments for all sessions", async () => {
|
||||||
|
await paymentCollectionService.authorize(
|
||||||
|
IdMap.getId("payment-collection-not-authorized")
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes(
|
||||||
|
2
|
||||||
|
)
|
||||||
|
expect(PaymentProviderServiceMock.createPaymentNew).toHaveBeenCalledTimes(
|
||||||
|
2
|
||||||
|
)
|
||||||
|
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should skip authorized sessions - partially authorized", async () => {
|
||||||
|
await paymentCollectionService.authorize(
|
||||||
|
IdMap.getId("payment-collection-partial")
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes(
|
||||||
|
1
|
||||||
|
)
|
||||||
|
expect(PaymentProviderServiceMock.createPaymentNew).toHaveBeenCalledTimes(
|
||||||
|
1
|
||||||
|
)
|
||||||
|
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should skip authorized sessions - fully authorized", async () => {
|
||||||
|
await paymentCollectionService.authorize(
|
||||||
|
IdMap.getId("payment-collection-fully")
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes(
|
||||||
|
0
|
||||||
|
)
|
||||||
|
expect(PaymentProviderServiceMock.createPaymentNew).toHaveBeenCalledTimes(
|
||||||
|
0
|
||||||
|
)
|
||||||
|
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Capture Payments", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw error if the status is not authorized", async () => {
|
||||||
|
paymentCollectionRepository.getPaymentCollectionIdByPaymentId = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(Promise.resolve(notAuthorizedSample))
|
||||||
|
|
||||||
|
PaymentProviderServiceMock.capturePayment = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(Promise.resolve())
|
||||||
|
|
||||||
|
const ret = paymentCollectionService.capture(
|
||||||
|
IdMap.getId("payment-collection-not-authorized")
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(ret).rejects.toThrowError(
|
||||||
|
new Error(
|
||||||
|
`A Payment Collection with status ${PaymentCollectionStatus.PARTIALLY_AUTHORIZED} cannot capture payment`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(PaymentProviderServiceMock.capturePayment).toBeCalledTimes(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should emit PAYMENT_CAPTURE_FAILED if payment capture has failed", async () => {
|
||||||
|
paymentCollectionRepository.getPaymentCollectionIdByPaymentId = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(Promise.resolve(fullyAuthorizedSample))
|
||||||
|
|
||||||
|
PaymentProviderServiceMock.retrievePayment = jest.fn().mockReturnValue(
|
||||||
|
Promise.resolve({
|
||||||
|
id: IdMap.getId("payment-123"),
|
||||||
|
amount: 35000,
|
||||||
|
captured_amount: 0,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
PaymentProviderServiceMock.capturePayment = jest
|
||||||
|
.fn()
|
||||||
|
.mockRejectedValue("capture failed")
|
||||||
|
|
||||||
|
const ret = paymentCollectionService.capture(IdMap.getId("payment-123"))
|
||||||
|
|
||||||
|
expect(ret).rejects.toThrowError(
|
||||||
|
new Error(`Failed to capture Payment ${IdMap.getId("payment-123")}`)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
@@ -1,19 +1,37 @@
|
|||||||
import { DeepPartial, EntityManager, IsNull } from "typeorm"
|
import { DeepPartial, EntityManager, Equal } from "typeorm"
|
||||||
import { MedusaError } from "medusa-core-utils"
|
import { MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
import { FindConfig } from "../types/common"
|
import { FindConfig } from "../types/common"
|
||||||
import { buildQuery, isDefined, setMetadata } from "../utils"
|
import { buildQuery, isDefined, setMetadata } from "../utils"
|
||||||
import { PaymentCollectionRepository } from "../repositories/payment-collection"
|
import { PaymentCollectionRepository } from "../repositories/payment-collection"
|
||||||
import { PaymentCollection, PaymentCollectionStatus } from "../models"
|
import {
|
||||||
|
Customer,
|
||||||
|
Payment,
|
||||||
|
PaymentCollection,
|
||||||
|
PaymentCollectionStatus,
|
||||||
|
PaymentSession,
|
||||||
|
PaymentSessionStatus,
|
||||||
|
Refund,
|
||||||
|
} from "../models"
|
||||||
import { TransactionBaseService } from "../interfaces"
|
import { TransactionBaseService } from "../interfaces"
|
||||||
import { EventBusService } from "./index"
|
import {
|
||||||
|
CustomerService,
|
||||||
|
EventBusService,
|
||||||
|
PaymentProviderService,
|
||||||
|
} from "./index"
|
||||||
|
|
||||||
import { CreatePaymentCollectionInput } from "../types/payment-collection"
|
import {
|
||||||
|
CreatePaymentCollectionInput,
|
||||||
|
PaymentCollectionSessionInput,
|
||||||
|
PaymentProviderDataInput,
|
||||||
|
} from "../types/payment-collection"
|
||||||
|
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
manager: EntityManager
|
manager: EntityManager
|
||||||
paymentCollectionRepository: typeof PaymentCollectionRepository
|
paymentCollectionRepository: typeof PaymentCollectionRepository
|
||||||
|
paymentProviderService: PaymentProviderService
|
||||||
eventBusService: EventBusService
|
eventBusService: EventBusService
|
||||||
|
customerService: CustomerService
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class PaymentCollectionService extends TransactionBaseService {
|
export default class PaymentCollectionService extends TransactionBaseService {
|
||||||
@@ -21,17 +39,26 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
|||||||
CREATED: "payment-collection.created",
|
CREATED: "payment-collection.created",
|
||||||
UPDATED: "payment-collection.updated",
|
UPDATED: "payment-collection.updated",
|
||||||
DELETED: "payment-collection.deleted",
|
DELETED: "payment-collection.deleted",
|
||||||
|
PAYMENT_AUTHORIZED: "payment-collection.payment_authorized",
|
||||||
|
PAYMENT_CAPTURED: "payment-collection.payment_captured",
|
||||||
|
PAYMENT_CAPTURE_FAILED: "payment-collection.payment_capture_failed",
|
||||||
|
REFUND_CREATED: "payment-collection.payment_refund_created",
|
||||||
|
REFUND_FAILED: "payment-collection.payment_refund_failed",
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readonly manager_: EntityManager
|
protected readonly manager_: EntityManager
|
||||||
protected transactionManager_: EntityManager | undefined
|
protected transactionManager_: EntityManager | undefined
|
||||||
protected readonly eventBusService_: EventBusService
|
protected readonly eventBusService_: EventBusService
|
||||||
|
protected readonly paymentProviderService_: PaymentProviderService
|
||||||
|
protected readonly customerService_: CustomerService
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
protected readonly paymentCollectionRepository_: typeof PaymentCollectionRepository
|
protected readonly paymentCollectionRepository_: typeof PaymentCollectionRepository
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
manager,
|
manager,
|
||||||
paymentCollectionRepository,
|
paymentCollectionRepository,
|
||||||
|
paymentProviderService,
|
||||||
|
customerService,
|
||||||
eventBusService,
|
eventBusService,
|
||||||
}: InjectedDependencies) {
|
}: InjectedDependencies) {
|
||||||
// eslint-disable-next-line prefer-rest-params
|
// eslint-disable-next-line prefer-rest-params
|
||||||
@@ -39,7 +66,9 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
|||||||
|
|
||||||
this.manager_ = manager
|
this.manager_ = manager
|
||||||
this.paymentCollectionRepository_ = paymentCollectionRepository
|
this.paymentCollectionRepository_ = paymentCollectionRepository
|
||||||
|
this.paymentProviderService_ = paymentProviderService
|
||||||
this.eventBusService_ = eventBusService
|
this.eventBusService_ = eventBusService
|
||||||
|
this.customerService_ = customerService
|
||||||
}
|
}
|
||||||
|
|
||||||
async retrieve(
|
async retrieve(
|
||||||
@@ -52,22 +81,22 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const query = buildQuery({ id: paymentCollectionId }, config)
|
const query = buildQuery({ id: paymentCollectionId }, config)
|
||||||
const paymentCollection = await paymentCollectionRepository.findOne(query)
|
|
||||||
|
|
||||||
if (!paymentCollection) {
|
const paymentCollection = await paymentCollectionRepository.find(query)
|
||||||
|
|
||||||
|
if (!paymentCollection.length) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.NOT_FOUND,
|
MedusaError.Types.NOT_FOUND,
|
||||||
`Payment collection with id ${paymentCollectionId} was not found`
|
`Payment collection with id ${paymentCollectionId} was not found`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return paymentCollection
|
return paymentCollection[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(data: CreatePaymentCollectionInput): Promise<PaymentCollection> {
|
async create(data: CreatePaymentCollectionInput): Promise<PaymentCollection> {
|
||||||
return await this.atomicPhase_(async (transactionManager) => {
|
return await this.atomicPhase_(async (manager) => {
|
||||||
const paymentCollectionRepository =
|
const paymentCollectionRepository = manager.getCustomRepository(
|
||||||
transactionManager.getCustomRepository(
|
|
||||||
this.paymentCollectionRepository_
|
this.paymentCollectionRepository_
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -87,7 +116,7 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
|||||||
)
|
)
|
||||||
|
|
||||||
await this.eventBusService_
|
await this.eventBusService_
|
||||||
.withTransaction(transactionManager)
|
.withTransaction(manager)
|
||||||
.emit(PaymentCollectionService.Events.CREATED, paymentCollection)
|
.emit(PaymentCollectionService.Events.CREATED, paymentCollection)
|
||||||
|
|
||||||
return paymentCollection
|
return paymentCollection
|
||||||
@@ -160,4 +189,493 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
|||||||
return paymentCollection
|
return paymentCollection
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isValidTotalAmount(
|
||||||
|
total: number,
|
||||||
|
sessionsInput: PaymentCollectionSessionInput[]
|
||||||
|
): boolean {
|
||||||
|
const sum = sessionsInput.reduce((cur, sess) => cur + sess.amount, 0)
|
||||||
|
return total === sum
|
||||||
|
}
|
||||||
|
|
||||||
|
async setPaymentSessions(
|
||||||
|
paymentCollectionId: string,
|
||||||
|
sessions: PaymentCollectionSessionInput[] | PaymentCollectionSessionInput
|
||||||
|
): Promise<PaymentCollection> {
|
||||||
|
let sessionsInput = Array.isArray(sessions) ? sessions : [sessions]
|
||||||
|
|
||||||
|
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}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionsInput = sessionsInput.filter((session) => {
|
||||||
|
return !!payCol.region.payment_providers.find(({ id }) => {
|
||||||
|
return id === session.provider_id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!this.isValidTotalAmount(payCol.amount, sessionsInput)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.UNEXPECTED_STATE,
|
||||||
|
`The sum of sessions is not equal to ${payCol.amount} on Payment Collection`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let customer: Customer | undefined = undefined
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
const inputData: PaymentProviderDataInput = {
|
||||||
|
resource_id: payCol.id,
|
||||||
|
currency_code: payCol.currency_code,
|
||||||
|
amount: session.amount,
|
||||||
|
provider_id: session.provider_id,
|
||||||
|
customer,
|
||||||
|
metadata: {
|
||||||
|
resource_id: payCol.id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingSession) {
|
||||||
|
const paymentSession = await this.paymentProviderService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.updateSessionNew(existingSession, inputData)
|
||||||
|
|
||||||
|
selectedSessionIds.push(existingSession.id)
|
||||||
|
paymentSessions.push(paymentSession)
|
||||||
|
} else {
|
||||||
|
const paymentSession = await this.paymentProviderService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.createSessionNew(inputData)
|
||||||
|
|
||||||
|
selectedSessionIds.push(paymentSession.id)
|
||||||
|
paymentSessions.push(paymentSession)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payCol.payment_sessions?.length) {
|
||||||
|
const removeIds: string[] = payCol.payment_sessions
|
||||||
|
.map((sess) => sess.id)
|
||||||
|
.filter((id) => !selectedSessionIds.includes(id))
|
||||||
|
|
||||||
|
if (removeIds.length) {
|
||||||
|
await paymentCollectionRepository.deleteMultiple(removeIds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
payCol.payment_sessions = paymentSessions
|
||||||
|
|
||||||
|
return await paymentCollectionRepository.save(payCol)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async refreshPaymentSession(
|
||||||
|
paymentCollectionId: string,
|
||||||
|
sessionId: string,
|
||||||
|
sessionInput: PaymentCollectionSessionInput
|
||||||
|
): Promise<PaymentSession> {
|
||||||
|
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
|
const paymentCollectionRepository = manager.getCustomRepository(
|
||||||
|
this.paymentCollectionRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
const payCol =
|
||||||
|
await paymentCollectionRepository.getPaymentCollectionIdBySessionId(
|
||||||
|
sessionId,
|
||||||
|
{
|
||||||
|
relations: [
|
||||||
|
"region",
|
||||||
|
"region.payment_providers",
|
||||||
|
"payment_sessions",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (paymentCollectionId !== payCol.id) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
`Payment Session ${sessionId} does not belong to Payment Collection ${paymentCollectionId}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = payCol.payment_sessions.find(
|
||||||
|
(sess) => sessionId === sess?.id
|
||||||
|
)
|
||||||
|
|
||||||
|
if (session?.amount !== sessionInput.amount) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"The amount has to be the same as the existing payment session"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const customer = await this.customerService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.retrieve(sessionInput.customer_id, {
|
||||||
|
select: ["id", "email", "metadata"],
|
||||||
|
})
|
||||||
|
|
||||||
|
const inputData: PaymentProviderDataInput = {
|
||||||
|
resource_id: payCol.id,
|
||||||
|
currency_code: payCol.currency_code,
|
||||||
|
amount: session.amount,
|
||||||
|
provider_id: session.provider_id,
|
||||||
|
customer,
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionRefreshed = await this.paymentProviderService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.refreshSessionNew(session, inputData)
|
||||||
|
|
||||||
|
payCol.payment_sessions = payCol.payment_sessions.map((sess) => {
|
||||||
|
if (sess.id === sessionId) {
|
||||||
|
return sessionRefreshed
|
||||||
|
}
|
||||||
|
return sess
|
||||||
|
})
|
||||||
|
|
||||||
|
if (session.payment_authorized_at) {
|
||||||
|
payCol.authorized_amount -= session.amount
|
||||||
|
}
|
||||||
|
|
||||||
|
await paymentCollectionRepository.save(payCol)
|
||||||
|
|
||||||
|
return sessionRefreshed
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async authorize(
|
||||||
|
paymentCollectionId: string,
|
||||||
|
context: Record<string, unknown> = {}
|
||||||
|
): Promise<PaymentCollection> {
|
||||||
|
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
|
const paymentCollectionRepository = manager.getCustomRepository(
|
||||||
|
this.paymentCollectionRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
const payCol = await this.retrieve(paymentCollectionId, {
|
||||||
|
relations: ["payment_sessions", "payments"],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (payCol.authorized_amount === payCol.amount) {
|
||||||
|
return payCol
|
||||||
|
}
|
||||||
|
|
||||||
|
// If cart total is 0, we don't perform anything payment related
|
||||||
|
if (payCol.amount <= 0) {
|
||||||
|
payCol.authorized_amount = 0
|
||||||
|
return await paymentCollectionRepository.save(payCol)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!payCol.payment_sessions?.length) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"You cannot complete a Payment Collection without a payment session."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let authorizedAmount = 0
|
||||||
|
for (const session of payCol.payment_sessions) {
|
||||||
|
if (session.payment_authorized_at) {
|
||||||
|
authorizedAmount += session.amount
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const auth = await this.paymentProviderService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.authorizePayment(session, context)
|
||||||
|
|
||||||
|
if (auth?.status === PaymentSessionStatus.AUTHORIZED) {
|
||||||
|
authorizedAmount += session.amount
|
||||||
|
|
||||||
|
const inputData: Omit<PaymentProviderDataInput, "customer"> = {
|
||||||
|
amount: session.amount,
|
||||||
|
currency_code: payCol.currency_code,
|
||||||
|
provider_id: session.provider_id,
|
||||||
|
resource_id: payCol.id,
|
||||||
|
}
|
||||||
|
|
||||||
|
payCol.payments.push(
|
||||||
|
await this.paymentProviderService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.createPaymentNew(inputData)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authorizedAmount === 0) {
|
||||||
|
payCol.status = PaymentCollectionStatus.AWAITING
|
||||||
|
} else if (authorizedAmount < payCol.amount) {
|
||||||
|
payCol.status = PaymentCollectionStatus.PARTIALLY_AUTHORIZED
|
||||||
|
} else if (authorizedAmount === payCol.amount) {
|
||||||
|
payCol.status = PaymentCollectionStatus.AUTHORIZED
|
||||||
|
}
|
||||||
|
|
||||||
|
payCol.authorized_amount = authorizedAmount
|
||||||
|
const payColCopy = await paymentCollectionRepository.save(payCol)
|
||||||
|
|
||||||
|
await this.eventBusService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.emit(PaymentCollectionService.Events.PAYMENT_AUTHORIZED, payColCopy)
|
||||||
|
|
||||||
|
return payCol
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private async capturePayment(
|
||||||
|
payCol: PaymentCollection,
|
||||||
|
payment: Payment
|
||||||
|
): Promise<Payment> {
|
||||||
|
if (payment?.captured_at) {
|
||||||
|
return payment
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
|
const allowedStatuses = [
|
||||||
|
PaymentCollectionStatus.AUTHORIZED,
|
||||||
|
PaymentCollectionStatus.PARTIALLY_CAPTURED,
|
||||||
|
PaymentCollectionStatus.REQUIRES_ACTION,
|
||||||
|
]
|
||||||
|
|
||||||
|
if (!allowedStatuses.includes(payCol.status)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
`A Payment Collection with status ${payCol.status} cannot capture payment`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let captureError: Error | null = null
|
||||||
|
const capturedPayment = await this.paymentProviderService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.capturePayment(payment)
|
||||||
|
.catch((err) => {
|
||||||
|
captureError = err
|
||||||
|
})
|
||||||
|
|
||||||
|
payCol.captured_amount = payCol.captured_amount ?? 0
|
||||||
|
if (capturedPayment) {
|
||||||
|
payCol.captured_amount += payment.amount
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payCol.captured_amount === 0) {
|
||||||
|
payCol.status = PaymentCollectionStatus.REQUIRES_ACTION
|
||||||
|
} else if (payCol.captured_amount === payCol.amount) {
|
||||||
|
payCol.status = PaymentCollectionStatus.CAPTURED
|
||||||
|
} else {
|
||||||
|
payCol.status = PaymentCollectionStatus.PARTIALLY_CAPTURED
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentCollectionRepository = manager.getCustomRepository(
|
||||||
|
this.paymentCollectionRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
await paymentCollectionRepository.save(payCol)
|
||||||
|
|
||||||
|
if (!capturedPayment) {
|
||||||
|
await this.eventBusService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.emit(PaymentCollectionService.Events.PAYMENT_CAPTURE_FAILED, {
|
||||||
|
...payment,
|
||||||
|
error: captureError,
|
||||||
|
})
|
||||||
|
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.UNEXPECTED_STATE,
|
||||||
|
`Failed to capture Payment ${payment.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.eventBusService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.emit(PaymentCollectionService.Events.PAYMENT_CAPTURED, capturedPayment)
|
||||||
|
|
||||||
|
return capturedPayment
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async capture(paymentId: string): Promise<Payment> {
|
||||||
|
const manager = this.transactionManager_ ?? this.manager_
|
||||||
|
const paymentCollectionRepository = manager.getCustomRepository(
|
||||||
|
this.paymentCollectionRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
const payCol =
|
||||||
|
await paymentCollectionRepository.getPaymentCollectionIdByPaymentId(
|
||||||
|
paymentId,
|
||||||
|
{
|
||||||
|
relations: ["payments"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const payment = payCol.payments.find((payment) => paymentId === payment?.id)
|
||||||
|
|
||||||
|
return await this.capturePayment(payCol, payment!)
|
||||||
|
}
|
||||||
|
|
||||||
|
async captureAll(paymentCollectionId: string): Promise<Payment[]> {
|
||||||
|
const payCol = await this.retrieve(paymentCollectionId, {
|
||||||
|
relations: ["payments"],
|
||||||
|
})
|
||||||
|
|
||||||
|
const allPayments: Payment[] = []
|
||||||
|
for (const payment of payCol.payments) {
|
||||||
|
const captured = await this.capturePayment(payCol, payment).catch(
|
||||||
|
() => void 0
|
||||||
|
)
|
||||||
|
|
||||||
|
if (captured) {
|
||||||
|
allPayments.push(captured)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allPayments
|
||||||
|
}
|
||||||
|
|
||||||
|
private async refundPayment(
|
||||||
|
payCol: PaymentCollection,
|
||||||
|
payment: Payment,
|
||||||
|
amount: number,
|
||||||
|
reason: string,
|
||||||
|
note?: string
|
||||||
|
): Promise<Refund> {
|
||||||
|
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
|
if (!payment.captured_at) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
`Payment ${payment.id} is not captured`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const refundable = payment.amount - payment.amount_refunded
|
||||||
|
if (amount > refundable) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
`Only ${refundable} can be refunded from Payment ${payment.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let refundError: Error | null = null
|
||||||
|
const refund = await this.paymentProviderService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.refundFromPayment(payment, amount, reason, note)
|
||||||
|
.catch((err) => {
|
||||||
|
refundError = err
|
||||||
|
})
|
||||||
|
|
||||||
|
payCol.refunded_amount = payCol.refunded_amount ?? 0
|
||||||
|
if (refund) {
|
||||||
|
payCol.refunded_amount += refund.amount
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payCol.refunded_amount === 0) {
|
||||||
|
payCol.status = PaymentCollectionStatus.REQUIRES_ACTION
|
||||||
|
} else if (payCol.refunded_amount === payCol.amount) {
|
||||||
|
payCol.status = PaymentCollectionStatus.REFUNDED
|
||||||
|
} else {
|
||||||
|
payCol.status = PaymentCollectionStatus.PARTIALLY_REFUNDED
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentCollectionRepository = manager.getCustomRepository(
|
||||||
|
this.paymentCollectionRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
await paymentCollectionRepository.save(payCol)
|
||||||
|
|
||||||
|
if (!refund) {
|
||||||
|
await this.eventBusService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.emit(PaymentCollectionService.Events.REFUND_FAILED, {
|
||||||
|
...payment,
|
||||||
|
error: refundError,
|
||||||
|
})
|
||||||
|
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.UNEXPECTED_STATE,
|
||||||
|
`Failed to refund Payment ${payment.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.eventBusService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.emit(PaymentCollectionService.Events.REFUND_CREATED, refund)
|
||||||
|
|
||||||
|
return refund
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async refund(
|
||||||
|
paymentId: string,
|
||||||
|
amount: number,
|
||||||
|
reason: string,
|
||||||
|
note?: string
|
||||||
|
): Promise<Refund> {
|
||||||
|
const manager = this.transactionManager_ ?? this.manager_
|
||||||
|
const paymentCollectionRepository = manager.getCustomRepository(
|
||||||
|
this.paymentCollectionRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
const payCol =
|
||||||
|
await paymentCollectionRepository.getPaymentCollectionIdByPaymentId(
|
||||||
|
paymentId
|
||||||
|
)
|
||||||
|
|
||||||
|
const payment = await this.paymentProviderService_.retrievePayment(
|
||||||
|
paymentId
|
||||||
|
)
|
||||||
|
|
||||||
|
return await this.refundPayment(payCol, payment, amount, reason, note)
|
||||||
|
}
|
||||||
|
|
||||||
|
async refundAll(
|
||||||
|
paymentCollectionId: string,
|
||||||
|
reason: string,
|
||||||
|
note?: string
|
||||||
|
): Promise<Refund[]> {
|
||||||
|
const payCol = await this.retrieve(paymentCollectionId, {
|
||||||
|
relations: ["payments"],
|
||||||
|
})
|
||||||
|
|
||||||
|
const allRefunds: Refund[] = []
|
||||||
|
for (const payment of payCol.payments) {
|
||||||
|
const refunded = await this.refundPayment(
|
||||||
|
payCol,
|
||||||
|
payment,
|
||||||
|
payment.amount,
|
||||||
|
reason,
|
||||||
|
note
|
||||||
|
).catch(() => void 0)
|
||||||
|
|
||||||
|
if (refunded) {
|
||||||
|
allRefunds.push(refunded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allRefunds
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
PaymentSessionStatus,
|
PaymentSessionStatus,
|
||||||
Refund,
|
Refund,
|
||||||
} from "../models"
|
} from "../models"
|
||||||
|
import { PaymentProviderDataInput } from "../types/payment-collection"
|
||||||
|
|
||||||
type PaymentProviderKey = `pp_${string}` | "systemPaymentProviderService"
|
type PaymentProviderKey = `pp_${string}` | "systemPaymentProviderService"
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
@@ -179,6 +180,33 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createSessionNew(
|
||||||
|
sessionInput: PaymentProviderDataInput
|
||||||
|
): Promise<PaymentSession> {
|
||||||
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
|
const provider: AbstractPaymentService = this.retrieveProvider(
|
||||||
|
sessionInput.provider_id
|
||||||
|
)
|
||||||
|
const sessionData = await provider
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.createPaymentNew(sessionInput)
|
||||||
|
|
||||||
|
const sessionRepo = transactionManager.getCustomRepository(
|
||||||
|
this.paymentSessionRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
const toCreate = {
|
||||||
|
provider_id: sessionInput.provider_id,
|
||||||
|
data: sessionData,
|
||||||
|
status: "pending",
|
||||||
|
amount: sessionInput.amount,
|
||||||
|
} as PaymentSession
|
||||||
|
|
||||||
|
const created = sessionRepo.create(toCreate)
|
||||||
|
return await sessionRepo.save(created)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refreshes a payment session with the given provider.
|
* Refreshes a payment session with the given provider.
|
||||||
* This means, that we delete the current one and create a new.
|
* This means, that we delete the current one and create a new.
|
||||||
@@ -219,6 +247,26 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async refreshSessionNew(
|
||||||
|
paymentSession: PaymentSession,
|
||||||
|
sessionInput: PaymentProviderDataInput
|
||||||
|
): Promise<PaymentSession> {
|
||||||
|
return this.atomicPhase_(async (transactionManager) => {
|
||||||
|
const session = await this.retrieveSession(paymentSession.id)
|
||||||
|
const provider = this.retrieveProvider(paymentSession.provider_id)
|
||||||
|
|
||||||
|
await provider.withTransaction(transactionManager).deletePayment(session)
|
||||||
|
|
||||||
|
const sessionRepo = transactionManager.getCustomRepository(
|
||||||
|
this.paymentSessionRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
await sessionRepo.remove(session)
|
||||||
|
|
||||||
|
return await this.createSessionNew(sessionInput)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates an existing payment session.
|
* Updates an existing payment session.
|
||||||
* @param paymentSession - the payment session object to
|
* @param paymentSession - the payment session object to
|
||||||
@@ -240,7 +288,29 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
const sessionRepo = transactionManager.getCustomRepository(
|
const sessionRepo = transactionManager.getCustomRepository(
|
||||||
this.paymentSessionRepository_
|
this.paymentSessionRepository_
|
||||||
)
|
)
|
||||||
return sessionRepo.save(session)
|
return await sessionRepo.save(session)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateSessionNew(
|
||||||
|
paymentSession: PaymentSession,
|
||||||
|
sessionInput: PaymentProviderDataInput
|
||||||
|
): Promise<PaymentSession> {
|
||||||
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
|
const session = await this.retrieveSession(paymentSession.id)
|
||||||
|
const provider = this.retrieveProvider(paymentSession.provider_id)
|
||||||
|
|
||||||
|
session.amount = sessionInput.amount
|
||||||
|
paymentSession.data.amount = sessionInput.amount
|
||||||
|
session.data = await provider
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.updatePaymentNew(paymentSession.data, sessionInput)
|
||||||
|
|
||||||
|
const sessionRepo = transactionManager.getCustomRepository(
|
||||||
|
this.paymentSessionRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
return await sessionRepo.save(session)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,7 +335,7 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
this.paymentSessionRepository_
|
this.paymentSessionRepository_
|
||||||
)
|
)
|
||||||
|
|
||||||
return sessionRepo.remove(session)
|
return await sessionRepo.remove(session)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,7 +391,34 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
cart_id: cart.id,
|
cart_id: cart.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
return paymentRepo.save(created)
|
return await paymentRepo.save(created)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async createPaymentNew(
|
||||||
|
paymentInput: Omit<PaymentProviderDataInput, "customer">
|
||||||
|
): Promise<Payment> {
|
||||||
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
|
const { payment_session, currency_code, amount, provider_id } =
|
||||||
|
paymentInput
|
||||||
|
|
||||||
|
const provider = this.retrieveProvider(provider_id)
|
||||||
|
const paymentData = await provider
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.getPaymentData(payment_session)
|
||||||
|
|
||||||
|
const paymentRepo = transactionManager.getCustomRepository(
|
||||||
|
this.paymentRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
const created = paymentRepo.create({
|
||||||
|
provider_id,
|
||||||
|
amount,
|
||||||
|
currency_code,
|
||||||
|
data: paymentData,
|
||||||
|
})
|
||||||
|
|
||||||
|
return await paymentRepo.save(created)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,7 +440,7 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
const payRepo = transactionManager.getCustomRepository(
|
const payRepo = transactionManager.getCustomRepository(
|
||||||
this.paymentRepository_
|
this.paymentRepository_
|
||||||
)
|
)
|
||||||
return payRepo.save(payment)
|
return await payRepo.save(payment)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,7 +468,7 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
const sessionRepo = transactionManager.getCustomRepository(
|
const sessionRepo = transactionManager.getCustomRepository(
|
||||||
this.paymentSessionRepository_
|
this.paymentSessionRepository_
|
||||||
)
|
)
|
||||||
return sessionRepo.save(session)
|
return await sessionRepo.save(session)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,7 +489,7 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
const sessionRepo = transactionManager.getCustomRepository(
|
const sessionRepo = transactionManager.getCustomRepository(
|
||||||
this.paymentSessionRepository_
|
this.paymentSessionRepository_
|
||||||
)
|
)
|
||||||
return sessionRepo.save(session)
|
return await sessionRepo.save(session)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,7 +534,7 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
const paymentRepo = transactionManager.getCustomRepository(
|
const paymentRepo = transactionManager.getCustomRepository(
|
||||||
this.paymentRepository_
|
this.paymentRepository_
|
||||||
)
|
)
|
||||||
return paymentRepo.save(payment)
|
return await paymentRepo.save(payment)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,7 +618,47 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const created = refundRepo.create(toCreate)
|
const created = refundRepo.create(toCreate)
|
||||||
return refundRepo.save(created)
|
return await refundRepo.save(created)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async refundFromPayment(
|
||||||
|
payment: Payment,
|
||||||
|
amount: number,
|
||||||
|
reason: string,
|
||||||
|
note?: string
|
||||||
|
): Promise<Refund> {
|
||||||
|
return await this.atomicPhase_(async (manager) => {
|
||||||
|
const refundable = payment.amount - payment.amount_refunded
|
||||||
|
|
||||||
|
if (refundable < amount) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"Refund amount is higher that the refundable amount"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.retrieveProvider(payment.provider_id)
|
||||||
|
payment.data = await provider
|
||||||
|
.withTransaction(manager)
|
||||||
|
.refundPayment(payment, amount)
|
||||||
|
|
||||||
|
payment.amount_refunded += amount
|
||||||
|
|
||||||
|
const paymentRepo = manager.getCustomRepository(this.paymentRepository_)
|
||||||
|
await paymentRepo.save(payment)
|
||||||
|
|
||||||
|
const refundRepo = manager.getCustomRepository(this.refundRepository_)
|
||||||
|
|
||||||
|
const toCreate = {
|
||||||
|
payment_id: payment.id,
|
||||||
|
amount,
|
||||||
|
reason,
|
||||||
|
note,
|
||||||
|
}
|
||||||
|
|
||||||
|
const created = refundRepo.create(toCreate)
|
||||||
|
return await refundRepo.save(created)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import { PaymentCollection, PaymentCollectionType } from "../models"
|
import {
|
||||||
|
Cart,
|
||||||
|
Customer,
|
||||||
|
PaymentCollection,
|
||||||
|
PaymentCollectionType,
|
||||||
|
PaymentSession,
|
||||||
|
} from "../models"
|
||||||
|
|
||||||
export type CreatePaymentCollectionInput = {
|
export type CreatePaymentCollectionInput = {
|
||||||
region_id: string
|
region_id: string
|
||||||
@@ -10,6 +16,25 @@ export type CreatePaymentCollectionInput = {
|
|||||||
description?: string
|
description?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PaymentCollectionSessionInput = {
|
||||||
|
provider_id: string
|
||||||
|
amount: number
|
||||||
|
session_id?: string
|
||||||
|
customer_id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PaymentProviderDataInput = {
|
||||||
|
resource_id: string
|
||||||
|
customer: Partial<Customer>
|
||||||
|
currency_code: string
|
||||||
|
provider_id: string
|
||||||
|
amount: number
|
||||||
|
payment_session?: PaymentSession
|
||||||
|
payment_description?: string
|
||||||
|
cart_id?: string
|
||||||
|
cart?: Cart
|
||||||
|
metadata?: any
|
||||||
|
}
|
||||||
export const defaultPaymentCollectionRelations = [
|
export const defaultPaymentCollectionRelations = [
|
||||||
"region",
|
"region",
|
||||||
"region.payment_providers",
|
"region.payment_providers",
|
||||||
|
|||||||
Reference in new issue
Block a user