fix(connector): [Adyen] ErrorHandling in case of Balance Check for Gift Cards (#1976)

This commit is contained in:
Sakil Mostak
2023-11-28 17:32:53 +05:30
committed by GitHub
parent 77fc92c99a
commit bd889c834d
26 changed files with 661 additions and 121 deletions

View File

@ -14,11 +14,8 @@ use crate::{
configs::settings,
connector::utils as connector_utils,
consts,
core::{
self,
errors::{self, CustomResult},
},
headers, logger, routes,
core::errors::{self, CustomResult},
headers, logger,
services::{
self,
request::{self, Mask},
@ -560,7 +557,6 @@ impl
}
}
#[async_trait::async_trait]
impl
services::ConnectorIntegration<
api::Authorize,
@ -568,49 +564,6 @@ impl
types::PaymentsResponseData,
> for Adyen
{
async fn execute_pretasks(
&self,
router_data: &mut types::PaymentsAuthorizeRouterData,
app_state: &routes::AppState,
) -> CustomResult<(), errors::ConnectorError> {
match &router_data.request.payment_method_data {
api_models::payments::PaymentMethodData::GiftCard(gift_card_data) => {
match gift_card_data.as_ref() {
api_models::payments::GiftCardData::Givex(_) => {
let integ: Box<
&(dyn services::ConnectorIntegration<
api::Balance,
types::PaymentsAuthorizeData,
types::PaymentsResponseData,
> + Send
+ Sync
+ 'static),
> = Box::new(&Self);
let authorize_data = &types::PaymentsBalanceRouterData::from((
&router_data.to_owned(),
router_data.request.clone(),
));
let resp = services::execute_connector_processing_step(
app_state,
integ,
authorize_data,
core::payments::CallConnectorAction::Trigger,
None,
)
.await?;
router_data.payment_method_balance = resp.payment_method_balance;
Ok(())
}
_ => Ok(()),
}
}
_ => Ok(()),
}
}
fn get_headers(
&self,
req: &types::PaymentsAuthorizeRouterData,
@ -667,7 +620,6 @@ impl
req: &types::PaymentsAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
check_for_payment_method_balance(req)?;
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Post)
@ -725,28 +677,23 @@ impl
}
}
impl api::PaymentsPreProcessing for Adyen {}
impl
services::ConnectorIntegration<
api::Balance,
types::PaymentsAuthorizeData,
api::PreProcessing,
types::PaymentsPreProcessingData,
types::PaymentsResponseData,
> for Adyen
{
fn get_headers(
&self,
req: &types::PaymentsBalanceRouterData,
req: &types::PaymentsPreProcessingRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError>
where
Self: services::ConnectorIntegration<
api::Balance,
types::PaymentsAuthorizeData,
types::PaymentsResponseData,
>,
{
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
let mut header = vec![(
headers::CONTENT_TYPE.to_string(),
types::PaymentsBalanceType::get_content_type(self)
types::PaymentsPreProcessingType::get_content_type(self)
.to_string()
.into(),
)];
@ -757,7 +704,7 @@ impl
fn get_url(
&self,
_req: &types::PaymentsBalanceRouterData,
_req: &types::PaymentsPreProcessingRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(format!(
@ -768,7 +715,7 @@ impl
fn get_request_body(
&self,
req: &types::PaymentsBalanceRouterData,
req: &types::PaymentsPreProcessingRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<Option<types::RequestBody>, errors::ConnectorError> {
let connector_req = adyen::AdyenBalanceRequest::try_from(req)?;
@ -783,18 +730,20 @@ impl
fn build_request(
&self,
req: &types::PaymentsBalanceRouterData,
req: &types::PaymentsPreProcessingRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Post)
.url(&types::PaymentsBalanceType::get_url(self, req, connectors)?)
.attach_default_headers()
.headers(types::PaymentsBalanceType::get_headers(
.url(&types::PaymentsPreProcessingType::get_url(
self, req, connectors,
)?)
.body(types::PaymentsBalanceType::get_request_body(
.attach_default_headers()
.headers(types::PaymentsPreProcessingType::get_headers(
self, req, connectors,
)?)
.body(types::PaymentsPreProcessingType::get_request_body(
self, req, connectors,
)?)
.build(),
@ -803,13 +752,40 @@ impl
fn handle_response(
&self,
data: &types::PaymentsBalanceRouterData,
data: &types::PaymentsPreProcessingRouterData,
res: types::Response,
) -> CustomResult<types::PaymentsBalanceRouterData, errors::ConnectorError> {
) -> CustomResult<types::PaymentsPreProcessingRouterData, errors::ConnectorError> {
let response: adyen::AdyenBalanceResponse = res
.response
.parse_struct("AdyenBalanceResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
let currency = match data.request.currency {
Some(currency) => currency,
None => Err(errors::ConnectorError::MissingRequiredField {
field_name: "currency",
})?,
};
let amount = match data.request.amount {
Some(amount) => amount,
None => Err(errors::ConnectorError::MissingRequiredField {
field_name: "amount",
})?,
};
if response.balance.currency != currency || response.balance.value < amount {
Ok(types::RouterData {
response: Err(types::ErrorResponse {
code: consts::NO_ERROR_CODE.to_string(),
message: consts::NO_ERROR_MESSAGE.to_string(),
reason: Some(consts::LOW_BALANCE_ERROR_MESSAGE.to_string()),
status_code: res.status_code,
attempt_status: Some(enums::AttemptStatus::Failure),
connector_transaction_id: None,
}),
..data.clone()
})
} else {
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
@ -817,6 +793,7 @@ impl
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
}
fn get_error_response(
&self,
@ -1634,7 +1611,7 @@ impl api::IncomingWebhook for Adyen {
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
Ok(api::disputes::DisputePayload {
amount: notif.amount.value.to_string(),
currency: notif.amount.currency,
currency: notif.amount.currency.to_string(),
dispute_stage: api_models::enums::DisputeStage::from(notif.event_code.clone()),
connector_dispute_id: notif.psp_reference,
connector_reason: notif.reason,
@ -1646,27 +1623,3 @@ impl api::IncomingWebhook for Adyen {
})
}
}
pub fn check_for_payment_method_balance(
req: &types::PaymentsAuthorizeRouterData,
) -> CustomResult<(), errors::ConnectorError> {
match &req.request.payment_method_data {
api_models::payments::PaymentMethodData::GiftCard(gift_card) => match gift_card.as_ref() {
api_models::payments::GiftCardData::Givex(_) => {
let payment_method_balance = req
.payment_method_balance
.as_ref()
.ok_or(errors::ConnectorError::RequestEncodingFailed)?;
if payment_method_balance.currency != req.request.currency.to_string()
|| payment_method_balance.amount < req.request.amount
{
Err(errors::ConnectorError::InSufficientBalanceInPaymentMethod.into())
} else {
Ok(())
}
}
_ => Ok(()),
},
_ => Ok(()),
}
}

View File

@ -213,8 +213,8 @@ pub struct AdyenBalanceRequest<'a> {
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdyenBalanceResponse {
psp_reference: String,
balance: Amount,
pub psp_reference: String,
pub balance: Amount,
}
/// This implementation will be used only in Authorize, Automatic capture flow.
@ -397,8 +397,8 @@ pub enum ActionType {
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct Amount {
currency: String,
value: i64,
pub currency: storage_enums::Currency,
pub value: i64,
}
#[derive(Debug, Clone, Serialize)]
@ -1392,11 +1392,11 @@ impl<'a> TryFrom<&AdyenRouterData<&types::PaymentsAuthorizeRouterData>>
}
}
impl<'a> TryFrom<&types::PaymentsBalanceRouterData> for AdyenBalanceRequest<'a> {
impl<'a> TryFrom<&types::PaymentsPreProcessingRouterData> for AdyenBalanceRequest<'a> {
type Error = Error;
fn try_from(item: &types::PaymentsBalanceRouterData) -> Result<Self, Self::Error> {
fn try_from(item: &types::PaymentsPreProcessingRouterData) -> Result<Self, Self::Error> {
let payment_method = match &item.request.payment_method_data {
payments::PaymentMethodData::GiftCard(gift_card_data) => {
Some(payments::PaymentMethodData::GiftCard(gift_card_data)) => {
match gift_card_data.as_ref() {
payments::GiftCardData::Givex(gift_card_data) => {
let balance_pm = BalancePmData {
@ -1510,7 +1510,7 @@ fn get_channel_type(pm_type: &Option<storage_enums::PaymentMethodType>) -> Optio
fn get_amount_data(item: &AdyenRouterData<&types::PaymentsAuthorizeRouterData>) -> Amount {
Amount {
currency: item.router_data.request.currency.to_string(),
currency: item.router_data.request.currency,
value: item.amount.to_owned(),
}
}
@ -2857,12 +2857,24 @@ impl TryFrom<types::PaymentsCancelResponseRouterData<AdyenCancelResponse>>
}
}
impl TryFrom<types::PaymentsBalanceResponseRouterData<AdyenBalanceResponse>>
for types::PaymentsBalanceRouterData
impl<F>
TryFrom<
types::ResponseRouterData<
F,
AdyenBalanceResponse,
types::PaymentsPreProcessingData,
types::PaymentsResponseData,
>,
> for types::RouterData<F, types::PaymentsPreProcessingData, types::PaymentsResponseData>
{
type Error = Error;
fn try_from(
item: types::PaymentsBalanceResponseRouterData<AdyenBalanceResponse>,
item: types::ResponseRouterData<
F,
AdyenBalanceResponse,
types::PaymentsPreProcessingData,
types::PaymentsResponseData,
>,
) -> Result<Self, Self::Error> {
Ok(Self {
response: Ok(types::PaymentsResponseData::TransactionResponse {
@ -3457,7 +3469,7 @@ impl TryFrom<&AdyenRouterData<&types::PaymentsCaptureRouterData>> for AdyenCaptu
merchant_account: auth_type.merchant_account,
reference,
amount: Amount {
currency: item.router_data.request.currency.to_string(),
currency: item.router_data.request.currency,
value: item.amount.to_owned(),
},
})
@ -3547,7 +3559,7 @@ impl<F> TryFrom<&AdyenRouterData<&types::RefundsRouterData<F>>> for AdyenRefundR
Ok(Self {
merchant_account: auth_type.merchant_account,
amount: Amount {
currency: item.router_data.request.currency.to_string(),
currency: item.router_data.request.currency,
value: item.router_data.request.refund_amount,
},
merchant_refund_reason: item.router_data.request.reason.clone(),
@ -3629,7 +3641,7 @@ pub struct AdyenAdditionalDataWH {
#[derive(Debug, Deserialize)]
pub struct AdyenAmountWH {
pub value: i64,
pub currency: String,
pub currency: storage_enums::Currency,
}
#[derive(Clone, Debug, Deserialize, Serialize, strum::Display, PartialEq)]
@ -3955,7 +3967,7 @@ impl<F> TryFrom<&AdyenRouterData<&types::PayoutsRouterData<F>>> for AdyenPayoutE
)?;
Ok(Self {
amount: Amount {
currency: item.router_data.request.destination_currency.to_string(),
currency: item.router_data.request.destination_currency,
value: item.amount.to_owned(),
},
merchant_account: auth_type.merchant_account,
@ -4030,7 +4042,7 @@ impl<F> TryFrom<&AdyenRouterData<&types::PayoutsRouterData<F>>> for AdyenPayoutC
Ok(Self {
amount: Amount {
value: item.amount.to_owned(),
currency: item.router_data.request.destination_currency.to_string(),
currency: item.router_data.request.destination_currency,
},
recurring: RecurringContract {
contract: Contract::Payout,
@ -4077,7 +4089,7 @@ impl<F> TryFrom<&AdyenRouterData<&types::PayoutsRouterData<F>>> for AdyenPayoutF
Ok(Self::Card(Box::new(PayoutFulfillCardRequest {
amount: Amount {
value: item.amount.to_owned(),
currency: item.router_data.request.destination_currency.to_string(),
currency: item.router_data.request.destination_currency,
},
card: get_payout_card_details(&item.router_data.get_payout_method_data()?)
.map_or(

View File

@ -27,6 +27,7 @@ pub const DEFAULT_FULFILLMENT_TIME: i64 = 15 * 60;
pub(crate) const NO_ERROR_MESSAGE: &str = "No error message";
pub(crate) const NO_ERROR_CODE: &str = "No error code";
pub(crate) const UNSUPPORTED_ERROR_MESSAGE: &str = "Unsupported response type";
pub(crate) const LOW_BALANCE_ERROR_MESSAGE: &str = "Insufficient balance in the payment method";
pub(crate) const CONNECTOR_UNAUTHORIZED_ERROR: &str = "Authentication Error from the connector";
pub(crate) const CANNOT_CONTINUE_AUTH: &str =
"Cannot continue with Authorization due to failed Liability Shift.";

View File

@ -1408,6 +1408,17 @@ where
(router_data, should_continue_payment)
}
}
Some(api_models::payments::PaymentMethodData::GiftCard(_)) => {
if connector.connector_name == router_types::Connector::Adyen {
router_data = router_data.preprocessing_steps(state, connector).await?;
let is_error_in_response = router_data.response.is_err();
// If is_error_in_response is true, should_continue_payment should be false, we should throw the error
(router_data, !is_error_in_response)
} else {
(router_data, should_continue_payment)
}
}
Some(api_models::payments::PaymentMethodData::BankDebit(_)) => {
if connector.connector_name == router_types::Connector::Gocardless {
router_data = router_data.preprocessing_steps(state, connector).await?;

View File

@ -832,7 +832,6 @@ impl<const T: u8>
default_imp_for_pre_processing_steps!(
connector::Aci,
connector::Adyen,
connector::Airwallex,
connector::Authorizedotnet,
connector::Bambora,

View File

@ -323,7 +323,7 @@ pub struct ApplePayCryptogramData {
#[derive(Debug, Clone)]
pub struct PaymentMethodBalance {
pub amount: i64,
pub currency: String,
pub currency: storage_enums::Currency,
}
#[cfg(feature = "payouts")]

View File

@ -0,0 +1,3 @@
{
"childrenOrder": ["Payments - Create", "Payments - Retrieve"]
}

View File

@ -0,0 +1,71 @@
// Validate status 2xx
pm.test("[POST]::/payments - Status code is 2xx", function () {
pm.response.to.be.success;
});
// Validate if response header has matching content-type
pm.test("[POST]::/payments - Content-Type is application/json", function () {
pm.expect(pm.response.headers.get("Content-Type")).to.include(
"application/json",
);
});
// Validate if response has JSON Body
pm.test("[POST]::/payments - Response has JSON Body", function () {
pm.response.to.have.jsonBody();
});
// Set response object as internal variable
let jsonData = {};
try {
jsonData = pm.response.json();
} catch (e) {}
// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id
if (jsonData?.payment_id) {
pm.collectionVariables.set("payment_id", jsonData.payment_id);
console.log(
"- use {{payment_id}} as collection variable for value",
jsonData.payment_id,
);
} else {
console.log(
"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.",
);
}
// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id
if (jsonData?.mandate_id) {
pm.collectionVariables.set("mandate_id", jsonData.mandate_id);
console.log(
"- use {{mandate_id}} as collection variable for value",
jsonData.mandate_id,
);
} else {
console.log(
"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.",
);
}
// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret
if (jsonData?.client_secret) {
pm.collectionVariables.set("client_secret", jsonData.client_secret);
console.log(
"- use {{client_secret}} as collection variable for value",
jsonData.client_secret,
);
} else {
console.log(
"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.",
);
}
// Response body should have value "succeeded" for "status"
if (jsonData?.status) {
pm.test(
"[POST]::/payments - Content check if value for 'status' matches 'succeeded'",
function () {
pm.expect(jsonData.status).to.eql("succeeded");
},
);
}

View File

@ -0,0 +1,88 @@
{
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Accept",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"options": {
"raw": {
"language": "json"
}
},
"raw_json_formatted": {
"amount": 1100,
"currency": "EUR",
"confirm": true,
"capture_method": "automatic",
"capture_on": "2022-09-10T10:11:12Z",
"amount_to_capture": 1100,
"customer_id": "StripeCustomer",
"email": "guest@example.com",
"name": "John Doe",
"phone": "999999999",
"phone_country_code": "+65",
"description": "Its my first payment request",
"authentication_type": "no_three_ds",
"return_url": "https://duck.com",
"payment_method": "gift_card",
"payment_method_type": "givex",
"payment_method_data": {
"gift_card": {
"givex": {
"number": "6364530000000000",
"cvc": "122222"
}
}
},
"routing": {
"type": "single",
"data": "adyen"
},
"billing": {
"address": {
"line1": "1467",
"line2": "Harrison Street",
"line3": "Harrison Street",
"city": "San Fransico",
"state": "California",
"zip": "94122",
"country": "US",
"first_name": "PiX"
}
},
"shipping": {
"address": {
"line1": "1467",
"line2": "Harrison Street",
"line3": "Harrison Street",
"city": "San Fransico",
"state": "California",
"zip": "94122",
"country": "US",
"first_name": "PiX"
}
},
"statement_descriptor_name": "joseph",
"statement_descriptor_suffix": "JS",
"metadata": {
"udf1": "value1",
"new_customer": "true",
"login_date": "2019-09-10T10:11:12Z"
}
}
},
"url": {
"raw": "{{baseUrl}}/payments",
"host": ["{{baseUrl}}"],
"path": ["payments"]
},
"description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture"
}

View File

@ -0,0 +1,71 @@
// Validate status 2xx
pm.test("[GET]::/payments/:id - Status code is 2xx", function () {
pm.response.to.be.success;
});
// Validate if response header has matching content-type
pm.test("[GET]::/payments/:id - Content-Type is application/json", function () {
pm.expect(pm.response.headers.get("Content-Type")).to.include(
"application/json",
);
});
// Validate if response has JSON Body
pm.test("[GET]::/payments/:id - Response has JSON Body", function () {
pm.response.to.have.jsonBody();
});
// Set response object as internal variable
let jsonData = {};
try {
jsonData = pm.response.json();
} catch (e) {}
// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id
if (jsonData?.payment_id) {
pm.collectionVariables.set("payment_id", jsonData.payment_id);
console.log(
"- use {{payment_id}} as collection variable for value",
jsonData.payment_id,
);
} else {
console.log(
"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.",
);
}
// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id
if (jsonData?.mandate_id) {
pm.collectionVariables.set("mandate_id", jsonData.mandate_id);
console.log(
"- use {{mandate_id}} as collection variable for value",
jsonData.mandate_id,
);
} else {
console.log(
"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.",
);
}
// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret
if (jsonData?.client_secret) {
pm.collectionVariables.set("client_secret", jsonData.client_secret);
console.log(
"- use {{client_secret}} as collection variable for value",
jsonData.client_secret,
);
} else {
console.log(
"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.",
);
}
// Response body should have value "Succeeded" for "status"
if (jsonData?.status) {
pm.test(
"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'",
function () {
pm.expect(jsonData.status).to.eql("succeeded");
},
);
}

View File

@ -0,0 +1,28 @@
{
"method": "GET",
"header": [
{
"key": "Accept",
"value": "application/json"
}
],
"url": {
"raw": "{{baseUrl}}/payments/:id?force_sync=true",
"host": ["{{baseUrl}}"],
"path": ["payments", ":id"],
"query": [
{
"key": "force_sync",
"value": "true"
}
],
"variable": [
{
"key": "id",
"value": "{{payment_id}}",
"description": "(Required) unique payment id"
}
]
},
"description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment"
}

View File

@ -189,6 +189,18 @@
"installment_payment_enabled": true
}
]
},
{
"payment_method": "gift_card",
"payment_method_types": [
{
"payment_method_type": "givex",
"minimum_amount": 1,
"maximum_amount": 68607706,
"recurring_enabled": true,
"installment_payment_enabled": true
}
]
},
{
"payment_method": "bank_redirect",

View File

@ -43,6 +43,10 @@
"card_cvc": "7373"
}
},
"routing": {
"type": "single",
"data": "adyen"
},
"billing": {
"address": {
"line1": "1467",

View File

@ -0,0 +1,3 @@
{
"childrenOrder": ["Payments - Create", "Payments - Retrieve"]
}

View File

@ -0,0 +1,81 @@
// Validate status 2xx
pm.test("[POST]::/payments - Status code is 2xx", function () {
pm.response.to.be.success;
});
// Validate if response header has matching content-type
pm.test("[POST]::/payments - Content-Type is application/json", function () {
pm.expect(pm.response.headers.get("Content-Type")).to.include(
"application/json",
);
});
// Validate if response has JSON Body
pm.test("[POST]::/payments - Response has JSON Body", function () {
pm.response.to.have.jsonBody();
});
// Set response object as internal variable
let jsonData = {};
try {
jsonData = pm.response.json();
} catch (e) {}
// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id
if (jsonData?.payment_id) {
pm.collectionVariables.set("payment_id", jsonData.payment_id);
console.log(
"- use {{payment_id}} as collection variable for value",
jsonData.payment_id,
);
} else {
console.log(
"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.",
);
}
// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id
if (jsonData?.mandate_id) {
pm.collectionVariables.set("mandate_id", jsonData.mandate_id);
console.log(
"- use {{mandate_id}} as collection variable for value",
jsonData.mandate_id,
);
} else {
console.log(
"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.",
);
}
// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret
if (jsonData?.client_secret) {
pm.collectionVariables.set("client_secret", jsonData.client_secret);
console.log(
"- use {{client_secret}} as collection variable for value",
jsonData.client_secret,
);
} else {
console.log(
"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.",
);
}
// Response body should have value "failed" for "status"
if (jsonData?.status) {
pm.test(
"[POST]::/payments - Content check if value for 'status' matches 'failed'",
function () {
pm.expect(jsonData.status).to.eql("failed");
},
);
}
// Response body should have error message as "Insufficient balance in the payment method"
if (jsonData?.error_message) {
pm.test(
"[POST]::/payments - Content check if value for 'error_message' matches 'Insufficient balance in the payment method'",
function () {
pm.expect(jsonData.error_message).to.eql("Insufficient balance in the payment method");
},
);
}

View File

@ -0,0 +1,88 @@
{
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Accept",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"options": {
"raw": {
"language": "json"
}
},
"raw_json_formatted": {
"amount": 14100,
"currency": "EUR",
"confirm": true,
"capture_method": "automatic",
"capture_on": "2022-09-10T10:11:12Z",
"amount_to_capture": 14100,
"customer_id": "StripeCustomer",
"email": "guest@example.com",
"name": "John Doe",
"phone": "999999999",
"phone_country_code": "+65",
"description": "Its my first payment request",
"authentication_type": "no_three_ds",
"return_url": "https://duck.com",
"payment_method": "gift_card",
"payment_method_type": "givex",
"payment_method_data": {
"gift_card": {
"givex": {
"number": "6364530000000000",
"cvc": "122222"
}
}
},
"routing": {
"type": "single",
"data": "adyen"
},
"billing": {
"address": {
"line1": "1467",
"line2": "Harrison Street",
"line3": "Harrison Street",
"city": "San Fransico",
"state": "California",
"zip": "94122",
"country": "US",
"first_name": "PiX"
}
},
"shipping": {
"address": {
"line1": "1467",
"line2": "Harrison Street",
"line3": "Harrison Street",
"city": "San Fransico",
"state": "California",
"zip": "94122",
"country": "US",
"first_name": "PiX"
}
},
"statement_descriptor_name": "joseph",
"statement_descriptor_suffix": "JS",
"metadata": {
"udf1": "value1",
"new_customer": "true",
"login_date": "2019-09-10T10:11:12Z"
}
}
},
"url": {
"raw": "{{baseUrl}}/payments",
"host": ["{{baseUrl}}"],
"path": ["payments"]
},
"description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture"
}

View File

@ -0,0 +1,71 @@
// Validate status 2xx
pm.test("[GET]::/payments/:id - Status code is 2xx", function () {
pm.response.to.be.success;
});
// Validate if response header has matching content-type
pm.test("[GET]::/payments/:id - Content-Type is application/json", function () {
pm.expect(pm.response.headers.get("Content-Type")).to.include(
"application/json",
);
});
// Validate if response has JSON Body
pm.test("[GET]::/payments/:id - Response has JSON Body", function () {
pm.response.to.have.jsonBody();
});
// Set response object as internal variable
let jsonData = {};
try {
jsonData = pm.response.json();
} catch (e) {}
// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id
if (jsonData?.payment_id) {
pm.collectionVariables.set("payment_id", jsonData.payment_id);
console.log(
"- use {{payment_id}} as collection variable for value",
jsonData.payment_id,
);
} else {
console.log(
"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.",
);
}
// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id
if (jsonData?.mandate_id) {
pm.collectionVariables.set("mandate_id", jsonData.mandate_id);
console.log(
"- use {{mandate_id}} as collection variable for value",
jsonData.mandate_id,
);
} else {
console.log(
"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.",
);
}
// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret
if (jsonData?.client_secret) {
pm.collectionVariables.set("client_secret", jsonData.client_secret);
console.log(
"- use {{client_secret}} as collection variable for value",
jsonData.client_secret,
);
} else {
console.log(
"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.",
);
}
// Response body should have value "Failed" for "status"
if (jsonData?.status) {
pm.test(
"[POST]::/payments/:id - Content check if value for 'status' matches 'failed'",
function () {
pm.expect(jsonData.status).to.eql("failed");
},
);
}

View File

@ -0,0 +1,28 @@
{
"method": "GET",
"header": [
{
"key": "Accept",
"value": "application/json"
}
],
"url": {
"raw": "{{baseUrl}}/payments/:id?force_sync=true",
"host": ["{{baseUrl}}"],
"path": ["payments", ":id"],
"query": [
{
"key": "force_sync",
"value": "true"
}
],
"variable": [
{
"key": "id",
"value": "{{payment_id}}",
"description": "(Required) unique payment id"
}
]
},
"description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment"
}