mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 17:19:15 +08:00
feat(connector): [Braintree] implement 3DS card payment for braintree (#2095)
This commit is contained in:
@ -13,7 +13,10 @@ use crate::{
|
|||||||
configs::settings,
|
configs::settings,
|
||||||
connector::utils as connector_utils,
|
connector::utils as connector_utils,
|
||||||
consts,
|
consts,
|
||||||
core::errors::{self, CustomResult},
|
core::{
|
||||||
|
errors::{self, CustomResult},
|
||||||
|
payments,
|
||||||
|
},
|
||||||
headers, logger,
|
headers, logger,
|
||||||
services::{
|
services::{
|
||||||
self,
|
self,
|
||||||
@ -156,7 +159,7 @@ impl api::PaymentAuthorize for Braintree {}
|
|||||||
impl api::PaymentSync for Braintree {}
|
impl api::PaymentSync for Braintree {}
|
||||||
impl api::PaymentVoid for Braintree {}
|
impl api::PaymentVoid for Braintree {}
|
||||||
impl api::PaymentCapture for Braintree {}
|
impl api::PaymentCapture for Braintree {}
|
||||||
|
impl api::PaymentsCompleteAuthorize for Braintree {}
|
||||||
impl api::PaymentSession for Braintree {}
|
impl api::PaymentSession for Braintree {}
|
||||||
impl api::ConnectorAccessToken for Braintree {}
|
impl api::ConnectorAccessToken for Braintree {}
|
||||||
|
|
||||||
@ -1248,3 +1251,146 @@ impl api::IncomingWebhook for Braintree {
|
|||||||
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
|
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl services::ConnectorRedirectResponse for Braintree {
|
||||||
|
fn get_flow_type(
|
||||||
|
&self,
|
||||||
|
_query_params: &str,
|
||||||
|
_json_payload: Option<serde_json::Value>,
|
||||||
|
_action: services::PaymentAction,
|
||||||
|
) -> CustomResult<payments::CallConnectorAction, errors::ConnectorError> {
|
||||||
|
Ok(payments::CallConnectorAction::Trigger)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl
|
||||||
|
ConnectorIntegration<
|
||||||
|
api::CompleteAuthorize,
|
||||||
|
types::CompleteAuthorizeData,
|
||||||
|
types::PaymentsResponseData,
|
||||||
|
> for Braintree
|
||||||
|
{
|
||||||
|
fn get_headers(
|
||||||
|
&self,
|
||||||
|
req: &types::PaymentsCompleteAuthorizeRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
|
||||||
|
let connector_api_version = &req.connector_api_version;
|
||||||
|
match self.is_braintree_graphql_version(connector_api_version) {
|
||||||
|
true => self.build_headers(req, connectors),
|
||||||
|
false => Err(errors::ConnectorError::NotImplemented(
|
||||||
|
"get_headers method".to_string(),
|
||||||
|
))?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn get_content_type(&self) -> &'static str {
|
||||||
|
self.common_get_content_type()
|
||||||
|
}
|
||||||
|
fn get_url(
|
||||||
|
&self,
|
||||||
|
req: &types::PaymentsCompleteAuthorizeRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<String, errors::ConnectorError> {
|
||||||
|
let connector_api_version = &req.connector_api_version;
|
||||||
|
match self.is_braintree_graphql_version(connector_api_version) {
|
||||||
|
true => {
|
||||||
|
let base_url = connectors
|
||||||
|
.braintree
|
||||||
|
.secondary_base_url
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(errors::ConnectorError::FailedToObtainIntegrationUrl)?;
|
||||||
|
Ok(base_url.to_string())
|
||||||
|
}
|
||||||
|
false => Err(errors::ConnectorError::NotImplemented(
|
||||||
|
"get_url method".to_string(),
|
||||||
|
))?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn get_request_body(
|
||||||
|
&self,
|
||||||
|
req: &types::PaymentsCompleteAuthorizeRouterData,
|
||||||
|
) -> CustomResult<Option<types::RequestBody>, errors::ConnectorError> {
|
||||||
|
let connector_api_version = &req.connector_api_version;
|
||||||
|
match self.is_braintree_graphql_version(connector_api_version) {
|
||||||
|
true => {
|
||||||
|
let connector_request =
|
||||||
|
braintree_graphql_transformers::BraintreePaymentsRequest::try_from(req)?;
|
||||||
|
let braintree_payment_request = types::RequestBody::log_and_get_request_body(
|
||||||
|
&connector_request,
|
||||||
|
utils::Encode::<braintree_graphql_transformers::BraintreePaymentsRequest>::encode_to_string_of_json,
|
||||||
|
)
|
||||||
|
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||||
|
Ok(Some(braintree_payment_request))
|
||||||
|
}
|
||||||
|
false => Err(errors::ConnectorError::NotImplemented(
|
||||||
|
"get_request_body method".to_string(),
|
||||||
|
))?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn build_request(
|
||||||
|
&self,
|
||||||
|
req: &types::PaymentsCompleteAuthorizeRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||||
|
let connector_api_version = &req.connector_api_version;
|
||||||
|
match self.is_braintree_graphql_version(connector_api_version) {
|
||||||
|
true => Ok(Some(
|
||||||
|
services::RequestBuilder::new()
|
||||||
|
.method(services::Method::Post)
|
||||||
|
.url(&types::PaymentsCompleteAuthorizeType::get_url(
|
||||||
|
self, req, connectors,
|
||||||
|
)?)
|
||||||
|
.attach_default_headers()
|
||||||
|
.headers(types::PaymentsCompleteAuthorizeType::get_headers(
|
||||||
|
self, req, connectors,
|
||||||
|
)?)
|
||||||
|
.body(types::PaymentsCompleteAuthorizeType::get_request_body(
|
||||||
|
self, req,
|
||||||
|
)?)
|
||||||
|
.build(),
|
||||||
|
)),
|
||||||
|
false => Err(errors::ConnectorError::NotImplemented(
|
||||||
|
"payment method".to_string(),
|
||||||
|
))?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn handle_response(
|
||||||
|
&self,
|
||||||
|
data: &types::PaymentsCompleteAuthorizeRouterData,
|
||||||
|
res: types::Response,
|
||||||
|
) -> CustomResult<types::PaymentsCompleteAuthorizeRouterData, errors::ConnectorError> {
|
||||||
|
match connector_utils::PaymentsCompleteAuthorizeRequestData::is_auto_capture(&data.request)?
|
||||||
|
{
|
||||||
|
true => {
|
||||||
|
let response: braintree_graphql_transformers::BraintreeCompleteChargeResponse = res
|
||||||
|
.response
|
||||||
|
.parse_struct("Braintree PaymentsResponse")
|
||||||
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||||
|
router_env::logger::info!(connector_response=?response);
|
||||||
|
types::RouterData::try_from(types::ResponseRouterData {
|
||||||
|
response,
|
||||||
|
data: data.clone(),
|
||||||
|
http_code: res.status_code,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
false => {
|
||||||
|
let response: braintree_graphql_transformers::BraintreeCompleteAuthResponse = res
|
||||||
|
.response
|
||||||
|
.parse_struct("Braintree AuthResponse")
|
||||||
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||||
|
types::RouterData::try_from(types::ResponseRouterData {
|
||||||
|
response,
|
||||||
|
data: data.clone(),
|
||||||
|
http_code: res.status_code,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_error_response(
|
||||||
|
&self,
|
||||||
|
res: types::Response,
|
||||||
|
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||||
|
self.build_error_response(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
use error_stack::ResultExt;
|
use error_stack::{IntoReport, ResultExt};
|
||||||
use masking::Secret;
|
use masking::{ExposeInterface, Secret};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
connector::utils::{self, PaymentsAuthorizeRequestData, RefundsRequestData, RouterData},
|
connector::utils::{self, PaymentsAuthorizeRequestData, RefundsRequestData, RouterData},
|
||||||
consts,
|
consts,
|
||||||
core::errors,
|
core::errors,
|
||||||
|
services,
|
||||||
types::{self, api, storage::enums},
|
types::{self, api, storage::enums},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const CLIENT_TOKEN_MUTATION: &str = "mutation createClientToken($input: CreateClientTokenInput!) { createClientToken(input: $input) { clientToken}}";
|
||||||
pub const TOKENIZE_CREDIT_CARD: &str = "mutation tokenizeCreditCard($input: TokenizeCreditCardInput!) { tokenizeCreditCard(input: $input) { clientMutationId paymentMethod { id } } }";
|
pub const TOKENIZE_CREDIT_CARD: &str = "mutation tokenizeCreditCard($input: TokenizeCreditCardInput!) { tokenizeCreditCard(input: $input) { clientMutationId paymentMethod { id } } }";
|
||||||
pub const CHARGE_CREDIT_CARD_MUTATION: &str = "mutation ChargeCreditCard($input: ChargeCreditCardInput!) { chargeCreditCard(input: $input) { transaction { id legacyId createdAt amount { value currencyCode } status } } }";
|
pub const CHARGE_CREDIT_CARD_MUTATION: &str = "mutation ChargeCreditCard($input: ChargeCreditCardInput!) { chargeCreditCard(input: $input) { transaction { id legacyId createdAt amount { value currencyCode } status } } }";
|
||||||
pub const AUTHORIZE_CREDIT_CARD_MUTATION: &str = "mutation authorizeCreditCard($input: AuthorizeCreditCardInput!) { authorizeCreditCard(input: $input) { transaction { id legacyId amount { value currencyCode } status } } }";
|
pub const AUTHORIZE_CREDIT_CARD_MUTATION: &str = "mutation authorizeCreditCard($input: AuthorizeCreditCardInput!) { authorizeCreditCard(input: $input) { transaction { id legacyId amount { value currencyCode } status } } }";
|
||||||
@ -29,11 +31,18 @@ pub struct VariablePaymentInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct BraintreePaymentsRequest {
|
pub struct CardPaymentRequest {
|
||||||
query: String,
|
query: String,
|
||||||
variables: VariablePaymentInput,
|
variables: VariablePaymentInput,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum BraintreePaymentsRequest {
|
||||||
|
Card(CardPaymentRequest),
|
||||||
|
CardThreeDs(BraintreeClientTokenRequest),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct BraintreeMeta {
|
pub struct BraintreeMeta {
|
||||||
merchant_account_id: Option<Secret<String>>,
|
merchant_account_id: Option<Secret<String>>,
|
||||||
@ -56,34 +65,13 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for BraintreePaymentsRequest {
|
|||||||
|
|
||||||
match item.request.payment_method_data.clone() {
|
match item.request.payment_method_data.clone() {
|
||||||
api::PaymentMethodData::Card(_) => {
|
api::PaymentMethodData::Card(_) => {
|
||||||
let query = match item.request.is_auto_capture()? {
|
if item.is_three_ds() {
|
||||||
true => CHARGE_CREDIT_CARD_MUTATION.to_string(),
|
Ok(Self::CardThreeDs(BraintreeClientTokenRequest::try_from(
|
||||||
false => AUTHORIZE_CREDIT_CARD_MUTATION.to_string(),
|
metadata,
|
||||||
};
|
)?))
|
||||||
Ok(Self {
|
} else {
|
||||||
query,
|
Ok(Self::Card(CardPaymentRequest::try_from((item, metadata))?))
|
||||||
variables: VariablePaymentInput {
|
}
|
||||||
input: PaymentInput {
|
|
||||||
payment_method_id: match item.get_payment_method_token()? {
|
|
||||||
types::PaymentMethodToken::Token(token) => token,
|
|
||||||
types::PaymentMethodToken::ApplePayDecrypt(_) => {
|
|
||||||
Err(errors::ConnectorError::InvalidWalletToken)?
|
|
||||||
}
|
|
||||||
},
|
|
||||||
transaction: TransactionBody {
|
|
||||||
amount: utils::to_currency_base_unit(
|
|
||||||
item.request.amount,
|
|
||||||
item.request.currency,
|
|
||||||
)?,
|
|
||||||
merchant_account_id: metadata.merchant_account_id.ok_or(
|
|
||||||
errors::ConnectorError::MissingRequiredField {
|
|
||||||
field_name: "merchant_account_id",
|
|
||||||
},
|
|
||||||
)?,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
api_models::payments::PaymentMethodData::CardRedirect(_)
|
api_models::payments::PaymentMethodData::CardRedirect(_)
|
||||||
| api_models::payments::PaymentMethodData::Wallet(_)
|
| api_models::payments::PaymentMethodData::Wallet(_)
|
||||||
@ -106,6 +94,33 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for BraintreePaymentsRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&types::PaymentsCompleteAuthorizeRouterData> for BraintreePaymentsRequest {
|
||||||
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
|
fn try_from(item: &types::PaymentsCompleteAuthorizeRouterData) -> Result<Self, Self::Error> {
|
||||||
|
match item.request.payment_method_data.clone() {
|
||||||
|
Some(api::PaymentMethodData::Card(_)) => {
|
||||||
|
Ok(Self::Card(CardPaymentRequest::try_from(item)?))
|
||||||
|
}
|
||||||
|
Some(api_models::payments::PaymentMethodData::CardRedirect(_))
|
||||||
|
| Some(api_models::payments::PaymentMethodData::Wallet(_))
|
||||||
|
| Some(api_models::payments::PaymentMethodData::PayLater(_))
|
||||||
|
| Some(api_models::payments::PaymentMethodData::BankRedirect(_))
|
||||||
|
| Some(api_models::payments::PaymentMethodData::BankDebit(_))
|
||||||
|
| Some(api_models::payments::PaymentMethodData::BankTransfer(_))
|
||||||
|
| Some(api_models::payments::PaymentMethodData::Crypto(_))
|
||||||
|
| Some(api_models::payments::PaymentMethodData::MandatePayment)
|
||||||
|
| Some(api_models::payments::PaymentMethodData::Reward)
|
||||||
|
| Some(api_models::payments::PaymentMethodData::Upi(_))
|
||||||
|
| Some(api_models::payments::PaymentMethodData::Voucher(_))
|
||||||
|
| Some(api_models::payments::PaymentMethodData::GiftCard(_))
|
||||||
|
| None => Err(errors::ConnectorError::NotImplemented(
|
||||||
|
utils::get_unimplemented_payment_method_error_message("complete authorize flow"),
|
||||||
|
)
|
||||||
|
.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct AuthResponse {
|
pub struct AuthResponse {
|
||||||
data: DataAuthResponse,
|
data: DataAuthResponse,
|
||||||
@ -114,6 +129,14 @@ pub struct AuthResponse {
|
|||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum BraintreeAuthResponse {
|
pub enum BraintreeAuthResponse {
|
||||||
|
AuthResponse(Box<AuthResponse>),
|
||||||
|
ClientTokenResponse(Box<ClientTokenResponse>),
|
||||||
|
ErrorResponse(Box<ErrorResponse>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum BraintreeCompleteAuthResponse {
|
||||||
AuthResponse(Box<AuthResponse>),
|
AuthResponse(Box<AuthResponse>),
|
||||||
ErrorResponse(Box<ErrorResponse>),
|
ErrorResponse(Box<ErrorResponse>),
|
||||||
}
|
}
|
||||||
@ -135,13 +158,24 @@ pub struct AuthChargeCreditCard {
|
|||||||
transaction: TransactionAuthChargeResponseBody,
|
transaction: TransactionAuthChargeResponseBody,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F, T>
|
impl<F>
|
||||||
TryFrom<types::ResponseRouterData<F, BraintreeAuthResponse, T, types::PaymentsResponseData>>
|
TryFrom<
|
||||||
for types::RouterData<F, T, types::PaymentsResponseData>
|
types::ResponseRouterData<
|
||||||
|
F,
|
||||||
|
BraintreeAuthResponse,
|
||||||
|
types::PaymentsAuthorizeData,
|
||||||
|
types::PaymentsResponseData,
|
||||||
|
>,
|
||||||
|
> for types::RouterData<F, types::PaymentsAuthorizeData, types::PaymentsResponseData>
|
||||||
{
|
{
|
||||||
type Error = error_stack::Report<errors::ConnectorError>;
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
fn try_from(
|
fn try_from(
|
||||||
item: types::ResponseRouterData<F, BraintreeAuthResponse, T, types::PaymentsResponseData>,
|
item: types::ResponseRouterData<
|
||||||
|
F,
|
||||||
|
BraintreeAuthResponse,
|
||||||
|
types::PaymentsAuthorizeData,
|
||||||
|
types::PaymentsResponseData,
|
||||||
|
>,
|
||||||
) -> Result<Self, Self::Error> {
|
) -> Result<Self, Self::Error> {
|
||||||
match item.response {
|
match item.response {
|
||||||
BraintreeAuthResponse::ErrorResponse(error_response) => Ok(Self {
|
BraintreeAuthResponse::ErrorResponse(error_response) => Ok(Self {
|
||||||
@ -164,6 +198,23 @@ impl<F, T>
|
|||||||
..item.data
|
..item.data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
BraintreeAuthResponse::ClientTokenResponse(client_token_data) => Ok(Self {
|
||||||
|
status: enums::AttemptStatus::AuthenticationPending,
|
||||||
|
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||||
|
resource_id: types::ResponseId::NoResponseId,
|
||||||
|
redirection_data: Some(get_braintree_redirect_form(
|
||||||
|
*client_token_data,
|
||||||
|
item.data.get_payment_method_token()?,
|
||||||
|
item.data.request.payment_method_data.clone(),
|
||||||
|
item.data.request.amount,
|
||||||
|
)?),
|
||||||
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
|
network_txn_id: None,
|
||||||
|
connector_response_reference_id: None,
|
||||||
|
}),
|
||||||
|
..item.data
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -286,16 +337,22 @@ impl From<BraintreePaymentStatus> for enums::AttemptStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F, T>
|
impl<F>
|
||||||
TryFrom<types::ResponseRouterData<F, BraintreePaymentsResponse, T, types::PaymentsResponseData>>
|
TryFrom<
|
||||||
for types::RouterData<F, T, types::PaymentsResponseData>
|
types::ResponseRouterData<
|
||||||
|
F,
|
||||||
|
BraintreePaymentsResponse,
|
||||||
|
types::PaymentsAuthorizeData,
|
||||||
|
types::PaymentsResponseData,
|
||||||
|
>,
|
||||||
|
> for types::RouterData<F, types::PaymentsAuthorizeData, types::PaymentsResponseData>
|
||||||
{
|
{
|
||||||
type Error = error_stack::Report<errors::ConnectorError>;
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
fn try_from(
|
fn try_from(
|
||||||
item: types::ResponseRouterData<
|
item: types::ResponseRouterData<
|
||||||
F,
|
F,
|
||||||
BraintreePaymentsResponse,
|
BraintreePaymentsResponse,
|
||||||
T,
|
types::PaymentsAuthorizeData,
|
||||||
types::PaymentsResponseData,
|
types::PaymentsResponseData,
|
||||||
>,
|
>,
|
||||||
) -> Result<Self, Self::Error> {
|
) -> Result<Self, Self::Error> {
|
||||||
@ -307,6 +364,111 @@ impl<F, T>
|
|||||||
BraintreePaymentsResponse::PaymentsResponse(payment_response) => {
|
BraintreePaymentsResponse::PaymentsResponse(payment_response) => {
|
||||||
let transaction_data = payment_response.data.charge_credit_card.transaction;
|
let transaction_data = payment_response.data.charge_credit_card.transaction;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
status: enums::AttemptStatus::from(transaction_data.status.clone()),
|
||||||
|
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||||
|
resource_id: types::ResponseId::ConnectorTransactionId(transaction_data.id),
|
||||||
|
redirection_data: None,
|
||||||
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
|
network_txn_id: None,
|
||||||
|
connector_response_reference_id: None,
|
||||||
|
}),
|
||||||
|
..item.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
BraintreePaymentsResponse::ClientTokenResponse(client_token_data) => Ok(Self {
|
||||||
|
status: enums::AttemptStatus::AuthenticationPending,
|
||||||
|
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||||
|
resource_id: types::ResponseId::NoResponseId,
|
||||||
|
redirection_data: Some(get_braintree_redirect_form(
|
||||||
|
*client_token_data,
|
||||||
|
item.data.get_payment_method_token()?,
|
||||||
|
item.data.request.payment_method_data.clone(),
|
||||||
|
item.data.request.amount,
|
||||||
|
)?),
|
||||||
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
|
network_txn_id: None,
|
||||||
|
connector_response_reference_id: None,
|
||||||
|
}),
|
||||||
|
..item.data
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F>
|
||||||
|
TryFrom<
|
||||||
|
types::ResponseRouterData<
|
||||||
|
F,
|
||||||
|
BraintreeCompleteChargeResponse,
|
||||||
|
types::CompleteAuthorizeData,
|
||||||
|
types::PaymentsResponseData,
|
||||||
|
>,
|
||||||
|
> for types::RouterData<F, types::CompleteAuthorizeData, types::PaymentsResponseData>
|
||||||
|
{
|
||||||
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
|
fn try_from(
|
||||||
|
item: types::ResponseRouterData<
|
||||||
|
F,
|
||||||
|
BraintreeCompleteChargeResponse,
|
||||||
|
types::CompleteAuthorizeData,
|
||||||
|
types::PaymentsResponseData,
|
||||||
|
>,
|
||||||
|
) -> Result<Self, Self::Error> {
|
||||||
|
match item.response {
|
||||||
|
BraintreeCompleteChargeResponse::ErrorResponse(error_response) => Ok(Self {
|
||||||
|
response: build_error_response(&error_response.errors.clone(), item.http_code),
|
||||||
|
..item.data
|
||||||
|
}),
|
||||||
|
BraintreeCompleteChargeResponse::PaymentsResponse(payment_response) => {
|
||||||
|
let transaction_data = payment_response.data.charge_credit_card.transaction;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
status: enums::AttemptStatus::from(transaction_data.status.clone()),
|
||||||
|
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||||
|
resource_id: types::ResponseId::ConnectorTransactionId(transaction_data.id),
|
||||||
|
redirection_data: None,
|
||||||
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
|
network_txn_id: None,
|
||||||
|
connector_response_reference_id: None,
|
||||||
|
}),
|
||||||
|
..item.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F>
|
||||||
|
TryFrom<
|
||||||
|
types::ResponseRouterData<
|
||||||
|
F,
|
||||||
|
BraintreeCompleteAuthResponse,
|
||||||
|
types::CompleteAuthorizeData,
|
||||||
|
types::PaymentsResponseData,
|
||||||
|
>,
|
||||||
|
> for types::RouterData<F, types::CompleteAuthorizeData, types::PaymentsResponseData>
|
||||||
|
{
|
||||||
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
|
fn try_from(
|
||||||
|
item: types::ResponseRouterData<
|
||||||
|
F,
|
||||||
|
BraintreeCompleteAuthResponse,
|
||||||
|
types::CompleteAuthorizeData,
|
||||||
|
types::PaymentsResponseData,
|
||||||
|
>,
|
||||||
|
) -> Result<Self, Self::Error> {
|
||||||
|
match item.response {
|
||||||
|
BraintreeCompleteAuthResponse::ErrorResponse(error_response) => Ok(Self {
|
||||||
|
response: build_error_response(&error_response.errors, item.http_code),
|
||||||
|
..item.data
|
||||||
|
}),
|
||||||
|
BraintreeCompleteAuthResponse::AuthResponse(auth_response) => {
|
||||||
|
let transaction_data = auth_response.data.authorize_credit_card.transaction;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
status: enums::AttemptStatus::from(transaction_data.status.clone()),
|
status: enums::AttemptStatus::from(transaction_data.status.clone()),
|
||||||
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||||
@ -332,6 +494,14 @@ pub struct PaymentsResponse {
|
|||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum BraintreePaymentsResponse {
|
pub enum BraintreePaymentsResponse {
|
||||||
|
PaymentsResponse(Box<PaymentsResponse>),
|
||||||
|
ClientTokenResponse(Box<ClientTokenResponse>),
|
||||||
|
ErrorResponse(Box<ErrorResponse>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum BraintreeCompleteChargeResponse {
|
||||||
PaymentsResponse(Box<PaymentsResponse>),
|
PaymentsResponse(Box<PaymentsResponse>),
|
||||||
ErrorResponse(Box<ErrorResponse>),
|
ErrorResponse(Box<ErrorResponse>),
|
||||||
}
|
}
|
||||||
@ -572,23 +742,46 @@ pub struct CreditCardData {
|
|||||||
cardholder_name: Secret<String>,
|
cardholder_name: Secret<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ClientTokenInput {
|
||||||
|
merchant_account_id: Secret<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Serialize)]
|
#[derive(Default, Debug, Clone, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct InputData {
|
pub struct InputData {
|
||||||
credit_card: CreditCardData,
|
credit_card: CreditCardData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct InputClientTokenData {
|
||||||
|
client_token: ClientTokenInput,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Serialize)]
|
#[derive(Default, Debug, Clone, Serialize)]
|
||||||
pub struct VariableInput {
|
pub struct VariableInput {
|
||||||
input: InputData,
|
input: InputData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone, Serialize)]
|
||||||
|
pub struct VariableClientTokenInput {
|
||||||
|
input: InputClientTokenData,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Serialize)]
|
#[derive(Default, Debug, Clone, Serialize)]
|
||||||
pub struct BraintreeTokenRequest {
|
pub struct BraintreeTokenRequest {
|
||||||
query: String,
|
query: String,
|
||||||
variables: VariableInput,
|
variables: VariableInput,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone, Serialize)]
|
||||||
|
pub struct BraintreeClientTokenRequest {
|
||||||
|
query: String,
|
||||||
|
variables: VariableClientTokenInput,
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<&types::TokenizationRouterData> for BraintreeTokenRequest {
|
impl TryFrom<&types::TokenizationRouterData> for BraintreeTokenRequest {
|
||||||
type Error = error_stack::Report<errors::ConnectorError>;
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
fn try_from(item: &types::TokenizationRouterData) -> Result<Self, Self::Error> {
|
fn try_from(item: &types::TokenizationRouterData) -> Result<Self, Self::Error> {
|
||||||
@ -641,12 +834,29 @@ pub struct TokenizeCreditCardData {
|
|||||||
payment_method: TokenizePaymentMethodData,
|
payment_method: TokenizePaymentMethodData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ClientToken {
|
||||||
|
client_token: Secret<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Deserialize)]
|
#[derive(Default, Debug, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TokenizeCreditCard {
|
pub struct TokenizeCreditCard {
|
||||||
tokenize_credit_card: TokenizeCreditCardData,
|
tokenize_credit_card: TokenizeCreditCardData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ClientTokenData {
|
||||||
|
create_client_token: ClientToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone, Deserialize)]
|
||||||
|
pub struct ClientTokenResponse {
|
||||||
|
data: ClientTokenData,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Deserialize)]
|
#[derive(Default, Debug, Clone, Deserialize)]
|
||||||
pub struct TokenResponse {
|
pub struct TokenResponse {
|
||||||
data: TokenizeCreditCard,
|
data: TokenizeCreditCard,
|
||||||
@ -987,3 +1197,166 @@ impl<F, T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct BraintreeThreeDsResponse {
|
||||||
|
pub nonce: String,
|
||||||
|
pub liability_shifted: bool,
|
||||||
|
pub liability_shift_possible: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct BraintreeRedirectionResponse {
|
||||||
|
pub authentication_response: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<BraintreeMeta> for BraintreeClientTokenRequest {
|
||||||
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
|
fn try_from(metadata: BraintreeMeta) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Self {
|
||||||
|
query: CLIENT_TOKEN_MUTATION.to_owned(),
|
||||||
|
variables: VariableClientTokenInput {
|
||||||
|
input: InputClientTokenData {
|
||||||
|
client_token: ClientTokenInput {
|
||||||
|
merchant_account_id: metadata.merchant_account_id.ok_or(
|
||||||
|
errors::ConnectorError::MissingRequiredField {
|
||||||
|
field_name: "merchant_account_id",
|
||||||
|
},
|
||||||
|
)?,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<(&types::PaymentsAuthorizeRouterData, BraintreeMeta)> for CardPaymentRequest {
|
||||||
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
|
fn try_from(
|
||||||
|
payment_info: (&types::PaymentsAuthorizeRouterData, BraintreeMeta),
|
||||||
|
) -> Result<Self, Self::Error> {
|
||||||
|
let item = payment_info.0;
|
||||||
|
let metadata = payment_info.1;
|
||||||
|
let query = match item.request.is_auto_capture()? {
|
||||||
|
true => CHARGE_CREDIT_CARD_MUTATION.to_string(),
|
||||||
|
false => AUTHORIZE_CREDIT_CARD_MUTATION.to_string(),
|
||||||
|
};
|
||||||
|
Ok(Self {
|
||||||
|
query,
|
||||||
|
variables: VariablePaymentInput {
|
||||||
|
input: PaymentInput {
|
||||||
|
payment_method_id: match item.get_payment_method_token()? {
|
||||||
|
types::PaymentMethodToken::Token(token) => token,
|
||||||
|
types::PaymentMethodToken::ApplePayDecrypt(_) => {
|
||||||
|
Err(errors::ConnectorError::InvalidWalletToken)?
|
||||||
|
}
|
||||||
|
},
|
||||||
|
transaction: TransactionBody {
|
||||||
|
amount: utils::to_currency_base_unit(
|
||||||
|
item.request.amount,
|
||||||
|
item.request.currency,
|
||||||
|
)?,
|
||||||
|
merchant_account_id: metadata.merchant_account_id.ok_or(
|
||||||
|
errors::ConnectorError::MissingRequiredField {
|
||||||
|
field_name: "merchant_account_id",
|
||||||
|
},
|
||||||
|
)?,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&types::PaymentsCompleteAuthorizeRouterData> for CardPaymentRequest {
|
||||||
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
|
fn try_from(item: &types::PaymentsCompleteAuthorizeRouterData) -> Result<Self, Self::Error> {
|
||||||
|
let metadata: BraintreeMeta =
|
||||||
|
utils::to_connector_meta_from_secret(item.connector_meta_data.clone())?;
|
||||||
|
utils::validate_currency(item.request.currency, metadata.merchant_config_currency)?;
|
||||||
|
let payload_data =
|
||||||
|
utils::PaymentsCompleteAuthorizeRequestData::get_redirect_response_payload(
|
||||||
|
&item.request,
|
||||||
|
)?
|
||||||
|
.expose();
|
||||||
|
let redirection_response: BraintreeRedirectionResponse =
|
||||||
|
serde_json::from_value(payload_data)
|
||||||
|
.into_report()
|
||||||
|
.change_context(errors::ConnectorError::MissingConnectorRedirectionPayload {
|
||||||
|
field_name: "redirection_response",
|
||||||
|
})?;
|
||||||
|
let three_ds_data = serde_json::from_str::<BraintreeThreeDsResponse>(
|
||||||
|
&redirection_response.authentication_response,
|
||||||
|
)
|
||||||
|
.into_report()
|
||||||
|
.change_context(errors::ConnectorError::MissingConnectorRedirectionPayload {
|
||||||
|
field_name: "three_ds_data",
|
||||||
|
})?;
|
||||||
|
let query =
|
||||||
|
match utils::PaymentsCompleteAuthorizeRequestData::is_auto_capture(&item.request)? {
|
||||||
|
true => CHARGE_CREDIT_CARD_MUTATION.to_string(),
|
||||||
|
false => AUTHORIZE_CREDIT_CARD_MUTATION.to_string(),
|
||||||
|
};
|
||||||
|
Ok(Self {
|
||||||
|
query,
|
||||||
|
variables: VariablePaymentInput {
|
||||||
|
input: PaymentInput {
|
||||||
|
payment_method_id: three_ds_data.nonce,
|
||||||
|
transaction: TransactionBody {
|
||||||
|
amount: utils::to_currency_base_unit(
|
||||||
|
item.request.amount,
|
||||||
|
item.request.currency,
|
||||||
|
)?,
|
||||||
|
merchant_account_id: metadata.merchant_account_id.ok_or(
|
||||||
|
errors::ConnectorError::MissingRequiredField {
|
||||||
|
field_name: "merchant_account_id",
|
||||||
|
},
|
||||||
|
)?,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_braintree_redirect_form(
|
||||||
|
client_token_data: ClientTokenResponse,
|
||||||
|
payment_method_token: types::PaymentMethodToken,
|
||||||
|
card_details: api_models::payments::PaymentMethodData,
|
||||||
|
amount: i64,
|
||||||
|
) -> Result<services::RedirectForm, error_stack::Report<errors::ConnectorError>> {
|
||||||
|
Ok(services::RedirectForm::Braintree {
|
||||||
|
client_token: client_token_data
|
||||||
|
.data
|
||||||
|
.create_client_token
|
||||||
|
.client_token
|
||||||
|
.expose(),
|
||||||
|
card_token: match payment_method_token {
|
||||||
|
types::PaymentMethodToken::Token(token) => token,
|
||||||
|
types::PaymentMethodToken::ApplePayDecrypt(_) => {
|
||||||
|
Err(errors::ConnectorError::InvalidWalletToken)?
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bin: match card_details {
|
||||||
|
api_models::payments::PaymentMethodData::Card(card_details) => {
|
||||||
|
card_details.card_number.get_card_isin()
|
||||||
|
}
|
||||||
|
api_models::payments::PaymentMethodData::CardRedirect(_)
|
||||||
|
| api_models::payments::PaymentMethodData::Wallet(_)
|
||||||
|
| api_models::payments::PaymentMethodData::PayLater(_)
|
||||||
|
| api_models::payments::PaymentMethodData::BankRedirect(_)
|
||||||
|
| api_models::payments::PaymentMethodData::BankDebit(_)
|
||||||
|
| api_models::payments::PaymentMethodData::BankTransfer(_)
|
||||||
|
| api_models::payments::PaymentMethodData::Crypto(_)
|
||||||
|
| api_models::payments::PaymentMethodData::MandatePayment
|
||||||
|
| api_models::payments::PaymentMethodData::Reward
|
||||||
|
| api_models::payments::PaymentMethodData::Upi(_)
|
||||||
|
| api_models::payments::PaymentMethodData::Voucher(_)
|
||||||
|
| api_models::payments::PaymentMethodData::GiftCard(_) => Err(
|
||||||
|
errors::ConnectorError::NotImplemented("given payment method".to_owned()),
|
||||||
|
)?,
|
||||||
|
},
|
||||||
|
amount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -145,7 +145,6 @@ default_imp_for_complete_authorize!(
|
|||||||
connector::Aci,
|
connector::Aci,
|
||||||
connector::Adyen,
|
connector::Adyen,
|
||||||
connector::Bitpay,
|
connector::Bitpay,
|
||||||
connector::Braintree,
|
|
||||||
connector::Boku,
|
connector::Boku,
|
||||||
connector::Cashtocode,
|
connector::Cashtocode,
|
||||||
connector::Checkout,
|
connector::Checkout,
|
||||||
@ -286,7 +285,6 @@ default_imp_for_connector_redirect_response!(
|
|||||||
connector::Adyen,
|
connector::Adyen,
|
||||||
connector::Bitpay,
|
connector::Bitpay,
|
||||||
connector::Boku,
|
connector::Boku,
|
||||||
connector::Braintree,
|
|
||||||
connector::Cashtocode,
|
connector::Cashtocode,
|
||||||
connector::Coinbase,
|
connector::Coinbase,
|
||||||
connector::Cryptopay,
|
connector::Cryptopay,
|
||||||
|
|||||||
@ -668,6 +668,12 @@ pub enum RedirectForm {
|
|||||||
payment_fields_token: String, // payment-field-token
|
payment_fields_token: String, // payment-field-token
|
||||||
},
|
},
|
||||||
Payme,
|
Payme,
|
||||||
|
Braintree {
|
||||||
|
client_token: String,
|
||||||
|
card_token: String,
|
||||||
|
bin: String,
|
||||||
|
amount: i64,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<(url::Url, Method)> for RedirectForm {
|
impl From<(url::Url, Method)> for RedirectForm {
|
||||||
@ -1147,6 +1153,92 @@ pub fn build_redirection_form(
|
|||||||
".to_string()))
|
".to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RedirectForm::Braintree {
|
||||||
|
client_token,
|
||||||
|
card_token,
|
||||||
|
bin,
|
||||||
|
amount,
|
||||||
|
} => {
|
||||||
|
maud::html! {
|
||||||
|
(maud::DOCTYPE)
|
||||||
|
html {
|
||||||
|
head {
|
||||||
|
meta name="viewport" content="width=device-width, initial-scale=1";
|
||||||
|
(PreEscaped(r#"<script src="https://js.braintreegateway.com/web/3.97.1/js/three-d-secure.js"></script>"#))
|
||||||
|
(PreEscaped(r#"<script src="https://js.braintreegateway.com/web/3.97.1/js/hosted-fields.js"></script>"#))
|
||||||
|
|
||||||
|
}
|
||||||
|
body style="background-color: #ffffff; padding: 20px; font-family: Arial, Helvetica, Sans-Serif;" {
|
||||||
|
|
||||||
|
div id="loader1" class="lottie" style="height: 150px; display: block; position: relative; margin-top: 150px; margin-left: auto; margin-right: auto;" { "" }
|
||||||
|
|
||||||
|
(PreEscaped(r#"<script src="https://cdnjs.cloudflare.com/ajax/libs/bodymovin/5.7.4/lottie.min.js"></script>"#))
|
||||||
|
|
||||||
|
(PreEscaped(r#"
|
||||||
|
<script>
|
||||||
|
var anime = bodymovin.loadAnimation({
|
||||||
|
container: document.getElementById('loader1'),
|
||||||
|
renderer: 'svg',
|
||||||
|
loop: true,
|
||||||
|
autoplay: true,
|
||||||
|
name: 'hyperswitch loader',
|
||||||
|
animationData: {"v":"4.8.0","meta":{"g":"LottieFiles AE 3.1.1","a":"","k":"","d":"","tc":""},"fr":29.9700012207031,"ip":0,"op":31.0000012626559,"w":400,"h":250,"nm":"loader_shape","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"circle 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[278.25,202.671,0],"ix":2},"a":{"a":0,"k":[23.72,23.72,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[12.935,0],[0,-12.936],[-12.935,0],[0,12.935]],"o":[[-12.952,0],[0,12.935],[12.935,0],[0,-12.936]],"v":[[0,-23.471],[-23.47,0.001],[0,23.471],[23.47,0.001]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.427451010311,0.976470648074,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":19.99,"s":[100]},{"t":29.9800012211104,"s":[10]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[23.72,23.721],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":48.0000019550801,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"square 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[196.25,201.271,0],"ix":2},"a":{"a":0,"k":[22.028,22.03,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.914,0],[0,0],[0,-1.914],[0,0],[-1.914,0],[0,0],[0,1.914],[0,0]],"o":[[0,0],[-1.914,0],[0,0],[0,1.914],[0,0],[1.914,0],[0,0],[0,-1.914]],"v":[[18.313,-21.779],[-18.312,-21.779],[-21.779,-18.313],[-21.779,18.314],[-18.312,21.779],[18.313,21.779],[21.779,18.314],[21.779,-18.313]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.427451010311,0.976470648074,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":5,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":14.99,"s":[100]},{"t":24.9800010174563,"s":[10]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[22.028,22.029],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":47.0000019143492,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Triangle 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[116.25,200.703,0],"ix":2},"a":{"a":0,"k":[27.11,21.243,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.558,-0.879],[0,0],[-1.133,0],[0,0],[0.609,0.947],[0,0]],"o":[[-0.558,-0.879],[0,0],[-0.609,0.947],[0,0],[1.133,0],[0,0],[0,0]],"v":[[1.209,-20.114],[-1.192,-20.114],[-26.251,18.795],[-25.051,20.993],[25.051,20.993],[26.251,18.795],[1.192,-20.114]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.427451010311,0.976470648074,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":9.99,"s":[100]},{"t":19.9800008138021,"s":[10]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.11,21.243],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":48.0000019550801,"st":0,"bm":0}],"markers":[]}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
"#))
|
||||||
|
|
||||||
|
|
||||||
|
h3 style="text-align: center;" { "Please wait while we process your payment..." }
|
||||||
|
}
|
||||||
|
|
||||||
|
(PreEscaped(format!("<script>
|
||||||
|
var my3DSContainer;
|
||||||
|
var clientToken = \"{client_token}\";
|
||||||
|
braintree.threeDSecure.create({{
|
||||||
|
authorization: clientToken,
|
||||||
|
version: 2
|
||||||
|
}}, function(err, threeDs) {{
|
||||||
|
threeDs.verifyCard({{
|
||||||
|
amount: \"{amount}\",
|
||||||
|
nonce: \"{card_token}\",
|
||||||
|
bin: \"{bin}\",
|
||||||
|
addFrame: function(err, iframe) {{
|
||||||
|
my3DSContainer = document.createElement('div');
|
||||||
|
my3DSContainer.appendChild(iframe);
|
||||||
|
document.body.appendChild(my3DSContainer);
|
||||||
|
}},
|
||||||
|
removeFrame: function() {{
|
||||||
|
if(my3DSContainer && my3DSContainer.parentNode) {{
|
||||||
|
my3DSContainer.parentNode.removeChild(my3DSContainer);
|
||||||
|
}}
|
||||||
|
}},
|
||||||
|
onLookupComplete: function(data, next) {{
|
||||||
|
console.log(\"onLookup Complete\", data);
|
||||||
|
next();
|
||||||
|
}}
|
||||||
|
}},
|
||||||
|
function(err, payload) {{
|
||||||
|
if(err) {{
|
||||||
|
console.error(err);
|
||||||
|
}} else {{
|
||||||
|
console.log(payload);
|
||||||
|
var f = document.createElement('form');
|
||||||
|
f.action=window.location.pathname.replace(/payments\\/redirect\\/(\\w+)\\/(\\w+)\\/\\w+/, \"payments/$1/$2/redirect/complete/braintree\");
|
||||||
|
var i = document.createElement('input');
|
||||||
|
i.type = 'hidden';
|
||||||
|
f.method='POST';
|
||||||
|
i.name = 'authentication_response';
|
||||||
|
i.value = JSON.stringify(payload);
|
||||||
|
f.appendChild(i);
|
||||||
|
f.body = JSON.stringify(payload);
|
||||||
|
document.body.appendChild(f);
|
||||||
|
f.submit();
|
||||||
|
}}
|
||||||
|
}});
|
||||||
|
}}); </script>"
|
||||||
|
)))
|
||||||
|
}}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user