-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Upgrading Paypal express from Client-side only integration to a Server-side integration * Fixing eslint issue
- Loading branch information
Showing
6 changed files
with
171 additions
and
776 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,29 @@ | ||
import {CreateOrderActions, CreateOrderData} from '@paypal/paypal-js/types/components/buttons'; | ||
import {CreateOrderRequestBody} from '@paypal/paypal-js/types/apis/orders'; | ||
import {getPaypalOrder} from 'src/paypal'; | ||
import { | ||
IApiSuccessResponse, | ||
IWalletPayCreateOrderRequest, IWalletPayCreateOrderResponse, | ||
walletPayCreateOrder | ||
} from '@boldcommerce/checkout-frontend-library'; | ||
import {API_RETRY} from 'src/types'; | ||
import {displayError} from 'src/actions'; | ||
|
||
export async function paypalCreateOrder(data: CreateOrderData, actions: CreateOrderActions): Promise<string> { | ||
const paypalOrder: CreateOrderRequestBody = getPaypalOrder(); | ||
return actions.order.create(paypalOrder); | ||
export async function paypalCreateOrder(): Promise<string> { | ||
|
||
const payment: IWalletPayCreateOrderRequest = { | ||
gateway_type: 'paypal', | ||
payment_data: { | ||
locale: navigator.language, | ||
payment_type: 'paypal', | ||
} | ||
}; | ||
|
||
const paymentResult = await walletPayCreateOrder(payment, API_RETRY); | ||
if(paymentResult.success) { | ||
const {data} = paymentResult.response as IApiSuccessResponse; | ||
const {payment_data} = data as IWalletPayCreateOrderResponse; | ||
const orderId = payment_data.id as string; | ||
return orderId; | ||
} else { | ||
displayError('There was an unknown error while loading the payment.', 'payment_gateway', 'unknown_error'); | ||
return ''; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,103 +1,52 @@ | ||
import {OnApproveActions, OnApproveData} from '@paypal/paypal-js/types/components/buttons'; | ||
import {OrderResponseBody, ShippingInfo} from '@paypal/paypal-js/types/apis/orders'; | ||
import {OnApproveData} from '@paypal/paypal-js/types/components/buttons'; | ||
import { | ||
callBillingAddressEndpoint, | ||
callGuestCustomerEndpoint, | ||
callShippingAddressEndpoint, | ||
getFirstAndLastName, | ||
getTotals, | ||
isObjectEquals | ||
} from 'src/utils'; | ||
import {formatPaypalToApiAddress} from 'src/paypal/formatPaypalToApiAddress'; | ||
import { | ||
addPayment, | ||
getCurrency, | ||
IAddPaymentRequest, | ||
setTaxes, | ||
IWalletPayOnApproveRequest, | ||
walletPayOnApprove, | ||
} from '@boldcommerce/checkout-frontend-library'; | ||
import {API_RETRY} from 'src/types'; | ||
import {getPaypalGatewayPublicId} from 'src/paypal/managePaypalState'; | ||
import {orderProcessing, displayError} from 'src/actions'; | ||
|
||
export async function paypalOnApprove(data: OnApproveData, actions: OnApproveActions): Promise<void> { | ||
export async function paypalOnApprove(data: OnApproveData): Promise<void> { | ||
const {iso_code: currencyCode} = getCurrency(); | ||
return actions.order?.get().then(async ({ id, payer, purchase_units }: OrderResponseBody) => { | ||
|
||
// extract all shipping info | ||
const { name, address: shippingAddress } = purchase_units[0].shipping as ShippingInfo; | ||
const shippingNames = getFirstAndLastName(name?.full_name); | ||
|
||
// extract all billing info | ||
const {name: payerName, address: billingAddress} = payer; | ||
const billingNames = {firstName: payerName?.given_name || '', lastName: payerName?.surname || ''}; | ||
const phone = payer.phone?.phone_number.national_number || ''; | ||
const email = payer.email_address || ''; | ||
const isBillingAddressFilled = ( | ||
!!billingAddress?.address_line_1 | ||
&& !!billingAddress.admin_area_1 | ||
&& !!billingAddress.admin_area_2 | ||
&& !!billingAddress.country_code | ||
&& !!billingAddress.postal_code | ||
); | ||
|
||
// set customer | ||
const customerResult = await callGuestCustomerEndpoint(billingNames.firstName, billingNames.lastName, email); | ||
const success = customerResult.success; | ||
if(!success){ | ||
displayError('There was an unknown error while validating your customer information.', 'generic', 'unknown_error'); | ||
return; | ||
} | ||
|
||
// check if shipping and billing are the same | ||
const isSameNames = isObjectEquals(shippingNames, billingNames); | ||
const isSameAddress = isBillingAddressFilled && isObjectEquals(shippingAddress, billingAddress); | ||
const isBillingSame = isSameNames && isSameAddress; | ||
const formattedShippingAddress = formatPaypalToApiAddress(shippingAddress, shippingNames.firstName, shippingNames.lastName, phone); | ||
const formattedBillingAddress = formatPaypalToApiAddress(isBillingAddressFilled ? billingAddress : shippingAddress, billingNames.firstName, billingNames.lastName, phone); | ||
|
||
// check and update shipping address | ||
const shippingAddressResponse = await callShippingAddressEndpoint(formattedShippingAddress, true); | ||
if(!shippingAddressResponse.success){ | ||
displayError('There was an unknown error while validating your shipping address.', 'shipping', 'unknown_error'); | ||
return; | ||
} | ||
|
||
|
||
// check and update billing address | ||
const billingAddressToSet = isBillingSame ? formattedShippingAddress : formattedBillingAddress; | ||
const billingAddressResponse = await callBillingAddressEndpoint(billingAddressToSet, (!isBillingSame && isBillingAddressFilled)); | ||
if(!billingAddressResponse.success){ | ||
displayError('There was an unknown error while validating your billing address.', 'generic', 'unknown_error'); | ||
return; | ||
} | ||
|
||
|
||
// update taxes | ||
|
||
const taxResponse = await setTaxes(API_RETRY); | ||
if(!taxResponse.success){ | ||
displayError('There was an unknown error while calculating the taxes.', 'payment_gateway', 'no_tax'); | ||
return; | ||
} | ||
|
||
|
||
// add payment | ||
const totals = getTotals(); | ||
const payment: IAddPaymentRequest = { | ||
token: `${id}:${payer.payer_id}`, | ||
nonce: `${id}:${payer.payer_id}`, // TODO: Temporarily required - It is not in the API documentation, but required for Paypal Express | ||
gateway_public_id: getPaypalGatewayPublicId(), | ||
currency: currencyCode, | ||
amount: totals.totalAmountDue, | ||
wallet_pay_type: 'paypal', | ||
} as IAddPaymentRequest; | ||
const paymentResult = await addPayment(payment, API_RETRY); | ||
if(!paymentResult.success){ | ||
displayError('There was an unknown error while processing your payment.', 'payment_gateway', 'unknown_error'); | ||
return; | ||
const body: IWalletPayOnApproveRequest = { | ||
gateway_type: 'paypal', | ||
payment_data: { | ||
locale: navigator.language, | ||
paypal_order_id: data.orderID | ||
} | ||
}; | ||
|
||
const res = await walletPayOnApprove(body, API_RETRY); | ||
|
||
if (!res.success) { | ||
displayError('There was an unknown error while processing your payment.', 'payment_gateway', 'unknown_error'); | ||
return; | ||
} | ||
|
||
const totals = getTotals(); | ||
const payment: IAddPaymentRequest = { | ||
token: `${data.orderID}:${data.payerID}`, | ||
nonce: `${data.orderID}:${data.payerID}`, // TODO: Temporarily required - It is not in the API documentation, but required for Paypal Express | ||
gateway_public_id: getPaypalGatewayPublicId(), | ||
currency: currencyCode, | ||
amount: totals.totalAmountDue, | ||
wallet_pay_type: 'paypal', | ||
} as IAddPaymentRequest; | ||
const paymentResult = await addPayment(payment, API_RETRY); | ||
if (!paymentResult.success) { | ||
displayError('There was an unknown error while processing your payment.', 'payment_gateway', 'unknown_error'); | ||
return; | ||
} | ||
|
||
// finalize order | ||
orderProcessing(); | ||
|
||
// finalize order | ||
orderProcessing(); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,75 +1,24 @@ | ||
import {OnShippingChangeActions, OnShippingChangeData} from '@paypal/paypal-js/types/components/buttons'; | ||
import {API_RETRY,} from 'src'; | ||
import { | ||
getPaypalPatchOperations, | ||
API_RETRY, | ||
formatPaypalToApiAddress, | ||
isSimilarStrings, | ||
callShippingAddressEndpoint, | ||
paypalConstants, | ||
getPhoneNumber | ||
} from 'src'; | ||
import { | ||
changeShippingLine, | ||
estimateShippingLines, | ||
estimateTaxes, | ||
getOrderInitialData, | ||
getShipping, | ||
getShippingLines, | ||
setTaxes, | ||
IWalletPayOnShippingRequest, | ||
walletPayOnShipping, | ||
} from '@boldcommerce/checkout-frontend-library'; | ||
import {OrderResponseBody, UpdateOrderRequestBody} from '@paypal/paypal-js/types/apis/orders'; | ||
import {OrderResponseBody} from '@paypal/paypal-js/types/apis/orders'; | ||
|
||
export async function paypalOnShippingChange(data: OnShippingChangeData, actions: OnShippingChangeActions): Promise<void|OrderResponseBody> { | ||
const {shipping_address: address, selected_shipping_option: selectedOption} = data; | ||
const {reject, order: {patch: patch}} = actions; | ||
const {MAX_STRING_LENGTH: maxStringSize} = paypalConstants; | ||
const {general_settings} = getOrderInitialData(); | ||
const rsaEnabled = general_settings.checkout_process.rsa_enabled; | ||
|
||
if (address) { | ||
const formattedAddress = formatPaypalToApiAddress(address, undefined, undefined , getPhoneNumber()); | ||
let success = false; | ||
if (rsaEnabled) { | ||
const shippingLinesResponse = await estimateShippingLines(formattedAddress, API_RETRY); | ||
if (shippingLinesResponse.success) { | ||
success = true; | ||
} | ||
} else { | ||
const shippingAddressResponse = await callShippingAddressEndpoint(formattedAddress, false); | ||
if (!shippingAddressResponse.success) { | ||
return reject(); | ||
} | ||
const shippingLinesResponse = await getShippingLines(API_RETRY); | ||
if (shippingLinesResponse.success) { | ||
success = true; | ||
} | ||
const body: IWalletPayOnShippingRequest = { | ||
gateway_type: 'paypal', | ||
payment_data: { | ||
locale: navigator.language, | ||
paypal_order_id: data.orderID, | ||
shipping_address: data.shipping_address, | ||
shipping_options: data.selected_shipping_option, | ||
} | ||
}; | ||
|
||
|
||
if (success) { | ||
const {selected_shipping: selectedShipping, available_shipping_lines: shippingLines} = getShipping(); | ||
if (selectedOption) { | ||
const option = shippingLines.find(line => isSimilarStrings(line.description.substring(0, maxStringSize), selectedOption.label)); | ||
option && await changeShippingLine(option.id, API_RETRY); | ||
} else if (!selectedShipping && shippingLines.length > 0) { | ||
await changeShippingLine(shippingLines[0].id, API_RETRY); | ||
} | ||
await getShippingLines(API_RETRY); | ||
} | ||
const res = await walletPayOnShipping(body, API_RETRY); | ||
if (!res.success) { | ||
return actions.reject(); | ||
} | ||
|
||
let taxResponse; | ||
if (rsaEnabled && address) { | ||
const formattedAddress = formatPaypalToApiAddress(address, undefined, undefined , getPhoneNumber()); | ||
taxResponse = await estimateTaxes(formattedAddress, API_RETRY); | ||
} else { | ||
taxResponse = await setTaxes(API_RETRY); | ||
} | ||
|
||
if (taxResponse.success) { | ||
const patchOperations = getPaypalPatchOperations(!!selectedOption); | ||
return await patch(patchOperations as UpdateOrderRequestBody); | ||
} | ||
|
||
return reject(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,62 +1,49 @@ | ||
import {getPaypalOrder, paypalCreateOrder} from 'src'; | ||
import {CreateOrderActions} from '@paypal/paypal-js/types/components/buttons'; | ||
import {AmountWithBreakdown, AmountWithCurrencyCode} from '@paypal/paypal-js'; | ||
import {CreateOrderRequestBody, PurchaseItem} from '@paypal/paypal-js/types/apis/orders'; | ||
import {displayError, paypalCreateOrder} from 'src'; | ||
import {mocked} from 'jest-mock'; | ||
|
||
jest.mock('src/paypal/getPaypalOrder'); | ||
const getPaypalOrderMock = mocked(getPaypalOrder, true); | ||
const createOrderActionMock: CreateOrderActions = {order: {create: jest.fn()}}; | ||
import { | ||
baseReturnObject, | ||
IWalletPayCreateOrderResponse, | ||
walletPayCreateOrder | ||
} from '@boldcommerce/checkout-frontend-library'; | ||
import {applicationStateMock} from '@boldcommerce/checkout-frontend-library/lib/variables/mocks'; | ||
|
||
jest.mock('@boldcommerce/checkout-frontend-library/lib/walletPay/walletPayCreateOrder'); | ||
jest.mock('src/actions/displayError'); | ||
const walletPayCreateOrderMock = mocked(walletPayCreateOrder, true); | ||
const displayErrorMock = mocked(displayError, true); | ||
|
||
describe('testing paypalCreateOrder function', () => { | ||
const publicOrderId = 'abc123'; | ||
const breakdownItemMock: AmountWithCurrencyCode = { | ||
currency_code: 'USD', | ||
value: '0.00', | ||
}; | ||
const breakdownItem100Mock: AmountWithCurrencyCode = { | ||
currency_code: 'USD', | ||
value: '100.00', | ||
}; | ||
|
||
const amountWithBreakdownMock: AmountWithBreakdown = { | ||
currency_code: 'USD', | ||
value: '100.00', | ||
breakdown: { | ||
item_total: breakdownItem100Mock, | ||
shipping: breakdownItemMock, | ||
tax_total: breakdownItemMock, | ||
discount: breakdownItemMock, | ||
shipping_discount: breakdownItemMock, | ||
} | ||
}; | ||
const itemsMock: Array<PurchaseItem> = [ | ||
{ | ||
name: 'Some Name', | ||
quantity: '1', | ||
unit_amount: breakdownItem100Mock | ||
} | ||
]; | ||
const paypalOrderMock: CreateOrderRequestBody = { | ||
purchase_units: [{ | ||
custom_id: publicOrderId, | ||
amount: amountWithBreakdownMock, | ||
items: itemsMock | ||
}] | ||
}; | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
getPaypalOrderMock.mockReturnValue(paypalOrderMock); | ||
}); | ||
|
||
test('testing call paypalCreateOrder success', async () => { | ||
await paypalCreateOrder({paymentSource: 'paypal'}, createOrderActionMock); | ||
test('testing with successful call', async () => { | ||
const response: IWalletPayCreateOrderResponse = { | ||
payment_data: { | ||
id: 'test-order' | ||
}, | ||
application_state: applicationStateMock | ||
}; | ||
|
||
const paymentReturn = {...baseReturnObject}; | ||
paymentReturn.success = true; | ||
paymentReturn.response = {data: response}; | ||
walletPayCreateOrderMock.mockReturnValue(Promise.resolve(paymentReturn)); | ||
|
||
const result = await paypalCreateOrder(); | ||
expect(result).toBe('test-order'); | ||
}); | ||
|
||
|
||
expect(getPaypalOrderMock).toHaveBeenCalledTimes(1); | ||
expect(createOrderActionMock.order.create).toHaveBeenCalledTimes(1); | ||
expect(createOrderActionMock.order.create).toHaveBeenCalledWith(paypalOrderMock); | ||
test('testing with unsuccessful call', async () => { | ||
const paymentReturn = {...baseReturnObject}; | ||
paymentReturn.success = false; | ||
walletPayCreateOrderMock.mockReturnValue(Promise.resolve(paymentReturn)); | ||
|
||
const result = await paypalCreateOrder(); | ||
expect(displayErrorMock).toHaveBeenCalledTimes(1); | ||
expect(displayErrorMock).toHaveBeenCalledWith('There was an unknown error while loading the payment.', 'payment_gateway', 'unknown_error'); | ||
expect(result).toBe(''); | ||
}); | ||
|
||
}); |
Oops, something went wrong.