clippy refactor, payment gateway payment flow revertion

This commit is contained in:
Djkáťo 2024-04-03 19:47:23 +02:00
parent 795793ca84
commit df27d18043
24 changed files with 413 additions and 444 deletions

7
.env
View file

@ -2,6 +2,7 @@
REQUIRED_SALEOR_VERSION="^3.13"
# only sets port, the host is always 0.0.0.0 (listens to everything). Set this to docker-compose service name
APP_API_BASE_URL="http://10.100.110.234:3000"
APP_IFRAME_BASE_URL="http://app-name.site.com"
APL="Redis"
APL_URL="redis://localhost:6380/2"
LOG_LEVEL="DEBUG"
@ -20,9 +21,9 @@ SITEMAP_PAGES_TEMPLATE="https://example.com/{page.slug}"
SITEMAP_INDEX_HOSTNAME="https://example.com"
## THESE VARIABLES ARE FOR SIMPLE-PAYMENT-GATEWAY APP
#To see all possible options, check simple-payment-gateway/src/app:GatewayTypes
ACTIVE_GATEWAYS="cod,cash,transfer"
#To see all possible options, check simple-payment-gateway/src/app:PaymentMethods
ACTIVE_PAYMENT_METHODS="cod,cash,transfer"
# only SK,EN available :). Determines what language the gateway names will be in storefront
LOCALE="SK"
LOCALE="Sk"
# uses https://crates.io/crates/iso_currency
CURRENCIES="EUR"

View file

@ -2,6 +2,7 @@
REQUIRED_SALEOR_VERSION="^3.13"
# only sets port, the host is always 0.0.0.0 (listens to everything). Set this to docker-compose service name
APP_API_BASE_URL="http://0.0.0.0:3000"
APP_IFRAME_BASE_URL="http://app-name.site.com"
APL="Redis"
APL_URL="redis://localhost:6379/1"
LOG_LEVEL="DEBUG"
@ -22,7 +23,7 @@ SITEMAP_INDEX_HOSTNAME="https://example.com"
## THESE VARIABLES ARE FOR SIMPLE-PAYMENT-GATEWAY APP
#To see all possible options, check simple-payment-gateway/src/app:GatewayTypes
ACTIVE_GATEWAYS="cod,cash,transfer"
# only SK,EN available :). Determines what language the gateway names will be in storefront
LOCALE="SK"
# only Sk,En available :). Determines what language the gateway names will be in storefront
LOCALE="Sk"
# uses https://crates.io/crates/iso_currency
CURRENCIES="EUR"

View file

@ -17,8 +17,11 @@ RUN cargo chef cook --release --recipe-path=recipe.json
COPY . .
RUN cargo build --release
FROM debian:bookworm-slim as chef-sitemap-generator
COPY --from=builder /apps/target/release/sitemap-generator /sitemap-generator
WORKDIR /app
COPY --from=builder /apps/target/release/sitemap-generator .
COPY ./sitemap-generator/public ./public
RUN apt-get update -y && \
apt-get install -y pkg-config libssl-dev curl
RUN mkdir /sitemaps
@ -31,8 +34,11 @@ LABEL org.opencontainers.image.title="djkato/saleor-sitemap-generator"\
org.opencontainers.image.authors="Djkáťo <djkatovfx@gmail.com>"\
org.opencontainers.image.licenses="PolyForm-Noncommercial-1.0.0"
FROM debian:bookworm-slim as chef-simple-payment-gateway
COPY --from=builder /apps/target/release/simple-payment-gateway /simple-payment-gateway
WORKDIR /app
COPY --from=builder /apps/target/release/simple-payment-gateway .
COPY ./simple-payment-gateway/public ./public
RUN apt-get update -y && \
apt-get install -y pkg-config libssl-dev curl
CMD [ "./simple-payment-gateway" ]

View file

