diff --git a/config/development.toml b/config/development.toml index a33286abed..fda63c7b71 100644 --- a/config/development.toml +++ b/config/development.toml @@ -58,6 +58,7 @@ psync = "GW_TXN_SYNC" connectors_with_webhook_source_verification_call = "paypal, truelayer" [connectors] +grabpay.base_url = "https://partner-api.grab.com/grabpay/partner/v2" maya.base_url = "https://pg-sandbox.paymaya.com" tesouro.base_url = "https://api.sandbox.tesouro.com" kount.base_url = "https://api-sandbox.kount.com" diff --git a/config/production.toml b/config/production.toml index 7b2766c983..f2ca87f988 100644 --- a/config/production.toml +++ b/config/production.toml @@ -23,6 +23,7 @@ connector_request_timeout = 30 bypass_urls = ["localhost", "local"] [connectors] +grabpay.base_url = "https://partner-api.grab.com/grabpay/partner/v2" maya.base_url = "https://pg.maya.ph" tesouro.base_url = "https://api.tesouro.com" kount.base_url = "https://api.kount.com" diff --git a/config/sandbox.toml b/config/sandbox.toml index cac865e7a1..ab65aa108d 100644 --- a/config/sandbox.toml +++ b/config/sandbox.toml @@ -23,6 +23,7 @@ connector_request_timeout = 30 bypass_urls = ["localhost", "local"] [connectors] +grabpay.base_url = "https://partner-api.stg-myteksi.com/grabpay/partner/v2" maya.base_url = "https://pg-sandbox.paymaya.com" tesouro.base_url = "https://api.sandbox.tesouro.com" kount.base_url = "https://api-sandbox.kount.com" diff --git a/crates/common/common_enums/src/enums.rs b/crates/common/common_enums/src/enums.rs index 6ba3f6d0b2..528aedc53b 100644 --- a/crates/common/common_enums/src/enums.rs +++ b/crates/common/common_enums/src/enums.rs @@ -1052,6 +1052,7 @@ pub enum PaymentMethodType { Giropay, Givex, GooglePay, + Grabpay, GoPay, Gcash, Ideal, @@ -1143,6 +1144,7 @@ impl PaymentMethodType { match self { Self::ApplePay => "Apple Pay".to_string(), Self::GooglePay => "Google Pay".to_string(), + Self::Grabpay => "GrabPay".to_string(), Self::SamsungPay => "Samsung Pay".to_string(), Self::AliPay => "AliPay".to_string(), Self::WeChatPay => "WeChat Pay".to_string(), diff --git a/crates/ffi/ffi/src/services/payments.rs b/crates/ffi/ffi/src/services/payments.rs index 5443bab2f0..45dbb5cf71 100644 --- a/crates/ffi/ffi/src/services/payments.rs +++ b/crates/ffi/ffi/src/services/payments.rs @@ -1015,6 +1015,8 @@ pub fn verify_redirect_response_transformer( use domain_types::utils::ForeignTryFrom as _; use interfaces::verification::ConnectorSourceVerificationSecrets; + let connector_feature_data = payload.connector_feature_data; + let request_details_proto = payload.request_details.ok_or_else(|| { Box::new(ConnectorError { error_message: "Missing required field: request_details".to_string(), @@ -1073,7 +1075,7 @@ pub fn verify_redirect_response_transformer( let redirect_details = connector_data .connector - .process_redirect_response(&updated_request_details) + .process_redirect_response(&updated_request_details, connector_feature_data.as_ref()) .map_err(|e| { Box::new(ConnectorError { error_message: format!("{e}"), diff --git a/crates/grpc-server/grpc-server/src/server/payments.rs b/crates/grpc-server/grpc-server/src/server/payments.rs index 1ccdd45768..05b70d1576 100644 --- a/crates/grpc-server/grpc-server/src/server/payments.rs +++ b/crates/grpc-server/grpc-server/src/server/payments.rs @@ -1381,6 +1381,7 @@ impl PaymentService for Payments { .transpose() .map_err(|e| e.to_grpc_error())? .map(ConnectorSourceVerificationSecrets::RedirectResponseSecret); + let connector_feature_data = payload.connector_feature_data; let connector_data: ConnectorData = ConnectorData::from_connector_variant(&connector) @@ -1433,6 +1434,7 @@ impl PaymentService for Payments { .connector .process_redirect_response( &updated_request_details, + connector_feature_data.as_ref(), ) .to_grpc_error()?; diff --git a/crates/integrations/connector-integration/src/connectors.rs b/crates/integrations/connector-integration/src/connectors.rs index caefe739dc..af6acd72aa 100644 --- a/crates/integrations/connector-integration/src/connectors.rs +++ b/crates/integrations/connector-integration/src/connectors.rs @@ -294,5 +294,8 @@ pub use self::kount::Kount; pub mod givepayments; pub use self::givepayments::Givepayments; + +pub mod grabpay; +pub use self::grabpay::Grabpay; pub mod tesouro; pub use self::tesouro::Tesouro; diff --git a/crates/integrations/connector-integration/src/connectors/aci/transformers.rs b/crates/integrations/connector-integration/src/connectors/aci/transformers.rs index a40d6550a4..a432601f9b 100644 --- a/crates/integrations/connector-integration/src/connectors/aci/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/aci/transformers.rs @@ -195,6 +195,7 @@ impl | WalletData::ApplePay(_) | WalletData::ApplePayThirdPartySdk(_) | WalletData::DanaRedirect { .. } + | WalletData::GrabpayRedirect { .. } | WalletData::GooglePay(_) | WalletData::BluecodeRedirect {} | WalletData::GooglePayThirdPartySdk(_) diff --git a/crates/integrations/connector-integration/src/connectors/adyen/transformers.rs b/crates/integrations/connector-integration/src/connectors/adyen/transformers.rs index d117f8cc3f..805b406db8 100644 --- a/crates/integrations/connector-integration/src/connectors/adyen/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/adyen/transformers.rs @@ -1576,6 +1576,7 @@ impl Err(IntegrationError::NotImplemented( ("payment_method").into(), Default::default(), diff --git a/crates/integrations/connector-integration/src/connectors/authorizedotnet.rs b/crates/integrations/connector-integration/src/connectors/authorizedotnet.rs index ed747fa3aa..4eaac33af6 100644 --- a/crates/integrations/connector-integration/src/connectors/authorizedotnet.rs +++ b/crates/integrations/connector-integration/src/connectors/authorizedotnet.rs @@ -89,7 +89,10 @@ impl bool { + fn should_do_session_token( + &self, + _connector_feature_data: Option<&hyperswitch_masking::Secret>, + ) -> bool { true } } diff --git a/crates/integrations/connector-integration/src/connectors/bankofamerica/transformers.rs b/crates/integrations/connector-integration/src/connectors/bankofamerica/transformers.rs index a8b76d8f9f..120819fedf 100644 --- a/crates/integrations/connector-integration/src/connectors/bankofamerica/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/bankofamerica/transformers.rs @@ -704,6 +704,7 @@ impl &'static str { WalletData::ApplePayRedirect(_) => "apple_pay_redirect", WalletData::ApplePayThirdPartySdk(_) => "apple_pay_third_party_sdk", WalletData::DanaRedirect {} => "dana_redirect", + WalletData::GrabpayRedirect {} => "grabpay_redirect", WalletData::GooglePay(_) => "google_pay", WalletData::GooglePayRedirect(_) => "google_pay_redirect", WalletData::GooglePayThirdPartySdk(_) => "google_pay_third_party_sdk", diff --git a/crates/integrations/connector-integration/src/connectors/cybersource/transformers.rs b/crates/integrations/connector-integration/src/connectors/cybersource/transformers.rs index 65ada7fae7..f1e261f0f2 100644 --- a/crates/integrations/connector-integration/src/connectors/cybersource/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/cybersource/transformers.rs @@ -290,6 +290,7 @@ impl fn process_redirect_response( &self, _request: &RequestDetails, + _connector_feature_data: Option<&hyperswitch_masking::Secret>, ) -> CustomResult { Ok(RedirectDetailsResponse { resource_id: None, @@ -112,6 +113,7 @@ impl error_reason: None, response_amount: None, raw_connector_response: None, + connector_feature_data: None, }) } } diff --git a/crates/integrations/connector-integration/src/connectors/grabpay.rs b/crates/integrations/connector-integration/src/connectors/grabpay.rs new file mode 100644 index 0000000000..937400e3ff --- /dev/null +++ b/crates/integrations/connector-integration/src/connectors/grabpay.rs @@ -0,0 +1,1183 @@ +pub mod transformers; + +use std::{fmt::Debug, sync::LazyLock}; + +use base64::Engine; +use common_enums::{enums, CurrencyUnit, PaymentMethodType}; +use common_utils::{ + consts::{BASE64_ENGINE_URL_SAFE_NO_PAD, NO_ERROR_CODE, NO_ERROR_MESSAGE}, + crypto, + errors::CustomResult, + events, + ext_traits::ByteSliceExt, +}; +use domain_types::{ + connector_flow::{ + Authenticate, Authorize, PSync, RSync, Refund, ServerSessionAuthenticationToken, + }, + connector_types::{ConnectorSpecifications, SupportedPaymentMethodsExt}, + connector_types::{ + ConnectorWebhookSecrets, EventContext, EventType, PaymentFlowData, + PaymentsAuthenticateData, PaymentsAuthorizeData, PaymentsResponseData, PaymentsSyncData, + RedirectDetailsResponse, RefundFlowData, RefundSyncData, RefundWebhookDetailsResponse, + RefundsData, RefundsResponseData, RequestDetails, ResponseId, + ServerSessionAuthenticationTokenRequestData, ServerSessionAuthenticationTokenResponseData, + WebhookDetailsResponse, WebhookResourceReference, + }, + errors::{self, IntegrationError, IntegrationErrorContext, WebhookError}, + merchant_authentication_flow_data::MerchantAuthenticationFlowData, + payment_method_data::PaymentMethodDataTypes, + router_data::{ConnectorSpecificConfig, ErrorResponse}, + router_data_v2::RouterDataV2, + router_response_types::Response, + types::{ + self, ConnectorInfo, Connectors, FeatureStatus, PaymentMethodDetails, + SupportedPaymentMethods, + }, +}; +use error_stack::ResultExt; +use hyperswitch_masking::{Mask, Maskable, PeekInterface, Secret}; +use interfaces::{ + api::ConnectorCommon, + connector_integration_v2::ConnectorIntegrationV2, + connector_types, + decode::BodyDecoding, + verification::{ConnectorSourceVerificationSecrets, SourceVerification}, +}; +use serde::Serialize; +use transformers::{ + self as grabpay, GrabpayAuthenticateRequest, GrabpayAuthenticateResponse, + GrabpayAuthorizeRequest, GrabpayAuthorizeResponse, GrabpayChargeCompleteResponse, + GrabpayRefundRequest, GrabpayRefundResponse, GrabpayRefundSyncResponse, + GrabpayServerSessionAuthenticationTokenRequest, + GrabpayServerSessionAuthenticationTokenResponse, GrabpayWebhookBody, +}; + +use super::macros; +use crate::{types::ResponseRouterData, utils, with_error_response_body}; + +const CONTENT_TYPE: &str = "application/json"; +const CHARGE_INIT_PATH: &str = "/charge/init"; +const CHARGE_COMPLETE_PATH: &str = "/charge/complete"; +const CHARGE_STATUS_PREFIX: &str = "/charge"; +const REFUND_PATH: &str = "/refund"; +const OAUTH_TOKEN_PATH: &str = "/grabid/v1/oauth2/token"; +pub(crate) const GRABPAY_DOC_URL: &str = + "https://developers.grab.com/docs/grabpay-online-integration"; +pub(crate) const GRABPAY_CONFIG_SUGGESTED_ACTION: &str = + "Verify the GrabPay connector configuration (partner_id, partner_secret, client_id, \ + client_secret, merchant_id, base_url) and the request payload, then retry."; + +pub const BASE64_ENGINE: base64::engine::GeneralPurpose = base64::engine::general_purpose::STANDARD; + +pub(crate) mod headers { + pub(crate) const AUTHORIZATION: &str = "Authorization"; + pub(crate) const CONTENT_TYPE: &str = "Content-Type"; + pub(crate) const DATE: &str = "Date"; +} + +fn grabpay_integration_context(additional_context: impl Into) -> IntegrationErrorContext { + IntegrationErrorContext { + suggested_action: Some(GRABPAY_CONFIG_SUGGESTED_ACTION.to_string()), + doc_url: Some(GRABPAY_DOC_URL.to_string()), + additional_context: Some(additional_context.into()), + } +} + +macros::create_all_prerequisites!( + connector_name: Grabpay, + generic_type: T, + api: [ + ( + flow: Authorize, + request_body: GrabpayAuthorizeRequest, + response_body: GrabpayAuthorizeResponse, + router_data: RouterDataV2, PaymentsResponseData>, + ), + ( + flow: PSync, + response_body: GrabpayChargeCompleteResponse, + router_data: RouterDataV2, + ), + ( + flow: Refund, + request_body: GrabpayRefundRequest, + response_body: GrabpayRefundResponse, + router_data: RouterDataV2, + ), + ( + flow: RSync, + response_body: GrabpayRefundSyncResponse, + router_data: RouterDataV2, + ), + ( + flow: ServerSessionAuthenticationToken, + request_body: GrabpayServerSessionAuthenticationTokenRequest, + response_body: GrabpayServerSessionAuthenticationTokenResponse, + router_data: RouterDataV2, + ), + ( + flow: Authenticate, + request_body: GrabpayAuthenticateRequest, + response_body: GrabpayAuthenticateResponse, + router_data: RouterDataV2, PaymentsResponseData>, + ) + ], + amount_converters: [], + member_functions: { + pub fn build_json_headers( + &self, + ) -> Vec<(String, Maskable)> { + vec![( + headers::CONTENT_TYPE.to_string(), + self.common_get_content_type().to_string().into(), + )] + } + + pub fn connector_base_url_payments<'a, F, Req, Res>( + &self, + req: &'a RouterDataV2, + ) -> &'a str { + &req.resource_common_data.connectors.grabpay.base_url + } + + pub fn connector_base_url_refunds<'a, F, Req, Res>( + &self, + req: &'a RouterDataV2, + ) -> &'a str { + &req.resource_common_data.connectors.grabpay.base_url + } + + pub fn build_hmac_headers( + &self, + auth: &grabpay::GrabpayAuthType, + method: &str, + path: &str, + body: &[u8], + ) -> CustomResult)>, IntegrationError> { + let date = format_rfc7231_date(time::OffsetDateTime::now_utc())?; + let authorization = + build_hmac_authorization(auth, method, CONTENT_TYPE, path, body, &date)?; + + let mut headers = self.build_json_headers(); + headers.push((headers::DATE.to_string(), date.into())); + headers.push((headers::AUTHORIZATION.to_string(), authorization.into_masked())); + Ok(headers) + } + + pub fn build_pop_headers( + &self, + auth: &grabpay::GrabpayAuthType, + access_token: &str, + ) -> CustomResult)>, IntegrationError> { + let now = time::OffsetDateTime::now_utc(); + let date = format_rfc7231_date(now)?; + let timestamp = now.unix_timestamp().to_string(); + let pop = build_pop_signature(auth, access_token, ×tamp)?; + + let mut headers = self.build_json_headers(); + headers.push((headers::DATE.to_string(), date.into())); + headers.push(( + headers::AUTHORIZATION.to_string(), + format!("Bearer {access_token}").into_masked(), + )); + headers.push(("X-GID-AUX-POP".to_string(), pop.into_masked())); + Ok(headers) + } + } +); + +pub(crate) fn oauth_endpoint(base_url: &str, path: &str) -> String { + url::Url::parse(base_url) + .ok() + .and_then(|url| { + let host = url.host_str()?; + let port = url + .port() + .map(|port| format!(":{port}")) + .unwrap_or_default(); + Some(format!("{}://{host}{port}{path}", url.scheme())) + }) + .unwrap_or_else(|| format!("{base_url}{path}")) +} + +/// Builds GrabPay's canonical signing string: +/// `{method}\n{content_type}\n{date}\n{path}\n{base64(sha256(body))}\n`. +fn grabpay_hmac_signing_string( + method: &str, + content_type: &str, + path: &str, + body: &[u8], + date: &str, +) -> CustomResult { + use common_utils::crypto::GenerateDigest; + + let body_digest = crypto::Sha256.generate_digest(body).change_context( + IntegrationError::RequestEncodingFailed { + context: grabpay_integration_context( + "GrabPay HMAC signing failed to hash request body", + ), + }, + )?; + let encoded_body_digest = BASE64_ENGINE.encode(body_digest); + Ok(format!( + "{method}\n{content_type}\n{date}\n{path}\n{encoded_body_digest}\n" + )) +} + +fn build_hmac_authorization( + auth: &grabpay::GrabpayAuthType, + method: &str, + content_type: &str, + path: &str, + body: &[u8], + date: &str, +) -> CustomResult { + use common_utils::crypto::SignMessage; + + let signing_string = grabpay_hmac_signing_string(method, content_type, path, body, date)?; + let signature = crypto::HmacSha256 + .sign_message( + auth.partner_secret.peek().as_bytes(), + signing_string.as_bytes(), + ) + .change_context(IntegrationError::RequestEncodingFailed { + context: grabpay_integration_context( + "GrabPay HMAC signing failed to sign canonical request", + ), + })?; + + Ok(format!( + "{}:{}", + auth.partner_id.peek(), + BASE64_ENGINE.encode(signature) + )) +} + +fn get_webhook_header<'a>( + headers: &'a std::collections::HashMap, + header_name: &'static str, +) -> Result<&'a str, error_stack::Report> { + headers + .iter() + .find_map(|(key, value)| { + key.eq_ignore_ascii_case(header_name) + .then_some(value.as_str()) + }) + .ok_or_else(|| { + error_stack::report!(WebhookError::WebhookMissingRequiredField { field: header_name }) + }) +} + +fn grabpay_webhook_path(uri: Option<&str>) -> Result> { + let uri = uri.ok_or_else(|| { + error_stack::report!(WebhookError::WebhookMissingRequiredField { field: "uri" }) + })?; + + if let Ok(url) = url::Url::parse(uri) { + return Ok(url.path().to_string()); + } + + Ok(uri.split('?').next().unwrap_or(uri).to_string()) +} + +fn build_pop_signature( + auth: &grabpay::GrabpayAuthType, + access_token: &str, + timestamp: &str, +) -> CustomResult { + use common_utils::crypto::SignMessage; + + let signing_message = format!("{timestamp}{access_token}"); + let signature = crypto::HmacSha256 + .sign_message( + auth.client_secret.peek().as_bytes(), + signing_message.as_bytes(), + ) + .change_context(IntegrationError::RequestEncodingFailed { + context: grabpay_integration_context("GrabPay PoP signing failed to sign token proof"), + })?; + let sig = BASE64_ENGINE_URL_SAFE_NO_PAD.encode(signature); + let time_since_epoch = + timestamp + .parse::() + .change_context(IntegrationError::RequestEncodingFailed { + context: grabpay_integration_context( + "GrabPay PoP signing failed to parse Unix timestamp", + ), + })?; + let payload = serde_json::json!({ + "time_since_epoch": time_since_epoch, + "sig": sig, + }); + let payload_json = serde_json::to_string(&payload).change_context( + IntegrationError::RequestEncodingFailed { + context: grabpay_integration_context( + "GrabPay PoP signing failed to serialize token proof payload", + ), + }, + )?; + + Ok(BASE64_ENGINE_URL_SAFE_NO_PAD.encode(payload_json.as_bytes())) +} + +/// Formats a timestamp as an HTTP-date (RFC 7231, e.g. `Wed, 02 Nov 2022 08:00:00 GMT`) +/// for GrabPay's `Date` header, using `time`'s format description instead of a manual match. +fn format_rfc7231_date(date_time: time::OffsetDateTime) -> CustomResult { + let format = time::macros::format_description!( + "[weekday repr:short], [day] [month repr:short] [year] [hour]:[minute]:[second] GMT" + ); + date_time + .format(&format) + .change_context(IntegrationError::RequestEncodingFailed { + context: grabpay_integration_context( + "GrabPay failed to format the RFC 7231 Date header", + ), + }) +} + +fn session_token_from_connector_feature_data( + connector_feature_data: Option<&common_utils::pii::SecretSerdeValue>, +) -> Option { + let metadata = + utils::to_connector_meta_from_secret::(connector_feature_data.cloned()) + .ok()?; + + metadata + .get("session_token") + .or_else(|| metadata.get("access_token")) + .and_then(serde_json::Value::as_str) + .map(ToOwned::to_owned) +} + +impl ConnectorCommon + for Grabpay +{ + fn id(&self) -> &'static str { + "grabpay" + } + + fn get_currency_unit(&self) -> CurrencyUnit { + CurrencyUnit::Minor + } + + fn common_get_content_type(&self) -> &'static str { + CONTENT_TYPE + } + + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + &connectors.grabpay.base_url + } + + fn get_auth_header( + &self, + auth_type: &ConnectorSpecificConfig, + ) -> CustomResult)>, IntegrationError> { + let _auth = + grabpay::GrabpayAuthType::try_from(auth_type) + .change_context(IntegrationError::FailedToObtainAuthType { + context: grabpay_integration_context( + "GrabPay connector configuration was not supplied in ConnectorSpecificConfig", + ), + })?; + + Ok(Vec::new()) + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut events::Event>, + _connector_config: &ConnectorSpecificConfig, + ) -> CustomResult { + if res.response.is_empty() { + return Ok(ErrorResponse { + status_code: res.status_code, + code: NO_ERROR_CODE.to_string(), + message: NO_ERROR_MESSAGE.to_string(), + reason: None, + attempt_status: None, + connector_transaction_id: None, + network_decline_code: None, + network_advice_code: None, + network_error_message: None, + }); + } + + let response: grabpay::GrabpayErrorResponse = res + .response + .parse_struct("GrabpayErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed { + context: errors::ResponseTransformationErrorContext { + http_status_code: Some(res.status_code), + additional_context: Some( + "GrabPay error response did not match the expected schema".to_string(), + ), + }, + })?; + + with_error_response_body!(event_builder, response); + + let message = response + .message + .or(response.error_description) + .or(response.reason.clone()) + .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()); + let code = response + .code + .or(response.error) + .or(response.reason.clone()) + .unwrap_or_else(|| NO_ERROR_CODE.to_string()); + + Ok(ErrorResponse { + status_code: res.status_code, + code, + message, + reason: response.reason, + attempt_status: None, + connector_transaction_id: None, + network_decline_code: None, + network_advice_code: None, + network_error_message: None, + }) + } +} + +macros::macro_connector_implementation!( + connector_default_implementations: [get_content_type, get_error_response_v2], + connector: Grabpay, + curl_request: Json(GrabpayAuthenticateRequest), + curl_response: GrabpayAuthenticateResponse, + flow_name: Authenticate, + resource_common_data: PaymentFlowData, + flow_request: PaymentsAuthenticateData, + flow_response: PaymentsResponseData, + http_method: Post, + generic_type: T, + [PaymentMethodDataTypes + Debug + Sync + Send + 'static + Serialize], + other_functions: { + fn get_headers( + &self, + req: &RouterDataV2, PaymentsResponseData>, + ) -> CustomResult)>, IntegrationError> { + let auth = grabpay::GrabpayAuthType::try_from(&req.connector_config).change_context( + IntegrationError::FailedToObtainAuthType { + context: grabpay_integration_context( + "GrabPay Authenticate requires GrabPay connector configuration", + ), + }, + )?; + let connector_request = GrabpayAuthenticateRequest::try_from(req.clone())?; + let body = serde_json::to_vec(&connector_request).change_context( + IntegrationError::RequestEncodingFailed { + context: grabpay_integration_context( + "GrabPay Authenticate failed to serialize HMAC request body", + ), + }, + )?; + let request_path = url::Url::parse(&self.get_url(req)?) + .change_context(IntegrationError::RequestEncodingFailed { + context: grabpay_integration_context( + "GrabPay Authenticate failed to parse charge init URL for HMAC path", + ), + })? + .path() + .to_string(); + + self.build_hmac_headers(&auth, "POST", &request_path, &body) + } + + fn get_url( + &self, + req: &RouterDataV2, PaymentsResponseData>, + ) -> CustomResult { + let base_url = self.connector_base_url_payments(req); + Ok(format!("{base_url}{CHARGE_INIT_PATH}")) + } + + } +); + +macros::macro_connector_implementation!( + connector_default_implementations: [get_content_type, get_error_response_v2], + connector: Grabpay, + curl_response: GrabpayRefundSyncResponse, + flow_name: RSync, + resource_common_data: RefundFlowData, + flow_request: RefundSyncData, + flow_response: RefundsResponseData, + http_method: Get, + generic_type: T, + [PaymentMethodDataTypes + Debug + Sync + Send + 'static + Serialize], + other_functions: { + fn get_headers( + &self, + req: &RouterDataV2, + ) -> CustomResult)>, IntegrationError> { + let auth = grabpay::GrabpayAuthType::try_from(&req.connector_config).change_context( + IntegrationError::FailedToObtainAuthType { + context: grabpay_integration_context( + "GrabPay RSync requires GrabPay connector configuration", + ), + }, + )?; + let access_token = req.resource_common_data.get_access_token().or_else(|err| { + session_token_from_connector_feature_data( + req.resource_common_data.connector_feature_data.as_ref(), + ) + .ok_or(err) + })?; + + self.build_pop_headers(&auth, &access_token) + } + + fn get_url( + &self, + req: &RouterDataV2, + ) -> CustomResult { + let partner_tx_id = req.request.connector_refund_id.clone(); + grabpay::validate_partner_tx_id(&partner_tx_id)?; + let currency = req + .request + .refund_money + .as_ref() + .map(|money| money.currency) + .map(Ok) + .unwrap_or_else(|| { + grabpay::currency_from_connector_feature_data( + req.resource_common_data.connector_feature_data.as_ref(), + ) + })?; + + Ok(format!( + "{}{REFUND_PATH}/{}/status?currency={}", + self.connector_base_url_refunds(req), + partner_tx_id, + currency, + )) + } + } +); + +macros::macro_connector_implementation!( + connector_default_implementations: [get_content_type, get_error_response_v2], + connector: Grabpay, + curl_response: GrabpayChargeCompleteResponse, + flow_name: PSync, + resource_common_data: PaymentFlowData, + flow_request: PaymentsSyncData, + flow_response: PaymentsResponseData, + http_method: Get, + generic_type: T, + [PaymentMethodDataTypes + Debug + Sync + Send + 'static + Serialize], + other_functions: { + fn get_headers( + &self, + req: &RouterDataV2, + ) -> CustomResult)>, IntegrationError> { + let auth = grabpay::GrabpayAuthType::try_from(&req.connector_config).change_context( + IntegrationError::FailedToObtainAuthType { + context: grabpay_integration_context( + "GrabPay PSync requires GrabPay connector configuration", + ), + }, + )?; + let access_token = req.resource_common_data.get_access_token().or_else(|err| { + session_token_from_connector_feature_data( + req.resource_common_data.connector_feature_data.as_ref(), + ) + .ok_or(err) + })?; + + self.build_pop_headers(&auth, &access_token) + } + + fn get_url( + &self, + req: &RouterDataV2, + ) -> CustomResult { + let partner_tx_id = req + .resource_common_data + .connector_request_reference_id + .clone(); + grabpay::validate_partner_tx_id(&partner_tx_id)?; + Ok(format!( + "{}{CHARGE_STATUS_PREFIX}/{}/status?currency={}", + self.connector_base_url_payments(req), + partner_tx_id, + req.request.currency, + )) + } + } +); + +macros::macro_connector_implementation!( + connector_default_implementations: [get_content_type, get_error_response_v2], + connector: Grabpay, + curl_request: Json(GrabpayRefundRequest), + curl_response: GrabpayRefundResponse, + flow_name: Refund, + resource_common_data: RefundFlowData, + flow_request: RefundsData, + flow_response: RefundsResponseData, + http_method: Post, + generic_type: T, + [PaymentMethodDataTypes + Debug + Sync + Send + 'static + Serialize], + other_functions: { + fn get_headers( + &self, + req: &RouterDataV2, + ) -> CustomResult)>, IntegrationError> { + let auth = grabpay::GrabpayAuthType::try_from(&req.connector_config).change_context( + IntegrationError::FailedToObtainAuthType { + context: grabpay_integration_context( + "GrabPay Refund requires GrabPay connector configuration", + ), + }, + )?; + let access_token = req.resource_common_data.get_access_token().or_else(|err| { + session_token_from_connector_feature_data( + req.resource_common_data.connector_feature_data.as_ref(), + ) + .ok_or(err) + })?; + + self.build_pop_headers(&auth, &access_token) + } + + fn get_url( + &self, + req: &RouterDataV2, + ) -> CustomResult { + Ok(format!("{}{}", self.connector_base_url_refunds(req), REFUND_PATH)) + } + } +); + +macros::macro_connector_implementation!( + connector_default_implementations: [get_content_type, get_error_response_v2], + connector: Grabpay, + curl_request: Json(GrabpayAuthorizeRequest), + curl_response: GrabpayAuthorizeResponse, + flow_name: Authorize, + resource_common_data: PaymentFlowData, + flow_request: PaymentsAuthorizeData, + flow_response: PaymentsResponseData, + http_method: Post, + generic_type: T, + [PaymentMethodDataTypes + Debug + Sync + Send + 'static + Serialize], + other_functions: { + fn get_headers( + &self, + req: &RouterDataV2, PaymentsResponseData>, + ) -> CustomResult)>, IntegrationError> { + let session_token = req + .resource_common_data + .get_session_token() + .change_context(IntegrationError::MissingRequiredField { + field_name: "session_token", + context: grabpay_integration_context( + "GrabPay post-redirect authorize requires a successful OAuth session-token exchange", + ), + })?; + let auth = grabpay::GrabpayAuthType::try_from(&req.connector_config).change_context( + IntegrationError::FailedToObtainAuthType { + context: grabpay_integration_context( + "GrabPay Authorize requires GrabPay connector configuration", + ), + }, + )?; + self.build_pop_headers(&auth, &session_token) + } + + fn get_url( + &self, + req: &RouterDataV2, PaymentsResponseData>, + ) -> CustomResult { + Ok(format!( + "{}{CHARGE_COMPLETE_PATH}", + self.connector_base_url_payments(req) + )) + } + } +); + +macros::macro_connector_implementation!( + connector_default_implementations: [get_content_type, get_error_response_v2], + connector: Grabpay, + curl_request: Json(GrabpayServerSessionAuthenticationTokenRequest), + curl_response: GrabpayServerSessionAuthenticationTokenResponse, + flow_name: ServerSessionAuthenticationToken, + resource_common_data: MerchantAuthenticationFlowData, + flow_request: ServerSessionAuthenticationTokenRequestData, + flow_response: ServerSessionAuthenticationTokenResponseData, + http_method: Post, + generic_type: T, + [PaymentMethodDataTypes + Debug + Sync + Send + 'static + Serialize], + other_functions: { + fn get_headers( + &self, + _req: &RouterDataV2, + ) -> CustomResult)>, IntegrationError> { + Ok(self.build_json_headers()) + } + + fn get_url( + &self, + req: &RouterDataV2, + ) -> CustomResult { + Ok(oauth_endpoint( + &req.resource_common_data.connectors.grabpay.base_url, + OAUTH_TOKEN_PATH, + )) + } + } +); + +impl BodyDecoding + for Grabpay +{ +} + +impl SourceVerification + for Grabpay +{ +} + +impl + connector_types::ConnectorServiceTrait for Grabpay +{ +} + +impl + connector_types::ValidationTrait for Grabpay +{ + /// GrabPay's flow: `Authenticate` (POST `/charge/init` → OAuth redirect URL) on the + /// initial request; `Authorize` (POST `/charge/complete`) after the customer completes + /// the OAuth consent and the caller redirects back (mirrors Flywire). + fn next_authentication_step( + &self, + _auth_type: common_enums::AuthenticationType, + _payment_method: common_enums::PaymentMethod, + redirect_state: connector_types::RedirectState, + _completed_step: Option, + ) -> connector_types::AuthenticationStep { + use interfaces::connector_types::{AuthenticationStep, RedirectState}; + match redirect_state { + RedirectState::InitialRequest => AuthenticationStep::Authenticate, + RedirectState::RedirectWithParams | RedirectState::RedirectWithoutParams => { + AuthenticationStep::Authorize + } + } + } + + fn should_do_session_token(&self, connector_feature_data: Option<&Secret>) -> bool { + connector_feature_data + .and_then(|data| serde_json::from_str::(data.peek()).ok()) + .map(|feature_data| { + feature_data + .get("code") + .and_then(serde_json::Value::as_str) + .is_some() + }) + .unwrap_or(false) + } + + fn requires_authorize_post_redirect(&self) -> bool { + true + } + + fn merchant_order_id_source(&self) -> connector_types::MerchantOrderIdSource { + connector_types::MerchantOrderIdSource::TransactionId + } +} + +impl + connector_types::IncomingWebhook for Grabpay +{ + fn verify_webhook_source( + &self, + request: RequestDetails, + _connector_webhook_secret: Option, + connector_account_details: Option, + ) -> Result> { + use common_utils::crypto::VerifySignature; + + let connector_account_details = connector_account_details + .ok_or_else(|| error_stack::report!(WebhookError::WebhookVerificationSecretNotFound))?; + let auth = grabpay::GrabpayAuthType::try_from(&connector_account_details) + .change_context(WebhookError::WebhookVerificationSecretInvalid)?; + + let incoming_authorization = + get_webhook_header(&request.headers, headers::AUTHORIZATION)?.trim(); + let content_type = get_webhook_header(&request.headers, headers::CONTENT_TYPE)?.trim(); + let date = get_webhook_header(&request.headers, headers::DATE)?; + let path = grabpay_webhook_path(request.uri.as_deref())?; + let method = format!("{:?}", request.method).to_uppercase(); + + // GrabPay's Authorization header is `{partner_id}:{base64(HMAC-SHA256(canonical))}`. + // The partner_id prefix is a public identifier; the signature must be compared in + // constant time. `HmacSha256::verify_signature` uses ring's constant-time verification. + let Some((incoming_partner_id, incoming_signature_b64)) = + incoming_authorization.split_once(':') + else { + return Ok(false); + }; + if incoming_partner_id != auth.partner_id.peek() { + return Ok(false); + } + let incoming_signature = match BASE64_ENGINE.decode(incoming_signature_b64) { + Ok(signature) => signature, + Err(_) => return Ok(false), + }; + let signing_string = + grabpay_hmac_signing_string(&method, content_type, &path, &request.body, date) + .change_context(WebhookError::WebhookSourceVerificationFailed)?; + + crypto::HmacSha256 + .verify_signature( + auth.partner_secret.peek().as_bytes(), + &incoming_signature, + signing_string.as_bytes(), + ) + .change_context(WebhookError::WebhookSourceVerificationFailed) + } + + fn get_event_type( + &self, + request: RequestDetails, + ) -> Result> { + let webhook_body: GrabpayWebhookBody = request + .body + .parse_struct("GrabpayWebhookBody") + .change_context(WebhookError::WebhookBodyDecodingFailed)?; + grabpay::grabpay_webhook_event_type(&webhook_body) + } + + fn get_webhook_event_reference( + &self, + request: RequestDetails, + ) -> Result, error_stack::Report> { + let webhook_body: GrabpayWebhookBody = request + .body + .parse_struct("GrabpayWebhookBody") + .change_context(WebhookError::WebhookBodyDecodingFailed)?; + Ok(Some(webhook_body.webhook_reference())) + } + + fn process_payment_webhook( + &self, + request: RequestDetails, + _connector_webhook_secret: Option, + _connector_account_details: Option, + _event_context: Option, + ) -> Result> { + let webhook_body: GrabpayWebhookBody = request + .body + .parse_struct("GrabpayWebhookBody") + .change_context(WebhookError::WebhookBodyDecodingFailed)?; + if webhook_body.is_refund_event() { + return Err(error_stack::report!( + WebhookError::WebhookBodyDecodingFailed + )); + } + + let status = grabpay::grabpay_webhook_attempt_status(&webhook_body)?; + let is_failure = status == enums::AttemptStatus::Failure; + let reason = webhook_body.effective_reason(); + let connector_transaction_id = webhook_body.tx_id.clone().ok_or_else(|| { + error_stack::report!(WebhookError::WebhookMissingRequiredField { field: "txID" }) + })?; + + Ok(WebhookDetailsResponse { + resource_id: Some(ResponseId::ConnectorTransactionId(connector_transaction_id)), + status, + connector_response_reference_id: None, + connector_request_reference_id: webhook_body.partner_tx_id, + mandate_reference: None, + error_code: if is_failure { reason.clone() } else { None }, + error_message: if is_failure { reason.clone() } else { None }, + error_reason: reason, + raw_connector_response: Some(String::from_utf8_lossy(&request.body).to_string()), + status_code: 200, + response_headers: None, + amount_captured: None, + minor_amount_captured: None, + network_txn_id: None, + payment_method_update: None, + sender_payment_instrument_id: None, + }) + } + + fn process_refund_webhook( + &self, + request: RequestDetails, + _connector_webhook_secret: Option, + _connector_account_details: Option, + ) -> Result> { + let webhook_body: GrabpayWebhookBody = request + .body + .parse_struct("GrabpayWebhookBody") + .change_context(WebhookError::WebhookBodyDecodingFailed)?; + if !webhook_body.is_refund_event() { + return Err(error_stack::report!( + WebhookError::WebhookBodyDecodingFailed + )); + } + + let status = grabpay::grabpay_webhook_refund_status(&webhook_body)?; + let is_failure = status == enums::RefundStatus::Failure; + let reason = webhook_body.effective_reason(); + + Ok(RefundWebhookDetailsResponse { + connector_refund_id: webhook_body.tx_id, + merchant_transaction_id: webhook_body + .payload + .as_ref() + .and_then(|payload| payload.partner_group_tx_id.clone()), + status, + connector_response_reference_id: webhook_body.partner_tx_id, + error_code: if is_failure { reason.clone() } else { None }, + error_message: if is_failure { reason } else { None }, + raw_connector_response: Some(String::from_utf8_lossy(&request.body).to_string()), + status_code: 200, + response_headers: None, + }) + } + + fn get_webhook_resource_object( + &self, + request: RequestDetails, + ) -> Result, error_stack::Report> + { + let webhook_body: GrabpayWebhookBody = request + .body + .parse_struct("GrabpayWebhookBody") + .change_context(WebhookError::WebhookBodyDecodingFailed)?; + Ok(Box::new(webhook_body)) + } + + fn sample_webhook_body(&self) -> &'static [u8] { + br#"{"txType":"payment","txStatus":"success","partnerID":"partner_123","partnerTxID":"txn_123","txID":"grab_txn_123","amount":100,"currency":"SGD","payload":{"newStatus":"success","paymentMethod":"GRABPAY"}}"# + } +} + +impl + connector_types::VerifyRedirectResponse for Grabpay +{ + fn decode_redirect_response_body( + &self, + request: &RequestDetails, + _secrets: Option, + ) -> CustomResult, IntegrationError> { + Ok(request.body.clone()) + } + + fn verify_redirect_response_source( + &self, + _request: &RequestDetails, + _secrets: Option, + ) -> CustomResult { + Ok(false) + } + + fn process_redirect_response( + &self, + request: &RequestDetails, + connector_feature_data: Option<&Secret>, + ) -> CustomResult { + process_grabpay_redirect_response(request, connector_feature_data) + } +} + +fn process_grabpay_redirect_response( + request: &RequestDetails, + base_connector_feature_data: Option<&Secret>, +) -> CustomResult { + let code = get_query_param(request, "code"); + let state = get_query_param(request, "state"); + let error = get_query_param(request, "error"); + let connector_feature_data = + build_redirect_connector_feature_data(base_connector_feature_data, &code, &state)?; + + Ok(RedirectDetailsResponse { + resource_id: None, + status: None, + connector_response_reference_id: None, + error_code: error, + error_message: None, + error_reason: None, + response_amount: None, + raw_connector_response: None, + connector_feature_data, + }) +} + +fn build_redirect_connector_feature_data( + base_connector_feature_data: Option<&Secret>, + code: &Option, + state: &Option, +) -> CustomResult, IntegrationError> { + let mut feature_data = match base_connector_feature_data { + Some(feature_data) => serde_json::from_str::(feature_data.peek()) + .change_context(IntegrationError::InvalidDataFormat { + field_name: "connector_feature_data", + context: grabpay_integration_context( + "GrabPay redirect response received malformed connector_feature_data JSON", + ), + })?, + None => serde_json::Value::Object(serde_json::Map::new()), + }; + + let feature_data_object = feature_data.as_object_mut().ok_or_else(|| { + error_stack::report!(IntegrationError::InvalidDataFormat { + field_name: "connector_feature_data", + context: grabpay_integration_context( + "GrabPay redirect response expected connector_feature_data to be a JSON object", + ), + }) + })?; + + if let Some(code) = code { + feature_data_object.insert("code".to_string(), serde_json::Value::String(code.clone())); + } + + if let Some(state) = state { + feature_data_object.insert( + "callback_state".to_string(), + serde_json::Value::String(state.clone()), + ); + } + + if feature_data_object.is_empty() { + Ok(None) + } else { + serde_json::to_string(&feature_data) + .map(Some) + .change_context(IntegrationError::RequestEncodingFailed { + context: grabpay_integration_context( + "GrabPay redirect response failed to serialize connector_feature_data", + ), + }) + } +} + +fn get_query_param(request: &RequestDetails, param_name: &str) -> Option { + request.query_params.as_ref().and_then(|query_params| { + serde_json::from_str::(query_params) + .ok() + .and_then(|value| { + value + .as_object() + .and_then(|object| object.get(param_name)) + .and_then(|value| value.as_str()) + .map(str::to_owned) + }) + .or_else(|| { + url::form_urlencoded::parse(query_params.as_bytes()) + .find(|(key, _)| key == param_name) + .map(|(_, value)| value.into_owned()) + }) + }) +} + +impl + connector_types::PaymentAuthenticateV2 for Grabpay +{ +} + +impl + connector_types::PaymentAuthorizeV2 for Grabpay +{ +} + +impl + connector_types::PaymentSyncV2 for Grabpay +{ +} + +impl + connector_types::RefundV2 for Grabpay +{ +} + +impl + connector_types::RefundSyncV2 for Grabpay +{ +} + +impl + connector_types::ServerSessionAuthentication for Grabpay +{ +} + +macros::create_amount_converter_wrapper!(connector_name: Grabpay, amount_type: MinorUnit); + +crate::connectors::macros::macro_connector_payout_implementation!( + connector: Grabpay, + generic_type: T, + [PaymentMethodDataTypes + Debug + Sync + Send + 'static + Serialize] +); + +static GRABPAY_SUPPORTED_PAYMENT_METHODS: LazyLock = LazyLock::new(|| { + let supported_capture_methods = vec![enums::CaptureMethod::Automatic]; + + let mut grabpay_supported_payment_methods = SupportedPaymentMethods::new(); + + grabpay_supported_payment_methods.add( + enums::PaymentMethod::Wallet, + PaymentMethodType::Grabpay, + PaymentMethodDetails { + mandates: FeatureStatus::NotSupported, + refunds: FeatureStatus::Supported, + supported_capture_methods, + specific_features: None, + }, + ); + + grabpay_supported_payment_methods +}); + +static GRABPAY_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo { + display_name: "GrabPay", + description: "GrabPay One-Time Charge wallet payments.", + connector_type: types::PaymentConnectorCategory::AlternativePaymentMethod, +}; + +impl ConnectorSpecifications for Grabpay { + fn get_connector_about(&self) -> Option<&'static ConnectorInfo> { + Some(&GRABPAY_CONNECTOR_INFO) + } + + fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> { + Some(&*GRABPAY_SUPPORTED_PAYMENT_METHODS) + } +} + +crate::connectors::macros::macro_connector_flow_status_impls!( + connector: Grabpay, + generic_type: T, + [PaymentMethodDataTypes + Debug + Sync + Send + 'static + Serialize], + not_implemented: [ + Accept, + ClientAuthenticationToken, + CreateConnectorCustomer, + CreateOrder, + CreatePaymentMethod, + DefendDispute, + GetConnectorCustomer, + GetPaymentMethod, + IncrementalAuthorization, + PostAuthenticate, + PreAuthenticate, + PaymentMethodToken, + PaymentMethodEligibility, + Recharge, + ServerAuthenticationToken, + SubmitEvidence, + VoidPC, + VoidPostRefund, + RepeatPayment + ], + not_supported: [Capture, MandateRevoke, SetupMandate, Void], +); diff --git a/crates/integrations/connector-integration/src/connectors/grabpay/transformers.rs b/crates/integrations/connector-integration/src/connectors/grabpay/transformers.rs new file mode 100644 index 0000000000..c2ca48bc92 --- /dev/null +++ b/crates/integrations/connector-integration/src/connectors/grabpay/transformers.rs @@ -0,0 +1,1475 @@ +use base64::Engine; +use common_enums::{AttemptStatus, CountryAlpha2, Currency, RefundStatus}; +use common_utils::{ + consts::BASE64_ENGINE_URL_SAFE_NO_PAD, pii::SecretSerdeValue, types::MinorUnit, +}; +use domain_types::{ + connector_flow::{ + Authenticate, Authorize, PSync, RSync, Refund, ServerSessionAuthenticationToken, + }, + connector_types::{ + EventType, PaymentWebhookReference, RefundWebhookReference, WebhookResourceReference, + }, + connector_types::{ + PaymentFlowData, PaymentsAuthenticateData, PaymentsAuthorizeData, PaymentsResponseData, + PaymentsSyncData, RefundFlowData, RefundSyncData, RefundsData, RefundsResponseData, + ResponseId, ServerSessionAuthenticationTokenRequestData, + ServerSessionAuthenticationTokenResponseData, + }, + errors, + errors::IntegrationErrorContext, + merchant_authentication_flow_data::MerchantAuthenticationFlowData, + payment_method_data::{PaymentMethodData, PaymentMethodDataTypes, WalletData}, + router_data::ConnectorSpecificConfig, + router_data_v2::RouterDataV2, + router_response_types::RedirectForm, +}; +use error_stack::ResultExt; +use hyperswitch_masking::{ExposeInterface, PeekInterface, Secret}; +use rand::distributions::{Alphanumeric, DistString}; +use serde::{Deserialize, Serialize}; + +use crate::{ + connectors::grabpay::{ + oauth_endpoint, GrabpayRouterData as GrabpayFlowData, GRABPAY_CONFIG_SUGGESTED_ACTION, + GRABPAY_DOC_URL, + }, + types::ResponseRouterData as ConnectorResponseData, + utils, +}; + +const AUTHORIZATION_CODE_GRANT: &str = "authorization_code"; +const OAUTH_AUTHORIZE_PATH: &str = "/grabid/v1/oauth2/authorize"; +const CODE_CHALLENGE_METHOD: &str = "S256"; +const RESPONSE_TYPE_CODE: &str = "code"; +const SCOPE_ONE_TIME_CHARGE: &str = "payment.one_time_charge"; + +fn grabpay_integration_context(additional_context: impl Into) -> IntegrationErrorContext { + IntegrationErrorContext { + suggested_action: Some(GRABPAY_CONFIG_SUGGESTED_ACTION.to_string()), + doc_url: Some(GRABPAY_DOC_URL.to_string()), + additional_context: Some(additional_context.into()), + } +} + +#[derive(Debug, Clone)] +pub struct GrabpayAuthType { + pub partner_id: Secret, + pub partner_secret: Secret, + pub client_id: Secret, + pub client_secret: Secret, + pub merchant_id: Secret, +} + +impl TryFrom<&ConnectorSpecificConfig> for GrabpayAuthType { + type Error = error_stack::Report; + + fn try_from(auth_type: &ConnectorSpecificConfig) -> Result { + match auth_type { + ConnectorSpecificConfig::Grabpay { + partner_id, + partner_secret, + client_id, + client_secret, + merchant_id, + .. + } => Ok(Self { + partner_id: partner_id.to_owned(), + partner_secret: partner_secret.to_owned(), + client_id: client_id.to_owned(), + client_secret: client_secret.to_owned(), + merchant_id: merchant_id.to_owned(), + }), + _ => Err(error_stack::report!( + errors::IntegrationError::FailedToObtainAuthType { + context: grabpay_integration_context( + "GrabPay auth type can only be built from Grabpay connector configuration" + ) + } + )), + } + } +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct GrabpayErrorResponse { + #[serde(default, deserialize_with = "deserialize_optional_string")] + pub code: Option, + #[serde(default, deserialize_with = "deserialize_optional_string")] + pub message: Option, + #[serde(default, deserialize_with = "deserialize_optional_string")] + pub error: Option, + #[serde(default, deserialize_with = "deserialize_optional_string")] + pub error_description: Option, + #[serde(default, deserialize_with = "deserialize_optional_string")] + pub reason: Option, +} + +fn deserialize_optional_string<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + let value = Option::::deserialize(deserializer)?; + Ok(value.and_then(|value| match value { + serde_json::Value::String(value) => Some(value), + serde_json::Value::Number(value) => Some(value.to_string()), + serde_json::Value::Bool(value) => Some(value.to_string()), + serde_json::Value::Null | serde_json::Value::Array(_) | serde_json::Value::Object(_) => { + None + } + })) +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GrabpayAuthenticateRequest { + #[serde(rename = "partnerGroupTxID")] + pub partner_group_tx_id: String, + #[serde(rename = "partnerTxID")] + pub partner_tx_id: String, + pub currency: Currency, + pub amount: MinorUnit, + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(rename = "merchantID")] + pub merchant_id: String, + #[serde(rename = "shippingDetails", skip_serializing_if = "Option::is_none")] + pub shipping_details: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub items: Option>, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GrabpayItem { + #[serde(rename = "itemName")] + pub item_name: String, + pub quantity: u16, + pub price: MinorUnit, + #[serde(skip_serializing_if = "Option::is_none")] + pub category: Option, + #[serde(rename = "itemCategory", skip_serializing_if = "Option::is_none")] + pub item_category: Option, + #[serde(rename = "imageURL", skip_serializing_if = "Option::is_none")] + pub image_url: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GrabpayShippingDetails { + #[serde(rename = "firstName", skip_serializing_if = "Option::is_none")] + pub first_name: Option>, + #[serde(rename = "lastName", skip_serializing_if = "Option::is_none")] + pub last_name: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub address: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub city: Option>, + #[serde(rename = "postalCode", skip_serializing_if = "Option::is_none")] + pub postal_code: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub phone: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub email: Option, + #[serde(rename = "countryCode", skip_serializing_if = "Option::is_none")] + pub country_code: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GrabpayAuthenticateResponse { + #[serde(rename = "partnerTxID")] + pub partner_tx_id: Option, + pub request: Option, + pub status: Option, + #[serde(rename = "txStatus")] + pub tx_status: Option, + pub reason: Option, + pub message: Option, + pub code: Option, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct GrabpayConnectorFeatureData { + pub state: Option>, + pub callback_state: Option, + pub code: Option, + pub nonce: Option, + pub code_verifier: Option>, + pub redirect_uri: Option, + pub partner_tx_id: Option, + pub currency: Option, + #[serde(rename = "txID")] + pub tx_id: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GrabpayAuthorizeRequest< + T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize, +> { + #[serde(rename = "partnerTxID")] + pub partner_tx_id: String, + #[serde(skip)] + pub phantom: std::marker::PhantomData, +} + +/// GrabPay's `/charge/complete` response — the post-redirect Authorize step. +/// The initial redirect is now owned by the `Authenticate` flow, so Authorize +/// only ever completes the charge. +pub type GrabpayAuthorizeResponse = GrabpayChargeCompleteResponse; + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GrabpayChargeCompleteResponse { + #[serde(rename = "txID")] + pub tx_id: String, + pub status: Option, + #[serde(rename = "paymentMethod")] + pub payment_method: Option, + pub description: Option, + #[serde(rename = "txStatus")] + pub tx_status: GrabpayPaymentStatus, + pub reason: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GrabpayRefundRequest { + #[serde(rename = "partnerGroupTxID")] + pub partner_group_tx_id: String, + #[serde(rename = "partnerTxID")] + pub partner_tx_id: String, + pub amount: MinorUnit, + pub currency: Currency, + #[serde(rename = "merchantID")] + pub merchant_id: String, + #[serde(rename = "originTxID")] + pub origin_tx_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub echo: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GrabpayRefundResponse { + #[serde(rename = "txID")] + pub tx_id: String, + pub status: Option, + #[serde(rename = "paymentMethod")] + pub payment_method: Option, + pub description: Option, + #[serde(rename = "txStatus")] + pub tx_status: GrabpayRefundStatus, + pub reason: Option, + pub echo: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GrabpayRefundSyncResponse { + #[serde(rename = "txID")] + pub tx_id: String, + pub status: Option, + #[serde(rename = "paymentMethod")] + pub payment_method: Option, + pub description: Option, + #[serde(rename = "txStatus")] + pub tx_status: GrabpayRefundStatus, + pub reason: Option, + pub echo: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize, strum::EnumString)] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case", ascii_case_insensitive)] +pub enum GrabpayPaymentStatus { + Success, + Failed, + Processing, + Cancelled, + Authorised, + AuthorisationDeclined, + TransactionAlreadyExist, + #[serde(other)] + Unknown, +} + +impl From for AttemptStatus { + fn from(status: GrabpayPaymentStatus) -> Self { + match status { + GrabpayPaymentStatus::Success => Self::Charged, + GrabpayPaymentStatus::Failed + | GrabpayPaymentStatus::Cancelled + | GrabpayPaymentStatus::AuthorisationDeclined => Self::Failure, + GrabpayPaymentStatus::Processing | GrabpayPaymentStatus::TransactionAlreadyExist => { + Self::Pending + } + GrabpayPaymentStatus::Authorised => Self::Authorized, + GrabpayPaymentStatus::Unknown => Self::Pending, + } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, strum::EnumString)] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case", ascii_case_insensitive)] +pub enum GrabpayRefundStatus { + Success, + Failed, + Cancelled, + AuthorisationDeclined, + Processing, + TransactionAlreadyExist, + #[serde(other)] + Unknown, +} + +impl From for RefundStatus { + fn from(status: GrabpayRefundStatus) -> Self { + match status { + GrabpayRefundStatus::Success => Self::Success, + GrabpayRefundStatus::Failed + | GrabpayRefundStatus::Cancelled + | GrabpayRefundStatus::AuthorisationDeclined => Self::Failure, + GrabpayRefundStatus::Processing | GrabpayRefundStatus::TransactionAlreadyExist => { + Self::Pending + } + GrabpayRefundStatus::Unknown => Self::Pending, + } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GrabpayWebhookBody { + #[serde(rename = "txType")] + pub tx_type: Option, + #[serde(rename = "txCategory")] + pub tx_category: Option, + #[serde(rename = "txStatus")] + pub tx_status: Option, + #[serde(rename = "partnerTxID")] + pub partner_tx_id: Option, + #[serde(rename = "txID")] + pub tx_id: Option, + #[serde(rename = "origTxID")] + pub orig_tx_id: Option, + pub amount: Option, + pub currency: Option, + pub status: Option, + pub reason: Option, + pub payload: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GrabpayWebhookPayload { + #[serde(rename = "partnerGroupTxID")] + pub partner_group_tx_id: Option, + #[serde(rename = "newStatus")] + pub new_status: Option, + pub reason: Option, + #[serde(rename = "paymentMethod")] + pub payment_method: Option, + pub echo: Option, +} + +impl GrabpayWebhookBody { + pub fn is_refund_event(&self) -> bool { + let tx_type_is_refund = self + .tx_type + .as_deref() + .map(|tx_type| tx_type.to_ascii_lowercase().contains("refund")) + .unwrap_or(false); + let tx_category_is_refund = self + .tx_category + .as_deref() + .map(|tx_category| tx_category.to_ascii_lowercase().contains("refund")) + .unwrap_or(false); + + tx_type_is_refund || tx_category_is_refund + } + + pub fn effective_status(&self) -> Option<&str> { + self.payload + .as_ref() + .and_then(|payload| payload.new_status.as_deref()) + .or(self.tx_status.as_deref()) + .or(self.status.as_deref()) + } + + pub fn effective_reason(&self) -> Option { + self.payload + .as_ref() + .and_then(|payload| payload.reason.clone()) + .or_else(|| self.reason.clone()) + } + + pub fn webhook_reference(&self) -> WebhookResourceReference { + if self.is_refund_event() { + WebhookResourceReference::Refund(RefundWebhookReference { + connector_refund_id: self.tx_id.clone(), + merchant_refund_id: self.partner_tx_id.clone(), + connector_transaction_id: self.orig_tx_id.clone(), + merchant_transaction_id: self + .payload + .as_ref() + .and_then(|payload| payload.partner_group_tx_id.clone()), + }) + } else { + WebhookResourceReference::Payment(PaymentWebhookReference { + connector_transaction_id: self.tx_id.clone(), + merchant_transaction_id: self.partner_tx_id.clone(), + }) + } + } +} + +pub fn grabpay_webhook_event_type( + webhook_body: &GrabpayWebhookBody, +) -> Result> { + if webhook_body.is_refund_event() { + let status = parse_webhook_status::(webhook_body)?; + Ok(match status { + GrabpayRefundStatus::Success => EventType::RefundSuccess, + GrabpayRefundStatus::Failed + | GrabpayRefundStatus::Cancelled + | GrabpayRefundStatus::AuthorisationDeclined => EventType::RefundFailure, + GrabpayRefundStatus::Processing | GrabpayRefundStatus::TransactionAlreadyExist => { + EventType::RefundProcessing + } + GrabpayRefundStatus::Unknown => EventType::IncomingWebhookEventUnspecified, + }) + } else { + let status = parse_webhook_status::(webhook_body)?; + Ok(match status { + GrabpayPaymentStatus::Success => EventType::PaymentIntentSuccess, + GrabpayPaymentStatus::Failed + | GrabpayPaymentStatus::Cancelled + | GrabpayPaymentStatus::AuthorisationDeclined => EventType::PaymentIntentFailure, + GrabpayPaymentStatus::Processing | GrabpayPaymentStatus::TransactionAlreadyExist => { + EventType::PaymentIntentProcessing + } + GrabpayPaymentStatus::Authorised => EventType::PaymentIntentAuthorizationSuccess, + GrabpayPaymentStatus::Unknown => EventType::IncomingWebhookEventUnspecified, + }) + } +} + +pub fn grabpay_webhook_attempt_status( + webhook_body: &GrabpayWebhookBody, +) -> Result> { + let status = parse_webhook_status::(webhook_body)?; + Ok(AttemptStatus::from(status)) +} + +pub fn grabpay_webhook_refund_status( + webhook_body: &GrabpayWebhookBody, +) -> Result> { + let status = parse_webhook_status::(webhook_body)?; + Ok(RefundStatus::from(status)) +} + +fn parse_webhook_status( + webhook_body: &GrabpayWebhookBody, +) -> Result> +where + T: std::str::FromStr, +{ + let status = webhook_body + .effective_status() + .ok_or_else(|| error_stack::report!(errors::WebhookError::WebhookBodyDecodingFailed))?; + status + .trim() + .parse::() + .map_err(|_| error_stack::report!(errors::WebhookError::WebhookBodyDecodingFailed)) +} + +#[derive(Debug, Clone, Deserialize)] +struct GrabpayRefundMetadata { + echo: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct GrabpayServerSessionAuthenticationTokenRequest { + pub grant_type: String, + pub client_id: Secret, + pub client_secret: Secret, + pub code_verifier: String, + pub redirect_uri: String, + pub code: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct GrabpayServerSessionAuthenticationTokenResponse { + pub access_token: Secret, + pub id_token: Option, + pub token_type: Option, + pub expires_in: Option, +} + +struct GrabpayRedirectContext { + redirect_url: String, + connector_feature_data: SecretSerdeValue, +} + +fn build_code_challenge( + code_verifier: &str, +) -> Result> { + use common_utils::crypto::GenerateDigest; + + let digest = common_utils::crypto::Sha256 + .generate_digest(code_verifier.as_bytes()) + .change_context(errors::IntegrationError::RequestEncodingFailed { + context: grabpay_integration_context( + "GrabPay redirect authorize failed to generate OAuth code challenge", + ), + })?; + Ok(BASE64_ENGINE_URL_SAFE_NO_PAD.encode(digest)) +} + +fn random_token(length: usize) -> String { + Alphanumeric.sample_string(&mut rand::thread_rng(), length) +} + +fn grabpay_request_currency( + currency: Option, +) -> Result> { + currency.ok_or_else(|| { + error_stack::report!(errors::IntegrationError::MissingRequiredField { + field_name: "currency", + context: grabpay_integration_context( + "GrabPay Authenticate requires a currency on the payment request" + ) + }) + }) +} + +fn grabpay_billing_country( + flow_data: &PaymentFlowData, +) -> Result> { + flow_data.get_optional_billing_country().ok_or_else(|| { + error_stack::report!(errors::IntegrationError::MissingRequiredField { + field_name: "billing.address.country", + context: grabpay_integration_context( + "Provide billing address country in the payment request".to_string(), + ) + }) + }) +} + +impl + TryFrom< + GrabpayFlowData< + RouterDataV2< + ServerSessionAuthenticationToken, + MerchantAuthenticationFlowData, + ServerSessionAuthenticationTokenRequestData, + ServerSessionAuthenticationTokenResponseData, + >, + T, + >, + > for GrabpayServerSessionAuthenticationTokenRequest +{ + type Error = error_stack::Report; + + fn try_from( + wrapper: GrabpayFlowData< + RouterDataV2< + ServerSessionAuthenticationToken, + MerchantAuthenticationFlowData, + ServerSessionAuthenticationTokenRequestData, + ServerSessionAuthenticationTokenResponseData, + >, + T, + >, + ) -> Result { + Self::try_from(&wrapper.router_data) + } +} + +impl + TryFrom< + &RouterDataV2< + ServerSessionAuthenticationToken, + MerchantAuthenticationFlowData, + ServerSessionAuthenticationTokenRequestData, + ServerSessionAuthenticationTokenResponseData, + >, + > for GrabpayServerSessionAuthenticationTokenRequest +{ + type Error = error_stack::Report; + + fn try_from( + router_data: &RouterDataV2< + ServerSessionAuthenticationToken, + MerchantAuthenticationFlowData, + ServerSessionAuthenticationTokenRequestData, + ServerSessionAuthenticationTokenResponseData, + >, + ) -> Result { + let auth = + GrabpayAuthType::try_from(&router_data.connector_config) + .change_context(errors::IntegrationError::FailedToObtainAuthType { + context: grabpay_integration_context( + "GrabPay OAuth session-token request requires GrabPay connector configuration", + ), + })?; + let feature_data = parse_connector_feature_data( + router_data + .resource_common_data + .connector_feature_data + .as_ref(), + )?; + + let code = feature_data + .code + .clone() + .ok_or_else(missing_oauth_code_error)?; + validate_callback_state(&feature_data)?; + + Ok(Self { + grant_type: AUTHORIZATION_CODE_GRANT.to_string(), + client_id: auth.client_id, + client_secret: auth.client_secret, + code_verifier: required_feature_field(feature_data.code_verifier, "code_verifier")? + .peek() + .to_string(), + redirect_uri: required_feature_field(feature_data.redirect_uri, "redirect_uri")?, + code, + }) + } +} + +impl TryFrom> + for RouterDataV2< + ServerSessionAuthenticationToken, + MerchantAuthenticationFlowData, + ServerSessionAuthenticationTokenRequestData, + ServerSessionAuthenticationTokenResponseData, + > +{ + type Error = error_stack::Report; + + fn try_from( + item: ConnectorResponseData, + ) -> Result { + if let Err(msg) = validate_id_token_nonce( + &item.response.id_token, + &item.router_data.resource_common_data.connector_feature_data, + ) { + return Err(errors::ConnectorError::ResponseDeserializationFailed { + context: errors::ResponseTransformationErrorContext { + http_status_code: Some(item.http_code), + additional_context: Some(msg), + }, + } + .into()); + } + + Ok(Self { + response: Ok(ServerSessionAuthenticationTokenResponseData { + session_token: item.response.access_token.peek().to_string(), + }), + ..item.router_data + }) + } +} + +fn extract_jwt_nonce(id_token: &str) -> Option { + use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; + let payload = id_token.split('.').nth(1)?; + let decoded = URL_SAFE_NO_PAD.decode(payload).ok()?; + let claims: serde_json::Value = serde_json::from_slice(&decoded).ok()?; + claims.get("nonce")?.as_str().map(String::from) +} + +fn validate_id_token_nonce( + id_token: &Option, + connector_feature_data: &Option, +) -> Result<(), String> { + let (Some(id_token), Some(feature_data)) = (id_token.as_ref(), connector_feature_data.as_ref()) + else { + return Ok(()); + }; + let expected_nonce = parse_connector_feature_data(Some(feature_data)) + .ok() + .and_then(|fd| fd.nonce); + let Some(expected) = expected_nonce else { + return Ok(()); + }; + let actual = extract_jwt_nonce(id_token); + if actual.as_deref() == Some(expected.as_str()) { + Ok(()) + } else { + Err("GrabPay id_token nonce mismatch".to_string()) + } +} + +impl + TryFrom< + GrabpayFlowData< + RouterDataV2< + Authorize, + PaymentFlowData, + PaymentsAuthorizeData, + PaymentsResponseData, + >, + T, + >, + > for GrabpayAuthorizeRequest +{ + type Error = error_stack::Report; + + fn try_from( + wrapper: GrabpayFlowData< + RouterDataV2< + Authorize, + PaymentFlowData, + PaymentsAuthorizeData, + PaymentsResponseData, + >, + T, + >, + ) -> Result { + Self::try_from(wrapper.router_data) + } +} + +impl + TryFrom< + RouterDataV2, PaymentsResponseData>, + > for GrabpayAuthorizeRequest +{ + type Error = error_stack::Report; + + fn try_from( + router_data: RouterDataV2< + Authorize, + PaymentFlowData, + PaymentsAuthorizeData, + PaymentsResponseData, + >, + ) -> Result { + let feature_data = parse_connector_feature_data( + router_data + .resource_common_data + .connector_feature_data + .as_ref(), + )?; + let partner_tx_id = feature_data.partner_tx_id.unwrap_or( + router_data + .resource_common_data + .connector_request_reference_id, + ); + validate_partner_tx_id(&partner_tx_id)?; + + Ok(Self { + partner_tx_id, + phantom: std::marker::PhantomData, + }) + } +} + +impl + TryFrom> + for RouterDataV2, PaymentsResponseData> +{ + type Error = error_stack::Report; + + fn try_from( + item: ConnectorResponseData, + ) -> Result { + let session_token = item + .router_data + .resource_common_data + .get_session_token() + .ok(); + let response = item.response; + let status = AttemptStatus::from(response.tx_status.clone()); + let resource_id = match item.router_data.response.as_ref() { + Ok(PaymentsResponseData::TransactionResponse { resource_id, .. }) => { + resource_id.clone() + } + _ => ResponseId::ConnectorTransactionId(response.tx_id.clone()), + }; + let connector_metadata = build_complete_connector_feature_data( + item.router_data + .resource_common_data + .connector_feature_data + .as_ref(), + &response, + session_token.as_deref(), + ); + Ok(Self { + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id, + redirection_data: None, + connector_metadata: Some(connector_metadata), + mandate_reference: None, + network_txn_id: None, + network_txn_link_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + splits: None, + status_code: item.http_code, + }), + resource_common_data: PaymentFlowData { + status, + ..item.router_data.resource_common_data + }, + ..item.router_data + }) + } +} + +impl TryFrom> + for RouterDataV2 +{ + type Error = error_stack::Report; + + fn try_from( + item: ConnectorResponseData, + ) -> Result { + let response = item.response; + let status = AttemptStatus::from(response.tx_status.clone()); + let resource_id = match item.router_data.response.as_ref() { + Ok(PaymentsResponseData::TransactionResponse { resource_id, .. }) => { + resource_id.clone() + } + _ => ResponseId::ConnectorTransactionId(response.tx_id.clone()), + }; + + Ok(Self { + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id, + redirection_data: None, + connector_metadata: Some(serde_json::json!({ + "txID": response.tx_id, + "status": response.status, + "paymentMethod": response.payment_method, + "description": response.description, + "txStatus": response.tx_status, + "reason": response.reason, + })), + mandate_reference: None, + network_txn_id: None, + network_txn_link_id: None, + connector_response_reference_id: Some( + item.router_data + .resource_common_data + .connector_request_reference_id + .clone(), + ), + incremental_authorization_allowed: None, + splits: None, + status_code: item.http_code, + }), + resource_common_data: PaymentFlowData { + status, + ..item.router_data.resource_common_data + }, + ..item.router_data + }) + } +} + +impl + TryFrom< + GrabpayFlowData, T>, + > for GrabpayRefundRequest +{ + type Error = error_stack::Report; + + fn try_from( + wrapper: GrabpayFlowData< + RouterDataV2, + T, + >, + ) -> Result { + let router_data = wrapper.router_data; + let auth = GrabpayAuthType::try_from(&router_data.connector_config).change_context( + errors::IntegrationError::FailedToObtainAuthType { + context: grabpay_integration_context( + "GrabPay Refund request requires GrabPay connector configuration", + ), + }, + )?; + let partner_tx_id = router_data.request.refund_id; + validate_partner_tx_id(&partner_tx_id)?; + let origin_tx_id = charge_tx_id_from_connector_feature_data( + router_data.resource_common_data.connector_feature_data.as_ref(), + ) + .or_else(|_| { + required_string( + router_data.request.connector_order_id.clone(), + "connector_order_id", + "GrabPay Refund requires the original charge txID as originTxID in connector_order_id", + ) + })?; + let echo = router_data + .request + .refund_connector_metadata + .and_then(|metadata| { + utils::to_connector_meta_from_secret::(Some(metadata)) + .ok() + .and_then(|metadata| metadata.echo) + }); + + Ok(Self { + partner_group_tx_id: router_data.request.connector_transaction_id, + partner_tx_id, + amount: router_data.request.minor_refund_amount, + currency: router_data.request.currency, + merchant_id: auth.merchant_id.peek().to_string(), + origin_tx_id, + description: router_data.request.reason, + echo, + }) + } +} + +impl TryFrom> + for RouterDataV2 +{ + type Error = error_stack::Report; + + fn try_from( + item: ConnectorResponseData, + ) -> Result { + let response = item.response; + let refund_status = RefundStatus::from(response.tx_status.clone()); + + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.router_data.request.refund_id.clone(), + refund_status, + status_code: item.http_code, + acquirer_reference_number: None, + }), + resource_common_data: RefundFlowData { + status: refund_status, + ..item.router_data.resource_common_data + }, + ..item.router_data + }) + } +} + +impl TryFrom> + for RouterDataV2 +{ + type Error = error_stack::Report; + + fn try_from( + item: ConnectorResponseData, + ) -> Result { + let response = item.response; + let refund_status = RefundStatus::from(response.tx_status.clone()); + + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.router_data.request.connector_refund_id.clone(), + refund_status, + status_code: item.http_code, + acquirer_reference_number: None, + }), + resource_common_data: RefundFlowData { + status: refund_status, + ..item.router_data.resource_common_data + }, + ..item.router_data + }) + } +} + +impl + TryFrom< + GrabpayFlowData< + RouterDataV2< + Authenticate, + PaymentFlowData, + PaymentsAuthenticateData, + PaymentsResponseData, + >, + T, + >, + > for GrabpayAuthenticateRequest +{ + type Error = error_stack::Report; + + fn try_from( + wrapper: GrabpayFlowData< + RouterDataV2< + Authenticate, + PaymentFlowData, + PaymentsAuthenticateData, + PaymentsResponseData, + >, + T, + >, + ) -> Result { + Self::try_from(wrapper.router_data) + } +} + +impl + TryFrom< + RouterDataV2< + Authenticate, + PaymentFlowData, + PaymentsAuthenticateData, + PaymentsResponseData, + >, + > for GrabpayAuthenticateRequest +{ + type Error = error_stack::Report; + + fn try_from( + router_data: RouterDataV2< + Authenticate, + PaymentFlowData, + PaymentsAuthenticateData, + PaymentsResponseData, + >, + ) -> Result { + match &router_data.request.payment_method_data { + Some(PaymentMethodData::Wallet(WalletData::GrabpayRedirect {})) => {} + other => { + return Err(error_stack::report!(errors::IntegrationError::NotImplemented( + format!("GrabPay only supports Wallet(GrabpayRedirect); received {other:?}"), + grabpay_integration_context( + "GrabPay Authenticate only accepts PaymentMethodData::Wallet(WalletData::GrabpayRedirect {{}})", + ), + ))); + } + } + + let auth = GrabpayAuthType::try_from(&router_data.connector_config).change_context( + errors::IntegrationError::FailedToObtainAuthType { + context: grabpay_integration_context( + "GrabPay Authenticate request requires GrabPay connector configuration", + ), + }, + )?; + let shipping_details = build_shipping_details(&router_data.resource_common_data); + let items = build_items(&router_data.resource_common_data.order_details); + let partner_tx_id = router_data + .resource_common_data + .connector_request_reference_id + .clone(); + validate_partner_tx_id(&partner_tx_id)?; + let currency = grabpay_request_currency(router_data.request.currency)?; + grabpay_billing_country(&router_data.resource_common_data)?; + + Ok(Self { + partner_group_tx_id: partner_tx_id.clone(), + partner_tx_id, + currency, + amount: router_data.request.amount, + description: router_data.resource_common_data.description, + merchant_id: auth.merchant_id.peek().to_string(), + shipping_details, + items, + }) + } +} + +fn build_items( + order_details: &Option>, +) -> Option> { + let items = order_details + .as_ref()? + .iter() + .filter(|detail| !detail.product_name.is_empty() && detail.quantity > 0) + .map(|detail| GrabpayItem { + item_name: detail.product_name.clone(), + quantity: detail.quantity, + price: detail.amount, + category: detail.category.clone(), + item_category: detail.sub_category.clone(), + image_url: detail.product_img_link.clone(), + }) + .collect::>(); + + (!items.is_empty()).then_some(items) +} + +fn build_shipping_details(flow_data: &PaymentFlowData) -> Option { + flow_data.get_optional_shipping().and_then(|_| { + let shipping_details = GrabpayShippingDetails { + first_name: flow_data.get_optional_shipping_first_name(), + last_name: flow_data.get_optional_shipping_last_name(), + address: build_shipping_address(flow_data), + city: flow_data.get_optional_shipping_city(), + postal_code: flow_data.get_optional_shipping_zip(), + phone: flow_data.get_optional_shipping_phone_number(), + email: flow_data.get_optional_shipping_email(), + country_code: flow_data.get_optional_shipping_country(), + }; + + shipping_details.has_any_field().then_some(shipping_details) + }) +} + +impl GrabpayShippingDetails { + fn has_any_field(&self) -> bool { + self.first_name.is_some() + || self.last_name.is_some() + || self.address.is_some() + || self.city.is_some() + || self.postal_code.is_some() + || self.phone.is_some() + || self.email.is_some() + || self.country_code.is_some() + } +} + +fn build_shipping_address(flow_data: &PaymentFlowData) -> Option> { + let line1 = flow_data + .get_optional_shipping_line1() + .map(|line1| line1.expose()); + let line2 = flow_data + .get_optional_shipping_line2() + .map(|line2| line2.expose()); + + match (line1, line2) { + (Some(line1), Some(line2)) if !line2.is_empty() => { + Some(Secret::new(format!("{line1}, {line2}"))) + } + (Some(line1), _) => Some(Secret::new(line1)), + (None, Some(line2)) => Some(Secret::new(line2)), + (None, None) => None, + } +} + +impl + TryFrom> + for RouterDataV2< + Authenticate, + PaymentFlowData, + PaymentsAuthenticateData, + PaymentsResponseData, + > +{ + type Error = error_stack::Report; + + fn try_from( + item: ConnectorResponseData, + ) -> Result { + let response = item.response; + let request_code = response.request.ok_or_else(|| { + error_stack::report!(errors::ConnectorError::ResponseHandlingFailed { + context: errors::ResponseTransformationErrorContext { + http_status_code: None, + additional_context: Some(format!( + "GrabPay Authenticate did not return request code; partner_tx_id={:?}, status={:?}, tx_status={:?}, reason={:?}, message={:?}, code={:?}", + response.partner_tx_id, + response.status, + response.tx_status, + response.reason, + response.message, + response.code, + )), + }, + }) + })?; + let partner_tx_id = response.partner_tx_id.unwrap_or_else(|| { + item.router_data + .resource_common_data + .connector_request_reference_id + .clone() + }); + validate_partner_tx_id(&partner_tx_id).change_context( + errors::ConnectorError::ResponseHandlingFailed { + context: errors::ResponseTransformationErrorContext { + http_status_code: Some(item.http_code), + additional_context: Some( + "GrabPay Authenticate returned an invalid partnerTxID".to_string(), + ), + }, + }, + )?; + let redirect_context = + build_authenticate_redirect_context(&item.router_data, &request_code, &partner_tx_id) + .change_context(errors::ConnectorError::ResponseHandlingFailed { + context: errors::ResponseTransformationErrorContext { + http_status_code: Some(item.http_code), + additional_context: Some( + "GrabPay Authenticate failed to build OAuth redirect URL".to_string(), + ), + }, + })?; + + Ok(Self { + response: Ok(PaymentsResponseData::AuthenticateResponse { + resource_id: Some(ResponseId::ConnectorTransactionId(partner_tx_id.clone())), + redirection_data: Some(Box::new(RedirectForm::Uri { + uri: redirect_context.redirect_url, + })), + authentication_data: None, + connector_feature_data: Some(redirect_context.connector_feature_data.expose()), + connector_response_reference_id: None, + status_code: item.http_code, + }), + resource_common_data: PaymentFlowData { + status: AttemptStatus::AuthenticationPending, + reference_id: Some(partner_tx_id.clone()), + connector_order_id: Some(partner_tx_id), + ..item.router_data.resource_common_data + }, + ..item.router_data + }) + } +} + +fn build_authenticate_redirect_context< + T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize, +>( + router_data: &RouterDataV2< + Authenticate, + PaymentFlowData, + PaymentsAuthenticateData, + PaymentsResponseData, + >, + request_code: &str, + partner_tx_id: &str, +) -> Result> { + let auth = GrabpayAuthType::try_from(&router_data.connector_config).change_context( + errors::IntegrationError::FailedToObtainAuthType { + context: grabpay_integration_context( + "GrabPay Authenticate redirect requires GrabPay connector configuration", + ), + }, + )?; + let redirect_uri = router_data + .resource_common_data + .return_url + .clone() + .or_else(|| { + router_data + .request + .router_return_url + .as_ref() + .map(ToString::to_string) + }) + .ok_or_else(|| { + error_stack::report!(errors::IntegrationError::MissingRequiredField { + field_name: "return_url", + context: grabpay_integration_context( + "GrabPay OAuth authorize URL requires a redirect_uri".to_string(), + ) + }) + })?; + let currency = grabpay_request_currency(router_data.request.currency)?; + let country = grabpay_billing_country(&router_data.resource_common_data)?.to_string(); + let state = random_token(32); + let nonce = random_token(32); + let code_verifier = random_token(64); + let code_challenge = build_code_challenge(&code_verifier)?; + let acr_values = format!("consent_ctx:countryCode={country},currency={currency}"); + + let authorize_endpoint = oauth_endpoint( + &router_data.resource_common_data.connectors.grabpay.base_url, + OAUTH_AUTHORIZE_PATH, + ); + let mut url = url::Url::parse(&authorize_endpoint).change_context( + errors::IntegrationError::RequestEncodingFailed { + context: grabpay_integration_context( + "GrabPay Authenticate redirect failed to parse OAuth authorize endpoint", + ), + }, + )?; + url.query_pairs_mut() + .append_pair("acr_values", &acr_values) + .append_pair("client_id", auth.client_id.peek()) + .append_pair("code_challenge", &code_challenge) + .append_pair("code_challenge_method", CODE_CHALLENGE_METHOD) + .append_pair("nonce", &nonce) + .append_pair("redirect_uri", &redirect_uri) + .append_pair("request", request_code) + .append_pair("response_type", RESPONSE_TYPE_CODE) + .append_pair("scope", SCOPE_ONE_TIME_CHARGE) + .append_pair("state", &state); + + let connector_feature_data = SecretSerdeValue::new(serde_json::json!({ + "state": state, + "nonce": nonce, + "code_verifier": code_verifier, + "redirect_uri": redirect_uri, + "partner_tx_id": partner_tx_id, + "currency": currency, + })); + + Ok(GrabpayRedirectContext { + redirect_url: url.to_string(), + connector_feature_data, + }) +} + +pub(crate) fn validate_partner_tx_id( + partner_tx_id: &str, +) -> Result<(), error_stack::Report> { + let is_valid = !partner_tx_id.is_empty() + && partner_tx_id.len() <= 32 + && partner_tx_id + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_')); + + if is_valid { + Ok(()) + } else { + Err(error_stack::report!( + errors::IntegrationError::InvalidDataFormat { + field_name: "connector_request_reference_id", + context: grabpay_integration_context( + "GrabPay partnerTxID must be non-empty and at most 32 ASCII alphanumeric, hyphen, or underscore characters" + ) + } + )) + } +} + +fn parse_connector_feature_data( + connector_feature_data: Option<&SecretSerdeValue>, +) -> Result> { + match connector_feature_data { + Some(data) => utils::to_connector_meta_from_secret(Some(data.clone())), + None => Ok(GrabpayConnectorFeatureData { + state: None, + callback_state: None, + code: None, + nonce: None, + code_verifier: None, + redirect_uri: None, + partner_tx_id: None, + currency: None, + tx_id: None, + }), + } +} + +pub(crate) fn currency_from_connector_feature_data( + connector_feature_data: Option<&SecretSerdeValue>, +) -> Result> { + let feature_data = parse_connector_feature_data(connector_feature_data)?; + feature_data.currency.ok_or_else(|| { + error_stack::report!(errors::IntegrationError::MissingRequiredField { + field_name: "connector_feature_data.currency", + context: grabpay_integration_context("GrabPay RSync requires either refund_amount.currency or connector_feature_data.currency" + .to_string(),), + }) + }) +} + +fn charge_tx_id_from_connector_feature_data( + connector_feature_data: Option<&SecretSerdeValue>, +) -> Result> { + let feature_data = parse_connector_feature_data(connector_feature_data)?; + required_feature_field(feature_data.tx_id, "txID") +} + +fn build_complete_connector_feature_data( + connector_feature_data: Option<&SecretSerdeValue>, + response: &GrabpayChargeCompleteResponse, + session_token: Option<&str>, +) -> serde_json::Value { + let mut connector_metadata = connector_feature_data + .and_then(|data| { + utils::to_connector_meta_from_secret::(Some(data.clone())).ok() + }) + .unwrap_or_else(|| serde_json::json!({})); + + if !connector_metadata.is_object() { + connector_metadata = serde_json::json!({}); + } + + let Some(metadata) = connector_metadata.as_object_mut() else { + return connector_metadata; + }; + + metadata.insert("txID".to_string(), serde_json::json!(response.tx_id)); + metadata.insert("status".to_string(), serde_json::json!(response.status)); + metadata.insert( + "paymentMethod".to_string(), + serde_json::json!(response.payment_method), + ); + metadata.insert( + "description".to_string(), + serde_json::json!(response.description), + ); + metadata.insert("reason".to_string(), serde_json::json!(response.reason)); + if let Some(session_token) = session_token { + metadata.insert( + "session_token".to_string(), + serde_json::json!(session_token), + ); + } + + // Strip OAuth-only fields that are dead after the token exchange completes. + metadata.remove("code_verifier"); + metadata.remove("nonce"); + metadata.remove("state"); + metadata.remove("code"); + metadata.remove("callback_state"); + metadata.remove("redirect_uri"); + + connector_metadata +} + +fn validate_callback_state( + feature_data: &GrabpayConnectorFeatureData, +) -> Result<(), error_stack::Report> { + let expected_state = required_feature_field(feature_data.state.clone(), "state")?; + let callback_state = + required_feature_field(feature_data.callback_state.clone(), "callback_state")?; + + if expected_state.peek().as_str() == callback_state.as_str() { + Ok(()) + } else { + Err(error_stack::report!(errors::IntegrationError::InvalidDataFormat { + field_name: "connector_feature_data.callback_state", + context: grabpay_integration_context("GrabPay OAuth callback state does not match the state generated during initial authorization" + .to_string(),) + })) + } +} + +fn required_feature_field( + value: Option, + field_name: &'static str, +) -> Result> { + value.ok_or_else(|| { + error_stack::report!(errors::IntegrationError::MissingRequiredField { + field_name, + context: grabpay_integration_context(format!( + "GrabPay OAuth token exchange requires connector_feature_data.{field_name}" + )) + }) + }) +} + +fn required_string( + value: Option, + field_name: &'static str, + message: &'static str, +) -> Result> { + value.ok_or_else(|| { + error_stack::report!(errors::IntegrationError::MissingRequiredField { + field_name, + context: grabpay_integration_context(message.to_string()) + }) + }) +} + +fn missing_oauth_code_error() -> error_stack::Report { + error_stack::report!(errors::IntegrationError::MissingRequiredField { + field_name: "connector_feature_data.code", + context: grabpay_integration_context( + "GrabPay OAuth code is not available yet; skipping token exchange request".to_string(), + ) + }) +} diff --git a/crates/integrations/connector-integration/src/connectors/imerchantsolutions/transformers.rs b/crates/integrations/connector-integration/src/connectors/imerchantsolutions/transformers.rs index b4a1ee1a19..55ed38f27c 100644 --- a/crates/integrations/connector-integration/src/connectors/imerchantsolutions/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/imerchantsolutions/transformers.rs @@ -564,7 +564,7 @@ impl { + impl<$g: $($b)*> ::interfaces::connector_types::RechargeV2 for $c<$g> {} + $crate::connectors::macros::flow_status_emit!( + connector: $c, status: $st, generic_type: $g, [$($b)*], + flow: ::domain_types::connector_flow::Recharge, + flow_name: "recharge", + flow_common_data: ::domain_types::connector_types::PaymentFlowData, + request: ::domain_types::connector_types::RechargeRequestData, + response: ::domain_types::connector_types::RechargeResponseData, + ); + }; + (connector: $c:ident, flow: CreatePaymentMethod, status: $st:ident, generic_type: $g:tt, [$($b:tt)*]) => { + impl<$g: $($b)*> ::interfaces::connector_types::CreatePaymentMethodV2 for $c<$g> {} + $crate::connectors::macros::flow_status_emit!( + connector: $c, status: $st, generic_type: $g, [$($b)*], + flow: ::domain_types::connector_flow::CreatePaymentMethod, + flow_name: "create_payment_method", + flow_common_data: ::domain_types::connector_types::PaymentFlowData, + request: ::domain_types::connector_types::CreatePaymentMethodData, + response: ::domain_types::connector_types::CreatePaymentMethodResponseData, + ); + }; + (connector: $c:ident, flow: GetPaymentMethod, status: $st:ident, generic_type: $g:tt, [$($b:tt)*]) => { + impl<$g: $($b)*> ::interfaces::connector_types::GetPaymentMethodV2 for $c<$g> {} + $crate::connectors::macros::flow_status_emit!( + connector: $c, status: $st, generic_type: $g, [$($b)*], + flow: ::domain_types::connector_flow::GetPaymentMethod, + flow_name: "get_payment_method", + flow_common_data: ::domain_types::connector_types::PaymentFlowData, + request: ::domain_types::connector_types::GetPaymentMethodData, + response: ::domain_types::connector_types::GetPaymentMethodResponseData, + ); + }; (connector: $c:ident, flow: PreAuthenticate, status: $st:ident, generic_type: $g:tt, [$($b:tt)*]) => { impl<$g: $($b)*> ::interfaces::connector_types::PaymentPreAuthenticateV2<$g> for $c<$g> {} $crate::connectors::macros::flow_status_emit!( diff --git a/crates/integrations/connector-integration/src/connectors/maya.rs b/crates/integrations/connector-integration/src/connectors/maya.rs index 1bd2f134b2..0aff3a3159 100644 --- a/crates/integrations/connector-integration/src/connectors/maya.rs +++ b/crates/integrations/connector-integration/src/connectors/maya.rs @@ -607,6 +607,7 @@ impl fn process_redirect_response( &self, _request: &RequestDetails, + _connector_feature_data: Option<&hyperswitch_masking::Secret>, ) -> CustomResult { // Maya is a redirect-only connector. The redirect body carries no // meaningful payment state; final status is confirmed via PSync or @@ -621,6 +622,7 @@ impl error_reason: None, response_amount: None, raw_connector_response: None, + connector_feature_data: None, }) } } diff --git a/crates/integrations/connector-integration/src/connectors/mifinity/transformers.rs b/crates/integrations/connector-integration/src/connectors/mifinity/transformers.rs index 045a55c55c..86081487da 100644 --- a/crates/integrations/connector-integration/src/connectors/mifinity/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/mifinity/transformers.rs @@ -206,6 +206,7 @@ impl( | WalletData::ApplePayRedirect(_) | WalletData::ApplePayThirdPartySdk(_) | WalletData::DanaRedirect {} + | WalletData::GrabpayRedirect {} | WalletData::GooglePayRedirect(_) | WalletData::GooglePayThirdPartySdk(_) | WalletData::MobilePayRedirect(_) @@ -307,6 +308,7 @@ fn get_gateway_from_payment_method( | WalletData::ApplePayRedirect(_) | WalletData::ApplePayThirdPartySdk(_) | WalletData::DanaRedirect {} + | WalletData::GrabpayRedirect {} | WalletData::GooglePayRedirect(_) | WalletData::GooglePayThirdPartySdk(_) | WalletData::MobilePayRedirect(_) diff --git a/crates/integrations/connector-integration/src/connectors/nexinets/transformers.rs b/crates/integrations/connector-integration/src/connectors/nexinets/transformers.rs index 01defb9309..8c49e9fa8e 100644 --- a/crates/integrations/connector-integration/src/connectors/nexinets/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/nexinets/transformers.rs @@ -873,6 +873,7 @@ fn get_wallet_details< | WalletData::ApplePayRedirect(_) | WalletData::ApplePayThirdPartySdk(_) | WalletData::DanaRedirect { .. } + | WalletData::GrabpayRedirect { .. } | WalletData::GooglePay(_) | WalletData::GooglePayRedirect(_) | WalletData::GooglePayThirdPartySdk(_) diff --git a/crates/integrations/connector-integration/src/connectors/noon/transformers.rs b/crates/integrations/connector-integration/src/connectors/noon/transformers.rs index 209550ce2d..3704a43ab4 100644 --- a/crates/integrations/connector-integration/src/connectors/noon/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/noon/transformers.rs @@ -337,6 +337,7 @@ impl impl connector_types::ValidationTrait for Nuvei { - fn should_do_session_token(&self) -> bool { + fn should_do_session_token( + &self, + _connector_feature_data: Option<&hyperswitch_masking::Secret>, + ) -> bool { true } } diff --git a/crates/integrations/connector-integration/src/connectors/paypal/transformers.rs b/crates/integrations/connector-integration/src/connectors/paypal/transformers.rs index 33dc580d88..436ec5e46d 100644 --- a/crates/integrations/connector-integration/src/connectors/paypal/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/paypal/transformers.rs @@ -1573,6 +1573,7 @@ impl connector_types::ValidationTrait for Paytm { - fn should_do_session_token(&self) -> bool { + fn should_do_session_token( + &self, + _connector_feature_data: Option<&hyperswitch_masking::Secret>, + ) -> bool { true // Enable ServerSessionAuthenticationToken flow for Paytm's initiate step } diff --git a/crates/integrations/connector-integration/src/connectors/payu.rs b/crates/integrations/connector-integration/src/connectors/payu.rs index 034da9e23d..ec792a511d 100644 --- a/crates/integrations/connector-integration/src/connectors/payu.rs +++ b/crates/integrations/connector-integration/src/connectors/payu.rs @@ -104,7 +104,10 @@ impl impl connector_types::ValidationTrait for Payu { - fn should_do_session_token(&self) -> bool { + fn should_do_session_token( + &self, + _connector_feature_data: Option<&hyperswitch_masking::Secret>, + ) -> bool { true // Enable SDKSessionToken (ServerSessionAuthenticationToken) flow } } // Authentication trait implementations diff --git a/crates/integrations/connector-integration/src/connectors/ppro.rs b/crates/integrations/connector-integration/src/connectors/ppro.rs index 40004f6b53..84f8ca65d0 100644 --- a/crates/integrations/connector-integration/src/connectors/ppro.rs +++ b/crates/integrations/connector-integration/src/connectors/ppro.rs @@ -402,6 +402,7 @@ impl fn process_redirect_response( &self, request: &RequestDetails, + _connector_feature_data: Option<&hyperswitch_masking::Secret>, ) -> CustomResult { let charge_id = request.query_params.as_deref().and_then(|qs| { url::form_urlencoded::parse(qs.as_bytes()) @@ -418,6 +419,7 @@ impl error_reason: None, response_amount: None, raw_connector_response: None, + connector_feature_data: None, }) } } diff --git a/crates/integrations/connector-integration/src/connectors/razorpay/transformers.rs b/crates/integrations/connector-integration/src/connectors/razorpay/transformers.rs index 437b081255..661f851bec 100644 --- a/crates/integrations/connector-integration/src/connectors/razorpay/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/razorpay/transformers.rs @@ -320,6 +320,7 @@ impl TryFrom<&WalletData> for RazorpayWalletType { | WalletData::ApplePayRedirect(_) | WalletData::ApplePayThirdPartySdk(_) | WalletData::DanaRedirect {} + | WalletData::GrabpayRedirect {} | WalletData::GooglePay(_) | WalletData::GooglePayRedirect(_) | WalletData::GooglePayThirdPartySdk(_) diff --git a/crates/integrations/connector-integration/src/connectors/revolut.rs b/crates/integrations/connector-integration/src/connectors/revolut.rs index 35a67817d2..b5f61694e7 100644 --- a/crates/integrations/connector-integration/src/connectors/revolut.rs +++ b/crates/integrations/connector-integration/src/connectors/revolut.rs @@ -105,6 +105,7 @@ impl fn process_redirect_response( &self, _request: &RequestDetails, + _connector_feature_data: Option<&hyperswitch_masking::Secret>, ) -> CustomResult { Ok(RedirectDetailsResponse { resource_id: None, @@ -115,6 +116,7 @@ impl error_reason: None, response_amount: None, raw_connector_response: None, + connector_feature_data: None, }) } } diff --git a/crates/integrations/connector-integration/src/connectors/stripe/transformers.rs b/crates/integrations/connector-integration/src/connectors/stripe/transformers.rs index 172d0c39f4..7420d8a15d 100644 --- a/crates/integrations/connector-integration/src/connectors/stripe/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/stripe/transformers.rs @@ -979,6 +979,7 @@ impl TryFrom for StripePaymentMethodType { | common_enums::PaymentMethodType::Paysera | common_enums::PaymentMethodType::Tamara | common_enums::PaymentMethodType::Netbanking + | common_enums::PaymentMethodType::Grabpay | common_enums::PaymentMethodType::Paymaya | common_enums::PaymentMethodType::QwikcilverWallet => { Err(IntegrationError::NotImplemented( @@ -1265,6 +1266,7 @@ fn get_stripe_payment_method_type_from_wallet_data( | WalletData::ApplePayRedirect(_) | WalletData::ApplePayThirdPartySdk(_) | WalletData::DanaRedirect {} + | WalletData::GrabpayRedirect {} | WalletData::GooglePayRedirect(_) | WalletData::GooglePayThirdPartySdk(_) | WalletData::MbWayRedirect(_) @@ -1759,6 +1761,7 @@ impl TryF | WalletData::ApplePayRedirect(_) | WalletData::ApplePayThirdPartySdk(_) | WalletData::DanaRedirect {} + | WalletData::GrabpayRedirect {} | WalletData::GooglePayRedirect(_) | WalletData::GooglePayThirdPartySdk(_) | WalletData::MbWayRedirect(_) diff --git a/crates/integrations/connector-integration/src/connectors/tamara.rs b/crates/integrations/connector-integration/src/connectors/tamara.rs index fd05200df4..c7a20dfe31 100644 --- a/crates/integrations/connector-integration/src/connectors/tamara.rs +++ b/crates/integrations/connector-integration/src/connectors/tamara.rs @@ -357,6 +357,7 @@ impl fn process_redirect_response( &self, request: &RequestDetails, + _connector_feature_data: Option<&hyperswitch_masking::Secret>, ) -> CustomResult { let order_id = get_query_param(request, "orderId"); @@ -369,6 +370,7 @@ impl error_reason: None, response_amount: None, raw_connector_response: None, + connector_feature_data: None, }) } } diff --git a/crates/integrations/connector-integration/src/connectors/worldpay/transformers.rs b/crates/integrations/connector-integration/src/connectors/worldpay/transformers.rs index 6bf274c5ec..af702183cf 100644 --- a/crates/integrations/connector-integration/src/connectors/worldpay/transformers.rs +++ b/crates/integrations/connector-integration/src/connectors/worldpay/transformers.rs @@ -206,7 +206,7 @@ fn fetch_payment_instrument< | WalletDataPaymentMethod::GcashRedirect(_) | WalletDataPaymentMethod::ApplePayRedirect(_) | WalletDataPaymentMethod::ApplePayThirdPartySdk(_) - | WalletDataPaymentMethod::DanaRedirect {} + | WalletDataPaymentMethod::DanaRedirect {} | WalletDataPaymentMethod::GrabpayRedirect {} | WalletDataPaymentMethod::GooglePayRedirect(_) | WalletDataPaymentMethod::GooglePayThirdPartySdk(_) | WalletDataPaymentMethod::MbWayRedirect(_) diff --git a/crates/integrations/connector-integration/src/default_implementations.rs b/crates/integrations/connector-integration/src/default_implementations.rs index 04416ea592..7e4682657a 100644 --- a/crates/integrations/connector-integration/src/default_implementations.rs +++ b/crates/integrations/connector-integration/src/default_implementations.rs @@ -286,6 +286,7 @@ default_impl_verify_webhook_source_v2!( Kount, Hyperswitch, Affirm, + Grabpay, Tesouro, ], ); @@ -990,6 +991,7 @@ default_impl_refresh_payment_method_v2!( Givepayments, Globalpay, Glomopay, + Grabpay, Helcim, Hipay, Hyperpg, diff --git a/crates/integrations/connector-integration/src/types.rs b/crates/integrations/connector-integration/src/types.rs index 6f821a14dc..a2573e38bf 100644 --- a/crates/integrations/connector-integration/src/types.rs +++ b/crates/integrations/connector-integration/src/types.rs @@ -139,6 +139,7 @@ impl Box::new(connectors::Affirm::::new()), ConnectorEnum::Kount => Box::new(connectors::Kount::::new()), ConnectorEnum::Givepayments => Box::new(connectors::Givepayments::::new()), + ConnectorEnum::Grabpay => Box::new(connectors::Grabpay::::new()), ConnectorEnum::Tesouro => Box::new(connectors::Tesouro::::new()), } } diff --git a/crates/internal/composite-service/src/payments.rs b/crates/internal/composite-service/src/payments.rs index 38b0608285..2eeaa89175 100644 --- a/crates/internal/composite-service/src/payments.rs +++ b/crates/internal/composite-service/src/payments.rs @@ -56,6 +56,7 @@ pub trait CompositeSessionTokenRequest { connector: &ConnectorEnum, ) -> MerchantAuthenticationServiceCreateServerSessionAuthenticationTokenRequest; fn has_session_token(&self) -> bool; + fn connector_feature_data(&self) -> Option<&hyperswitch_masking::Secret>; } /// Trait for abstracting request construction for composite pre-authenticate flows. @@ -119,6 +120,10 @@ impl CompositeSessionTokenRequest for CompositeAuthorizeRequest { fn has_session_token(&self) -> bool { self.session_token.is_some() } + + fn connector_feature_data(&self) -> Option<&hyperswitch_masking::Secret> { + self.connector_feature_data.as_ref() + } } impl CompositePreAuthenticatePayload for CompositeAuthorizeRequest { @@ -280,6 +285,10 @@ impl CompositeSessionTokenRequest fn has_session_token(&self) -> bool { self.session_token.is_some() } + + fn connector_feature_data(&self) -> Option<&hyperswitch_masking::Secret> { + self.connector_feature_data.as_ref() + } } /// Holds the mutable state accumulated during composite authorize flow execution. @@ -402,7 +411,9 @@ where tonic::Status, > { let connector_data = ConnectorData::::get_connector_by_name(connector); - let should_do_session_token = connector_data.connector.should_do_session_token(); + let should_do_session_token = connector_data + .connector + .should_do_session_token(payload.connector_feature_data()); let should_create_session_token = !payload.has_session_token() && should_do_session_token; @@ -1206,6 +1217,9 @@ where ), tonic::Status, > { + // `payload.connector_feature_data` already carries the redirect callback data + // (folded in by the caller from the verify-redirect response), so the token + // steps read it through the normal channel — no per-call payload mutation. let access_token_response = self .create_server_authentication_token(connector, payload, metadata, extensions) .await?; @@ -1252,6 +1266,7 @@ where merchant_order_id: payload.merchant_order_id.clone(), request_details: payload.request_details.clone(), redirect_response_secrets: payload.redirect_response_secrets.clone(), + connector_feature_data: payload.connector_feature_data.clone(), }; // Create tonic request with metadata @@ -1277,7 +1292,7 @@ where tonic::Response, tonic::Status, > { - let (metadata, extensions, payload) = request.into_parts(); + let (metadata, extensions, mut payload) = request.into_parts(); let connector = connector_from_composite_authorize_metadata(&metadata).map_err(|err| *err)?; @@ -1285,6 +1300,14 @@ where .verify_redirect_response(&payload, &metadata, &extensions) .await?; + // `process_redirect_response` folds the redirect callback (e.g. the OAuth `code`) + // into `connector_feature_data`. Carry that forward on the payload so the + // post-redirect token/authorize steps see it via the normal + // `connector_feature_data` channel — same as the initial authorize flow. + if verify_response.connector_feature_data.is_some() { + payload.connector_feature_data = verify_response.connector_feature_data.clone(); + } + let connector_data = ConnectorData::< domain_types::payment_method_data::DefaultPCIHolder, >::get_connector_by_name(&connector); @@ -1374,8 +1397,7 @@ where tonic::Response, tonic::Status, > { - self.process_composite_verify_redirect_response(request) - .await + Box::pin(self.process_composite_verify_redirect_response(request)).await } } diff --git a/crates/internal/field-probe/src/auth.rs b/crates/internal/field-probe/src/auth.rs index 27183755e5..3ad234d7fc 100644 --- a/crates/internal/field-probe/src/auth.rs +++ b/crates/internal/field-probe/src/auth.rs @@ -772,6 +772,14 @@ pub(crate) fn dummy_auth(connector: &ConnectorEnum) -> ConnectorSpecificConfig { api_key: k(), base_url: None, }, + ConnectorEnum::Grabpay => ConnectorSpecificConfig::Grabpay { + partner_id: k(), + partner_secret: k(), + client_id: k(), + client_secret: k(), + merchant_id: k(), + base_url: None, + }, ConnectorEnum::Tesouro => ConnectorSpecificConfig::Tesouro { api_key: k(), key1: k(), diff --git a/crates/internal/field-probe/src/normalizer.rs b/crates/internal/field-probe/src/normalizer.rs index 06e9c371d7..fb39089d60 100644 --- a/crates/internal/field-probe/src/normalizer.rs +++ b/crates/internal/field-probe/src/normalizer.rs @@ -496,6 +496,8 @@ pub(crate) fn normalize_header_value(name: &str, value: String) -> String { "salt" => "probeSaltVal0001".to_string(), "idempotency-key" => "HS_probe00000000000000000".to_string(), "timestamp" => "0000000000".to_string(), + "date" => "2020-01-01T00:00:00+00:00".to_string(), + "x-gid-aux-pop" => "signature".to_string(), _ => normalize_content(&value), } } diff --git a/crates/internal/integration-tests/src/connector_specs/grabpay/specs.json b/crates/internal/integration-tests/src/connector_specs/grabpay/specs.json new file mode 100644 index 0000000000..cb233f74b1 --- /dev/null +++ b/crates/internal/integration-tests/src/connector_specs/grabpay/specs.json @@ -0,0 +1,13 @@ +{ + "connector": "grabpay", + "supported_suites": [ + "PaymentMethodAuthenticationService/Authenticate", + "PaymentService/Authorize", + "PaymentService/Get", + "PaymentService/Refund", + "RefundService/Get", + "MerchantAuthenticationService/CreateServerAuthenticationToken", + "MerchantAuthenticationService/CreateServerSessionAuthenticationToken", + "PaymentService/CreateOrder" + ] +} diff --git a/crates/types-traits/domain_types/src/connector_types.rs b/crates/types-traits/domain_types/src/connector_types.rs index e345f4e35a..a0c54b9cad 100644 --- a/crates/types-traits/domain_types/src/connector_types.rs +++ b/crates/types-traits/domain_types/src/connector_types.rs @@ -159,6 +159,7 @@ pub enum ConnectorEnum { Affirm, Kount, Givepayments, + Grabpay, Tesouro, } @@ -516,6 +517,7 @@ impl ForeignTryFrom for ConnectorEnum { grpc_api_types::payments::Connector::Tesouro => Ok(Self::Tesouro), grpc_api_types::payments::Connector::Glomopay => Ok(Self::Glomopay), grpc_api_types::payments::Connector::Givepayments => Ok(Self::Givepayments), + grpc_api_types::payments::Connector::Grabpay => Ok(Self::Grabpay), grpc_api_types::payments::Connector::Unspecified => { Err(IntegrationError::InvalidDataFormat { field_name: "connector", @@ -2761,6 +2763,7 @@ pub struct RedirectDetailsResponse { pub error_message: Option, pub error_reason: Option, pub raw_connector_response: Option, + pub connector_feature_data: Option, } #[derive(Debug, Clone)] @@ -3978,6 +3981,7 @@ impl From> for PaymentMethodData Self::ApplePayThirdPartySdk } payment_method_data::WalletData::DanaRedirect {} => Self::DanaRedirect, + payment_method_data::WalletData::GrabpayRedirect {} => Self::GrabpayRedirect, payment_method_data::WalletData::GooglePay(_) => Self::GooglePay, payment_method_data::WalletData::GooglePayRedirect(_) => Self::GooglePayRedirect, payment_method_data::WalletData::GooglePayThirdPartySdk(_) => { @@ -5563,6 +5567,7 @@ impl ForeignTryFrom AuthType::Payconex(_) => Ok(Self::Payment(ConnectorEnum::Payconex)), AuthType::Kount(_) => Ok(Self::Payment(ConnectorEnum::Kount)), AuthType::Hyperswitch(_) => Ok(Self::Payment(ConnectorEnum::Hyperswitch)), + AuthType::Grabpay(_) => Ok(Self::Payment(ConnectorEnum::Grabpay)), AuthType::Maya(_) => Ok(Self::Payment(ConnectorEnum::Maya)), AuthType::Tesouro(_) => Ok(Self::Payment(ConnectorEnum::Tesouro)), AuthType::Imerchantsolutions(_) => Ok(Self::Payment(ConnectorEnum::Imerchantsolutions)), diff --git a/crates/types-traits/domain_types/src/payment_method_data.rs b/crates/types-traits/domain_types/src/payment_method_data.rs index 7a2befb58b..c1b7449e25 100644 --- a/crates/types-traits/domain_types/src/payment_method_data.rs +++ b/crates/types-traits/domain_types/src/payment_method_data.rs @@ -816,6 +816,7 @@ pub enum WalletData { ApplePayRedirect(Box), ApplePayThirdPartySdk(Box), DanaRedirect {}, + GrabpayRedirect {}, GooglePay(GooglePayWalletData), GooglePayRedirect(Box), GooglePayThirdPartySdk(Box), diff --git a/crates/types-traits/domain_types/src/router_data.rs b/crates/types-traits/domain_types/src/router_data.rs index c4f26e9606..357aa0418e 100644 --- a/crates/types-traits/domain_types/src/router_data.rs +++ b/crates/types-traits/domain_types/src/router_data.rs @@ -940,6 +940,14 @@ pub enum ConnectorSpecificConfig { auth_server_id: Option, base_url: Option, }, + Grabpay { + partner_id: Secret, + partner_secret: Secret, + client_id: Secret, + client_secret: Secret, + merchant_id: Secret, + base_url: Option, + }, Plaid { client_id: Secret, secret: Secret, @@ -1286,6 +1294,13 @@ impl ConnectorSpecificConfig { Tamara { api_key }, Kount { api_key }, Hyperswitch { api_key }, + Grabpay { + partner_id, + partner_secret, + client_id, + client_secret, + merchant_id + }, Tesouro { api_key, key1, @@ -1748,6 +1763,13 @@ impl ConnectorSpecificConfig { Tamara { api_key }, Kount { api_key }, Hyperswitch { api_key }, + Grabpay { + partner_id, + partner_secret, + client_id, + client_secret, + merchant_id + }, Tesouro { api_key, key1, @@ -2355,6 +2377,14 @@ impl ForeignTryFrom for Conne api_key: hyperswitch.api_key.ok_or_else(err)?, base_url: hyperswitch.base_url, }), + AuthType::Grabpay(grabpay) => Ok(Self::Grabpay { + partner_id: grabpay.partner_id.ok_or_else(err)?, + partner_secret: grabpay.partner_secret.ok_or_else(err)?, + client_id: grabpay.client_id.ok_or_else(err)?, + client_secret: grabpay.client_secret.ok_or_else(err)?, + merchant_id: grabpay.merchant_id.ok_or_else(err)?, + base_url: grabpay.base_url, + }), AuthType::Tesouro(tesouro) => Ok(Self::Tesouro { api_key: tesouro.api_key.ok_or_else(err)?, key1: tesouro.key1.ok_or_else(err)?, @@ -3546,6 +3576,7 @@ impl ForeignTryFrom<(&ConnectorAuthType, &connector_types::ConnectorVariant)> }), _ => Err(err().into()), }, + ConnectorEnum::Grabpay => Err(err().into()), ConnectorEnum::Tesouro => match auth { ConnectorAuthType::SignatureKey { api_key, diff --git a/crates/types-traits/domain_types/src/types.rs b/crates/types-traits/domain_types/src/types.rs index 1d1e9ab986..39c3df1d78 100644 --- a/crates/types-traits/domain_types/src/types.rs +++ b/crates/types-traits/domain_types/src/types.rs @@ -424,6 +424,7 @@ pub struct Connectors { pub kount: ConnectorParams, pub plaid: ConnectorParams, pub givepayments: ConnectorParams, + pub grabpay: ConnectorParams, pub tesouro: ConnectorParams, pub santander: ConnectorParams, } @@ -1501,6 +1502,9 @@ impl< payment_method_data::GcashRedirection {}, )), ), + grpc_api_types::payments::payment_method::PaymentMethod::GrabpayRedirect(_) => Ok( + Self::Wallet(payment_method_data::WalletData::GrabpayRedirect {}), + ), grpc_api_types::payments::payment_method::PaymentMethod::DanaRedirect(_) => Ok( Self::Wallet(payment_method_data::WalletData::DanaRedirect {}), ), @@ -2627,6 +2631,7 @@ impl ForeignTryFrom for PaymentMeth } grpc_api_types::payments::PaymentMethodType::AliPay => Ok(PaymentMethodType::AliPay), grpc_api_types::payments::PaymentMethodType::Gcash => Ok(PaymentMethodType::Gcash), + grpc_api_types::payments::PaymentMethodType::GrabPay => Ok(PaymentMethodType::Grabpay), grpc_api_types::payments::PaymentMethodType::Cashapp => Ok(PaymentMethodType::Cashapp), grpc_api_types::payments::PaymentMethodType::SepaBankTransfer => { Ok(PaymentMethodType::SepaBankTransfer) @@ -2783,6 +2788,7 @@ impl ForeignTryFrom for Option Ok(Some(PaymentMethodType::AliPayHk)), grpc_api_types::payments::payment_method::PaymentMethod::DanaRedirect(_) => Ok(Some(PaymentMethodType::Dana)), grpc_api_types::payments::payment_method::PaymentMethod::GcashRedirect(_) => Ok(Some(PaymentMethodType::Gcash)), + grpc_api_types::payments::payment_method::PaymentMethod::GrabpayRedirect(_) => Ok(Some(PaymentMethodType::Grabpay)), grpc_api_types::payments::payment_method::PaymentMethod::GoPayRedirect(_) => Ok(Some(PaymentMethodType::GoPay)), grpc_api_types::payments::payment_method::PaymentMethod::KakaoPayRedirect(_) => Ok(Some(PaymentMethodType::KakaoPay)), grpc_api_types::payments::payment_method::PaymentMethod::MbWayRedirect(_) => Ok(Some(PaymentMethodType::MbWay)), @@ -6721,6 +6727,10 @@ impl ForeignTryFrom for PaymentMethod { payment_method: Some(grpc_api_types::payments::payment_method::PaymentMethod::GcashRedirect(_)), } => Ok(Self::Wallet), + grpc_api_types::payments::PaymentMethod { + payment_method: + Some(grpc_api_types::payments::payment_method::PaymentMethod::GrabpayRedirect(_)), + } => Ok(Self::Wallet), grpc_api_types::payments::PaymentMethod { payment_method: Some(grpc_api_types::payments::payment_method::PaymentMethod::DanaRedirect(_)), @@ -8466,6 +8476,7 @@ impl ForeignTryFrom for PaymentMeth grpc_api_types::payments::PaymentMethodType::Paymaya => Ok(Self::Wallet), grpc_api_types::payments::PaymentMethodType::QwikcilverWallet => Ok(Self::Wallet), grpc_api_types::payments::PaymentMethodType::Skrill => Ok(Self::Wallet), + grpc_api_types::payments::PaymentMethodType::GrabPay => Ok(Self::Wallet), grpc_api_types::payments::PaymentMethodType::UpiCollect => Ok(Self::Upi), grpc_api_types::payments::PaymentMethodType::UpiIntent => Ok(Self::Upi), @@ -12662,6 +12673,7 @@ pub enum PaymentMethodDataType { ApplePayRedirect, ApplePayThirdPartySdk, DanaRedirect, + GrabpayRedirect, DuitNow, GooglePay, GooglePayRedirect, @@ -16389,6 +16401,9 @@ impl ForeignTryFrom<(bool, RedirectDetailsResponse)> raw_connector_response: redirect_details_response .raw_connector_response .map(|response| response.into()), + connector_feature_data: redirect_details_response + .connector_feature_data + .map(|feature_data| feature_data.into()), }) } } diff --git a/crates/types-traits/grpc-api-types/proto/composite_payment.proto b/crates/types-traits/grpc-api-types/proto/composite_payment.proto index fa1755ac9d..a6fde3b36f 100644 --- a/crates/types-traits/grpc-api-types/proto/composite_payment.proto +++ b/crates/types-traits/grpc-api-types/proto/composite_payment.proto @@ -141,7 +141,7 @@ message CompositeAuthorizeRequest { enum CompositeStatus { COMPOSITE_STATUS_UNSPECIFIED = 0; // Default/unset value COMPLETED = 1; // Flow is done; inspect authorize_response for final payment status - REDIRECT_REQUIRED = 2; // Caller must redirect the customer (see authorize_response.redirection_data), then call CompositeAuthorize again with redirection_response + REDIRECT_REQUIRED = 2; // Caller must redirect the customer (see redirection_data on authorize_response, authenticate_response, or pre_authenticate_response), then call the follow-up redirect flow (CompositeAuthorize again with redirection_response, or CompositeVerifyRedirectResponse for connectors that require a post-redirect authorize) } // Response message for composite authorize flow. diff --git a/crates/types-traits/grpc-api-types/proto/payment.proto b/crates/types-traits/grpc-api-types/proto/payment.proto index c993eace31..af5c4fc8cd 100644 --- a/crates/types-traits/grpc-api-types/proto/payment.proto +++ b/crates/types-traits/grpc-api-types/proto/payment.proto @@ -898,6 +898,7 @@ enum Connector { TESOURO = 132; SANTANDER = 133; MAYA = 134; + GRABPAY = 135; } // Payment method types @@ -1018,6 +1019,7 @@ enum PaymentMethodType { QWIKCILVER_WALLET = 113; SKRILL = 114; PAYMAYA = 115; + GRAB_PAY = 116; } // Product type enumeration @@ -4131,6 +4133,9 @@ message PaymentServiceVerifyRedirectResponseRequest { // Security optional RedirectResponseSecrets redirect_response_secrets = 3; + + // Connector-owned metadata from the original redirect flow. + optional SecretString connector_feature_data = 4; } // Response message for VerifyRedirectResponse @@ -4153,6 +4158,9 @@ message PaymentServiceVerifyRedirectResponseResponse { // Raw Response optional SecretString raw_connector_response = 7; + + // Connector-specific metadata produced while parsing the redirect response. + optional SecretString connector_feature_data = 8; } // ============================================================================ @@ -5558,6 +5566,15 @@ message GlomopayConfig { optional string base_url = 50; } +message GrabpayConfig { + SecretString partner_id = 1; + SecretString partner_secret = 2; + SecretString client_id = 3; + SecretString client_secret = 4; + SecretString merchant_id = 5; + optional string base_url = 50; +} + message TesouroConfig { SecretString api_key = 1; SecretString key1 = 2; @@ -5877,6 +5894,8 @@ message ConnectorSpecificConfig { SantanderConfig santander = 142; // MAYA = 143 MayaConfig maya = 143; + // GRABPAY = 144 + GrabpayConfig grabpay = 144; } } diff --git a/crates/types-traits/grpc-api-types/proto/payment_methods.proto b/crates/types-traits/grpc-api-types/proto/payment_methods.proto index 9f4e197509..8ea7225315 100644 --- a/crates/types-traits/grpc-api-types/proto/payment_methods.proto +++ b/crates/types-traits/grpc-api-types/proto/payment_methods.proto @@ -104,6 +104,9 @@ message PaymentMethod { // --- DIRECT WALLETS --- (server-to-server; wallet id rides on the payment_method itself) QwikcilverDirectWallet qwikcilver_wallet_direct = 185; // Qwikcilver / Pine Labs stored-value wallet + // --- GRABPAY WALLET --- + GrabpayRedirectWallet grabpay_redirect = 187; // GrabPay one-time-charge redirect wallet + // --- DEPRECATED REDIRECT WALLETS --- RevolutPayWallet revolut_pay = 181; // Revolut Pay MBWay mb_way = 182; // MB WAY @@ -220,6 +223,8 @@ message PaymentMethod { } } +message GrabpayRedirectWallet {} + // ============================================================================ // PAYMENT METHOD CATEGORIES // ============================================================================ diff --git a/crates/types-traits/interfaces/src/connector_types.rs b/crates/types-traits/interfaces/src/connector_types.rs index b7d73c8171..5ffb7c8238 100644 --- a/crates/types-traits/interfaces/src/connector_types.rs +++ b/crates/types-traits/interfaces/src/connector_types.rs @@ -226,7 +226,10 @@ pub trait ValidationTrait: ConnectorCommon { false } - fn should_do_session_token(&self) -> bool { + fn should_do_session_token( + &self, + _connector_feature_data: Option<&hyperswitch_masking::Secret>, + ) -> bool { false } @@ -722,6 +725,7 @@ pub trait VerifyRedirectResponse: SourceVerification + BodyDecoding { fn process_redirect_response( &self, _request: &RequestDetails, + _connector_feature_data: Option<&hyperswitch_masking::Secret>, ) -> CustomResult { Err(domain_types::errors::IntegrationError::NotImplemented( "process_redirect_response".to_string(), diff --git a/creds_dummy.json b/creds_dummy.json index 63e3d27b30..53cb68b048 100644 --- a/creds_dummy.json +++ b/creds_dummy.json @@ -147,6 +147,14 @@ "api_key": { "value": "" }, "_comment": "Configuration for globalpay connector" }, + "grabpay": { + "partner_id": { "value": "" }, + "partner_secret": { "value": "" }, + "client_id": { "value": "" }, + "client_secret": { "value": "" }, + "merchant_id": { "value": "" }, + "_comment": "Configuration for grabpay connector" + }, "helcim": { "api_key": { "value": "" }, "_comment": "Configuration for helcim connector" diff --git a/data/field_probe/grabpay.json b/data/field_probe/grabpay.json new file mode 100644 index 0000000000..586e90c209 --- /dev/null +++ b/data/field_probe/grabpay.json @@ -0,0 +1,3687 @@ +{ + "connector": "grabpay", + "flows": { + "authenticate": { + "default": { + "status": "not_implemented", + "error": "This feature is not implemented: GrabPay only supports Wallet(GrabpayRedirect); received Some(Card(Card { card_number: RawCardNumber(CardNumber(411111**********)), card_exp_month: *** alloc::string::String ***, card_exp_year: *** alloc::string::String ***, card_cvc: *** alloc::string::String ***, card_issuer: None, card_network: None, card_type: None, card_issuing_country: None, bank_code: None, nick_name: None, card_holder_name: Some(*** alloc::string::String ***), co_badged_card_data: None })). GrabPay Authenticate only accepts PaymentMethodData::Wallet(WalletData::GrabpayRedirect {{}})" + } + }, + "authorize": { + "Ach": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "ach": { + "account_number": "000123456789", + "routing_number": "110000000", + "bank_account_holder_name": "John Doe" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "AchBankTransfer": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "ach_bank_transfer": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Affirm": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "affirm": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Afterpay": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "afterpay_clearpay": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Alfamart": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "alfamart": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "AliPayRedirect": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "ali_pay_redirect": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "AmazonPayRedirect": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "amazon_pay_redirect": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "ApplePay": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "apple_pay_sdk": { + "payment_data": { + "encrypted_data": "eyJ2ZXJzaW9uIjoiRUNfdjEiLCJkYXRhIjoicHJvYmUiLCJzaWduYXR1cmUiOiJwcm9iZSJ9" + }, + "payment_method": { + "display_name": "Visa 1111", + "network": "Visa", + "type": "debit" + }, + "transaction_identifier": "probe_txn_id" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "ApplePayDecrypted": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "apple_pay_sdk": { + "payment_data": { + "decrypted_data": { + "application_primary_account_number": "4111111111111111", + "application_expiration_month": "03", + "application_expiration_year": "2030", + "payment_data": { + "online_payment_cryptogram": "AAAAAA==", + "eci_indicator": "05" + } + } + }, + "payment_method": { + "display_name": "Visa 1111", + "network": "Visa", + "type": "debit" + }, + "transaction_identifier": "probe_txn_id" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "ApplePayThirdPartySdk": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "apple_pay_third_party_sdk": { + "token": "probe_apple_pay_third_party_token" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Bacs": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "bacs": { + "account_number": "55779911", + "sort_code": "200000", + "bank_account_holder_name": "John Doe" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "BacsBankTransfer": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "bacs_bank_transfer": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "BancontactCard": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "bancontact_card": { + "card_number": "4111111111111111", + "card_exp_month": "03", + "card_exp_year": "2030", + "card_holder_name": "John Doe" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "BcaBankTransfer": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "bca_bank_transfer": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Becs": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "becs": { + "account_number": "000123456", + "bsb_number": "000000", + "bank_account_holder_name": "John Doe" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "BillDeskRedirect": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "billdesk_redirect": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Bizum": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "bizum": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Blik": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "blik": { + "blik_code": "777124" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Bluecode": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "bluecode_redirect": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "BniVaBankTransfer": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "bni_va_bank_transfer": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Boleto": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "boleto": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "BriVaBankTransfer": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "bri_va_bank_transfer": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Card": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "card": { + "card_number": "4111111111111111", + "card_exp_month": "03", + "card_exp_year": "2030", + "card_cvc": "737", + "card_holder_name": "John Doe" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "CashappQr": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "cashapp_qr": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "CashfreeRedirect": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "cashfree_redirect": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "CimbVaBankTransfer": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "cimb_va_bank_transfer": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "ClassicReward": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "classic_reward": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Crypto": { + "status": "not_supported", + "error": "Invalid data format: payment_method. The provided payment method variant is empty or not supported by this flow" + }, + "Dana": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "dana_redirect": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "DanamonVaBankTransfer": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "danamon_va_bank_transfer": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "DuitNow": { + "status": "not_supported", + "error": "Invalid data format: payment_method. The provided payment method variant is empty or not supported by this flow" + }, + "EVoucher": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "e_voucher": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "EaseBuzzRedirect": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "easebuzz_redirect": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Efecty": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "efecty": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Eft": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "eft_bank_redirect": { + "provider": "ozow" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Eps": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "eps": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "FamilyMart": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "family_mart": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "GCash": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "gcash_redirect": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Giropay": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "giropay": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Givex": { + "status": "not_supported", + "error": "Invalid data format: payment_method. The provided payment method variant is empty or not supported by this flow" + }, + "GoPay": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "go_pay_redirect": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "GooglePay": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "google_pay_sdk": { + "type": "CARD", + "description": "Visa 1111", + "info": { + "card_network": "VISA", + "card_details": "1111" + }, + "tokenization_data": { + "encrypted_data": { + "token_type": "PAYMENT_GATEWAY", + "token": "{\"id\":\"tok_probe_gpay\",\"object\":\"token\",\"type\":\"card\"}" + } + } + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "GooglePayDecrypted": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "google_pay_sdk": { + "type": "CARD", + "description": "Visa 1111", + "info": { + "card_network": "VISA", + "card_details": "1111" + }, + "tokenization_data": { + "decrypted_data": { + "card_exp_month": "03", + "card_exp_year": "2030", + "application_primary_account_number": "4111111111111111", + "cryptogram": "AAAAAA==", + "eci_indicator": "05" + } + } + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "GooglePayThirdPartySdk": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "google_pay_third_party_sdk": { + "token": "probe_google_pay_third_party_token" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Ideal": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "ideal": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Indomaret": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "indomaret": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "IndonesianBankTransfer": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "indonesian_bank_transfer": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "InstantBankTransfer": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "instant_bank_transfer": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "InstantBankTransferFinland": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "instant_bank_transfer_finland": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "InstantBankTransferPoland": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "instant_bank_transfer_poland": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Interac": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "interac": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "KakaoPay": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "kakao_pay_redirect": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Klarna": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "klarna": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Lawson": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "lawson": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "LazyPayRedirect": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "lazypay_redirect": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "LocalBankRedirect": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "local_bank_redirect": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "LocalBankTransfer": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "local_bank_transfer": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "MandiriVaBankTransfer": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "mandiri_va_bank_transfer": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "MbWay": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "mb_way": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Mifinity": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "mifinity_redirect": { + "date_of_birth": "1990-01-01", + "language_preference": "en" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "MiniStop": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "mini_stop": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "MobilePayRedirect": { + "status": "not_supported", + "error": "Invalid data format: payment_method. The provided payment method variant is empty or not supported by this flow" + }, + "Momo": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "momo_redirect": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "MultibancoBankTransfer": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "multibanco_bank_transfer": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Netbanking": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "netbanking": { + "issuer": "HdfcBank" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "OnlineBankingCzechRepublic": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "online_banking_czech_republic": { + "issuer": "CeskaSporitelna" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "OnlineBankingFinland": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "online_banking_finland": { + "email": "test@example.com" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "OnlineBankingFpx": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "online_banking_fpx": { + "issuer": "Maybank" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "OnlineBankingPoland": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "online_banking_poland": { + "issuer": "BankPekaoSa" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "OnlineBankingSlovakia": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "online_banking_slovakia": { + "issuer": "TatraPay" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "OnlineBankingThailand": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "online_banking_thailand": { + "issuer": "BangkokBank" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "OpenBanking": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "open_banking": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "OpenBankingPis": { + "status": "not_supported", + "error": "Invalid data format: payment_method. The provided payment method variant is empty or not supported by this flow" + }, + "OpenBankingUk": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "open_banking_uk": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Oxxo": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "oxxo": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "PagoEfectivo": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "pago_efectivo": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "PayEasy": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "pay_easy": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "PaySafeCard": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "pay_safe_card": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "PayURedirect": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "payu_redirect": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "PaypalRedirect": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "paypal_redirect": { + "email": "test@example.com" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "PaypalSdk": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "paypal_sdk": { + "token": "probe_paypal_sdk_token" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Paysera": { + "status": "not_supported", + "error": "Invalid data format: payment_method. The provided payment method variant is empty or not supported by this flow" + }, + "Paze": { + "status": "not_supported", + "error": "Invalid data format: payment_method. The provided payment method variant is empty or not supported by this flow" + }, + "PermataBankTransfer": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "permata_bank_transfer": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "PhonePeRedirect": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "phonepe_redirect": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Pix": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "pix": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Przelewy24": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "przelewy24": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Pse": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "pse": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "RedCompra": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "red_compra": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "RedPagos": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "red_pagos": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "RevolutPay": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "revolut_pay": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "SamsungPay": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "samsung_pay_sdk": { + "payment_credential": { + "method": "3DS", + "recurring_payment": false, + "card_brand": "VISA", + "card_last_four_digits": "1234", + "token_data": { + "type": "S", + "version": "100", + "data": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNhbXN1bmdfcHJvYmVfa2V5XzEyMyJ9.eyJwYXltZW50TWV0aG9kVG9rZW4iOiJwcm9iZV9zYW1zdW5nX3Rva2VuIn0.ZHVtbXlfc2lnbmF0dXJl" + } + } + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Satispay": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "satispay": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Seicomart": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "seicomart": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Sepa": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "sepa": { + "iban": "DE89370400440532013000", + "bank_account_holder_name": "John Doe" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "SepaBankTransfer": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "sepa_bank_transfer": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "SepaGuaranteedDebit": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "sepa_guaranteed_debit": { + "iban": "DE89370400440532013000", + "bank_account_holder_name": "John Doe" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "SevenEleven": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "seven_eleven": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Skrill": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "skrill_redirect": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Sofort": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "sofort": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Swish": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "swish_qr": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "TouchNGo": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "touch_n_go_redirect": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Trustly": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "trustly": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Twint": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "twint_redirect": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "UpiCollect": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "upi_collect": { + "vpa_id": "test@upi" + } + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "UpiIntent": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "upi_intent": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "UpiQr": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "upi_qr": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Vipps": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "vipps_redirect": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "WeChatPayQr": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "we_chat_pay_qr": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + }, + "Wero": { + "status": "supported", + "proto_request": { + "merchant_transaction_id": "probe_txn_001", + "amount": { + "minor_amount": 1000, + "currency": "USD" + }, + "payment_method": { + "wero": {} + }, + "capture_method": "AUTOMATIC", + "address": { + "billing_address": {} + }, + "auth_type": "NO_THREE_DS", + "return_url": "https://example.com/return", + "session_token": "probe_session_token" + }, + "sample": { + "url": "https://partner-api.grab.com/grabpay/partner/v2/charge/complete", + "method": "Post", + "headers": { + "authorization": "Bearer probe_session_token", + "content-type": "application/json", + "date": "2020-01-01T00:00:00+00:00", + "via": "HyperSwitch", + "x-gid-aux-pop": "signature" + }, + "body": "{\"partnerTxID\":\"probe_txn_001\"}" + } + } + }, + "capture": { + "default": { + "status": "not_supported", + "error": "capture flow not supported by grabpay connector" + } + }, + "create_client_authentication_token": { + "default": { + "status": "not_implemented", + "error": "This feature is not implemented: client_authentication_token flow for grabpay" + } + }, + "create_order": { + "default": { + "status": "not_implemented", + "error": "This feature is not implemented: create_order flow for grabpay" + } + }, + "create_server_authentication_token": { + "default": { + "status": "not_implemented", + "error": "This feature is not implemented: server_authentication_token flow for grabpay" + } + }, + "create_server_session_authentication_token": { + "default": { + "status": "error", + "error": "Stuck on field: connector_feature_data.code. GrabPay OAuth code is not available yet; skipping token exchange request — Missing required field: connector_feature_data.code. GrabPay OAuth code is not available yet; skipping token exchange request" + } + }, + "customer_create": { + "default": { + "status": "not_implemented", + "error": "This feature is not implemented: create_connector_customer flow for grabpay" + } + }, + "customer_get": { + "default": { + "status": "not_implemented", + "error": "This feature is not implemented: get_connector_customer flow for grabpay" + } + }, + "dispute_accept": { + "default": { + "status": "not_implemented", + "error": "This feature is not implemented: accept_dispute flow for grabpay" + } + }, + "dispute_defend": { + "default": { + "status": "not_implemented", + "error": "This feature is not implemented: defend_dispute flow for grabpay" + } + }, + "dispute_get": { + "default": { + "status": "not_implemented" + } + }, + "dispute_submit_evidence": { + "default": { + "status": "not_implemented", + "error": "This feature is not implemented: submit_evidence flow for grabpay" + } + }, + "eligibility": { + "default": { + "status": "not_implemented", + "error": "This feature is not implemented: eligibility flow for grabpay" + } + }, + "get": { + "default": { + "status": "error", + "error": "Stuck on field: access_token — Missing required field: access_token" + } + }, + "handle_event": { + "default": { + "status": "supported", + "proto_request": { + "merchant_event_id": "probe_event_001", + "request_details": { + "method": "HTTP_METHOD_POST", + "uri": "https://example.com/webhook", + "headers": {}, + "body": "{\"txType\":\"payment\",\"txStatus\":\"success\",\"partnerID\":\"partner_123\",\"partnerTxID\":\"txn_123\",\"txID\":\"grab_txn_123\",\"amount\":100,\"currency\":\"SGD\",\"payload\":{\"newStatus\":\"success\",\"paymentMethod\":\"GRABPAY\"}}" + } + }, + "sample": { + "url": "", + "method": "Post", + "headers": {}, + "body": "{\"txType\":\"payment\",\"txStatus\":\"success\",\"partnerID\":\"partner_123\",\"partnerTxID\":\"txn_123\",\"txID\":\"grab_txn_123\",\"amount\":100,\"currency\":\"SGD\",\"payload\":{\"newStatus\":\"success\",\"paymentMethod\":\"GRABPAY\"}}" + } + } + }, + "incremental_authorization": { + "default": { + "status": "not_implemented", + "error": "This feature is not implemented: incremental_authorization flow for grabpay" + } + }, + "parse_event": { + "default": { + "status": "supported", + "proto_request": { + "request_details": { + "method": "HTTP_METHOD_POST", + "uri": "https://example.com/webhook", + "headers": {}, + "body": "{\"txType\":\"payment\",\"txStatus\":\"success\",\"partnerID\":\"partner_123\",\"partnerTxID\":\"txn_123\",\"txID\":\"grab_txn_123\",\"amount\":100,\"currency\":\"SGD\",\"payload\":{\"newStatus\":\"success\",\"paymentMethod\":\"GRABPAY\"}}" + } + }, + "sample": { + "url": "", + "method": "Post", + "headers": {}, + "body": "{\"txType\":\"payment\",\"txStatus\":\"success\",\"partnerID\":\"partner_123\",\"partnerTxID\":\"txn_123\",\"txID\":\"grab_txn_123\",\"amount\":100,\"currency\":\"SGD\",\"payload\":{\"newStatus\":\"success\",\"paymentMethod\":\"GRABPAY\"}}" + } + } + }, + "payment_method_eligibility": { + "default": { + "status": "not_implemented" + } + }, + "post_authenticate": { + "default": { + "status": "not_implemented", + "error": "This feature is not implemented: post_authenticate flow for grabpay" + } + }, + "pre_authenticate": { + "default": { + "status": "not_implemented", + "error": "This feature is not implemented: pre_authenticate flow for grabpay" + } + }, + "proxy_authorize": { + "default": { + "status": "error", + "error": "Stuck on field: session_token. GrabPay post-redirect authorize requires a successful OAuth session-token exchange — Missing required field: session_token. GrabPay post-redirect authorize requires a successful OAuth session-token exchange" + } + }, + "proxy_setup_recurring": { + "default": { + "status": "not_supported", + "error": "setup_mandate flow not supported by grabpay connector" + } + }, + "recurring_charge": { + "default": { + "status": "not_implemented", + "error": "This feature is not implemented: repeat_payment flow for grabpay" + } + }, + "recurring_revoke": { + "default": { + "status": "not_supported", + "error": "mandate_revoke flow not supported by grabpay connector" + } + }, + "refresh": { + "default": { + "status": "not_implemented" + } + }, + "refund": { + "default": { + "status": "error", + "error": "Stuck on field: access_token — Missing required field: access_token" + } + }, + "refund_get": { + "default": { + "status": "error", + "error": "Stuck on field: connector_feature_data.currency. GrabPay RSync requires either refund_amount.currency or connector_feature_data.currency — Missing required field: connector_feature_data.currency. GrabPay RSync requires either refund_amount.currency or connector_feature_data.currency" + } + }, + "reverse": { + "default": { + "status": "not_implemented", + "error": "This feature is not implemented: void_post_capture flow for grabpay" + } + }, + "setup_recurring": { + "default": { + "status": "not_supported", + "error": "setup_mandate flow not supported by grabpay connector" + } + }, + "token_authorize": { + "default": { + "status": "error", + "error": "Stuck on field: session_token. GrabPay post-redirect authorize requires a successful OAuth session-token exchange — Missing required field: session_token. GrabPay post-redirect authorize requires a successful OAuth session-token exchange" + } + }, + "token_setup_recurring": { + "default": { + "status": "not_supported", + "error": "setup_mandate flow not supported by grabpay connector" + } + }, + "tokenize": { + "default": { + "status": "not_implemented", + "error": "This feature is not implemented: payment_method_token flow for grabpay" + } + }, + "verify_redirect": { + "default": { + "status": "supported" + } + }, + "void": { + "default": { + "status": "not_supported", + "error": "void flow not supported by grabpay connector" + } + } + } +} \ No newline at end of file diff --git a/docs-generated/all_connector.md b/docs-generated/all_connector.md index f94546198f..4e3e03be23 100644 --- a/docs-generated/all_connector.md +++ b/docs-generated/all_connector.md @@ -62,6 +62,7 @@ Authorize a payment amount on a payment method. This reserves funds without capt | [Givepayments](connectors/givepayments.md) | ✓ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | | [Globalpay](connectors/globalpay.md) | ✓ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | ⚠ | ✓ | ⚠ | ⚠ | ⚠ | ✓ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | | [Glomopay](connectors/glomopay.md) | ✓ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | +| [Grabpay](connectors/grabpay.md) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | x | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | x | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | x | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | x | ✓ | x | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | [Helcim](connectors/helcim.md) | ✓ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | | [Hipay](connectors/hipay.md) | ✓ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | | [Hyperpg](connectors/hyperpg.md) | ✓ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | x | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | ⚠ | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | @@ -174,6 +175,7 @@ Consolidated view of Get, Void, Refund, Capture, Reverse, CreateOrder, and other | [Givepayments](connectors/givepayments.md) | ✓ | ⚠ | ⚠ | x | ⚠ | ✓ | x | ⚠ | ⚠ | ⚠ | ⚠ | ? | ⚠ | ✓ | ⚠ | ✓ | x | x | x | ⚠ | x | x | x | x | x | ⚠ | x | x | x | x | x | x | ⚠ | x | x | ✓ | ✓ | x | | [Globalpay](connectors/globalpay.md) | ✓ | ✓ | ⚠ | ✓ | ⚠ | ✓ | ⚠ | ⚠ | ✓ | ✓ | ⚠ | ✓ | ✓ | ✓ | ⚠ | ✓ | x | ⚠ | ⚠ | ⚠ | x | x | x | x | x | ✓ | ⚠ | ✓ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | | [Glomopay](connectors/glomopay.md) | ✓ | x | x | ⚠ | ? | ✓ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ✓ | ⚠ | ⚠ | ⚠ | ✓ | x | ? | ✓ | ⚠ | x | x | x | x | x | ⚠ | ⚠ | ⚠ | x | x | x | x | ⚠ | x | x | ✓ | ✓ | x | +| [Grabpay](connectors/grabpay.md) | ? | x | ⚠ | x | ⚠ | ? | ⚠ | ✓ | x | ? | x | ? | x | ⚠ | x | ? | x | ⚠ | ⚠ | ⚠ | x | x | x | x | ⚠ | ⚠ | ? | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ✓ | ✓ | x | | [Helcim](connectors/helcim.md) | ✓ | ✓ | ⚠ | ✓ | x | ✓ | x | ⚠ | ⚠ | ⚠ | ⚠ | ✓ | ⚠ | ⚠ | ⚠ | ✓ | x | ⚠ | ⚠ | ⚠ | x | x | x | x | x | ⚠ | ⚠ | ⚠ | x | x | x | x | ⚠ | x | x | ⚠ | ⚠ | x | | [Hipay](connectors/hipay.md) | ✓ | ✓ | x | ✓ | ⚠ | ✓ | x | ⚠ | ⚠ | ✓ | ⚠ | ✓ | ⚠ | ⚠ | x | ✓ | x | x | x | ✓ | x | x | x | x | x | x | x | x | x | x | x | x | ⚠ | x | ⚠ | ⚠ | ⚠ | x | | [Hyperpg](connectors/hyperpg.md) | ✓ | ⚠ | x | ⚠ | ⚠ | ✓ | x | ⚠ | ⚠ | ⚠ | ⚠ | ✓ | ⚠ | ⚠ | ⚠ | ✓ | x | ⚠ | ⚠ | ⚠ | x | x | x | x | x | x | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | ⚠ | x | diff --git a/docs-generated/connectors/grabpay.md b/docs-generated/connectors/grabpay.md new file mode 100644 index 0000000000..21e0c420a1 --- /dev/null +++ b/docs-generated/connectors/grabpay.md @@ -0,0 +1,464 @@ +# Grabpay + + + +## SDK Configuration + +Use this config for all flows in this connector. Replace `YOUR_API_KEY` with your actual credentials. + + + + + + + + + +
PythonJavaScriptKotlinRust
+ +
Python + +```python +from payments.generated import sdk_config_pb2, payment_pb2, payment_methods_pb2 + +config = sdk_config_pb2.ConnectorConfig( + options=sdk_config_pb2.SdkOptions(environment=sdk_config_pb2.Environment.SANDBOX), + connector_config=payment_pb2.ConnectorSpecificConfig( + grabpay=payment_pb2.GrabpayConfig( + partner_id=payment_methods_pb2.SecretString(value="YOUR_PARTNER_ID"), + partner_secret=payment_methods_pb2.SecretString(value="YOUR_PARTNER_SECRET"), + client_id=payment_methods_pb2.SecretString(value="YOUR_CLIENT_ID"), + client_secret=payment_methods_pb2.SecretString(value="YOUR_CLIENT_SECRET"), + merchant_id=payment_methods_pb2.SecretString(value="YOUR_MERCHANT_ID"), + base_url="YOUR_BASE_URL", + ), + ), +) + +``` + +
+ +
+ +
JavaScript + +```javascript +const { PaymentClient } = require('hyperswitch-prism'); +const { ConnectorConfig, Environment, Connector } = require('hyperswitch-prism').types; + +const config = ConnectorConfig.create({ + connector: Connector.GRABPAY, + environment: Environment.SANDBOX, + auth: { + grabpay: { + partnerId: { value: 'YOUR_PARTNER_ID' }, + partnerSecret: { value: 'YOUR_PARTNER_SECRET' }, + clientId: { value: 'YOUR_CLIENT_ID' }, + clientSecret: { value: 'YOUR_CLIENT_SECRET' }, + merchantId: { value: 'YOUR_MERCHANT_ID' }, + baseUrl: 'YOUR_BASE_URL', + } + }, +}); +``` + +
+ +
+ +
Kotlin + +```kotlin +val config = ConnectorConfig.newBuilder() + .setOptions(SdkOptions.newBuilder().setEnvironment(Environment.SANDBOX).build()) + .setConnectorConfig( + ConnectorSpecificConfig.newBuilder() + .setGrabpay(GrabpayConfig.newBuilder() + .setPartnerId(SecretString.newBuilder().setValue("YOUR_PARTNER_ID").build()) + .setPartnerSecret(SecretString.newBuilder().setValue("YOUR_PARTNER_SECRET").build()) + .setClientId(SecretString.newBuilder().setValue("YOUR_CLIENT_ID").build()) + .setClientSecret(SecretString.newBuilder().setValue("YOUR_CLIENT_SECRET").build()) + .setMerchantId(SecretString.newBuilder().setValue("YOUR_MERCHANT_ID").build()) + .setBaseUrl("YOUR_BASE_URL") + .build()) + .build() + ) + .build() +``` + +
+ +
+ +
Rust + +```rust +use grpc_api_types::payments::*; +use grpc_api_types::payments::connector_specific_config; + +let config = ConnectorConfig { + connector_config: Some(ConnectorSpecificConfig { + config: Some(connector_specific_config::Config::Grabpay(GrabpayConfig { + partner_id: Some(hyperswitch_masking::Secret::new("YOUR_PARTNER_ID".to_string())), // Authentication credential + partner_secret: Some(hyperswitch_masking::Secret::new("YOUR_PARTNER_SECRET".to_string())), // Authentication credential + client_id: Some(hyperswitch_masking::Secret::new("YOUR_CLIENT_ID".to_string())), // Authentication credential + client_secret: Some(hyperswitch_masking::Secret::new("YOUR_CLIENT_SECRET".to_string())), // Authentication credential + merchant_id: Some(hyperswitch_masking::Secret::new("YOUR_MERCHANT_ID".to_string())), // Authentication credential + base_url: Some("https://sandbox.example.com".to_string()), // Base URL for API calls + ..Default::default() + })), + }), + options: Some(SdkOptions { + environment: Environment::Sandbox.into(), + }), +}; +``` + +
+ +
+ +## Integration Scenarios + +Complete, runnable examples for common integration patterns. Each example shows the full flow with status handling. Copy-paste into your app and replace placeholder values. + +### One-step Payment (Authorize + Capture) + +Simple payment that authorizes and captures in one call. Use for immediate charges. + +**Response status handling:** + +| Status | Recommended action | +|--------|-------------------| +| `AUTHORIZED` | Payment authorized and captured — funds will be settled automatically | +| `PENDING` | Payment processing — await webhook for final status before fulfilling | +| `FAILED` | Payment declined — surface error to customer, do not retry without new details | + +**Examples:** [Python](../../examples/grabpay/grabpay.py#L67) · [JavaScript](../../examples/grabpay/grabpay.js) · [Kotlin](../../examples/grabpay/grabpay.kt#L74) · [Rust](../../examples/grabpay/grabpay.rs#L112) + +## API Reference + +| Flow (Service.RPC) | Category | gRPC Request Message | +|--------------------|----------|----------------------| +| [PaymentService.Authorize](#paymentserviceauthorize) | Payments | `PaymentServiceAuthorizeRequest` | +| [EventService.HandleEvent](#eventservicehandleevent) | Events | `EventServiceHandleRequest` | +| [EventService.ParseEvent](#eventserviceparseevent) | Events | `EventServiceParseRequest` | +| [PaymentService.VerifyRedirectResponse](#paymentserviceverifyredirectresponse) | Payments | `PaymentServiceVerifyRedirectResponseRequest` | + +### Payments + +#### PaymentService.Authorize + +Authorize a payment amount on a payment method. This reserves funds without capturing them, essential for verifying availability before finalizing. + +| | Message | +|---|---------| +| **Request** | `PaymentServiceAuthorizeRequest` | +| **Response** | `PaymentServiceAuthorizeResponse` | + +**Supported payment method types:** + +| Payment Method | Supported | +|----------------|:---------:| +| Card | ✓ | +| Bancontact | ✓ | +| Apple Pay | ✓ | +| Apple Pay Dec | ✓ | +| Apple Pay SDK | ✓ | +| Google Pay | ✓ | +| Google Pay Dec | ✓ | +| Google Pay SDK | ✓ | +| PayPal SDK | ✓ | +| Amazon Pay | ✓ | +| Cash App | ✓ | +| PayPal | ✓ | +| WeChat Pay | ✓ | +| Alipay | ✓ | +| Revolut Pay | ✓ | +| MiFinity | ✓ | +| Bluecode | ✓ | +| Paze | x | +| Samsung Pay | ✓ | +| MB Way | ✓ | +| Satispay | ✓ | +| Wero | ✓ | +| GoPay | ✓ | +| GCash | ✓ | +| Momo | ✓ | +| Dana | ✓ | +| Kakao Pay | ✓ | +| Touch 'n Go | ✓ | +| Twint | ✓ | +| Vipps | ✓ | +| Swish | ✓ | +| Affirm | ✓ | +| Afterpay | ✓ | +| Klarna | ✓ | +| UPI Collect | ✓ | +| UPI Intent | ✓ | +| UPI QR | ✓ | +| Thailand | ✓ | +| Czech | ✓ | +| Finland | ✓ | +| FPX | ✓ | +| Poland | ✓ | +| Slovakia | ✓ | +| UK | ✓ | +| PIS | x | +| Generic | ✓ | +| Local | ✓ | +| iDEAL | ✓ | +| Sofort | ✓ | +| Trustly | ✓ | +| Giropay | ✓ | +| EPS | ✓ | +| Przelewy24 | ✓ | +| PSE | ✓ | +| BLIK | ✓ | +| Interac | ✓ | +| Bizum | ✓ | +| EFT | ✓ | +| DuitNow | x | +| ACH | ✓ | +| SEPA | ✓ | +| BACS | ✓ | +| Multibanco | ✓ | +| Instant | ✓ | +| Instant FI | ✓ | +| Instant PL | ✓ | +| Pix | ✓ | +| Permata | ✓ | +| BCA | ✓ | +| BNI VA | ✓ | +| BRI VA | ✓ | +| CIMB VA | ✓ | +| Danamon VA | ✓ | +| Mandiri VA | ✓ | +| Local | ✓ | +| Indonesian | ✓ | +| ACH | ✓ | +| SEPA | ✓ | +| BACS | ✓ | +| BECS | ✓ | +| SEPA Guaranteed | ✓ | +| Crypto | x | +| Reward | ✓ | +| Givex | x | +| PaySafeCard | ✓ | +| E-Voucher | ✓ | +| Boleto | ✓ | +| Efecty | ✓ | +| Pago Efectivo | ✓ | +| Red Compra | ✓ | +| Red Pagos | ✓ | +| Alfamart | ✓ | +| Indomaret | ✓ | +| Oxxo | ✓ | +| 7-Eleven | ✓ | +| Lawson | ✓ | +| Mini Stop | ✓ | +| Family Mart | ✓ | +| Seicomart | ✓ | +| Pay Easy | ✓ | + +**Payment method objects** — use these in the `payment_method` field of the Authorize request. + +##### Card (Raw PAN) + +```python +"payment_method": { + "card": { + "card_number": "4111111111111111", + "card_exp_month": "03", + "card_exp_year": "2030", + "card_cvc": "737", + "card_holder_name": "John Doe" + } +} +``` + +##### Google Pay + +```python +"payment_method": { + "google_pay_sdk": { + "type": "CARD", + "description": "Visa 1111", + "info": { + "card_network": "VISA", + "card_details": "1111" + }, + "tokenization_data": { + "encrypted_data": { + "token_type": "PAYMENT_GATEWAY", + "token": "{\"id\":\"tok_probe_gpay\",\"object\":\"token\",\"type\":\"card\"}" + } + } + } +} +``` + +##### Apple Pay + +```python +"payment_method": { + "apple_pay_sdk": { + "payment_data": { + "encrypted_data": "eyJ2ZXJzaW9uIjoiRUNfdjEiLCJkYXRhIjoicHJvYmUiLCJzaWduYXR1cmUiOiJwcm9iZSJ9" + }, + "payment_method": { + "display_name": "Visa 1111", + "network": "Visa", + "type": "debit" + }, + "transaction_identifier": "probe_txn_id" + } +} +``` + +##### SEPA Direct Debit + +```python +"payment_method": { + "sepa": { + "iban": "DE89370400440532013000", + "bank_account_holder_name": "John Doe" + } +} +``` + +##### BACS Direct Debit + +```python +"payment_method": { + "bacs": { + "account_number": "55779911", + "sort_code": "200000", + "bank_account_holder_name": "John Doe" + } +} +``` + +##### ACH Direct Debit + +```python +"payment_method": { + "ach": { + "account_number": "000123456789", + "routing_number": "110000000", + "bank_account_holder_name": "John Doe" + } +} +``` + +##### BECS Direct Debit + +```python +"payment_method": { + "becs": { + "account_number": "000123456", + "bsb_number": "000000", + "bank_account_holder_name": "John Doe" + } +} +``` + +##### iDEAL + +```python +"payment_method": { + "ideal": {} +} +``` + +##### PayPal Redirect + +```python +"payment_method": { + "paypal_redirect": { + "email": "test@example.com" + } +} +``` + +##### BLIK + +```python +"payment_method": { + "blik": { + "blik_code": "777124" + } +} +``` + +##### Klarna + +```python +"payment_method": { + "klarna": {} +} +``` + +##### Afterpay / Clearpay + +```python +"payment_method": { + "afterpay_clearpay": {} +} +``` + +##### UPI Collect + +```python +"payment_method": { + "upi_collect": { + "vpa_id": "test@upi" + } +} +``` + +##### Affirm + +```python +"payment_method": { + "affirm": {} +} +``` + +##### Samsung Pay + +```python +"payment_method": { + "samsung_pay_sdk": { + "payment_credential": { + "method": "3DS", + "recurring_payment": false, + "card_brand": "VISA", + "card_last_four_digits": "1234", + "token_data": { + "type": "S", + "version": "100", + "data": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNhbXN1bmdfcHJvYmVfa2V5XzEyMyJ9.eyJwYXltZW50TWV0aG9kVG9rZW4iOiJwcm9iZV9zYW1zdW5nX3Rva2VuIn0.ZHVtbXlfc2lnbmF0dXJl" + } + } + } +} +``` + +**Examples:** [Python](../../examples/grabpay/grabpay.py) · [TypeScript](../../examples/grabpay/grabpay.ts#L108) · [Kotlin](../../examples/grabpay/grabpay.kt#L89) · [Rust](../../examples/grabpay/grabpay.rs) + +#### PaymentService.VerifyRedirectResponse + +Verify and process redirect responses from 3D Secure or other external flows. Validates authentication results and updates payment state accordingly. + +| | Message | +|---|---------| +| **Request** | `PaymentServiceVerifyRedirectResponseRequest` | +| **Response** | `PaymentServiceVerifyRedirectResponseResponse` | + +**Examples:** [Python](../../examples/grabpay/grabpay.py) · [TypeScript](../../examples/grabpay/grabpay.ts#L135) · [Kotlin](../../examples/grabpay/grabpay.kt#L132) · [Rust](../../examples/grabpay/grabpay.rs) diff --git a/docs-generated/llms.txt b/docs-generated/llms.txt index b3bdac3969..e0571f9428 100644 --- a/docs-generated/llms.txt +++ b/docs-generated/llms.txt @@ -1,5 +1,5 @@ # Connector Service — LLM Navigation Index -# Connectors: 103 +# Connectors: 104 # # This file helps AI coding assistants navigate connector-service documentation. # Each connector block lists: doc path, scenarios, supported payment methods, @@ -8,7 +8,7 @@ # Usage: fetch this file first, then fetch the specific connector doc or example. overview: - total_connectors: 103 + total_connectors: 104 docs_root: docs-generated/connectors/ examples_root: examples/ all_connectors_matrix: docs-generated/all_connector.md @@ -334,6 +334,14 @@ payment_methods: Card flows: authorize, customer_get, get, handle_event, parse_event, proxy_authorize, refund, refund_get examples_python: examples/glomopay/glomopay.py +## Grabpay +connector_id: grabpay +doc: docs/connectors/grabpay.md +scenarios: checkout_autocapture +payment_methods: Ach, AchBankTransfer, Affirm, Afterpay, Alfamart, AliPayRedirect, AmazonPayRedirect, ApplePay, ApplePayDecrypted, ApplePayThirdPartySdk, Bacs, BacsBankTransfer, BancontactCard, BcaBankTransfer, Becs, BillDeskRedirect, Bizum, Blik, Bluecode, BniVaBankTransfer, Boleto, BriVaBankTransfer, Card, CashappQr, CashfreeRedirect, CimbVaBankTransfer, ClassicReward, Dana, DanamonVaBankTransfer, EVoucher, EaseBuzzRedirect, Efecty, Eft, Eps, FamilyMart, GCash, Giropay, GoPay, GooglePay, GooglePayDecrypted, GooglePayThirdPartySdk, Ideal, Indomaret, IndonesianBankTransfer, InstantBankTransfer, InstantBankTransferFinland, InstantBankTransferPoland, Interac, KakaoPay, Klarna, Lawson, LazyPayRedirect, LocalBankRedirect, LocalBankTransfer, MandiriVaBankTransfer, MbWay, Mifinity, MiniStop, Momo, MultibancoBankTransfer, Netbanking, OnlineBankingCzechRepublic, OnlineBankingFinland, OnlineBankingFpx, OnlineBankingPoland, OnlineBankingSlovakia, OnlineBankingThailand, OpenBanking, OpenBankingUk, Oxxo, PagoEfectivo, PayEasy, PaySafeCard, PayURedirect, PaypalRedirect, PaypalSdk, PermataBankTransfer, PhonePeRedirect, Pix, Przelewy24, Pse, RedCompra, RedPagos, RevolutPay, SamsungPay, Satispay, Seicomart, Sepa, SepaBankTransfer, SepaGuaranteedDebit, SevenEleven, Skrill, Sofort, Swish, TouchNGo, Trustly, Twint, UpiCollect, UpiIntent, UpiQr, Vipps, WeChatPayQr, Wero +flows: authorize, handle_event, parse_event, verify_redirect +examples_python: examples/grabpay/grabpay.py + ## Helcim connector_id: helcim doc: docs/connectors/helcim.md diff --git a/examples/grabpay/grabpay.kt b/examples/grabpay/grabpay.kt new file mode 100644 index 0000000000..14d7b3edb8 --- /dev/null +++ b/examples/grabpay/grabpay.kt @@ -0,0 +1,153 @@ +// This file is auto-generated. Do not edit manually. +// Replace YOUR_API_KEY and placeholder values with real data. +// Regenerate: python3 scripts/generate-connector-docs.py grabpay +// +// Grabpay — all scenarios and flows in one file. +// Run a scenario: ./gradlew run --args="grabpay processCheckoutCard" + +package examples.grabpay + +import types.Payment.* +import types.PaymentMethods.* +import payments.PaymentClient +import payments.EventClient +import payments.AuthenticationType +import payments.CaptureMethod +import payments.Currency +import payments.HttpMethod +import payments.ConnectorConfig +import payments.SdkOptions +import payments.Environment +import payments.ConnectorSpecificConfig +import types.Payment.GrabpayConfig +import payments.SecretString + +val SUPPORTED_FLOWS = listOf("authorize", "parse_event") + +val _defaultConfig: ConnectorConfig = ConnectorConfig.newBuilder() + .setOptions(SdkOptions.newBuilder().setEnvironment(Environment.SANDBOX).build()) + .setConnectorConfig( + ConnectorSpecificConfig.newBuilder() + .setGrabpay(GrabpayConfig.newBuilder() + .setPartnerId(SecretString.newBuilder().setValue("YOUR_PARTNER_ID").build()) + .setPartnerSecret(SecretString.newBuilder().setValue("YOUR_PARTNER_SECRET").build()) + .setClientId(SecretString.newBuilder().setValue("YOUR_CLIENT_ID").build()) + .setClientSecret(SecretString.newBuilder().setValue("YOUR_CLIENT_SECRET").build()) + .setMerchantId(SecretString.newBuilder().setValue("YOUR_MERCHANT_ID").build()) + .setBaseUrl("YOUR_BASE_URL") + .build()) + .build() + ) + .build() + + + +private fun buildAuthorizeRequest(captureMethodStr: String): PaymentServiceAuthorizeRequest { + return PaymentServiceAuthorizeRequest.newBuilder().apply { + merchantTransactionId = "probe_txn_001" // Identification. + amountBuilder.apply { // The amount for the payment. + minorAmount = 1000L // Amount in minor units (e.g., 1000 = $10.00). + currency = Currency.USD // ISO 4217 currency code (e.g., "USD", "EUR"). + } + paymentMethodBuilder.apply { // Payment method to be used. + cardBuilder.apply { // Generic card payment. + cardNumberBuilder.value = "4111111111111111" // Card Identification. + cardExpMonthBuilder.value = "03" + cardExpYearBuilder.value = "2030" + cardCvcBuilder.value = "737" + cardHolderNameBuilder.value = "John Doe" // Cardholder Information. + } + } + captureMethod = CaptureMethod.valueOf(captureMethodStr) // Method for capturing the payment. + addressBuilder.apply { // Address Information. + billingAddressBuilder.apply { + } + } + authType = AuthenticationType.NO_THREE_DS // Authentication Details. + returnUrl = "https://example.com/return" // URLs for Redirection and Webhooks. + sessionToken = "probe_session_token" // Session and Token Information. + }.build() +} + +// Scenario: One-step Payment (Authorize + Capture) +// Simple payment that authorizes and captures in one call. Use for immediate charges. +fun processCheckoutAutocapture(txnId: String, config: ConnectorConfig = _defaultConfig): Map { + val paymentClient = PaymentClient(config) + + // Step 1: Authorize — reserve funds on the payment method + val authorizeResponse = paymentClient.authorize(buildAuthorizeRequest("AUTOMATIC")) + + when (authorizeResponse.status.name) { + "FAILED" -> throw RuntimeException("Payment failed: ${authorizeResponse.error.unifiedDetails.message}") + "PENDING" -> return mapOf("status" to "PENDING") // await webhook before proceeding + } + + return mapOf("status" to authorizeResponse.status.name, "transactionId" to authorizeResponse.connectorTransactionId, "error" to authorizeResponse.error) +} + +// Flow: PaymentService.Authorize (Card) +fun authorize(txnId: String, config: ConnectorConfig = _defaultConfig) { + val client = PaymentClient(config) + val request = buildAuthorizeRequest("AUTOMATIC") + val response = client.authorize(request) + when (response.status.name) { + "FAILED" -> throw RuntimeException("Authorize failed: ${response.error.unifiedDetails.message}") + "PENDING" -> println("Pending — await webhook before proceeding") + else -> println("Authorized: ${response.connectorTransactionId}") + } +} + +// Flow: EventService.HandleEvent +fun handleEvent(txnId: String, config: ConnectorConfig = _defaultConfig) { + val client = EventClient(config) + val request = EventServiceHandleRequest.newBuilder().apply { + merchantEventId = "probe_event_001" // Caller-supplied correlation key, echoed in the response. Not used by UCS for processing. + requestDetailsBuilder.apply { + method = HttpMethod.HTTP_METHOD_POST // HTTP method of the request (e.g., GET, POST). + uri = "https://example.com/webhook" // URI of the request. + putAllHeaders(mapOf()) // Headers of the HTTP request. + body = com.google.protobuf.ByteString.copyFromUtf8("{\"txType\":\"payment\",\"txStatus\":\"success\",\"partnerID\":\"partner_123\",\"partnerTxID\":\"txn_123\",\"txID\":\"grab_txn_123\",\"amount\":100,\"currency\":\"SGD\",\"payload\":{\"newStatus\":\"success\",\"paymentMethod\":\"GRABPAY\"}}") // Body of the HTTP request. + } + }.build() + val response = client.handle_event(request) + println("Webhook: type=${response.eventType.name} verified=${response.sourceVerified}") +} + +// Flow: EventService.ParseEvent +fun parseEvent(txnId: String, config: ConnectorConfig = _defaultConfig) { + val client = EventClient(config) + val request = EventServiceParseRequest.newBuilder().apply { + requestDetailsBuilder.apply { + method = HttpMethod.HTTP_METHOD_POST // HTTP method of the request (e.g., GET, POST). + uri = "https://example.com/webhook" // URI of the request. + putAllHeaders(mapOf()) // Headers of the HTTP request. + body = com.google.protobuf.ByteString.copyFromUtf8("{\"txType\":\"payment\",\"txStatus\":\"success\",\"partnerID\":\"partner_123\",\"partnerTxID\":\"txn_123\",\"txID\":\"grab_txn_123\",\"amount\":100,\"currency\":\"SGD\",\"payload\":{\"newStatus\":\"success\",\"paymentMethod\":\"GRABPAY\"}}") // Body of the HTTP request. + } + }.build() + val response = client.parse_event(request) + println("Webhook parsed: type=${response.eventType.name}") +} + +// Flow: PaymentService.VerifyRedirectResponse +fun verifyRedirect(txnId: String, config: ConnectorConfig = _defaultConfig) { + val client = PaymentClient(config) + val request = PaymentServiceVerifyRedirectResponseRequest.newBuilder().apply { + + }.build() + val response = client.verify_redirect_response(request) + println("Source verified: ${response.sourceVerified}") +} + + +fun main(args: Array) { + val txnId = "order_001" + val flow = args.firstOrNull() ?: "processCheckoutAutocapture" + when (flow) { + "processCheckoutAutocapture" -> processCheckoutAutocapture(txnId) + "authorize" -> authorize(txnId) + "handleEvent" -> handleEvent(txnId) + "parseEvent" -> parseEvent(txnId) + "verifyRedirect" -> verifyRedirect(txnId) + else -> System.err.println("Unknown flow: $flow. Available: processCheckoutAutocapture, authorize, handleEvent, parseEvent, verifyRedirect") + } +} diff --git a/examples/grabpay/grabpay.py b/examples/grabpay/grabpay.py new file mode 100644 index 0000000000..2cfcf7da86 --- /dev/null +++ b/examples/grabpay/grabpay.py @@ -0,0 +1,110 @@ +# This file is auto-generated. Do not edit manually. +# Replace YOUR_API_KEY and placeholder values with real data. +# Regenerate: python3 scripts/generate-connector-docs.py grabpay +# +# Grabpay — all integration scenarios and flows in one file. +# Run a scenario: python3 grabpay.py checkout_card + +import asyncio +import sys +from payments import PaymentClient +from payments import EventClient +from payments.generated import sdk_config_pb2, payment_pb2, payment_methods_pb2 + +SUPPORTED_FLOWS = ["authorize", "parse_event"] + +_default_config = sdk_config_pb2.ConnectorConfig( + options=sdk_config_pb2.SdkOptions(environment=sdk_config_pb2.Environment.SANDBOX), + connector_config=payment_pb2.ConnectorSpecificConfig( + grabpay=payment_pb2.GrabpayConfig( + partner_id=payment_methods_pb2.SecretString(value="YOUR_PARTNER_ID"), + partner_secret=payment_methods_pb2.SecretString(value="YOUR_PARTNER_SECRET"), + client_id=payment_methods_pb2.SecretString(value="YOUR_CLIENT_ID"), + client_secret=payment_methods_pb2.SecretString(value="YOUR_CLIENT_SECRET"), + merchant_id=payment_methods_pb2.SecretString(value="YOUR_MERCHANT_ID"), + base_url="YOUR_BASE_URL", + ), + ), +) + + + + +def _build_authorize_request(capture_method: str): + return payment_pb2.PaymentServiceAuthorizeRequest( + merchant_transaction_id="probe_txn_001", # Identification. + amount=payment_pb2.Money( # The amount for the payment. + minor_amount=1000, # Amount in minor units (e.g., 1000 = $10.00). + currency=payment_pb2.Currency.Value("USD"), # ISO 4217 currency code (e.g., "USD", "EUR"). + ), + payment_method=payment_methods_pb2.PaymentMethod( # Payment method to be used. + card=payment_methods_pb2.CardDetails( + card_number=payment_methods_pb2.CardNumberType(value="4111111111111111"), # Card Identification. + card_exp_month=payment_methods_pb2.SecretString(value="03"), + card_exp_year=payment_methods_pb2.SecretString(value="2030"), + card_cvc=payment_methods_pb2.SecretString(value="737"), + card_holder_name=payment_methods_pb2.SecretString(value="John Doe"), # Cardholder Information. + ), + ), + capture_method=payment_pb2.CaptureMethod.Value(capture_method), # Method for capturing the payment. + address=payment_pb2.PaymentAddress( # Address Information. + billing_address=payment_pb2.Address(), + ), + auth_type=payment_pb2.AuthenticationType.Value("NO_THREE_DS"), # Authentication Details. + return_url="https://example.com/return", # URLs for Redirection and Webhooks. + session_token="probe_session_token", # Session and Token Information. + ) + +def _build_parse_event_request(): + return payment_pb2.EventServiceParseRequest( + request_details=payment_pb2.RequestDetails( + method=payment_pb2.HttpMethod.Value("HTTP_METHOD_POST"), # HTTP method of the request (e.g., GET, POST). + uri="https://example.com/webhook", # URI of the request. + headers={}, # Headers of the HTTP request. + body="{\"txType\":\"payment\",\"txStatus\":\"success\",\"partnerID\":\"partner_123\",\"partnerTxID\":\"txn_123\",\"txID\":\"grab_txn_123\",\"amount\":100,\"currency\":\"SGD\",\"payload\":{\"newStatus\":\"success\",\"paymentMethod\":\"GRABPAY\"}}".encode(), # Body of the HTTP request. + ), + ) +async def process_checkout_autocapture(merchant_transaction_id: str, config: sdk_config_pb2.ConnectorConfig = _default_config): + """One-step Payment (Authorize + Capture) + + Simple payment that authorizes and captures in one call. Use for immediate charges. + """ + payment_client = PaymentClient(config) + + # Step 1: Authorize — reserve funds on the payment method + authorize_response = await payment_client.authorize(_build_authorize_request("AUTOMATIC")) + + if authorize_response.status == "FAILED": + raise RuntimeError(f"Payment failed: {authorize_response.error}") + if authorize_response.status == "PENDING": + # Awaiting async confirmation — handle via webhook + return {"status": "pending", "transaction_id": authorize_response.connector_transaction_id} + + return {"status": getattr(authorize_response, "status", ""), "transaction_id": getattr(authorize_response, "connector_transaction_id", ""), "error": getattr(authorize_response, "error", None)} + + +async def process_authorize(merchant_transaction_id: str, config: sdk_config_pb2.ConnectorConfig = _default_config): + """Flow: PaymentService.Authorize (Card)""" + payment_client = PaymentClient(config) + + authorize_response = await payment_client.authorize(_build_authorize_request("AUTOMATIC")) + + return {"status": authorize_response.status, "transaction_id": authorize_response.connector_transaction_id} + + +async def process_parse_event(merchant_transaction_id: str, config: sdk_config_pb2.ConnectorConfig = _default_config): + """Flow: EventService.ParseEvent""" + event_client = EventClient(config) + + parse_response = event_client.parse_event(_build_parse_event_request()) + + return {"event_type": parse_response.event_type} + +if __name__ == "__main__": + scenario = sys.argv[1] if len(sys.argv) > 1 else "checkout_autocapture" + fn = globals().get(f"process_{scenario}") + if not fn: + available = [k[8:] for k in globals() if k.startswith("process_")] + print(f"Unknown scenario: {scenario}. Available: {available}", file=sys.stderr) + sys.exit(1) + asyncio.run(fn("order_001")) diff --git a/examples/grabpay/grabpay.rs b/examples/grabpay/grabpay.rs new file mode 100644 index 0000000000..e48e3eed46 --- /dev/null +++ b/examples/grabpay/grabpay.rs @@ -0,0 +1,205 @@ +// This file is auto-generated. Do not edit manually. +// Replace YOUR_API_KEY and placeholder values with real data. +// Regenerate: python3 scripts/generate-connector-docs.py grabpay +// +// Grabpay — all scenarios and flows in one file. +// Run a scenario: cargo run --example grabpay -- process_checkout_card +use cards::CardNumber; +use grpc_api_types::payments::connector_specific_config; +use grpc_api_types::payments::payment_method; +use grpc_api_types::payments::*; +use hyperswitch_masking::Secret; +use hyperswitch_payments_client::ConnectorClient; +use std::collections::HashMap; +use std::str::FromStr; + +#[allow(dead_code)] +pub const SUPPORTED_FLOWS: &[&str] = &["authorize", "parse_event"]; + +#[allow(dead_code)] +fn build_client() -> ConnectorClient { + // Configure the connector with authentication + let config = ConnectorConfig { + connector_config: Some(ConnectorSpecificConfig { + config: Some(connector_specific_config::Config::Grabpay(GrabpayConfig { + partner_id: Some(hyperswitch_masking::Secret::new( + "YOUR_PARTNER_ID".to_string(), + )), // Authentication credential + partner_secret: Some(hyperswitch_masking::Secret::new( + "YOUR_PARTNER_SECRET".to_string(), + )), // Authentication credential + client_id: Some(hyperswitch_masking::Secret::new( + "YOUR_CLIENT_ID".to_string(), + )), // Authentication credential + client_secret: Some(hyperswitch_masking::Secret::new( + "YOUR_CLIENT_SECRET".to_string(), + )), // Authentication credential + merchant_id: Some(hyperswitch_masking::Secret::new( + "YOUR_MERCHANT_ID".to_string(), + )), // Authentication credential + base_url: Some("https://sandbox.example.com".to_string()), // Base URL for API calls + ..Default::default() + })), + }), + options: Some(SdkOptions { + environment: Environment::Sandbox.into(), + }), + }; + ConnectorClient::new(config, None).unwrap() +} + +pub fn build_authorize_request(capture_method: &str) -> PaymentServiceAuthorizeRequest { + PaymentServiceAuthorizeRequest { + merchant_transaction_id: Some("probe_txn_001".to_string()), // Identification. + amount: Some(Money { + // The amount for the payment. + minor_amount: 1000, // Amount in minor units (e.g., 1000 = $10.00). + currency: Currency::Usd.into(), // ISO 4217 currency code (e.g., "USD", "EUR"). + }), + payment_method: Some(PaymentMethod { + // Payment method to be used. + payment_method: Some(payment_method::PaymentMethod::Card(CardDetails { + card_number: Some(CardNumber::from_str("4111111111111111").unwrap()), // Card Identification. + card_exp_month: Some(Secret::new("03".to_string())), + card_exp_year: Some(Secret::new("2030".to_string())), + card_cvc: Some(Secret::new("737".to_string())), + card_holder_name: Some(Secret::new("John Doe".to_string())), // Cardholder Information. + ..Default::default() + })), + ..Default::default() + }), + capture_method: Some( + CaptureMethod::from_str_name(capture_method) + .unwrap_or_default() + .into(), + ), // Method for capturing the payment. + address: Some(PaymentAddress { + // Address Information. + billing_address: Some(Address { + ..Default::default() + }), + ..Default::default() + }), + auth_type: AuthenticationType::NoThreeDs.into(), // Authentication Details. + return_url: Some("https://example.com/return".to_string()), // URLs for Redirection and Webhooks. + session_token: Some("probe_session_token".to_string()), // Session and Token Information. + ..Default::default() + } +} + +#[allow(dead_code)] +pub fn build_handle_event_request() -> EventServiceHandleRequest { + EventServiceHandleRequest { + merchant_event_id: Some("probe_event_001".to_string()), // Caller-supplied correlation key, echoed in the response. Not used by UCS for processing. + request_details: Some(RequestDetails { + method: HttpMethod::Post.into(), // HTTP method of the request (e.g., GET, POST). + uri: Some("https://example.com/webhook".to_string()), // URI of the request. + headers: [].into_iter().collect::>(), // Headers of the HTTP request. + body: "{\"txType\":\"payment\",\"txStatus\":\"success\",\"partnerID\":\"partner_123\",\"partnerTxID\":\"txn_123\",\"txID\":\"grab_txn_123\",\"amount\":100,\"currency\":\"SGD\",\"payload\":{\"newStatus\":\"success\",\"paymentMethod\":\"GRABPAY\"}}".as_bytes().to_vec(), // Body of the HTTP request. + ..Default::default() + }), + ..Default::default() + } +} + +pub fn build_parse_event_request() -> EventServiceParseRequest { + EventServiceParseRequest { + request_details: Some(RequestDetails { + method: HttpMethod::Post.into(), // HTTP method of the request (e.g., GET, POST). + uri: Some("https://example.com/webhook".to_string()), // URI of the request. + headers: [].into_iter().collect::>(), // Headers of the HTTP request. + body: "{\"txType\":\"payment\",\"txStatus\":\"success\",\"partnerID\":\"partner_123\",\"partnerTxID\":\"txn_123\",\"txID\":\"grab_txn_123\",\"amount\":100,\"currency\":\"SGD\",\"payload\":{\"newStatus\":\"success\",\"paymentMethod\":\"GRABPAY\"}}".as_bytes().to_vec(), // Body of the HTTP request. + ..Default::default() + }), + } +} + +#[allow(dead_code)] +pub fn build_verify_redirect_request() -> PaymentServiceVerifyRedirectResponseRequest { + PaymentServiceVerifyRedirectResponseRequest { + ..Default::default() + } +} + +// Scenario: One-step Payment (Authorize + Capture) +// Simple payment that authorizes and captures in one call. Use for immediate charges. +#[allow(dead_code)] +pub async fn process_checkout_autocapture( + client: &ConnectorClient, + _merchant_transaction_id: &str, +) -> Result> { + // Step 1: Authorize — reserve funds on the payment method + let authorize_response = client + .authorize(build_authorize_request("AUTOMATIC"), &HashMap::new(), None) + .await?; + + match authorize_response.status() { + PaymentStatus::Failure | PaymentStatus::AuthorizationFailed => { + return Err(format!("Payment failed: {:?}", authorize_response.error).into()) + } + PaymentStatus::Pending => return Ok("pending — awaiting webhook".to_string()), + _ => {} + } + + Ok(format!( + "Payment: {:?} — {}", + authorize_response.status(), + authorize_response + .connector_transaction_id + .as_deref() + .unwrap_or("") + )) +} + +// Flow: PaymentService.Authorize (Card) +#[allow(dead_code)] +pub async fn process_authorize( + client: &ConnectorClient, + _merchant_transaction_id: &str, +) -> Result> { + let response = client + .authorize(build_authorize_request("AUTOMATIC"), &HashMap::new(), None) + .await?; + match response.status() { + PaymentStatus::Failure | PaymentStatus::AuthorizationFailed => { + Err(format!("Authorize failed: {:?}", response.error).into()) + } + PaymentStatus::Pending => Ok("pending — await webhook".to_string()), + _ => Ok(format!( + "Authorized: {}", + response.connector_transaction_id.as_deref().unwrap_or("") + )), + } +} + +// Flow: EventService.ParseEvent +#[allow(dead_code)] +pub async fn process_parse_event( + client: &ConnectorClient, + _merchant_transaction_id: &str, +) -> Result> { + let response = client.parse_event(build_parse_event_request())?; + Ok(format!("{response:?}")) +} + +#[allow(dead_code)] +#[tokio::main] +async fn main() { + let client = build_client(); + let flow = std::env::args() + .nth(1) + .unwrap_or_else(|| "process_checkout_autocapture".to_string()); + let result: Result> = match flow.as_str() { + "process_checkout_autocapture" => process_checkout_autocapture(&client, "order_001").await, + "process_authorize" => process_authorize(&client, "txn_001").await, + "process_parse_event" => process_parse_event(&client, "txn_001").await, + _ => { + eprintln!("Unknown flow: {}. Available: process_checkout_autocapture, process_authorize, process_parse_event", flow); + return; + } + }; + match result { + Ok(msg) => println!("✓ {msg}"), + Err(e) => eprintln!("✗ {e}"), + } +} diff --git a/examples/grabpay/grabpay.ts b/examples/grabpay/grabpay.ts new file mode 100644 index 0000000000..2f48260def --- /dev/null +++ b/examples/grabpay/grabpay.ts @@ -0,0 +1,162 @@ +// This file is auto-generated. Do not edit manually. +// Replace YOUR_API_KEY and placeholder values with real data. +// Regenerate: python3 scripts/generate-connector-docs.py grabpay +// +// Grabpay — all integration scenarios and flows in one file. +// Run a scenario: npx tsx grabpay.ts checkout_autocapture + +import { PaymentClient, EventClient, types } from 'hyperswitch-prism'; +const { Environment, AuthenticationType, CaptureMethod, Currency, HttpMethod } = types; +export const SUPPORTED_FLOWS = ["authorize", "parse_event"]; + +const _defaultConfig: types.IConnectorConfig = { + options: { + environment: Environment.SANDBOX, + }, + connectorConfig: { + grabpay: { + partnerId: { value: 'YOUR_PARTNER_ID' }, + partnerSecret: { value: 'YOUR_PARTNER_SECRET' }, + clientId: { value: 'YOUR_CLIENT_ID' }, + clientSecret: { value: 'YOUR_CLIENT_SECRET' }, + merchantId: { value: 'YOUR_MERCHANT_ID' }, + baseUrl: 'YOUR_BASE_URL', + } + }, +}; + + +function _buildAuthorizeRequest(captureMethod: types.CaptureMethod): types.IPaymentServiceAuthorizeRequest { + return { + "merchantTransactionId": "probe_txn_001", // Identification. + "amount": { // The amount for the payment. + "minorAmount": 1000, // Amount in minor units (e.g., 1000 = $10.00). + "currency": Currency.USD // ISO 4217 currency code (e.g., "USD", "EUR"). + }, + "paymentMethod": { // Payment method to be used. + "card": { // Generic card payment. + "cardNumber": {"value": "4111111111111111"}, // Card Identification. + "cardExpMonth": {"value": "03"}, + "cardExpYear": {"value": "2030"}, + "cardCvc": {"value": "737"}, + "cardHolderName": {"value": "John Doe"} // Cardholder Information. + } + }, + "captureMethod": captureMethod, // Method for capturing the payment. + "address": { // Address Information. + "billingAddress": { + } + }, + "authType": AuthenticationType.NO_THREE_DS, // Authentication Details. + "returnUrl": "https://example.com/return", // URLs for Redirection and Webhooks. + "sessionToken": "probe_session_token" // Session and Token Information. + }; +} + +function _buildHandleEventRequest(): types.IEventServiceHandleRequest { + return { + "merchantEventId": "probe_event_001", // Caller-supplied correlation key, echoed in the response. Not used by UCS for processing. + "requestDetails": { + "method": HttpMethod.HTTP_METHOD_POST, // HTTP method of the request (e.g., GET, POST). + "uri": "https://example.com/webhook", // URI of the request. + "headers": { // Headers of the HTTP request. + }, + "body": new Uint8Array(Buffer.from("{\"txType\":\"payment\",\"txStatus\":\"success\",\"partnerID\":\"partner_123\",\"partnerTxID\":\"txn_123\",\"txID\":\"grab_txn_123\",\"amount\":100,\"currency\":\"SGD\",\"payload\":{\"newStatus\":\"success\",\"paymentMethod\":\"GRABPAY\"}}", "utf-8")) // Body of the HTTP request. + } + }; +} + +function _buildParseEventRequest(): types.IEventServiceParseRequest { + return { + "requestDetails": { + "method": HttpMethod.HTTP_METHOD_POST, // HTTP method of the request (e.g., GET, POST). + "uri": "https://example.com/webhook", // URI of the request. + "headers": { // Headers of the HTTP request. + }, + "body": new Uint8Array(Buffer.from("{\"txType\":\"payment\",\"txStatus\":\"success\",\"partnerID\":\"partner_123\",\"partnerTxID\":\"txn_123\",\"txID\":\"grab_txn_123\",\"amount\":100,\"currency\":\"SGD\",\"payload\":{\"newStatus\":\"success\",\"paymentMethod\":\"GRABPAY\"}}", "utf-8")) // Body of the HTTP request. + } + }; +} + +function _buildVerifyRedirectRequest(): types.IPaymentServiceVerifyRedirectResponseRequest { + return { + }; +} + + +// ANCHOR: scenario_functions +// One-step Payment (Authorize + Capture) +// Simple payment that authorizes and captures in one call. Use for immediate charges. +async function processCheckoutAutocapture(merchantTransactionId: string, config: types.IConnectorConfig = _defaultConfig) { + const paymentClient = new PaymentClient(config); + + // Step 1: Authorize — reserve funds on the payment method + const authorizeResponse = await paymentClient.authorize(_buildAuthorizeRequest(CaptureMethod.AUTOMATIC)); + + if (authorizeResponse.status === types.PaymentStatus.FAILURE) { + throw new Error(`Payment failed: ${JSON.stringify(authorizeResponse.error)}`); + } + if (authorizeResponse.status === types.PaymentStatus.PENDING) { + // Awaiting async confirmation — handle via webhook + return { status: 'pending', connectorTransactionId: authorizeResponse.connectorTransactionId }; + } + + return { status: authorizeResponse.status, transactionId: authorizeResponse.connectorTransactionId!, error: authorizeResponse.error } as any; +} + +// Flow: PaymentService.Authorize (Card) +async function authorize(merchantTransactionId: string, config: types.IConnectorConfig = _defaultConfig) { + const paymentClient = new PaymentClient(config); + + const authorizeResponse = await paymentClient.authorize(_buildAuthorizeRequest(CaptureMethod.AUTOMATIC)); + + return authorizeResponse; +} + +// Flow: EventService.HandleEvent +async function handleEvent(merchantTransactionId: string, config: types.IConnectorConfig = _defaultConfig) { + const eventClient = new EventClient(config); + + const handleResponse = await eventClient.handleEvent(_buildHandleEventRequest()); + + return handleResponse; +} + +// Flow: EventService.ParseEvent +async function parseEvent(merchantTransactionId: string, config: types.IConnectorConfig = _defaultConfig) { + const eventClient = new EventClient(config); + + const parseResponse = await eventClient.parseEvent(_buildParseEventRequest()); + + return parseResponse; +} + +// Flow: PaymentService.VerifyRedirectResponse +async function verifyRedirect(merchantTransactionId: string, config: types.IConnectorConfig = _defaultConfig) { + const paymentClient = new PaymentClient(config); + + const verifyResponse = await paymentClient.verifyRedirectResponse(_buildVerifyRedirectRequest()); + + return verifyResponse; +} + + +// Export all process* functions for the smoke test +export { + processCheckoutAutocapture, authorize, handleEvent, parseEvent, verifyRedirect, _buildAuthorizeRequest, _buildHandleEventRequest, _buildParseEventRequest, _buildVerifyRedirectRequest +}; + +// CLI runner +if (require.main === module) { + const scenario = process.argv[2] || 'checkout_autocapture'; + const key = 'process' + scenario.replace(/_([a-z])/g, (_, l) => l.toUpperCase()).replace(/^(.)/, c => c.toUpperCase()); + const fn = (globalThis as any)[key] || (exports as any)[key]; + if (!fn) { + const available = Object.keys(exports).map(k => + k.replace(/^process/, '').replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, '') + ); + console.error(`Unknown scenario: ${scenario}. Available: ${available.join(', ')}`); + process.exit(1); + } + fn('order_001').catch(console.error); +} diff --git a/sdk/javascript/src/payments/_generated_grpc_client.ts b/sdk/javascript/src/payments/_generated_grpc_client.ts index d10db3bedc..76d932e7f6 100644 --- a/sdk/javascript/src/payments/_generated_grpc_client.ts +++ b/sdk/javascript/src/payments/_generated_grpc_client.ts @@ -186,7 +186,8 @@ const _SECRET_STRING_FIELDS: Record = { PaymentMethodAuthenticationServicePostAuthenticateResponse: ["connectorFeatureData", "rawConnectorResponse"], PaymentServiceIncrementalAuthorizationRequest: ["connectorFeatureData"], PaymentServiceIncrementalAuthorizationResponse: ["rawConnectorRequest", "rawConnectorResponse"], - PaymentServiceVerifyRedirectResponseResponse: ["rawConnectorResponse"], + PaymentServiceVerifyRedirectResponseRequest: ["connectorFeatureData"], + PaymentServiceVerifyRedirectResponseResponse: ["rawConnectorResponse", "connectorFeatureData"], RefundServiceGetRequest: ["refundMetadata", "connectorFeatureData"], DisputeServiceSubmitEvidenceResponse: ["rawConnectorRequest"], DisputeServiceDefendResponse: ["rawConnectorRequest"], @@ -318,6 +319,7 @@ const _SECRET_STRING_FIELDS: Record = { KountConfig: ["apiKey"], HyperswitchConfig: ["apiKey"], GlomopayConfig: ["apiKey"], + GrabpayConfig: ["partnerId", "partnerSecret", "clientId", "clientSecret", "merchantId"], TesouroConfig: ["apiKey", "key1", "apiSecret"], PaymentServiceTokenAuthorizeRequest: ["connectorToken", "metadata", "connectorFeatureData"], PaymentServiceTokenSetupRecurringRequest: ["connectorToken", "metadata", "connectorFeatureData"], @@ -356,7 +358,7 @@ const _SECRET_STRING_FIELDS: Record = { }; const _MSG_FIELD_TYPES: Record> = { - PaymentMethod: { "card": "CardDetails", "cardRedirect": "CardRedirect", "cardWithNoCvc": "CardDetailsWithNoCvc", "cardProxy": "ProxyCardDetails", "token": "TokenPaymentMethodType", "applePaySdk": "AppleWallet", "googlePaySdk": "GoogleWallet", "paypalSdk": "PaypalSdkWallet", "pazeSdk": "PazeWallet", "samsungPaySdk": "SamsungWallet", "applePayThirdPartySdk": "ApplePayThirdPartySdkWallet", "googlePayThirdPartySdk": "GooglePayThirdPartySdkWallet", "amazonPayRedirect": "AmazonPayRedirectWallet", "paypalRedirect": "PaypalRedirectWallet", "aliPayRedirect": "AliPayRedirectWallet", "revolutPayRedirect": "RevolutPayRedirectWallet", "mifinityRedirect": "MifinityRedirectWallet", "bluecodeRedirect": "BluecodeRedirectWallet", "satispayRedirect": "SatispayRedirectWallet", "weroRedirect": "WeroRedirectWallet", "lazypayRedirect": "LazyPayRedirectWallet", "phonepeRedirect": "PhonePeRedirectWallet", "billdeskRedirect": "BillDeskRedirectWallet", "cashfreeRedirect": "CashfreeRedirectWallet", "payuRedirect": "PayURedirectWallet", "easebuzzRedirect": "EaseBuzzRedirectWallet", "kakaoPayRedirect": "KakaoPayRedirectWallet", "mbWayRedirect": "MbWayRedirectWallet", "momoRedirect": "MomoRedirectWallet", "touchNGoRedirect": "TouchNGoRedirectWallet", "twintRedirect": "TwintRedirectWallet", "vippsRedirect": "VippsRedirectWallet", "weChatPayRedirect": "WeChatPayRedirectWallet", "aliPayHkRedirect": "AliPayHKRedirectWallet", "danaRedirect": "DanaRedirectWallet", "gcashRedirect": "GcashRedirectWallet", "goPayRedirect": "GoPayRedirectWallet", "mobilePayRedirect": "MobilePayRedirectWallet", "venmoRedirect": "VenmoRedirectWallet", "skrillRedirect": "SkrillRedirectWallet", "payseraRedirect": "PayseraRedirectWallet", "paymayaRedirect": "PaymayaRedirectWallet", "qwikcilverWalletDirect": "QwikcilverDirectWallet", "revolutPay": "RevolutPayWallet", "mbWay": "MBWay", "satispay": "Satispay", "wero": "Wero", "cashappQr": "CashappQrWallet", "weChatPayQr": "WeChatPayQrWallet", "swishQr": "SwishQrWallet", "upiCollect": "UpiCollect", "upiIntent": "UpiIntent", "upiQr": "UpiQr", "onlineBankingThailand": "OnlineBankingThailand", "onlineBankingCzechRepublic": "OnlineBankingCzechRepublic", "onlineBankingFinland": "OnlineBankingFinland", "onlineBankingFpx": "OnlineBankingFPX", "onlineBankingPoland": "OnlineBankingPoland", "onlineBankingSlovakia": "OnlineBankingSlovakia", "openBankingUk": "OpenBankingUK", "openBankingPis": "OpenBankingPIS", "localBankRedirect": "LocalBankRedirect", "ideal": "Ideal", "sofort": "Sofort", "trustly": "Trustly", "giropay": "Giropay", "eps": "Eps", "przelewy24": "Przelewy24", "pse": "Pse", "bancontactCard": "BancontactCard", "blik": "Blik", "openBanking": "OpenBanking", "interac": "Interac", "bizum": "Bizum", "eftBankRedirect": "EftBankRedirect", "duitNow": "DuitNow", "crypto": "CryptoCurrency", "classicReward": "ClassicReward", "eVoucher": "EVoucher", "instantBankTransfer": "InstantBankTransfer", "achBankTransfer": "AchBankTransfer", "sepaBankTransfer": "SepaBankTransfer", "bacsBankTransfer": "BacsBankTransfer", "multibancoBankTransfer": "MultibancoBankTransfer", "instantBankTransferFinland": "InstantBankTransferFinland", "instantBankTransferPoland": "InstantBankTransferPoland", "pix": "PixPayment", "permataBankTransfer": "PermataBankTransfer", "bcaBankTransfer": "BCABankTransfer", "bniVaBankTransfer": "BNIVaBankTransfer", "briVaBankTransfer": "BRIVaBankTransfer", "cimbVaBankTransfer": "CIMBVaBankTransfer", "danamonVaBankTransfer": "DanamonVaBankTransfer", "mandiriVaBankTransfer": "MandiriVaBankTransfer", "localBankTransfer": "LocalBankTransfer", "indonesianBankTransfer": "IndonesianBankTransfer", "ach": "Ach", "sepa": "Sepa", "bacs": "Bacs", "becs": "Becs", "sepaGuaranteedDebit": "SepaGuaranteedDebit", "eft": "Eft", "affirm": "Affirm", "afterpayClearpay": "AfterpayClearpay", "klarna": "Klarna", "alma": "Alma", "tamaraRedirect": "TamaraRedirect", "atome": "Atome", "cardDetailsForNetworkTransactionId": "CardDetailsForNetworkTransactionId", "networkToken": "NetworkTokenData", "decryptedWalletTokenDetailsForNetworkTransactionId": "DecryptedWalletTokenDetailsForNetworkTransactionId", "givex": "Givex", "paySafeCard": "PaySafeCard", "boleto": "Boleto", "efecty": "Efecty", "pagoEfectivo": "PagoEfectivo", "redCompra": "RedCompra", "redPagos": "RedPagos", "alfamart": "Alfamart", "indomaret": "Indomaret", "oxxo": "Oxxo", "sevenEleven": "SevenEleven", "lawson": "Lawson", "miniStop": "MiniStop", "familyMart": "FamilyMart", "seicomart": "Seicomart", "payEasy": "PayEasy", "netbanking": "NetbankingPayment" }, + PaymentMethod: { "card": "CardDetails", "cardRedirect": "CardRedirect", "cardWithNoCvc": "CardDetailsWithNoCvc", "cardProxy": "ProxyCardDetails", "token": "TokenPaymentMethodType", "applePaySdk": "AppleWallet", "googlePaySdk": "GoogleWallet", "paypalSdk": "PaypalSdkWallet", "pazeSdk": "PazeWallet", "samsungPaySdk": "SamsungWallet", "applePayThirdPartySdk": "ApplePayThirdPartySdkWallet", "googlePayThirdPartySdk": "GooglePayThirdPartySdkWallet", "amazonPayRedirect": "AmazonPayRedirectWallet", "paypalRedirect": "PaypalRedirectWallet", "aliPayRedirect": "AliPayRedirectWallet", "revolutPayRedirect": "RevolutPayRedirectWallet", "mifinityRedirect": "MifinityRedirectWallet", "bluecodeRedirect": "BluecodeRedirectWallet", "satispayRedirect": "SatispayRedirectWallet", "weroRedirect": "WeroRedirectWallet", "lazypayRedirect": "LazyPayRedirectWallet", "phonepeRedirect": "PhonePeRedirectWallet", "billdeskRedirect": "BillDeskRedirectWallet", "cashfreeRedirect": "CashfreeRedirectWallet", "payuRedirect": "PayURedirectWallet", "easebuzzRedirect": "EaseBuzzRedirectWallet", "kakaoPayRedirect": "KakaoPayRedirectWallet", "mbWayRedirect": "MbWayRedirectWallet", "momoRedirect": "MomoRedirectWallet", "touchNGoRedirect": "TouchNGoRedirectWallet", "twintRedirect": "TwintRedirectWallet", "vippsRedirect": "VippsRedirectWallet", "weChatPayRedirect": "WeChatPayRedirectWallet", "aliPayHkRedirect": "AliPayHKRedirectWallet", "danaRedirect": "DanaRedirectWallet", "gcashRedirect": "GcashRedirectWallet", "goPayRedirect": "GoPayRedirectWallet", "mobilePayRedirect": "MobilePayRedirectWallet", "venmoRedirect": "VenmoRedirectWallet", "skrillRedirect": "SkrillRedirectWallet", "payseraRedirect": "PayseraRedirectWallet", "paymayaRedirect": "PaymayaRedirectWallet", "qwikcilverWalletDirect": "QwikcilverDirectWallet", "grabpayRedirect": "GrabpayRedirectWallet", "revolutPay": "RevolutPayWallet", "mbWay": "MBWay", "satispay": "Satispay", "wero": "Wero", "cashappQr": "CashappQrWallet", "weChatPayQr": "WeChatPayQrWallet", "swishQr": "SwishQrWallet", "upiCollect": "UpiCollect", "upiIntent": "UpiIntent", "upiQr": "UpiQr", "onlineBankingThailand": "OnlineBankingThailand", "onlineBankingCzechRepublic": "OnlineBankingCzechRepublic", "onlineBankingFinland": "OnlineBankingFinland", "onlineBankingFpx": "OnlineBankingFPX", "onlineBankingPoland": "OnlineBankingPoland", "onlineBankingSlovakia": "OnlineBankingSlovakia", "openBankingUk": "OpenBankingUK", "openBankingPis": "OpenBankingPIS", "localBankRedirect": "LocalBankRedirect", "ideal": "Ideal", "sofort": "Sofort", "trustly": "Trustly", "giropay": "Giropay", "eps": "Eps", "przelewy24": "Przelewy24", "pse": "Pse", "bancontactCard": "BancontactCard", "blik": "Blik", "openBanking": "OpenBanking", "interac": "Interac", "bizum": "Bizum", "eftBankRedirect": "EftBankRedirect", "duitNow": "DuitNow", "crypto": "CryptoCurrency", "classicReward": "ClassicReward", "eVoucher": "EVoucher", "instantBankTransfer": "InstantBankTransfer", "achBankTransfer": "AchBankTransfer", "sepaBankTransfer": "SepaBankTransfer", "bacsBankTransfer": "BacsBankTransfer", "multibancoBankTransfer": "MultibancoBankTransfer", "instantBankTransferFinland": "InstantBankTransferFinland", "instantBankTransferPoland": "InstantBankTransferPoland", "pix": "PixPayment", "permataBankTransfer": "PermataBankTransfer", "bcaBankTransfer": "BCABankTransfer", "bniVaBankTransfer": "BNIVaBankTransfer", "briVaBankTransfer": "BRIVaBankTransfer", "cimbVaBankTransfer": "CIMBVaBankTransfer", "danamonVaBankTransfer": "DanamonVaBankTransfer", "mandiriVaBankTransfer": "MandiriVaBankTransfer", "localBankTransfer": "LocalBankTransfer", "indonesianBankTransfer": "IndonesianBankTransfer", "ach": "Ach", "sepa": "Sepa", "bacs": "Bacs", "becs": "Becs", "sepaGuaranteedDebit": "SepaGuaranteedDebit", "eft": "Eft", "affirm": "Affirm", "afterpayClearpay": "AfterpayClearpay", "klarna": "Klarna", "alma": "Alma", "tamaraRedirect": "TamaraRedirect", "atome": "Atome", "cardDetailsForNetworkTransactionId": "CardDetailsForNetworkTransactionId", "networkToken": "NetworkTokenData", "decryptedWalletTokenDetailsForNetworkTransactionId": "DecryptedWalletTokenDetailsForNetworkTransactionId", "givex": "Givex", "paySafeCard": "PaySafeCard", "boleto": "Boleto", "efecty": "Efecty", "pagoEfectivo": "PagoEfectivo", "redCompra": "RedCompra", "redPagos": "RedPagos", "alfamart": "Alfamart", "indomaret": "Indomaret", "oxxo": "Oxxo", "sevenEleven": "SevenEleven", "lawson": "Lawson", "miniStop": "MiniStop", "familyMart": "FamilyMart", "seicomart": "Seicomart", "payEasy": "PayEasy", "netbanking": "NetbankingPayment" }, AppleWallet: { "paymentData": "PaymentData", "paymentMethod": "PaymentMethod" }, PaymentData: { "decryptedData": "ApplePayDecryptedData" }, ApplePayDecryptedData: { "paymentData": "ApplePayCryptogramData" }, @@ -508,7 +510,7 @@ const _MSG_FIELD_TYPES: Record> = { CashtocodeConfig: { "authKeyMap": "AuthKeyMapEntry" }, AuthKeyMapEntry: { "value": "PayloadCurrencyAuthData" }, PayloadConfig: { "authKeyMap": "AuthKeyMapEntry" }, - ConnectorSpecificConfig: { "adyen": "AdyenConfig", "airwallex": "AirwallexConfig", "bambora": "BamboraConfig", "bankofamerica": "BankOfAmericaConfig", "billwerk": "BillwerkConfig", "bluesnap": "BluesnapConfig", "braintree": "BraintreeConfig", "cashtocode": "CashtocodeConfig", "cryptopay": "CryptopayConfig", "cybersource": "CybersourceConfig", "datatrans": "DatatransConfig", "dlocal": "DlocalConfig", "elavon": "ElavonConfig", "fiserv": "FiservConfig", "fiservemea": "FiservemeaConfig", "forte": "ForteConfig", "getnet": "GetnetConfig", "globalpay": "GlobalpayConfig", "hipay": "HipayConfig", "helcim": "HelcimConfig", "iatapay": "IatapayConfig", "jpmorgan": "JpmorganConfig", "mifinity": "MifinityConfig", "mollie": "MollieConfig", "multisafepay": "MultisafepayConfig", "nexinets": "NexinetsConfig", "nexixpay": "NexixpayConfig", "nmi": "NmiConfig", "noon": "NoonConfig", "novalnet": "NovalnetConfig", "nuvei": "NuveiConfig", "paybox": "PayboxConfig", "payme": "PaymeConfig", "payu": "PayuConfig", "powertranz": "PowertranzConfig", "rapyd": "RapydConfig", "redsys": "RedsysConfig", "shift4": "Shift4Config", "stax": "StaxConfig", "stripe": "StripeConfig", "trustpay": "TrustpayConfig", "tsys": "TsysConfig", "volt": "VoltConfig", "wellsfargo": "WellsfargoConfig", "worldpay": "WorldpayConfig", "worldpayvantiv": "WorldpayvantivConfig", "xendit": "XenditConfig", "phonepe": "PhonepeConfig", "cashfree": "CashfreeConfig", "paytm": "PaytmConfig", "calida": "CalidaConfig", "payload": "PayloadConfig", "authipay": "AuthipayConfig", "silverflow": "SilverflowConfig", "celero": "CeleroConfig", "trustpayments": "TrustpaymentsConfig", "paysafe": "PaysafeConfig", "barclaycard": "BarclaycardConfig", "worldpayxml": "WorldpayxmlConfig", "revolut": "RevolutConfig", "loonio": "LoonioConfig", "gigadat": "GigadatConfig", "hyperpg": "HyperpgConfig", "zift": "ZiftConfig", "screenstream": "ScreenstreamConfig", "ebanx": "EbanxConfig", "fiuu": "FiuuConfig", "globepay": "GlobepayConfig", "coinbase": "CoinbaseConfig", "coingate": "CoingateConfig", "revolv3": "Revolv3Config", "authorizedotnet": "AuthorizedotnetConfig", "peachpayments": "PeachpaymentsConfig", "paypal": "PaypalConfig", "truelayer": "TruelayerConfig", "fiservcommercehub": "FiservcommercehubConfig", "itaubank": "ItaubankConfig", "ppro": "PproConfig", "trustly": "TrustlyConfig", "absaSanlam": "AbsaSanlamConfig", "pinelabsOnline": "PinelabsOnlineConfig", "imerchantsolutions": "ImerchantsolutionsConfig", "axisbank": "AxisbankConfig", "easebuzz": "EasebuzzConfig", "twocTwopPaco": "TwocTwopPacoConfig", "bamboraapac": "BamboraapacConfig", "placetopay": "PlacetopayConfig", "finix": "FinixConfig", "aci": "AciConfig", "interpayments": "InterpaymentsConfig", "juspay": "JuspayConfig", "tamara": "TamaraConfig", "payconex": "PayconexConfig", "qwikcilver": "QwikcilverConfig", "checkout": "CheckoutConfig", "hyperswitch": "HyperswitchConfig", "tsysTransit": "TsysTransitConfig", "kount": "KountConfig", "affirm": "AffirmConfig", "flywire": "FlywireConfig", "glomopay": "GlomopayConfig", "givepayments": "GivepaymentsConfig", "tesouro": "TesouroConfig", "deutschebank": "DeutschebankConfig", "plaid": "PlaidConfig", "santander": "SantanderConfig", "maya": "MayaConfig" }, + ConnectorSpecificConfig: { "adyen": "AdyenConfig", "airwallex": "AirwallexConfig", "bambora": "BamboraConfig", "bankofamerica": "BankOfAmericaConfig", "billwerk": "BillwerkConfig", "bluesnap": "BluesnapConfig", "braintree": "BraintreeConfig", "cashtocode": "CashtocodeConfig", "cryptopay": "CryptopayConfig", "cybersource": "CybersourceConfig", "datatrans": "DatatransConfig", "dlocal": "DlocalConfig", "elavon": "ElavonConfig", "fiserv": "FiservConfig", "fiservemea": "FiservemeaConfig", "forte": "ForteConfig", "getnet": "GetnetConfig", "globalpay": "GlobalpayConfig", "hipay": "HipayConfig", "helcim": "HelcimConfig", "iatapay": "IatapayConfig", "jpmorgan": "JpmorganConfig", "mifinity": "MifinityConfig", "mollie": "MollieConfig", "multisafepay": "MultisafepayConfig", "nexinets": "NexinetsConfig", "nexixpay": "NexixpayConfig", "nmi": "NmiConfig", "noon": "NoonConfig", "novalnet": "NovalnetConfig", "nuvei": "NuveiConfig", "paybox": "PayboxConfig", "payme": "PaymeConfig", "payu": "PayuConfig", "powertranz": "PowertranzConfig", "rapyd": "RapydConfig", "redsys": "RedsysConfig", "shift4": "Shift4Config", "stax": "StaxConfig", "stripe": "StripeConfig", "trustpay": "TrustpayConfig", "tsys": "TsysConfig", "volt": "VoltConfig", "wellsfargo": "WellsfargoConfig", "worldpay": "WorldpayConfig", "worldpayvantiv": "WorldpayvantivConfig", "xendit": "XenditConfig", "phonepe": "PhonepeConfig", "cashfree": "CashfreeConfig", "paytm": "PaytmConfig", "calida": "CalidaConfig", "payload": "PayloadConfig", "authipay": "AuthipayConfig", "silverflow": "SilverflowConfig", "celero": "CeleroConfig", "trustpayments": "TrustpaymentsConfig", "paysafe": "PaysafeConfig", "barclaycard": "BarclaycardConfig", "worldpayxml": "WorldpayxmlConfig", "revolut": "RevolutConfig", "loonio": "LoonioConfig", "gigadat": "GigadatConfig", "hyperpg": "HyperpgConfig", "zift": "ZiftConfig", "screenstream": "ScreenstreamConfig", "ebanx": "EbanxConfig", "fiuu": "FiuuConfig", "globepay": "GlobepayConfig", "coinbase": "CoinbaseConfig", "coingate": "CoingateConfig", "revolv3": "Revolv3Config", "authorizedotnet": "AuthorizedotnetConfig", "peachpayments": "PeachpaymentsConfig", "paypal": "PaypalConfig", "truelayer": "TruelayerConfig", "fiservcommercehub": "FiservcommercehubConfig", "itaubank": "ItaubankConfig", "ppro": "PproConfig", "trustly": "TrustlyConfig", "absaSanlam": "AbsaSanlamConfig", "pinelabsOnline": "PinelabsOnlineConfig", "imerchantsolutions": "ImerchantsolutionsConfig", "axisbank": "AxisbankConfig", "easebuzz": "EasebuzzConfig", "twocTwopPaco": "TwocTwopPacoConfig", "bamboraapac": "BamboraapacConfig", "placetopay": "PlacetopayConfig", "finix": "FinixConfig", "aci": "AciConfig", "interpayments": "InterpaymentsConfig", "juspay": "JuspayConfig", "tamara": "TamaraConfig", "payconex": "PayconexConfig", "qwikcilver": "QwikcilverConfig", "checkout": "CheckoutConfig", "hyperswitch": "HyperswitchConfig", "tsysTransit": "TsysTransitConfig", "kount": "KountConfig", "affirm": "AffirmConfig", "flywire": "FlywireConfig", "glomopay": "GlomopayConfig", "givepayments": "GivepaymentsConfig", "tesouro": "TesouroConfig", "deutschebank": "DeutschebankConfig", "plaid": "PlaidConfig", "santander": "SantanderConfig", "maya": "MayaConfig", "grabpay": "GrabpayConfig" }, PaymentServiceTokenAuthorizeRequest: { "amount": "Money", "customer": "Customer", "address": "PaymentAddress", "browserInfo": "BrowserInformation", "state": "ConnectorState", "billingDescriptor": "BillingDescriptor", "l2L3Data": "L2L3Data", "customerAcceptance": "CustomerAcceptance" }, PaymentServiceTokenSetupRecurringRequest: { "amount": "Money", "customer": "Customer", "address": "PaymentAddress", "state": "ConnectorState", "customerAcceptance": "CustomerAcceptance", "setupMandateDetails": "SetupMandateDetails", "billingDescriptor": "BillingDescriptor" }, PaymentServiceProxyAuthorizeRequest: { "amount": "Money", "cardProxy": "ProxyCardDetails", "customer": "Customer", "address": "PaymentAddress", "authenticationData": "AuthenticationData", "browserInfo": "BrowserInformation", "state": "ConnectorState", "setupMandateDetails": "SetupMandateDetails", "billingDescriptor": "BillingDescriptor", "redirectionResponse": "RedirectionResponse", "l2L3Data": "L2L3Data", "customerAcceptance": "CustomerAcceptance", "domainData": "DomainData" },