use anyhow::Context; use axum::{ body::Body, extract::State, http::{HeaderMap, StatusCode}, response::Response, Json, }; use cynic::{http::SurfExt, MutationBuilder}; use rust_decimal::Decimal; use saleor_app_sdk::{ headers::SALEOR_API_URL_HEADER, webhooks::{ sync_response::{ ChargeRequestedResult, PaymentGatewayInitializeSessionResponse, RefundRequestedResult, TransactionChargeRequestedResponse, TransactionInitializeSessionResponse, TransactionProcessSessionResponse, TransactionRefundRequestedResponse, TransactionSessionResult, }, utils::{get_webhook_event_type, EitherWebhookType}, SyncWebhookEventType, }, }; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::str::FromStr; use tracing::{debug, error, info}; use crate::{ app::{ActiveGateway, AppError, AppState, GatewayType}, queries::{ event_transactions::{ PaymentGatewayInitializeSession, TransactionActionEnum, TransactionChargeRequested2, TransactionFlowStrategyEnum, TransactionInitializeSession2, TransactionProcessSession, TransactionProcessSession2, TransactionRefundRequested2, }, mutation_transaction_update::{ TransactionUpdate, TransactionUpdateInput, TransactionUpdateVariables, }, }, }; pub async fn webhooks( headers: HeaderMap, State(state): State, body: String, ) -> Result, AppError> { debug!("/api/webhooks"); debug!("req: {:?}", body); debug!("headers: {:?}", headers); let saleor_api_url = headers .get(SALEOR_API_URL_HEADER) .context("missing saleor api url header")? .to_str()? .to_owned(); let event_type = get_webhook_event_type(&headers)?; let res: Json = match event_type { EitherWebhookType::Sync(a) => match a { SyncWebhookEventType::PaymentGatewayInitializeSession => Json::from( serde_json::to_value(PaymentGatewayInitializeSessionResponse:: { data: None })?, ), SyncWebhookEventType::TransactionProcessSession => { let data = serde_json::from_str::(&body)?; Json::from(serde_json::to_value(TransactionProcessSessionResponse::< u8, > { data: None, time: None, psp_reference: None, external_url: None, message: None, amount: Decimal::from_str(&data.action.amount.0)?, result: match data.action.action_type { TransactionFlowStrategyEnum::Charge => { TransactionSessionResult::ChargeSuccess } TransactionFlowStrategyEnum::Authorization => { TransactionSessionResult::AuthorizationSuccess } }, })?) } SyncWebhookEventType::TransactionChargeRequested => { let data = serde_json::from_str::(&body)?; Json::from(serde_json::to_value(TransactionChargeRequestedResponse { time: None, psp_reference: "".to_owned(), external_url: None, message: None, amount: data .action .amount .and_then(|a| Decimal::from_str(&a.0).ok()), result: Some(ChargeRequestedResult::ChargeSuccess), })?) } SyncWebhookEventType::TransactionRefundRequested => { let data = serde_json::from_str::(&body)?; Json::from(serde_json::to_value(TransactionRefundRequestedResponse { time: None, psp_reference: "".to_owned(), external_url: None, message: None, amount: data .action .amount .and_then(|a| Decimal::from_str(&a.0).ok()), result: Some(RefundRequestedResult::RefundSuccess), })?) } SyncWebhookEventType::TransactionInitializeSession => { let data = serde_json::from_str::(&body)?; let app = state.saleor_app.lock().await; let auth_data = app.apl.get(&saleor_api_url).await?; let operation = TransactionUpdate::build(TransactionUpdateVariables { id: &data.transaction.id, transaction: Some(TransactionUpdateInput { message: Some(""), ..Default::default() }), }); let mut res = surf::post(&saleor_api_url).run_graphql(operation).await; let mut webhook_result = WebhookResult::Failiure; if let Ok(r) = &mut res && let Some(data) = &mut r.data && let Some(q_res) = &mut data.transaction_update { if !q_res.errors.is_empty() { q_res .errors .iter() .for_each(|e| error!("failed update transaction, {:?}", e)); } else { if let Some(tr) = &mut q_res.transaction { tr.message = serde_json::to_string(&PaymentMethod { payment_method: GatewayType::COD, })?; webhook_result = WebhookResult::Success; } } } Json::from(serde_json::to_value( TransactionInitializeSessionResponse:: { data: None, time: None, psp_reference: None, external_url: None, message: None, amount: Decimal::from_str(&data.action.amount.0)?, result: match (data.action.action_type, webhook_result) { (TransactionFlowStrategyEnum::Charge, WebhookResult::Success) => { TransactionSessionResult::ChargeSuccess } ( TransactionFlowStrategyEnum::Authorization, WebhookResult::Success, ) => TransactionSessionResult::AuthorizationSuccess, (TransactionFlowStrategyEnum::Charge, WebhookResult::Failiure) => { TransactionSessionResult::ChargeFailiure } ( TransactionFlowStrategyEnum::Authorization, WebhookResult::Failiure, ) => TransactionSessionResult::AuthorizationFailure, }, }, )?) } _ => Json::from(Value::from_str("")?), }, _ => Json::from(Value::from_str("")?), }; info!("got webhooks!"); Ok(res) } #[derive(Serialize, Clone, Debug)] pub struct JsonResponse { data: JsonResponseData, } #[derive(Serialize, Clone, Debug)] pub struct JsonResponseData { current_gateway: ActiveGateway, } enum WebhookResult { Success, Failiure, } #[derive(Serialize, Clone, Debug)] struct PaymentMethod { payment_method: GatewayType, }