hotfix(paypal): correct payment retrieval on refunds
This commit is contained in:
@@ -26,12 +26,13 @@
|
|||||||
"cross-env": "^5.2.1",
|
"cross-env": "^5.2.1",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
"jest": "^25.5.2",
|
"jest": "^25.5.2",
|
||||||
|
"medusa-interfaces": "^1.1.10",
|
||||||
"medusa-test-utils": "^1.1.12"
|
"medusa-test-utils": "^1.1.12"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "babel src -d . --ignore **/__tests__",
|
"build": "babel src -d . --ignore **/__tests__,**/__mocks__",
|
||||||
"prepare": "cross-env NODE_ENV=production npm run build",
|
"prepare": "cross-env NODE_ENV=production npm run build",
|
||||||
"watch": "babel -w src --out-dir . --ignore **/__tests__",
|
"watch": "babel -w src --out-dir . --ignore **/__tests__,**/__mocks__",
|
||||||
"test": "jest"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
export const PayPalClientMock = {
|
||||||
|
execute: jest.fn().mockImplementation((r) => {
|
||||||
|
return {
|
||||||
|
result: r.result,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PayPalMock = {
|
||||||
|
core: {
|
||||||
|
SandboxEnvironment: function () {
|
||||||
|
this.env = {
|
||||||
|
sandbox: true,
|
||||||
|
live: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
LiveEnvironment: function () {
|
||||||
|
this.env = {
|
||||||
|
sandbox: false,
|
||||||
|
live: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
PayPalHttpClient: function () {
|
||||||
|
return PayPalClientMock
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
payments: {
|
||||||
|
AuthorizationsGetRequest: jest.fn().mockImplementation(() => {}),
|
||||||
|
AuthorizationsVoidRequest: jest.fn().mockImplementation(() => {}),
|
||||||
|
AuthorizationsCaptureRequest: jest.fn().mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
result: {
|
||||||
|
id: "test",
|
||||||
|
},
|
||||||
|
capture: true,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
CapturesRefundRequest: jest.fn().mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
result: {
|
||||||
|
id: "test",
|
||||||
|
},
|
||||||
|
body: null,
|
||||||
|
requestBody: function (d) {
|
||||||
|
this.body = d
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
orders: {
|
||||||
|
OrdersCreateRequest: jest.fn().mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
result: {
|
||||||
|
id: "test",
|
||||||
|
},
|
||||||
|
order: true,
|
||||||
|
body: null,
|
||||||
|
requestBody: function (d) {
|
||||||
|
this.body = d
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
OrdersPatchRequest: jest.fn().mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
result: {
|
||||||
|
id: "test",
|
||||||
|
},
|
||||||
|
order: true,
|
||||||
|
body: null,
|
||||||
|
requestBody: function (d) {
|
||||||
|
this.body = d
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
OrdersGetRequest: jest.fn().mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
result: {
|
||||||
|
id: "test",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PayPalMock
|
||||||
@@ -0,0 +1,346 @@
|
|||||||
|
import PayPalProviderService from "../paypal-provider"
|
||||||
|
import {
|
||||||
|
PayPalMock,
|
||||||
|
PayPalClientMock,
|
||||||
|
} from "../../__mocks__/@paypal/checkout-server-sdk"
|
||||||
|
|
||||||
|
const TotalsServiceMock = {
|
||||||
|
getTotal: jest.fn().mockImplementation((c) => c.total),
|
||||||
|
}
|
||||||
|
|
||||||
|
const RegionServiceMock = {
|
||||||
|
retrieve: jest.fn().mockImplementation((id) =>
|
||||||
|
Promise.resolve({
|
||||||
|
currency_code: "eur",
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("PaypalProviderService", () => {
|
||||||
|
describe("createPayment", () => {
|
||||||
|
let result
|
||||||
|
const paypalProviderService = new PayPalProviderService(
|
||||||
|
{
|
||||||
|
regionService: RegionServiceMock,
|
||||||
|
totalsService: TotalsServiceMock,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
api_key: "test",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("creates paypal order", async () => {
|
||||||
|
result = await paypalProviderService.createPayment({
|
||||||
|
id: "test_cart",
|
||||||
|
region_id: "fr",
|
||||||
|
total: 1000,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(PayPalMock.orders.OrdersCreateRequest).toHaveBeenCalledTimes(1)
|
||||||
|
expect(PayPalClientMock.execute).toHaveBeenCalledTimes(1)
|
||||||
|
expect(PayPalClientMock.execute).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
order: true,
|
||||||
|
body: {
|
||||||
|
intent: "AUTHORIZE",
|
||||||
|
application_context: {
|
||||||
|
shipping_preference: "NO_SHIPPING",
|
||||||
|
},
|
||||||
|
purchase_units: [
|
||||||
|
{
|
||||||
|
custom_id: "test_cart",
|
||||||
|
amount: {
|
||||||
|
currency_code: "EUR",
|
||||||
|
value: "10.00",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result.id).toEqual("test")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("retrievePayment", () => {
|
||||||
|
let result
|
||||||
|
const paypalProviderService = new PayPalProviderService(
|
||||||
|
{
|
||||||
|
regionService: RegionServiceMock,
|
||||||
|
totalsService: TotalsServiceMock,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
api_key: "test",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("retrieves paypal order", async () => {
|
||||||
|
result = await paypalProviderService.retrievePayment({ id: "test" })
|
||||||
|
expect(PayPalMock.orders.OrdersGetRequest).toHaveBeenCalledTimes(1)
|
||||||
|
expect(PayPalMock.orders.OrdersGetRequest).toHaveBeenCalledWith("test")
|
||||||
|
expect(PayPalClientMock.execute).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
expect(result.id).toEqual("test")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("updatePayment", () => {
|
||||||
|
let result
|
||||||
|
const paypalProviderService = new PayPalProviderService(
|
||||||
|
{
|
||||||
|
regionService: RegionServiceMock,
|
||||||
|
totalsService: TotalsServiceMock,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
api_key: "test",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("updates paypal order", async () => {
|
||||||
|
result = await paypalProviderService.updatePayment(
|
||||||
|
{ id: "test" },
|
||||||
|
{
|
||||||
|
id: "test_cart",
|
||||||
|
region_id: "fr",
|
||||||
|
total: 1000,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(PayPalMock.orders.OrdersPatchRequest).toHaveBeenCalledTimes(1)
|
||||||
|
expect(PayPalMock.orders.OrdersPatchRequest).toHaveBeenCalledWith("test")
|
||||||
|
expect(PayPalClientMock.execute).toHaveBeenCalledTimes(1)
|
||||||
|
expect(PayPalClientMock.execute).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
order: true,
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
op: "replace",
|
||||||
|
path: "/purchase_units/@reference_id=='default'",
|
||||||
|
value: {
|
||||||
|
amount: {
|
||||||
|
currency_code: "EUR",
|
||||||
|
value: "10.00",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result.id).toEqual("test")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("capturePayment", () => {
|
||||||
|
let result
|
||||||
|
const paypalProviderService = new PayPalProviderService(
|
||||||
|
{
|
||||||
|
regionService: RegionServiceMock,
|
||||||
|
totalsService: TotalsServiceMock,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
api_key: "test",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("updates paypal order", async () => {
|
||||||
|
result = await paypalProviderService.capturePayment({
|
||||||
|
data: {
|
||||||
|
id: "test",
|
||||||
|
purchase_units: [
|
||||||
|
{
|
||||||
|
payments: {
|
||||||
|
authorizations: [
|
||||||
|
{
|
||||||
|
id: "test_auth",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(
|
||||||
|
PayPalMock.payments.AuthorizationsCaptureRequest
|
||||||
|
).toHaveBeenCalledTimes(1)
|
||||||
|
expect(
|
||||||
|
PayPalMock.payments.AuthorizationsCaptureRequest
|
||||||
|
).toHaveBeenCalledWith("test_auth")
|
||||||
|
expect(PayPalMock.orders.OrdersGetRequest).toHaveBeenCalledWith("test")
|
||||||
|
expect(PayPalClientMock.execute).toHaveBeenCalledTimes(2)
|
||||||
|
|
||||||
|
expect(result.id).toEqual("test")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("refundPayment", () => {
|
||||||
|
let result
|
||||||
|
const paypalProviderService = new PayPalProviderService(
|
||||||
|
{
|
||||||
|
regionService: RegionServiceMock,
|
||||||
|
totalsService: TotalsServiceMock,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
api_key: "test",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("refunds payment", async () => {
|
||||||
|
result = await paypalProviderService.refundPayment(
|
||||||
|
{
|
||||||
|
currency_code: "eur",
|
||||||
|
data: {
|
||||||
|
id: "test",
|
||||||
|
purchase_units: [
|
||||||
|
{
|
||||||
|
payments: {
|
||||||
|
captures: [
|
||||||
|
{
|
||||||
|
id: "test_cap",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
2000
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(PayPalMock.payments.CapturesRefundRequest).toHaveBeenCalledWith(
|
||||||
|
"test_cap"
|
||||||
|
)
|
||||||
|
expect(PayPalMock.orders.OrdersGetRequest).toHaveBeenCalledWith("test")
|
||||||
|
expect(PayPalClientMock.execute).toHaveBeenCalledTimes(2)
|
||||||
|
expect(PayPalClientMock.execute).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
body: {
|
||||||
|
amount: {
|
||||||
|
currency_code: "EUR",
|
||||||
|
value: "20.00",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result.id).toEqual("test")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("doesn't refund without captures", async () => {
|
||||||
|
await expect(
|
||||||
|
paypalProviderService.refundPayment(
|
||||||
|
{
|
||||||
|
currency_code: "eur",
|
||||||
|
data: {
|
||||||
|
id: "test",
|
||||||
|
purchase_units: [
|
||||||
|
{
|
||||||
|
payments: {
|
||||||
|
captures: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
2000
|
||||||
|
)
|
||||||
|
).rejects.toThrow("Order not yet captured")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("cancelPayment", () => {
|
||||||
|
let result
|
||||||
|
const paypalProviderService = new PayPalProviderService(
|
||||||
|
{
|
||||||
|
regionService: RegionServiceMock,
|
||||||
|
totalsService: TotalsServiceMock,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
api_key: "test",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("refunds if captured", async () => {
|
||||||
|
result = await paypalProviderService.cancelPayment({
|
||||||
|
captured_at: "true",
|
||||||
|
currency_code: "eur",
|
||||||
|
data: {
|
||||||
|
id: "test",
|
||||||
|
purchase_units: [
|
||||||
|
{
|
||||||
|
payments: {
|
||||||
|
captures: [
|
||||||
|
{
|
||||||
|
id: "test_cap",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(PayPalMock.payments.CapturesRefundRequest).toHaveBeenCalledWith(
|
||||||
|
"test_cap"
|
||||||
|
)
|
||||||
|
expect(PayPalMock.orders.OrdersGetRequest).toHaveBeenCalledWith("test")
|
||||||
|
expect(PayPalClientMock.execute).toHaveBeenCalledTimes(2)
|
||||||
|
|
||||||
|
expect(result.id).toEqual("test")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("voids if not captured", async () => {
|
||||||
|
result = await paypalProviderService.cancelPayment({
|
||||||
|
currency_code: "eur",
|
||||||
|
data: {
|
||||||
|
id: "test",
|
||||||
|
purchase_units: [
|
||||||
|
{
|
||||||
|
payments: {
|
||||||
|
authorizations: [
|
||||||
|
{
|
||||||
|
id: "test_auth",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(
|
||||||
|
PayPalMock.payments.AuthorizationsVoidRequest
|
||||||
|
).toHaveBeenCalledWith("test_auth")
|
||||||
|
expect(PayPalMock.orders.OrdersGetRequest).toHaveBeenCalledWith("test")
|
||||||
|
expect(PayPalClientMock.execute).toHaveBeenCalledTimes(2)
|
||||||
|
|
||||||
|
expect(result.id).toEqual("test")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -5,7 +5,7 @@ import { PaymentService } from "medusa-interfaces"
|
|||||||
class PayPalProviderService extends PaymentService {
|
class PayPalProviderService extends PaymentService {
|
||||||
static identifier = "paypal"
|
static identifier = "paypal"
|
||||||
|
|
||||||
constructor({ customerService, totalsService, regionService }, options) {
|
constructor({ totalsService, regionService }, options) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,9 +35,6 @@ class PayPalProviderService extends PaymentService {
|
|||||||
/** @private @const {PayPalHttpClient} */
|
/** @private @const {PayPalHttpClient} */
|
||||||
this.paypal_ = new PayPal.core.PayPalHttpClient(environment)
|
this.paypal_ = new PayPal.core.PayPalHttpClient(environment)
|
||||||
|
|
||||||
/** @private @const {CustomerService} */
|
|
||||||
this.customerService_ = customerService
|
|
||||||
|
|
||||||
/** @private @const {RegionService} */
|
/** @private @const {RegionService} */
|
||||||
this.regionService_ = regionService
|
this.regionService_ = regionService
|
||||||
|
|
||||||
@@ -196,7 +193,7 @@ class PayPalProviderService extends PaymentService {
|
|||||||
value: {
|
value: {
|
||||||
amount: {
|
amount: {
|
||||||
currency_code: currency_code.toUpperCase(),
|
currency_code: currency_code.toUpperCase(),
|
||||||
value: (cart.total / 100).toFixed(),
|
value: (cart.total / 100).toFixed(2),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -240,7 +237,7 @@ class PayPalProviderService extends PaymentService {
|
|||||||
* Refunds a given amount.
|
* Refunds a given amount.
|
||||||
* @param {object} payment - payment to refund
|
* @param {object} payment - payment to refund
|
||||||
* @param {number} amountToRefund - amount to refund
|
* @param {number} amountToRefund - amount to refund
|
||||||
* @returns {string} the resulting PayPal order
|
* @returns {Promise<Object>} the resulting PayPal order
|
||||||
*/
|
*/
|
||||||
async refundPayment(payment, amountToRefund) {
|
async refundPayment(payment, amountToRefund) {
|
||||||
const { purchase_units } = payment.data
|
const { purchase_units } = payment.data
|
||||||
@@ -257,13 +254,13 @@ class PayPalProviderService extends PaymentService {
|
|||||||
request.requestBody({
|
request.requestBody({
|
||||||
amount: {
|
amount: {
|
||||||
currency_code: payment.currency_code.toUpperCase(),
|
currency_code: payment.currency_code.toUpperCase(),
|
||||||
value: (amountToRefund / 100).toFixed(),
|
value: (amountToRefund / 100).toFixed(2),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
await this.paypal_.execute(request)
|
await this.paypal_.execute(request)
|
||||||
|
|
||||||
return this.retrievePayment(payment.id)
|
return this.retrievePayment(payment.data)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
@@ -272,7 +269,7 @@ class PayPalProviderService extends PaymentService {
|
|||||||
/**
|
/**
|
||||||
* Cancels payment for Stripe payment intent.
|
* Cancels payment for Stripe payment intent.
|
||||||
* @param {object} paymentData - payment method data from cart
|
* @param {object} paymentData - payment method data from cart
|
||||||
* @returns {object} canceled payment intent
|
* @returns {Promise<object>} canceled payment intent
|
||||||
*/
|
*/
|
||||||
async cancelPayment(payment) {
|
async cancelPayment(payment) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user