mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2026-07-29 02:07:27 +08:00
394 lines
9.9 KiB
TypeScript
394 lines
9.9 KiB
TypeScript
/**
|
|
* External dependencies
|
|
*/
|
|
import { APIRequestContext, expect } from '@playwright/test';
|
|
import {
|
|
createAuthHeader,
|
|
getLast4CardDigits,
|
|
} from '@inpsyde/playwright-utils/build';
|
|
/**
|
|
* Internal dependencies
|
|
*/
|
|
import { PayPalPaymentDetails, Pcp, ShopOrder } from '../resources';
|
|
|
|
/**
|
|
* Class for PayPal API
|
|
*/
|
|
|
|
export class PayPalApi {
|
|
request: APIRequestContext;
|
|
private sandboxBaseUrl = 'https://api-m.sandbox.paypal.com';
|
|
private apiBaseUrl = `${ this.sandboxBaseUrl }/v2`;
|
|
|
|
constructor( { request } ) {
|
|
this.request = request;
|
|
}
|
|
|
|
private apiRequest = async (
|
|
requestType: 'get' | 'post' | 'put' | 'delete',
|
|
endPoint: string,
|
|
merchant: Pcp.Merchant,
|
|
data?: any
|
|
) => {
|
|
try {
|
|
const token = await this.getAuthToken( merchant );
|
|
const response = await this.request[ requestType ](
|
|
this.apiBaseUrl + endPoint,
|
|
{
|
|
headers: {
|
|
Authorization: `Bearer ${ token }`,
|
|
},
|
|
data,
|
|
}
|
|
);
|
|
|
|
if ( ! ( await response.ok() ) ) {
|
|
const body = await response.text();
|
|
throw new Error(
|
|
`Request failed with status ${ await response.status() } for ${ endPoint }: ${ body }`
|
|
);
|
|
}
|
|
|
|
return await response.json();
|
|
} catch ( error ) {
|
|
console.error( 'An error occurred:', error );
|
|
throw error; // Re-throw the error to propagate it further if needed
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Gets payment info by PayPal Transaction ID
|
|
*
|
|
* @param merchant - { client_id: '...', client_secret: '...' }
|
|
*/
|
|
getAuthToken = async ( merchant: Pcp.Merchant ) => {
|
|
const response = await this.request.post(
|
|
`${ this.sandboxBaseUrl }/v1/oauth2/token`,
|
|
{
|
|
headers: createAuthHeader(
|
|
merchant.client_id,
|
|
merchant.client_secret
|
|
),
|
|
form: { grant_type: 'client_credentials' },
|
|
}
|
|
);
|
|
const json = await response.json();
|
|
if ( ! response.ok() ) {
|
|
throw new Error( `getAuthToken failed with status ${ response.status() }: ${ JSON.stringify( json ) }` );
|
|
}
|
|
return json.access_token;
|
|
};
|
|
|
|
/**
|
|
* Gets payment info by PayPal Transaction ID
|
|
*
|
|
* @param resourceId - PayPal transaction ID
|
|
* @param merchant - { client_id: '...', client_secret: '...' }
|
|
*/
|
|
getCapturedPayment = async (
|
|
resourceId: string,
|
|
merchant: Pcp.Merchant
|
|
) => {
|
|
return await this.apiRequest(
|
|
'get',
|
|
`/payments/captures/${ resourceId }`,
|
|
merchant
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Gets payment info for authorized transactions by PayPal Transaction ID
|
|
*
|
|
* @param resourceId - PayPal transaction ID
|
|
* @param merchant - { client_id: '...', client_secret: '...' }
|
|
*/
|
|
getAuthorizedPayment = async (
|
|
resourceId: string,
|
|
merchant: Pcp.Merchant
|
|
) => {
|
|
return await this.apiRequest(
|
|
'get',
|
|
`/payments/authorizations/${ resourceId }`,
|
|
merchant
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Gets order info by PayPal Order ID
|
|
*
|
|
* @param resourceId - PayPal order ID
|
|
* @param merchant - { client_id: '...', client_secret: '...' }
|
|
*/
|
|
getOrder = async ( resourceId: string, merchant: Pcp.Merchant ) => {
|
|
return await this.apiRequest(
|
|
'get',
|
|
`/checkout/orders/${ resourceId }`,
|
|
merchant
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Gets order info by PayPal Order ID
|
|
*
|
|
* @param resourceId - PayPal order ID
|
|
* @param merchant - { client_id: '...', client_secret: '...' }
|
|
*/
|
|
getRefund = async ( resourceId: string, merchant: Pcp.Merchant ) => {
|
|
return await this.apiRequest(
|
|
'get',
|
|
`/payments/refunds/${ resourceId }`,
|
|
merchant
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Gets PayPal order ID stored in WooCommerce meta_data
|
|
*
|
|
* @param wooCommerceOrderJson
|
|
*/
|
|
getOrderIdFromWooCommerce = async (
|
|
wooCommerceOrderJson: WooCommerce.Order
|
|
): Promise< string | undefined > => {
|
|
const paypalOrderIdMeta = wooCommerceOrderJson.meta_data.find(
|
|
( meta ) => meta.key === '_ppcp_paypal_order_id'
|
|
);
|
|
|
|
return paypalOrderIdMeta?.value;
|
|
};
|
|
|
|
/**
|
|
* Gets PayPal payment ID for different gateways
|
|
*
|
|
* @param payPalOrder
|
|
* @param payment
|
|
*/
|
|
getPaymentIdFromOrder = async ( payPalOrder, payment: Pcp.Payment ) => {
|
|
const fundingSource = payment.gateway.shortcut;
|
|
|
|
if ( fundingSource === 'pay_upon_invoice' ) {
|
|
return '';
|
|
}
|
|
|
|
if ( fundingSource === 'oxxo' ) {
|
|
return payPalOrder.purchase_units[ 0 ].payments.captures[ 0 ].id;
|
|
}
|
|
|
|
if ( payment.isAuthorized ) {
|
|
return payPalOrder.purchase_units[ 0 ].payments.authorizations[ 0 ]
|
|
.id;
|
|
}
|
|
|
|
return payPalOrder.purchase_units[ 0 ].payments.captures[ 0 ].id;
|
|
};
|
|
|
|
/**
|
|
* Fetches a payment (capture or authorization) from the PayPal API.
|
|
*
|
|
* @param paymentId PayPal capture/authorization resource id.
|
|
* @param merchant Credentials of the merchant that owns the resource.
|
|
* @param isAuthorized When true, query the authorizations endpoint instead of captures.
|
|
*/
|
|
getPayment = (
|
|
paymentId: string,
|
|
merchant: Pcp.Merchant,
|
|
isAuthorized = false,
|
|
) =>
|
|
isAuthorized
|
|
? this.getAuthorizedPayment( paymentId, merchant )
|
|
: this.getCapturedPayment( paymentId, merchant );
|
|
|
|
/**
|
|
*
|
|
* @param resourceId
|
|
* @param shopOrder
|
|
*/
|
|
getPayPalPaymentDetails = async (
|
|
resourceId: string,
|
|
shopOrder: ShopOrder,
|
|
): Promise< PayPalPaymentDetails > => {
|
|
const { merchant, payment } = shopOrder;
|
|
const fundingSource = payment.gateway.shortcut;
|
|
if ( fundingSource === 'pay_upon_invoice' ) {
|
|
// PUI is not captured via PayPal payments endpoints
|
|
return undefined;
|
|
}
|
|
const payPalPayment = await this.getPayment( resourceId, merchant, payment.isAuthorized );
|
|
return {
|
|
transactionId: resourceId,
|
|
currency: payPalPayment.amount.currency_code,
|
|
amount: payPalPayment.amount.value,
|
|
grossAmount: payPalPayment.seller_receivable_breakdown?.gross_amount?.value,
|
|
payPalFee: payPalPayment.seller_receivable_breakdown?.paypal_fee?.value,
|
|
netAmount: payPalPayment.seller_receivable_breakdown?.net_amount?.value,
|
|
};
|
|
};
|
|
|
|
// Assertions
|
|
|
|
/**
|
|
* Asserts PayPal order for different funding sources
|
|
*
|
|
* @param wooCommerceOrderJson
|
|
* @param shopOrder
|
|
*/
|
|
assertOrder = async (
|
|
wooCommerceOrderJson: WooCommerce.Order,
|
|
shopOrder: ShopOrder
|
|
) => {
|
|
const { merchant, payment, customer } = shopOrder;
|
|
const fundingSource = payment.gateway.shortcut;
|
|
const payPalOrderId =
|
|
await this.getOrderIdFromWooCommerce( wooCommerceOrderJson );
|
|
|
|
await expect
|
|
.soft(
|
|
payPalOrderId,
|
|
'"_ppcp_paypal_order_id" should be present in WooCommerce Order "meta_data"'
|
|
)
|
|
.toBeDefined();
|
|
|
|
if ( ! payPalOrderId ) {
|
|
return;
|
|
}
|
|
|
|
await expect.soft( payPalOrderId ).toMatch( /^[A-Z0-9]{17}$/ );
|
|
|
|
const payPalOrder = await this.getOrder(
|
|
payPalOrderId,
|
|
merchant
|
|
);
|
|
|
|
if ( fundingSource !== 'oxxo' ) {
|
|
const payPalPaymentId = await this.getPaymentIdFromOrder(
|
|
payPalOrder,
|
|
payment
|
|
);
|
|
await expect
|
|
.soft( String( wooCommerceOrderJson.transaction_id ) )
|
|
.toEqual( String( payPalPaymentId ) );
|
|
}
|
|
|
|
const expectedIntent = payment.isAuthorized
|
|
? 'AUTHORIZE'
|
|
: 'CAPTURE';
|
|
await expect.soft( payPalOrder.intent ).toEqual( expectedIntent );
|
|
|
|
switch ( fundingSource ) {
|
|
case 'oxxo': {
|
|
await expect.soft( payPalOrder.status ).toEqual( 'COMPLETED' );
|
|
await expect
|
|
.soft( payPalOrder.payment_source )
|
|
.toHaveProperty( 'oxxo' );
|
|
await expect
|
|
.soft( payPalOrder.payment_source.oxxo.email )
|
|
.toEqual( customer.email );
|
|
break;
|
|
}
|
|
|
|
case 'acdc':
|
|
case 'fastlane': {
|
|
await expect.soft( payPalOrder.status ).toEqual( 'COMPLETED' );
|
|
await expect
|
|
.soft( payPalOrder.payment_source )
|
|
.toHaveProperty( 'card' );
|
|
await expect
|
|
.soft( payPalOrder.payment_source.card.last_digits )
|
|
.toEqual(
|
|
getLast4CardDigits( payment.card.card_number )
|
|
);
|
|
break;
|
|
}
|
|
|
|
case 'card':
|
|
await expect.soft( payPalOrder.status ).toEqual( 'COMPLETED' );
|
|
await expect
|
|
.soft( payPalOrder.payment_source )
|
|
.toHaveProperty( 'paypal' );
|
|
await expect
|
|
.soft( payPalOrder.payment_source.paypal.email_address )
|
|
.toEqual( customer.email );
|
|
await expect
|
|
.soft( payPalOrder.payment_source.paypal.name.given_name )
|
|
.toEqual( customer.first_name );
|
|
await expect
|
|
.soft( payPalOrder.payment_source.paypal.name.surname )
|
|
.toEqual( customer.last_name );
|
|
break;
|
|
|
|
case 'pay_upon_invoice': {
|
|
await expect
|
|
.soft( payPalOrder.status )
|
|
.toEqual( 'PENDING_APPROVAL' );
|
|
const birthDate = payment.birthDate.split( '.' );
|
|
await expect
|
|
.soft( payPalOrder.payment_source )
|
|
.toHaveProperty( 'pay_upon_invoice' );
|
|
await expect
|
|
.soft(
|
|
payPalOrder.payment_source.pay_upon_invoice.birth_date
|
|
)
|
|
.toEqual(
|
|
`${ birthDate[ 2 ] }-${ birthDate[ 1 ] }-${ birthDate[ 0 ] }`
|
|
);
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
await expect.soft( payPalOrder.status ).toEqual( 'COMPLETED' );
|
|
await expect
|
|
.soft( payPalOrder.payment_source )
|
|
.toHaveProperty( 'paypal' );
|
|
if ( payment.isVaulted ) {
|
|
await expect
|
|
.soft( payPalOrder.payment_source.paypal )
|
|
.toHaveProperty( 'attributes' );
|
|
await expect
|
|
.soft( payPalOrder.payment_source.paypal.attributes )
|
|
.toHaveProperty( 'vault' );
|
|
await expect
|
|
.soft(
|
|
payPalOrder.payment_source.paypal.attributes.vault
|
|
.status
|
|
)
|
|
.toEqual( 'VAULTED' );
|
|
break;
|
|
}
|
|
await expect
|
|
.soft( payPalOrder.payment_source.paypal.email_address )
|
|
.toEqual( payment.payPalAccount.email );
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Asserts PayPal payment
|
|
*
|
|
* @param paymentId PayPal payment ID
|
|
* @param shopOrder
|
|
*/
|
|
assertPayment = async (
|
|
paymentId: string,
|
|
shopOrder: ShopOrder
|
|
) => {
|
|
const { payment, merchant } = shopOrder;
|
|
const fundingSource = payment.gateway.shortcut;
|
|
|
|
if ( fundingSource === 'pay_upon_invoice' ) {
|
|
return;
|
|
}
|
|
|
|
const payPalPayment = await this.getPayment( paymentId, merchant, payment.isAuthorized );
|
|
|
|
if ( fundingSource === 'oxxo' ) {
|
|
await expect.soft( payPalPayment.status ).toEqual( 'PENDING' );
|
|
return;
|
|
}
|
|
|
|
if ( payment.isAuthorized ) {
|
|
await expect.soft( payPalPayment.status ).toEqual( 'CREATED' );
|
|
return;
|
|
}
|
|
|
|
await expect.soft( payPalPayment.status ).toEqual( 'COMPLETED' );
|
|
};
|
|
}
|