@ -79,6 +79,7 @@ and set all necessary env variables in `app-simple-gateway.env` according to the
To use, you need to have [Rust environment prepared](https://rustup.rs/).
Every folder represents a different workspace. To add a new lib, do `cargo new <project-name> --lib` or `cargo new <project-name>` for binary apps. It should appear as a new member under root `Cargo.toml`
To run apps propery, use `cargo run -c <crate name>`
# Unofficial Saleor App SDK

View file

@ -33,22 +33,18 @@ where
}
}
pub fn trace_to_std(config: &Config) {
pub fn trace_to_std(config: &Config) -> anyhow::Result<()> {
let filter = EnvFilter::builder()
.with_default_directive(LevelFilter::DEBUG.into())
.from_env()
.unwrap()
.add_directive(
format!("{}={}", env!("CARGO_PKG_NAME"), config.log_level)
.parse()
.unwrap(),
);
.from_env()?
.add_directive(format!("{}={}", env!("CARGO_PKG_NAME"), config.log_level).parse()?);
tracing_subscriber::fmt()
.with_max_level(config.log_level)
.with_env_filter(filter)
.with_target(true)
.compact()
.init();
Ok(())
}
#[derive(Debug, Clone)]

View file

@ -1,3 +1,11 @@
#![allow(
non_upper_case_globals,
clippy::large_enum_variant,
clippy::upper_case_acronyms
)]
#![feature(let_chains)]
#![deny(clippy::unwrap_used, clippy::expect_used)]
mod app;
mod queries;
mod routes;
@ -5,8 +13,8 @@ mod routes;
use saleor_app_sdk::{
cargo_info,
config::Config,
manifest::{AppManifest, AppPermission},
webhooks::{AsyncWebhookEventType, WebhookManifest},
manifest::{AppManifestBuilder, AppPermission},
webhooks::{AsyncWebhookEventType, WebhookManifestBuilder},
SaleorApp,
};
use std::sync::Arc;
@ -20,13 +28,13 @@ use crate::{
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let config = Config::load()?;
trace_to_std(&config);
trace_to_std(&config)?;
let saleor_app = SaleorApp::new(&config)?;
let app_manifest = AppManifest::new(&config, cargo_info!())
let app_manifest = AppManifestBuilder::new(&config, cargo_info!())
.add_webhook(
WebhookManifest::new(&config)
WebhookManifestBuilder::new(&config)
.set_query(
r#"
subscription QueryProductsChanged {
@ -85,7 +93,7 @@ async fn main() -> anyhow::Result<()> {
.unwrap_or(&"3000"),
)
.await?;
tracing::debug!("listening on {}", listener.local_addr().unwrap());
tracing::debug!("listening on {}", listener.local_addr()?);
match axum::serve(listener, app).await {
Ok(o) => Ok(o),
Err(e) => anyhow::bail!(e),

View file

@ -22,7 +22,7 @@ pub fn create_routes(state: AppState) -> Router {
(StatusCode::NOT_FOUND, "Not found")
}
let service = handle_404.into_service();
let serve_dir = ServeDir::new("saleor-app-template/public").not_found_service(service);
let serve_dir = ServeDir::new("./public").not_found_service(service);
Router::new()
.layer(middleware::from_fn(webhook_signature_verifier))

View file

@ -37,16 +37,15 @@ pub async fn webhooks(
.get(SALEOR_API_URL_HEADER)
.context("missing saleor api url header")?;
let event_type = get_webhook_event_type(&headers)?;
match event_type {
EitherWebhookType::Async(a) => match a {
if let EitherWebhookType::Async(a) = event_type {
match a {
AsyncWebhookEventType::ProductUpdated
| AsyncWebhookEventType::ProductCreated
| AsyncWebhookEventType::ProductDeleted => {
update_product(product, url.to_str()?, state).await?
}
_ => (),
},
_ => (),
}
}
info!("got webhooks!");

View file

@ -1,8 +1,7 @@
## COMMON VARIABLES FOR ALL APPS
REQUIRED_SALEOR_VERSION="^3.13"
APP_API_BASE_URL="http://0.0.0.0:3001"
MANIFEST_URL="http://0.0.0.0:3001"
CONFIG_URL="http://0.0.0.0:3001"
APP_API_BASE_URL="http://app-payment-gateway:3000"
APP_IFRAME_BASE_URL="http://app-payment-gateway.site.com"
APL="Redis"
APL_URL="redis://redis-apl:6379/1"
LOG_LEVEL="DEBUG"
@ -23,5 +22,7 @@ SITEMAP_INDEX_HOSTNAME="https://example.com"
## THESE VARIABLES ARE FOR SIMPLE-PAYMENT-GATEWAY APP
#To see all possible options, check simple-payment-gateway/src/app:GatewayTypes
ACTIVE_GATEWAYS="cod,cash,transfer"
# only SK,EN available :). Determines what language the gateway names will be in storefront
LOCALE="SK"
# only Sk,En available :). Determines what language the gateway names will be in storefront
LOCALE="Sk"
# uses https://crates.io/crates/iso_currency
CURRENCIES="EUR"

View file

@ -1,6 +1,7 @@
## COMMON VARIABLES FOR ALL APPS
REQUIRED_SALEOR_VERSION="^3.13"
APP_API_BASE_URL="http://0.0.0.0:3002"
APP_API_BASE_URL="http://0.0.0.0:3000"
APP_IFRAME_BASE_URL="http://app-payment-gateway.site.com"
APL="Redis"
APL_URL="redis://redis-apl:6379/1"
LOG_LEVEL="DEBUG"

View file

@ -24,6 +24,7 @@ pub struct Config {
#[serde(default = "version_default")]
pub required_saleor_version: String,
pub app_api_base_url: String,
pub app_iframe_base_url: String,
pub apl: AplType,
pub apl_url: String,
#[serde(with = "LocalTracingLevel")]

View file

@ -214,7 +214,7 @@ macro_rules! cargo_info {
}
pub use cargo_info;
impl AppManifest {
impl AppManifestBuilder {
/**
* Builder for AppManifest
*
@ -227,7 +227,7 @@ impl AppManifest {
* To set webhooks and permissions use the add_webhook() and add_permissions()
*
*/
pub fn new(config: &Config, info: CargoInfo) -> AppManifestBuilder {
pub fn new(config: &Config, info: CargoInfo) -> Self {
AppManifestBuilder {
manifest: AppManifest {
id: info.name.clone(),

View file

@ -269,15 +269,15 @@ impl WebhookManifestBuilder {
}
}
impl WebhookManifest {
impl WebhookManifestBuilder {
/**
* Creates defaults of name(<cargo_app_name> webhook) and target url(/api/webhooks) from config and env.
*/
pub fn new(config: &Config) -> WebhookManifestBuilder {
pub fn new(config: &Config) -> Self {
WebhookManifestBuilder {
webhook_manifest: WebhookManifest {
target_url: format!("{}/api/webhooks", config.app_api_base_url),
name: env!("CARGO_PKG_NAME").to_owned() + " webhook",
name: "webhook".to_owned(),
is_active: Some(true),
..Default::default()
},

View file

@ -4,6 +4,7 @@ use crate::headers::SALEOR_EVENT_HEADER;
use super::{AsyncWebhookEventType, SyncWebhookEventType};
#[derive(Debug)]
pub enum EitherWebhookType {
Sync(SyncWebhookEventType),
Async(AsyncWebhookEventType),

View file

@ -5,14 +5,11 @@ use axum::{
use enum_iterator::{all, Sequence};
use iso_currency::Currency;
use std::{str::FromStr, sync::Arc};
use tracing::level_filters::LevelFilter;
use tracing::{debug, level_filters::LevelFilter};
use tracing_subscriber::EnvFilter;
use saleor_app_sdk::{
config::Config, locales::LocaleCode, manifest::AppManifest,
webhooks::sync_response::PaymentGateway, SaleorApp,
};
use serde::Serialize;
use saleor_app_sdk::{config::Config, locales::LocaleCode, manifest::AppManifest, SaleorApp};
use serde::{Deserialize, Serialize};
// Make our own error that wraps `anyhow::Error`.
pub struct AppError(anyhow::Error);
@ -38,27 +35,23 @@ where
}
}
pub fn trace_to_std(config: &Config) {
pub fn trace_to_std(config: &Config) -> anyhow::Result<()> {
let filter = EnvFilter::builder()
.with_default_directive(LevelFilter::DEBUG.into())
.from_env()
.unwrap()
.add_directive(
format!("{}={}", env!("CARGO_PKG_NAME"), config.log_level)
.parse()
.unwrap(),
);
.from_env()?
.add_directive(format!("{}={}", env!("CARGO_PKG_NAME"), config.log_level).parse()?);
tracing_subscriber::fmt()
.with_max_level(config.log_level)
.with_env_filter(filter)
.with_target(true)
.compact()
.init();
Ok(())
}
#[derive(Debug, Clone, Sequence, Serialize)]
#[derive(Debug, Clone, Sequence, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum GatewayType {
pub enum PaymentMethodType {
Accreditation,
Cash,
/**
@ -75,16 +68,16 @@ pub struct AppState {
pub saleor_app: Arc<tokio::sync::Mutex<SaleorApp>>,
pub config: Config,
pub manifest: AppManifest,
pub active_gateways: Vec<ActiveGateway>,
pub active_payment_methods: Vec<ActivePaymentMethod>,
}
pub fn get_active_gateways_from_env() -> anyhow::Result<Vec<ActiveGateway>> {
pub fn get_active_payment_methods_from_env() -> anyhow::Result<Vec<ActivePaymentMethod>> {
_ = dotenvy::dotenv();
//eg: "accreditation,cod,other,transfer"
let env_types = std::env::var("ACTIVE_GATEWAYS")?;
let env_methods = std::env::var("ACTIVE_PAYMENT_METHODS")?;
let locale = std::env::var("LOCALE")?;
let locale = LocaleCode::from_str(&locale)?;
let currencies = std::env::var("CURRENCIES")?;
let locale = LocaleCode::from_str(&locale)?;
let currencies = currencies.split(',').collect::<Vec<_>>();
let currencies = currencies
.iter()
@ -92,44 +85,52 @@ pub fn get_active_gateways_from_env() -> anyhow::Result<Vec<ActiveGateway>> {
.collect::<Result<Vec<_>, _>>()
.map_err(|e| anyhow::anyhow!(format!("{:?}", e)))?;
let str_types: Vec<_> = env_types.split(',').collect();
let gateway_types = str_types
let str_types: Vec<_> = env_methods.split(',').collect();
let payment_methods = str_types
.iter()
.zip(all::<GatewayType>())
.flat_map(|s| all::<PaymentMethodType>().map(move |g| (s, g)))
.filter_map(|(s, g)| match format!("{:?}", g).to_lowercase() == *s {
true => Some(g),
false => None,
})
.map(|g| ActiveGateway {
gateway_type: g.clone(),
gateway: PaymentGateway {
currencies: currencies.clone(),
id: format!("{:?}", &g).to_lowercase(),
config: vec![],
.map(|g| ActivePaymentMethod {
typ: g.clone(),
name: match (g, &locale) {
(GatewayType::COD, LocaleCode::Sk) => "Dobierka".to_owned(),
(GatewayType::Cash, LocaleCode::Sk) => "Hotovosť".to_owned(),
(GatewayType::Transfer, LocaleCode::Sk) => "Bankový prevod".to_owned(),
(GatewayType::Inkaso, LocaleCode::Sk) => "Inkaso".to_owned(),
(GatewayType::Accreditation, LocaleCode::Sk) => "Vzajomný zápočet".to_owned(),
(GatewayType::Other, LocaleCode::Sk) => "Iné".to_owned(),
(GatewayType::COD, LocaleCode::En) => "Cash on delivery".to_owned(),
(GatewayType::Cash, LocaleCode::En) => "Cash".to_owned(),
(GatewayType::Transfer, LocaleCode::En) => "Bank transfer".to_owned(),
(GatewayType::Inkaso, LocaleCode::En) => "Encashment".to_owned(),
(GatewayType::Accreditation, LocaleCode::En) => "Mutual credit".to_owned(),
(GatewayType::Other, LocaleCode::En) => "Other".to_owned(),
(PaymentMethodType::COD, LocaleCode::Sk) => "Dobierka".to_owned(),
(PaymentMethodType::Cash, LocaleCode::Sk) => "Hotovosť".to_owned(),
(PaymentMethodType::Transfer, LocaleCode::Sk) => "Bankový prevod".to_owned(),
(PaymentMethodType::Inkaso, LocaleCode::Sk) => "Inkaso".to_owned(),
(PaymentMethodType::Accreditation, LocaleCode::Sk) => "Vzajomný zápočet".to_owned(),
(PaymentMethodType::Other, LocaleCode::Sk) => "Iné".to_owned(),
(PaymentMethodType::COD, LocaleCode::En) => "Cash on delivery".to_owned(),
(PaymentMethodType::Cash, LocaleCode::En) => "Cash".to_owned(),
(PaymentMethodType::Transfer, LocaleCode::En) => "Bank transfer".to_owned(),
(PaymentMethodType::Inkaso, LocaleCode::En) => "Encashment".to_owned(),
(PaymentMethodType::Accreditation, LocaleCode::En) => "Mutual credit".to_owned(),
(PaymentMethodType::Other, LocaleCode::En) => "Other".to_owned(),
(g, l) => unimplemented!("Gateway {:?} in locale {:?} not implemented", g, l),
},
},
})
.collect::<Vec<_>>();
Ok(gateway_types)
debug!(
"active gateway types:{:?}\ncurrencies:{:?}\nlocale:{:?}",
&payment_methods, &currencies, &locale
);
Ok(payment_methods)
}
#[derive(Debug, Clone, Serialize)]
pub struct ActiveGateway {
pub gateway_type: GatewayType,
pub gateway: PaymentGateway,
pub struct ActivePaymentMethod {
pub typ: PaymentMethodType,
pub name: String,
}
#[derive(Serialize)]
pub struct PaymentGatewayInitializeSessionData {
pub payment_methods: Vec<ActivePaymentMethod>,
}
#[derive(Deserialize, Serialize)]
pub struct TransactionInitializeSessionData {
pub payment_method: PaymentMethodType,
}

View file

@ -1,4 +1,8 @@
#![allow(non_upper_case_globals)]
#![allow(
non_upper_case_globals,
clippy::large_enum_variant,
clippy::upper_case_acronyms
)]
#![feature(let_chains)]
#![deny(clippy::unwrap_used, clippy::expect_used)]
mod app;
@ -8,20 +12,19 @@ mod routes;
use saleor_app_sdk::{
cargo_info,
config::Config,
manifest::{AppManifest, AppPermission},
webhooks::{SyncWebhookEventType, WebhookManifest},
manifest::{AppManifestBuilder, AppPermission},
webhooks::{SyncWebhookEventType, WebhookManifestBuilder},
SaleorApp,
};
use std::sync::Arc;
use tokio::sync::Mutex;
use crate::{
app::{get_active_gateways_from_env, trace_to_std, AppState},
app::{get_active_payment_methods_from_env, trace_to_std, AppState},
queries::event_transactions::{
sub_list_payment_gateways, sub_payment_gateway_initialize_session,
sub_transaction_cancelation_requested, sub_transaction_charge_requested,
sub_transaction_initialize_session, sub_transaction_process_session,
sub_transaction_refund_requested,
sub_payment_gateway_initialize_session, sub_transaction_cancelation_requested,
sub_transaction_charge_requested, sub_transaction_initialize_session,
sub_transaction_process_session, sub_transaction_refund_requested,
},
routes::create_routes,
};
@ -29,53 +32,53 @@ use crate::{
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let config = Config::load()?;
trace_to_std(&config);
trace_to_std(&config)?;
let saleor_app = SaleorApp::new(&config)?;
let app_manifest = AppManifest::new(&config, cargo_info!())
let app_manifest = AppManifestBuilder::new(&config, cargo_info!())
.add_webhook(
WebhookManifest::new(&config)
WebhookManifestBuilder::new(&config)
.set_query(sub_transaction_process_session)
.add_sync_event(SyncWebhookEventType::TransactionProcessSession)
.build(),
)
.add_webhook(
WebhookManifest::new(&config)
WebhookManifestBuilder::new(&config)
.set_query(sub_transaction_charge_requested)
.add_sync_event(SyncWebhookEventType::TransactionChargeRequested)
.build(),
)
.add_webhook(
WebhookManifest::new(&config)
WebhookManifestBuilder::new(&config)
.set_query(sub_transaction_refund_requested)
.add_sync_event(SyncWebhookEventType::TransactionRefundRequested)
.build(),
)
.add_webhook(
WebhookManifest::new(&config)
WebhookManifestBuilder::new(&config)
.set_query(sub_transaction_initialize_session)
.add_sync_event(SyncWebhookEventType::TransactionInitializeSession)
.build(),
)
.add_webhook(
WebhookManifest::new(&config)
WebhookManifestBuilder::new(&config)
.set_query(sub_payment_gateway_initialize_session)
.add_sync_event(SyncWebhookEventType::PaymentGatewayInitializeSession)
.build(),
)
.add_webhook(
WebhookManifest::new(&config)
WebhookManifestBuilder::new(&config)
.set_query(sub_transaction_cancelation_requested)
.add_sync_event(SyncWebhookEventType::TransactionCancelationRequested)
.build(),
)
.add_webhook(
WebhookManifest::new(&config)
.set_query(sub_list_payment_gateways)
.add_sync_event(SyncWebhookEventType::PaymentListGateways)
.build(),
)
// .add_webhook(
// WebhookManifestBuilder::new(&config)
// .set_query(sub_list_payment_gateways)
// .add_sync_event(SyncWebhookEventType::PaymentListGateways)
// .build(),
// )
.add_permissions(vec![
AppPermission::HandlePayments,
AppPermission::ManageOrders,
@ -85,7 +88,7 @@ async fn main() -> anyhow::Result<()> {
.build();
let app_state = AppState {
active_gateways: get_active_gateways_from_env()?,
active_payment_methods: get_active_payment_methods_from_env()?,
manifest: app_manifest,
config: config.clone(),
saleor_app: Arc::new(Mutex::new(saleor_app)),
@ -102,7 +105,7 @@ async fn main() -> anyhow::Result<()> {
.unwrap_or(&"3000"),
)
.await?;
tracing::debug!("listening on {}", listener.local_addr().unwrap());
tracing::debug!("listening on {}", listener.local_addr()?);
match axum::serve(listener, app).await {
Ok(o) => Ok(o),
Err(e) => anyhow::bail!(e),

View file

@ -55,17 +55,17 @@ fragment OrderDetails on Order {
}
"#;
pub const sub_list_payment_gateways: &str = r#"
subscription ListPaymentGateways {
event {
... on PaymentListGateways {
checkout {
id
}
}
}
}
"#;
// pub const sub_list_payment_gateways: &str = r#"
// subscription ListPaymentGateways {
// event {
// ... on PaymentListGateways {
// checkout {
// id
// }
// }
// }
// }
// "#;
pub const sub_payment_gateway_initialize_session: &str = concatcp!(
r#"

View file

@ -22,7 +22,7 @@ pub fn create_routes(state: AppState) -> Router {
(StatusCode::NOT_FOUND, "Not found")
}
let service = handle_404.into_service();
let serve_dir = ServeDir::new("simple-payment-gateway/public").not_found_service(service);
let serve_dir = ServeDir::new("./public").not_found_service(service);
Router::new()
.layer(middleware::from_fn(webhook_signature_verifier))

View file

@ -7,23 +7,23 @@ use saleor_app_sdk::{
webhooks::{
sync_response::{
CancelationRequestedResult, ChargeRequestedResult,
PaymentGatewayInitializeSessionResponse, PaymentListGatewaysResponse,
RefundRequestedResult, TransactionCancelationRequestedResponse,
TransactionChargeRequestedResponse, TransactionInitializeSessionResponse,
TransactionProcessSessionResponse, TransactionRefundRequestedResponse,
TransactionSessionResult,
PaymentGatewayInitializeSessionResponse, RefundRequestedResult,
TransactionCancelationRequestedResponse, TransactionChargeRequestedResponse,
TransactionInitializeSessionResponse, TransactionProcessSessionResponse,
TransactionRefundRequestedResponse, TransactionSessionResult,
},
utils::{get_webhook_event_type, EitherWebhookType},
SyncWebhookEventType,
},
};
use serde::Serialize;
use serde_json::Value;
use std::str::FromStr;
use tracing::{debug, error, info};
use crate::{
app::{ActiveGateway, AppError, AppState, GatewayType},
app::{
AppError, AppState, PaymentGatewayInitializeSessionData, TransactionInitializeSessionData,
},
queries::{
event_transactions::{
TransactionCancelationRequested2, TransactionChargeRequested2,
@ -43,7 +43,6 @@ pub async fn webhooks(
) -> Result<Json<Value>, AppError> {
debug!("/api/webhooks");
debug!("req: {:?}", body);
debug!("headers: {:?}", headers);
let saleor_api_url = headers
.get(SALEOR_API_URL_HEADER)
@ -51,57 +50,96 @@ pub async fn webhooks(
.to_str()?
.to_owned();
let event_type = get_webhook_event_type(&headers)?;
debug!("event: {:?}", event_type);
let res: Json<Value> = match event_type {
EitherWebhookType::Sync(a) => match a {
SyncWebhookEventType::PaymentListGateways => {
let gateways = state
.active_gateways
.iter()
.cloned()
.map(|g| g.gateway)
.collect::<Vec<_>>();
Json::from(serde_json::to_value(PaymentListGatewaysResponse(gateways))?)
}
SyncWebhookEventType::TransactionCancelationRequested => {
let data = serde_json::from_str::<TransactionCancelationRequested2>(&body)?;
// SyncWebhookEventType::PaymentListGateways => {
// let gateways = state
// .active_gateways
// .iter()
// .cloned()
// .map(|g| g.gateway)
// .collect::<Vec<_>>();
// Json::from(serde_json::to_value(PaymentListGatewaysResponse(gateways))?)
// }
SyncWebhookEventType::PaymentGatewayInitializeSession => {
let data = serde_json::to_value(PaymentGatewayInitializeSessionData {
payment_methods: state.active_payment_methods,
})?;
Json::from(serde_json::to_value(
TransactionCancelationRequestedResponse {
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(CancelationRequestedResult::CancelSuccess),
},
PaymentGatewayInitializeSessionResponse::<Value> { data: Some(data) },
)?)
}
SyncWebhookEventType::PaymentGatewayInitializeSession => Json::from(
serde_json::to_value(PaymentGatewayInitializeSessionResponse::<u8> { data: None })?,
),
SyncWebhookEventType::TransactionProcessSession => {
let data = serde_json::from_str::<TransactionProcessSession2>(&body)?;
Json::from(serde_json::to_value(TransactionProcessSessionResponse::<
u8,
> {
SyncWebhookEventType::TransactionInitializeSession => {
let session_data = serde_json::from_str::<TransactionInitializeSession2>(&body)?;
let payment_method: TransactionInitializeSessionData = serde_json::from_str(
&session_data
.data
.context("Missing data field on TransactionInitializeSession2 body")?
.0,
)?;
debug!(
"Transaction session initialised with '{:?}' payment method.",
&payment_method.payment_method
);
let str_payment_method = serde_json::to_string(&payment_method)?;
let app = state.saleor_app.lock().await;
let auth_data = app.apl.get(&saleor_api_url).await?;
let operation = TransactionUpdate::build(TransactionUpdateVariables {
id: &session_data.transaction.id,
transaction: Some(TransactionUpdateInput {
message: Some(&str_payment_method),
..Default::default()
}),
});
let mut res = surf::post(&saleor_api_url)
.header("authorization-bearer", auth_data.token)
.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 q_res.transaction.is_some() {
webhook_result = WebhookResult::Success;
}
}
Json::from(serde_json::to_value(
TransactionInitializeSessionResponse::<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 => {
amount: Decimal::from_str(&session_data.action.amount.0)?,
result: match (session_data.action.action_type, webhook_result) {
(TransactionFlowStrategyEnum::Charge, WebhookResult::Success) => {
TransactionSessionResult::ChargeSuccess
}
TransactionFlowStrategyEnum::Authorization => {
TransactionSessionResult::AuthorizationSuccess
(
TransactionFlowStrategyEnum::Authorization,
WebhookResult::Success,
) => TransactionSessionResult::AuthorizationSuccess,
(TransactionFlowStrategyEnum::Charge, WebhookResult::Failiure) => {
TransactionSessionResult::ChargeFailiure
}
(
TransactionFlowStrategyEnum::Authorization,
WebhookResult::Failiure,
) => TransactionSessionResult::AuthorizationFailure,
},
})?)
},
)?)
}
SyncWebhookEventType::TransactionChargeRequested => {
let data = serde_json::from_str::<TransactionChargeRequested2>(&body)?;
@ -131,91 +169,54 @@ pub async fn webhooks(
result: Some(RefundRequestedResult::RefundSuccess),
})?)
}
SyncWebhookEventType::TransactionInitializeSession => {
let data = serde_json::from_str::<TransactionInitializeSession2>(&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)
.header("authorization-bearer", auth_data.token)
.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;
}
}
SyncWebhookEventType::TransactionCancelationRequested => {
let data = serde_json::from_str::<TransactionCancelationRequested2>(&body)?;
Json::from(serde_json::to_value(
TransactionInitializeSessionResponse::<u8> {
TransactionCancelationRequestedResponse {
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(CancelationRequestedResult::CancelSuccess),
},
)?)
}
SyncWebhookEventType::TransactionProcessSession => {
let data = serde_json::from_str::<TransactionProcessSession2>(&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, webhook_result) {
(TransactionFlowStrategyEnum::Charge, WebhookResult::Success) => {
result: match data.action.action_type {
TransactionFlowStrategyEnum::Charge => {
TransactionSessionResult::ChargeSuccess
}
(
TransactionFlowStrategyEnum::Authorization,
WebhookResult::Success,
) => TransactionSessionResult::AuthorizationSuccess,
(TransactionFlowStrategyEnum::Charge, WebhookResult::Failiure) => {
TransactionSessionResult::ChargeFailiure
TransactionFlowStrategyEnum::Authorization => {
TransactionSessionResult::AuthorizationSuccess
}
(
TransactionFlowStrategyEnum::Authorization,
WebhookResult::Failiure,
) => TransactionSessionResult::AuthorizationFailure,
},
},
)?)
})?)
}
_ => Json::from(Value::from_str("")?),
},
_ => Json::from(Value::from_str("")?),
};
debug!("{:?}", res.to_string());
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,
}

View file

@ -36,22 +36,18 @@ where
}
}
pub fn trace_to_std(config: &Config) {
pub fn trace_to_std(config: &Config) -> anyhow::Result<()> {
let filter = EnvFilter::builder()
.with_default_directive(LevelFilter::DEBUG.into())
.from_env()
.unwrap()
.add_directive(
format!("{}={}", env!("CARGO_PKG_NAME"), config.log_level)
.parse()
.unwrap(),
);
.from_env()?
.add_directive(format!("{}={}", env!("CARGO_PKG_NAME"), config.log_level).parse()?);
tracing_subscriber::fmt()
.with_max_level(config.log_level)
.with_env_filter(filter)
.with_target(true)
.compact()
.init();
Ok(())
}
/**

View file

@ -1,3 +1,8 @@
#![allow(
non_upper_case_globals,
clippy::large_enum_variant,
clippy::upper_case_acronyms
)]
#![feature(let_chains)]
#![deny(clippy::unwrap_used, clippy::expect_used)]
mod app;
@ -6,8 +11,8 @@ mod routes;
use saleor_app_sdk::{
config::Config,
manifest::{cargo_info, AppManifest, AppPermission},
webhooks::{AsyncWebhookEventType, WebhookManifest},
manifest::{cargo_info, AppManifestBuilder, AppPermission},
webhooks::{AsyncWebhookEventType, WebhookManifestBuilder},
SaleorApp,
};
use std::sync::Arc;
@ -23,7 +28,7 @@ use crate::{
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let config = Config::load()?;
trace_to_std(&config);
trace_to_std(&config)?;
let sitemap_config = SitemapConfig::load()?;
debug!("Creating configs...");
@ -31,13 +36,13 @@ async fn main() -> anyhow::Result<()> {
debug!("Creating saleor App...");
let app_manifest = AppManifest::new(&config, cargo_info!())
let app_manifest = AppManifestBuilder::new(&config, cargo_info!())
.add_permissions(vec![
AppPermission::ManageProducts,
AppPermission::ManagePages,
])
.add_webhook(
WebhookManifest::new(&config)
WebhookManifestBuilder::new(&config)
.set_query(EVENTS_QUERY)
.add_async_events(vec![
AsyncWebhookEventType::ProductCreated,
@ -98,7 +103,7 @@ async fn main() -> anyhow::Result<()> {
.unwrap_or(&"3000"),
)
.await?;
info!("listening on {}", listener.local_addr().unwrap());
info!("listening on {}", listener.local_addr()?);
match axum::serve(listener, app).await {
Ok(o) => Ok(o),
Err(e) => anyhow::bail!(e),

View file

@ -23,7 +23,7 @@ pub fn create_routes(state: AppState) -> Router {
}
let service = handle_404.into_service();
//TODO : Fix this relative path issue in workspaces
let serve_dir = ServeDir::new("./sitemap-generator/public").not_found_service(service);
let serve_dir = ServeDir::new("./public").not_found_service(service);
//TODO Query for everything using the app auth token
//TODO "Failed fetching initial products: More than one channel exists, please spocify which

View file

@ -198,10 +198,7 @@ pub async fn regenerate(state: AppState, saleor_api_url: String) -> anyhow::Resu
category: match xml_data.iter().find(|all| {
x.relations
.iter()
.find(|rel| {
all.id == **rel && all.data_type == XmlDataType::Category
})
.is_some()
.any(|rel| all.id == *rel && all.data_type == XmlDataType::Category)
}) {
Some(c) => Some(event_subjects_updated::Category {
slug: c.slug.clone(),
@ -286,8 +283,7 @@ async fn get_all_pages(
);
//Keep fetching next page
let mut next_cursor = pages.page_info.end_cursor.clone();
loop {
if let Some(cursor) = &mut next_cursor {
while let Some(cursor) = &mut next_cursor {
let res = surf::post(saleor_api_url)
.header("authorization-bearer", token)
.run_graphql(GetPagesNext::build(GetPagesNextVariables { after: cursor }))
@ -304,16 +300,10 @@ async fn get_all_pages(
.collect::<Vec<_>>(),
);
debug!("fetched next pages, eg.:{:?}", &pages.edges.first());
if !pages.page_info.has_next_page {
break;
}
next_cursor.clone_from(&pages.page_info.end_cursor);
} else {
error!("Failed fetching initial pages! {:?}", &res);
anyhow::bail!("Failed fetching initial pages! {:?}", res);
}
} else {
break;
error!("Failed fetching next pages! {:?}", &res);
anyhow::bail!("Failed fetching next pages! {:?}", res);
}
}
} else {
@ -349,8 +339,7 @@ async fn get_all_categories(saleor_api_url: &str, token: &str) -> anyhow::Result
);
//Keep fetching next page
let mut next_cursor = categories.page_info.end_cursor.clone();
loop {
if let Some(cursor) = &mut next_cursor {
while let Some(cursor) = &mut next_cursor {
let res = surf::post(saleor_api_url)
.header("authorization-bearer", token)
.run_graphql(GetCategoriesNext::build(GetCategoriesNextVariables {
@ -372,21 +361,15 @@ async fn get_all_categories(saleor_api_url: &str, token: &str) -> anyhow::Result
"fetched first categories, eg.:{:?}",
&categories.edges.first()
);
if !categories.page_info.has_next_page {
break;
}
next_cursor.clone_from(&categories.page_info.end_cursor);
} else {
error!("Failed fetching initial pages! {:?}", &res);
anyhow::bail!("Failed fetching initial pages! {:?}", res);
}
} else {
break;
error!("Failed fetching next categories! {:?}", &res);
anyhow::bail!("Failed fetching next categories! {:?}", res);
}
}
} else {
error!("Failed fetching initial pages! {:?}", &res);
anyhow::bail!("Failed fetching initial pages! {:?}", res);
error!("Failed fetching initial Categories! {:?}", &res);
anyhow::bail!("Failed fetching initial Categories! {:?}", res);
};
info!("All categories collected");
Ok(all_categories)
@ -418,8 +401,7 @@ async fn get_all_collections(saleor_api_url: &str, token: &str) -> anyhow::Resul
//Keep fetching next page
let mut next_cursor = collections.page_info.end_cursor.clone();
loop {
if let Some(cursor) = &mut next_cursor {
while let Some(cursor) = &mut next_cursor {
let res = surf::post(saleor_api_url)
.header("authorization-bearer", token)
.run_graphql(GetCollectionsNext::build(GetCollectionsNextVariables {
@ -441,16 +423,10 @@ async fn get_all_collections(saleor_api_url: &str, token: &str) -> anyhow::Resul
"fetched next collections, eg.:{:?}",
&collections.edges.first()
);
if !collections.page_info.has_next_page {
break;
}
next_cursor.clone_from(&collections.page_info.end_cursor);
} else {
error!("Failed fetching initial collecnios! {:?}", &res);
anyhow::bail!("Failed fetching initial collections! {:?}", res);
}
} else {
break;
error!("Failed fetching next collecnios! {:?}", &res);
anyhow::bail!("Failed fetching next collections! {:?}", res);
}
}
} else {
@ -498,7 +474,7 @@ async fn get_all_products(
debug!("fetched first products, eg: {:?}", products.edges.first());
let mut next_cursor = products.page_info.end_cursor.clone();
loop {
if let Some(cursor) = &mut next_cursor {
while let Some(cursor) = &mut next_cursor {
let res = surf::post(saleor_api_url)
.header("authorization-bearer", token)
.run_graphql(GetCategoryProductsNext::build(
@ -526,22 +502,14 @@ async fn get_all_products(
.collect::<Vec<_>>(),
);
debug!("fetched next products, eg: {:?}", products.edges.first());
if !products.page_info.has_next_page {
break;
}
next_cursor.clone_from(&products.page_info.end_cursor);
} else {
error!("Failed fetching initial products! {:?}", &res);
anyhow::bail!("Failed fetching initial products! {:?}", res);
}
} else {
break;
}
}
} else {
error!("Failed fetching initial products! {:?}", &res);
anyhow::bail!("Failed fetching initial products! {:?}", res);
};
}
info!("All products collected...");
Ok(all_categorised_products)
}

View file

@ -13,12 +13,7 @@ use saleor_app_sdk::{
AsyncWebhookEventType,
},
};
use sitemap_rs::{
sitemap::Sitemap,
sitemap_index::SitemapIndex,
url::{Url},
url_set::UrlSet,
};
use sitemap_rs::{sitemap::Sitemap, sitemap_index::SitemapIndex, url::Url, url_set::UrlSet};
use tinytemplate::TinyTemplate;
use tokio::spawn;
use tracing::{debug, error, info};
@ -26,8 +21,8 @@ use tracing::{debug, error, info};
use crate::{
app::{AppError, AppState, XmlData, XmlDataType},
queries::event_subjects_updated::{
Category, Category2, CategoryUpdated, Collection, CollectionUpdated, Page,
PageUpdated, Product, ProductUpdated,
Category, Category2, CategoryUpdated, Collection, CollectionUpdated, Page, PageUpdated,
Product, ProductUpdated,
},
};
@ -46,8 +41,8 @@ pub async fn webhooks(
.to_str()?
.to_owned();
let event_type = get_webhook_event_type(&headers)?;
match event_type {
EitherWebhookType::Async(a) => match a {
if let EitherWebhookType::Async(a) = event_type {
match a {
AsyncWebhookEventType::ProductUpdated
| AsyncWebhookEventType::ProductCreated
| AsyncWebhookEventType::ProductDeleted => {
@ -90,8 +85,7 @@ pub async fn webhooks(
}
_ => (),
},
_ => (),
}
}
info!("webhook proccessed");
@ -185,10 +179,7 @@ async fn update_sitemap_product(
xml_cat.last_modified = chrono::offset::Utc::now().fixed_offset();
// If the category exists but product isn't in relation to it yet,
// add it
if !xml_cat
.relations
.iter().any(|c| *c == product.id)
{
if !xml_cat.relations.iter().any(|c| *c == product.id) {
xml_cat.relations.push(product.id.clone());
}
//if cat isn't in xml data, add it
@ -216,10 +207,7 @@ async fn update_sitemap_product(
category: match xml_data.iter().find(|all| {
x.relations
.iter()
.find(|rel| {
all.id == **rel && all.data_type == XmlDataType::Category
})
.is_some()
.any(|rel| all.id == *rel && all.data_type == XmlDataType::Category)
}) {
Some(c) => Some(Category {
slug: c.slug.clone(),
@ -322,13 +310,11 @@ async fn update_sitemap_category(
let mut tt = TinyTemplate::new();
if is_category_in_product_url && x.data_type == XmlDataType::Product {
tt.add_template("product_url", &state.sitemap_config.product_template)?;
let context;
//If current xml products category is this changed category, just use that instead
//of searching for it again
match x.relations.iter().find(|c| *c == &category.id) {
Some(_) => {
context = ProductUpdated {
product: Some(Product {
let context = ProductUpdated {
product: match x.relations.iter().find(|c| *c == &category.id) {
Some(_) => Some(Product {
id: x.id.clone(),
slug: x.slug.clone(),
category: Some(Category {
@ -336,21 +322,13 @@ async fn update_sitemap_category(
id: category.id.clone(),
}),
}),
};
}
None => {
context = ProductUpdated {
product: Some(Product {
None => Some(Product {
id: x.id.clone(),
slug: x.slug.clone(),
category: match xml_data.iter().find(|all| {
x.relations
.iter()
.find(|rel| {
all.id == **rel
&& all.data_type == XmlDataType::Category
x.relations.iter().any(|rel| {
all.id == *rel && all.data_type == XmlDataType::Category
})
.is_some()
}) {
Some(c) => Some(Category {
slug: c.slug.clone(),
@ -362,9 +340,8 @@ async fn update_sitemap_category(
}),
},
}),
},
};
}
}
product_urls.push(
Url::builder(tt.render("product_url", &context)?)
.last_modified(x.last_modified)
@ -564,8 +541,7 @@ pub async fn write_xml(
let mut sitemaps: Vec<UrlSet> = vec![];
for urls in sliced_urls {
for url in urls.iter().cloned() {
let mut sitemap_urls: Vec<Url> = vec![];
sitemap_urls.push(url);
let sitemap_urls = vec![url];
sitemaps.push(UrlSet::new(sitemap_urls)?);
}
}
@ -616,14 +592,13 @@ async fn update_sitemap_index(state: &AppState) -> anyhow::Result<()> {
let sitemaps: Vec<Sitemap> = paths
.into_iter()
.map(|p| {
Sitemap::new(
.filter_map(|p| {
if let Some(file_name) = p.file_name() {
Some(Sitemap::new(
format!(
"{}/{}",
state.sitemap_config.index_hostname,
p.file_name()
.expect("file dissapeared or broke during sitemap-index construction")
.to_string_lossy()
file_name.to_string_lossy()
),
p.metadata().map_or(None, |meta| {
meta.modified().map_or(None, |modified| {
@ -631,7 +606,11 @@ async fn update_sitemap_index(state: &AppState) -> anyhow::Result<()> {
Some(dt_utc.fixed_offset())
})
}),
)
))
} else {
error!("file dissapeared or broke during sitemap-index construction");
None
}
})
.collect::<Vec<_>>();
let sitemap_index = SitemapIndex::new(sitemaps)?;
@ -646,7 +625,7 @@ async fn update_sitemap_index(state: &AppState) -> anyhow::Result<()> {
let mut buf = Vec::<u8>::new();
sitemap_index.write(&mut buf)?;
file.write_all(&mut buf).await?;
file.write_all(&buf).await?;
Ok(())
}