Skip to content
Merged
7 changes: 4 additions & 3 deletions crates/api_models/src/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,16 @@ pub struct MerchantAccountCreate {
/// Refers to the hash key used for calculating the signature for webhooks and redirect response. If the value is not provided, a default value is used.
pub payment_response_hash_key: Option<String>,

/// A boolean value to indicate if redirect to merchant with http post needs to be enabled
/// A boolean value to indicate if redirect to merchant with http post needs to be enabled.
#[schema(default = false, example = true)]
pub redirect_to_merchant_with_http_post: Option<bool>,

/// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.
#[schema(value_type = Option<Object>, example = r#"{ "city": "NY", "unit": "245" }"#)]
pub metadata: Option<MerchantAccountMetadata>,

/// API key that will be used for server side API access
/// API key that will be used for client side API access. A publishable key has to be always paired with a `client_secret`.
/// A `client_secret` can be obtained by creating a payment with `confirm` set to false
#[schema(example = "AH3423bkjbkjdsfbkj")]
pub publishable_key: Option<String>,

Expand Down Expand Up @@ -195,7 +196,7 @@ pub struct MerchantAccountResponse {
#[schema(value_type = Option<String>,example = "NewAge Retailer")]
pub merchant_name: OptionalEncryptableName,

/// The URL to redirect after the completion of the operation
/// The URL to redirect after completion of the payment
#[schema(max_length = 255, example = "https://www.example.com/success")]
pub return_url: Option<String>,

Expand Down
8 changes: 4 additions & 4 deletions crates/router/src/compatibility/stripe/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,10 @@ pub enum StripeErrorCode {
#[error(error_type = StripeErrorType::InvalidRequestError, code = "token_already_used", message = "duplicate merchant account")]
DuplicateMerchantAccount,

#[error(error_type = StripeErrorType::InvalidRequestError, code = "token_already_used", message = "The merchant connector account with the specified profile_id '{profile_id}' and connector_name '{connector_name}' already exists in our records")]
#[error(error_type = StripeErrorType::InvalidRequestError, code = "token_already_used", message = "The merchant connector account with the specified profile_id '{profile_id}' and connector_label '{connector_label}' already exists in our records")]
DuplicateMerchantConnectorAccount {
profile_id: String,
connector_name: String,
connector_label: String,
},

#[error(error_type = StripeErrorType::InvalidRequestError, code = "token_already_used", message = "duplicate payment method")]
Expand Down Expand Up @@ -523,10 +523,10 @@ impl From<errors::ApiErrorResponse> for StripeErrorCode {
errors::ApiErrorResponse::DuplicateMerchantAccount => Self::DuplicateMerchantAccount,
errors::ApiErrorResponse::DuplicateMerchantConnectorAccount {
profile_id,
connector_name,
connector_label,
} => Self::DuplicateMerchantConnectorAccount {
profile_id,
connector_name,
connector_label,
},
errors::ApiErrorResponse::DuplicatePaymentMethod => Self::DuplicatePaymentMethod,
errors::ApiErrorResponse::PaymentBlockedError {
Expand Down
6 changes: 3 additions & 3 deletions crates/router/src/core/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -920,7 +920,7 @@ pub async fn create_payment_connector(
disabled,
metadata: req.metadata,
frm_configs,
connector_label: Some(connector_label),
connector_label: Some(connector_label.clone()),
business_country: req.business_country,
business_label: req.business_label.clone(),
business_sub_label: req.business_sub_label.clone(),
Expand Down Expand Up @@ -958,7 +958,7 @@ pub async fn create_payment_connector(
.to_duplicate_response(
errors::ApiErrorResponse::DuplicateMerchantConnectorAccount {
profile_id: profile_id.clone(),
connector_name: req.connector_name.to_string(),
connector_label,
},
)?;

Expand Down Expand Up @@ -1280,7 +1280,7 @@ pub async fn update_payment_connector(
.change_context(
errors::ApiErrorResponse::DuplicateMerchantConnectorAccount {
profile_id,
connector_name: request_connector_label.unwrap_or_default(),
connector_label: request_connector_label.unwrap_or_default(),
},
)
.attach_printable_lazy(|| {
Expand Down
4 changes: 2 additions & 2 deletions crates/router/src/core/errors/api_error_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,10 @@ pub enum ApiErrorResponse {
DuplicateMandate,
#[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The merchant account with the specified details already exists in our records")]
DuplicateMerchantAccount,
#[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The merchant connector account with the specified profile_id '{profile_id}' and connector_name '{connector_name}' already exists in our records")]
#[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The merchant connector account with the specified profile_id '{profile_id}' and connector_label '{connector_label}' already exists in our records")]
DuplicateMerchantConnectorAccount {
profile_id: String,
connector_name: String,
connector_label: String,
},
#[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The payment method with the specified details already exists in our records")]
DuplicatePaymentMethod,
Expand Down
8 changes: 4 additions & 4 deletions crates/router/src/core/errors/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ impl ErrorSwitch<api_models::errors::types::ApiErrorResponse> for ApiErrorRespon
connector,
reason,
status_code,
} => AER::ConnectorError(ApiError::new("CE", 0, format!("{code}: {message}"), Some(Extra {connector: Some(connector.clone()), reason: reason.clone(), ..Default::default()})), StatusCode::from_u16(*status_code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)),
} => AER::ConnectorError(ApiError::new("CE", 0, format!("{code}: {message}"), Some(Extra {connector: Some(connector.clone()), reason: reason.to_owned().map(Into::into), ..Default::default()})), StatusCode::from_u16(*status_code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)),
Self::PaymentAuthorizationFailed { data } => {
AER::BadRequest(ApiError::new("CE", 1, "Payment failed during authorization with connector. Retry payment", Some(Extra { data: data.clone(), ..Default::default()})))
}
Expand Down Expand Up @@ -133,8 +133,8 @@ impl ErrorSwitch<api_models::errors::types::ApiErrorResponse> for ApiErrorRespon
Self::DuplicateRefundRequest => AER::BadRequest(ApiError::new("HE", 1, "Duplicate refund request. Refund already attempted with the refund ID", None)),
Self::DuplicateMandate => AER::BadRequest(ApiError::new("HE", 1, "Duplicate mandate request. Mandate already attempted with the Mandate ID", None)),
Self::DuplicateMerchantAccount => AER::BadRequest(ApiError::new("HE", 1, "The merchant account with the specified details already exists in our records", None)),
Self::DuplicateMerchantConnectorAccount { profile_id, connector_name } => {
AER::BadRequest(ApiError::new("HE", 1, format!("The merchant connector account with the specified profile_id '{profile_id}' and connector_name '{connector_name}' already exists in our records"), None))
Self::DuplicateMerchantConnectorAccount { profile_id, connector_label: connector_name } => {
AER::BadRequest(ApiError::new("HE", 1, format!("The merchant connector account with the specified profile_id '{profile_id}' and connector_label '{connector_name}' already exists in our records"), None))
}
Self::DuplicatePaymentMethod => AER::BadRequest(ApiError::new("HE", 1, "The payment method with the specified details already exists in our records", None)),
Self::DuplicatePayment { payment_id } => {
Expand Down Expand Up @@ -187,7 +187,7 @@ impl ErrorSwitch<api_models::errors::types::ApiErrorResponse> for ApiErrorRespon
AER::BadRequest(ApiError::new("HE", 3, format!("This refund is not possible through Hyperswitch. Please raise the refund through {connector} dashboard"), None))
}
Self::MandateValidationFailed { reason } => {
AER::BadRequest(ApiError::new("HE", 3, "Mandate Validation Failed", Some(Extra { reason: Some(reason.clone()), ..Default::default() })))
AER::BadRequest(ApiError::new("HE", 3, "Mandate Validation Failed", Some(Extra { reason: Some(reason.to_owned()), ..Default::default() })))
}
Self::PaymentNotSucceeded => AER::BadRequest(ApiError::new("HE", 3, "The payment has not succeeded yet. Please pass a successful payment to initiate refund", None)),
Self::PaymentBlockedError {
Expand Down
8 changes: 4 additions & 4 deletions crates/router/src/core/payments/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -779,7 +779,7 @@ pub fn validate_mandate(
let req: api::MandateValidationFields = req.into();
match req.validate_and_get_mandate_type().change_context(
errors::ApiErrorResponse::MandateValidationFailed {
reason: "Expected one out of mandate_id and mandate_data but got both".to_string(),
reason: "Expected one out of mandate_id and mandate_data but got both".into(),
},
)? {
Some(api::MandateTransactionType::NewMandateTransaction) => {
Expand Down Expand Up @@ -950,7 +950,7 @@ pub fn verify_mandate_details(
.unwrap_or(true),
|| {
Err(report!(errors::ApiErrorResponse::MandateValidationFailed {
reason: "request amount is greater than mandate amount".to_string()
reason: "request amount is greater than mandate amount".into()
}))
},
),
Expand All @@ -963,7 +963,7 @@ pub fn verify_mandate_details(
.unwrap_or(false),
|| {
Err(report!(errors::ApiErrorResponse::MandateValidationFailed {
reason: "request amount is greater than mandate amount".to_string()
reason: "request amount is greater than mandate amount".into()
}))
},
),
Expand All @@ -975,7 +975,7 @@ pub fn verify_mandate_details(
.unwrap_or(false),
|| {
Err(report!(errors::ApiErrorResponse::MandateValidationFailed {
reason: "cross currency mandates not supported".to_string()
reason: "cross currency mandates not supported".into()
}))
},
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- This file should undo anything in `up.sql`
CREATE UNIQUE INDEX IF NOT EXISTS "merchant_connector_account_profile_id_connector_id_index" ON merchant_connector_account (profile_id, connector_name);

ALTER TABLE merchant_connector_account DROP CONSTRAINT IF EXISTS "merchant_connector_account_profile_id_connector_label_key";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Add the new index before deleting the old one, in case it fails.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- Your SQL goes here
ALTER TABLE merchant_connector_account
ADD UNIQUE (profile_id, connector_label);

DROP INDEX IF EXISTS "merchant_connector_account_profile_id_connector_id_index";
6 changes: 3 additions & 3 deletions openapi/openapi_spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -9392,7 +9392,7 @@
},
"redirect_to_merchant_with_http_post": {
"type": "boolean",
"description": "A boolean value to indicate if redirect to merchant with http post needs to be enabled",
"description": "A boolean value to indicate if redirect to merchant with http post needs to be enabled.",
"default": false,
"example": true,
"nullable": true
Expand All @@ -9404,7 +9404,7 @@
},
"publishable_key": {
"type": "string",
"description": "API key that will be used for server side API access",
"description": "API key that will be used for client side API access. A publishable key has to be always paired with a `client_secret`.\nA `client_secret` can be obtained by creating a payment with `confirm` set to false",
"example": "AH3423bkjbkjdsfbkj",
"nullable": true
},
Expand Down Expand Up @@ -9480,7 +9480,7 @@
},
"return_url": {
"type": "string",
"description": "The URL to redirect after the completion of the operation",
"description": "The URL to redirect after completion of the payment",
"example": "https://www.example.com/success",
"nullable": true,
"maxLength": 255
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"eventOrder": ["event.test.js"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Validate status 2xx
pm.test(
"[POST]::/accounts/:account_id/connectors - Status code is 2xx",
function () {
pm.response.to.be.success;
},
);

// Validate if response header has matching content-type
pm.test(
"[POST]::/accounts/:account_id/connectors - Content-Type is application/json",
function () {
pm.expect(pm.response.headers.get("Content-Type")).to.include(
"application/json",
);
},
);

// Set response object as internal variable
let jsonData = {};
try {
jsonData = pm.response.json();
} catch (e) { }

// pm.collectionVariables - Set merchant_connector_id as variable for jsonData.merchant_connector_id
if (jsonData?.merchant_connector_id) {
pm.collectionVariables.set(
"merchant_connector_id",
jsonData.merchant_connector_id,
);
console.log(
"- use {{merchant_connector_id}} as collection variable for value",
jsonData.merchant_connector_id,
);
} else {
console.log(
"INFO - Unable to assign variable {{merchant_connector_id}}, as jsonData.merchant_connector_id is undefined.",
);
}

// Validate if the connector label is the one that is passed in the request
pm.test(
"[POST]::/accounts/:account_id/connectors - connector_label is the same as that is passed in the request",
function () {
pm.expect(jsonData.connector_label).to.eql("first_stripe")
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
{
"auth": {
"type": "apikey",
"apikey": [
{
"key": "value",
"value": "{{admin_api_key}}",
"type": "string"
},
{
"key": "key",
"value": "api-key",
"type": "string"
},
{
"key": "in",
"value": "header",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Accept",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"options": {
"raw": {
"language": "json"
}
},
"raw_json_formatted": {
"connector_type": "fiz_operations",
"connector_name": "stripe",
"business_country": "US",
"business_label": "default",
"connector_label": "first_stripe",
"connector_account_details": {
"auth_type": "HeaderKey",
"api_key": "{{connector_api_key}}"
},
"test_mode": false,
"disabled": false,
"payment_methods_enabled": [
{
"payment_method": "card",
"payment_method_types": [
{
"payment_method_type": "credit",
"card_networks": ["Visa", "Mastercard"],
"minimum_amount": 1,
"maximum_amount": 68607706,
"recurring_enabled": true,
"installment_payment_enabled": true
},
{
"payment_method_type": "debit",
"card_networks": ["Visa", "Mastercard"],
"minimum_amount": 1,
"maximum_amount": 68607706,
"recurring_enabled": true,
"installment_payment_enabled": true
}
]
},
{
"payment_method": "pay_later",
"payment_method_types": [
{
"payment_method_type": "klarna",
"payment_experience": "redirect_to_url",
"minimum_amount": 1,
"maximum_amount": 68607706,
"recurring_enabled": true,
"installment_payment_enabled": true
},
{
"payment_method_type": "affirm",
"payment_experience": "redirect_to_url",
"minimum_amount": 1,
"maximum_amount": 68607706,
"recurring_enabled": true,
"installment_payment_enabled": true
},
{
"payment_method_type": "afterpay_clearpay",
"payment_experience": "redirect_to_url",
"minimum_amount": 1,
"maximum_amount": 68607706,
"recurring_enabled": true,
"installment_payment_enabled": true
}
]
}
],
"metadata": {
"city": "NY",
"unit": "245"
}
}
},
"url": {
"raw": "{{baseUrl}}/account/:account_id/connectors",
"host": ["{{baseUrl}}"],
"path": ["account", ":account_id", "connectors"],
"variable": [
{
"key": "account_id",
"value": "{{merchant_id}}",
"description": "(Required) The unique identifier for the merchant account"
}
]
},
"description": "Create a new Payment Connector for the merchant account. The connector could be a payment processor / facilitator / acquirer or specialised services like Fraud / Accounting etc."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"eventOrder": ["event.test.js"]
}
Loading
Loading