before adding GH CI
This commit is contained in:
parent
ee34b1bf22
commit
f6b1977057
23 changed files with 597 additions and 64 deletions
35
.github/rust.yml
vendored
35
.github/rust.yml
vendored
|
@ -1,35 +0,0 @@
|
||||||
name: Rust
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
pull_request:
|
|
||||||
branches: [master]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Cache cargo registry
|
|
||||||
uses: actions/cache@v1
|
|
||||||
with:
|
|
||||||
path: ~/.cargo/registry
|
|
||||||
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
- name: Cache cargo index
|
|
||||||
uses: actions/cache@v1
|
|
||||||
with:
|
|
||||||
path: ~/.cargo/git
|
|
||||||
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
- name: Cache cargo build
|
|
||||||
uses: actions/cache@v1
|
|
||||||
with:
|
|
||||||
path: target
|
|
||||||
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
- name: Run Linter
|
|
||||||
run: cargo fmt -- --check && cargo clippy --all-targets --all-features -- -D warnings
|
|
||||||
- name: Build
|
|
||||||
run: cargo build --verbose
|
|
||||||
- name: Run tests
|
|
||||||
run: cargo test --verbose
|
|
46
Cargo.lock
generated
46
Cargo.lock
generated
|
@ -535,6 +535,26 @@ version = "0.4.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935"
|
checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "const_format"
|
||||||
|
version = "0.2.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673"
|
||||||
|
dependencies = [
|
||||||
|
"const_format_proc_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "const_format_proc_macros"
|
||||||
|
version = "0.2.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cookie"
|
name = "cookie"
|
||||||
version = "0.14.4"
|
version = "0.14.4"
|
||||||
|
@ -2674,6 +2694,26 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simple-payment-gateway"
|
name = "simple-payment-gateway"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"axum",
|
||||||
|
"const_format",
|
||||||
|
"cynic",
|
||||||
|
"cynic-codegen",
|
||||||
|
"dotenvy",
|
||||||
|
"envy",
|
||||||
|
"redis",
|
||||||
|
"saleor-app-sdk",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"surf",
|
||||||
|
"tokio",
|
||||||
|
"tower",
|
||||||
|
"tower-http",
|
||||||
|
"tracing",
|
||||||
|
"tracing-serde",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simple_asn1"
|
name = "simple_asn1"
|
||||||
|
@ -3406,6 +3446,12 @@ dependencies = [
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "universal-hash"
|
name = "universal-hash"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
|
|
@ -23,7 +23,7 @@ to use in your project inside this repo, create a new workspace member and add `
|
||||||
|
|
||||||
## Creating a new Saleor App from template
|
## Creating a new Saleor App from template
|
||||||
|
|
||||||
If using the `saleor-app-template`, create a new workspace member `cargo new <project-name>`, then `cp saleor-app-template/* <project-name>`.
|
If using the `saleor-app-template`, create a new workspace member `cargo new <project-name>`,`rm -rf <project-name>/*` then `cp -r app-template/* <project-name>/`.
|
||||||
|
|
||||||
## Adding new dependencies
|
## Adding new dependencies
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
To update the schema, you can download it from https://raw.githubusercontent.com/saleor/saleor/main/saleor/graphql/schema.graphql
|
|
|
@ -25,27 +25,3 @@ pub struct SaleorHeaders<'a> {
|
||||||
#[serde(rename = "content-length")]
|
#[serde(rename = "content-length")]
|
||||||
content_length: u16,
|
content_length: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO!
|
|
||||||
impl SaleorHeaders {
|
|
||||||
pub fn verify(&self, payload: &str) -> anyhow::Result<()> {
|
|
||||||
/*
|
|
||||||
if let Some(saleor_signature) = self.signature {
|
|
||||||
let split: Vec<String> = saleor_signature.split(".").collect();
|
|
||||||
let header = split.get(0);
|
|
||||||
let signature = split.get(2);
|
|
||||||
if let Some(header) = header {
|
|
||||||
if let Some(signature) = signature {
|
|
||||||
let jws = jose_jws::Signature {
|
|
||||||
signature: signature.into(),
|
|
||||||
header,
|
|
||||||
protected: None,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ pub enum AppPermission {
|
||||||
ManageGiftCard,
|
ManageGiftCard,
|
||||||
ManageMenus,
|
ManageMenus,
|
||||||
ManageOrders,
|
ManageOrders,
|
||||||
|
ManageOrdersImport,
|
||||||
ManagePages,
|
ManagePages,
|
||||||
ManagePageTypesAndAttributes,
|
ManagePageTypesAndAttributes,
|
||||||
HandlePayments,
|
HandlePayments,
|
||||||
|
|
|
@ -2,5 +2,38 @@
|
||||||
name = "simple-payment-gateway"
|
name = "simple-payment-gateway"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
authors = ["Djkáťo <djkatovfx@gmail.com>"]
|
||||||
|
description = "Payment gateway that adds payment methods that don't need actual verification: Cash on delivery, Cash on warehouse pickup, bank tranfer."
|
||||||
|
homepage = "https://github.com/djkato/saleor-app-rs-template"
|
||||||
|
repository = "https://github.com/djkato/saleor-app-rs-template"
|
||||||
|
documentation = "https://github.com/djkato/saleor-app-rs-template"
|
||||||
|
keywords = ["saleor", "sdk", "plugin", "template"]
|
||||||
|
categories = ["api-bindings", "web-programming::http-server"]
|
||||||
|
license = "PolyForm-Noncommercial-1.0.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
|
tokio = { workspace = true, features = ["full"] }
|
||||||
|
redis = { workspace = true, features = [
|
||||||
|
"aio",
|
||||||
|
"tokio-comp",
|
||||||
|
"connection-manager",
|
||||||
|
] }
|
||||||
|
envy.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
tracing-serde.workspace = true
|
||||||
|
tracing-subscriber.workspace = true
|
||||||
|
dotenvy.workspace = true
|
||||||
|
axum.workspace = true
|
||||||
|
saleor-app-sdk.workspace = true
|
||||||
|
tower = { workspace = true, features = ["util"] }
|
||||||
|
tower-http = { workspace = true, features = ["fs", "trace"] }
|
||||||
|
surf.workspace = true
|
||||||
|
cynic = { workspace = true, features = ["http-surf"] }
|
||||||
|
cynic-codegen.workspace = true
|
||||||
|
const_format = "0.2.32"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
cynic-codegen.workspace = true
|
||||||
|
|
19
simple-payment-gateway/Dockerfile
Normal file
19
simple-payment-gateway/Dockerfile
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
FROM lukemathwalker/cargo-chef:latest as chef
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
FROM chef AS planner
|
||||||
|
COPY ./Cargo.toml ./Cargo.lock ./
|
||||||
|
COPY ./src ./src
|
||||||
|
RUN cargo chef prepare
|
||||||
|
|
||||||
|
FROM chef AS builder
|
||||||
|
COPY --from=planner /app/recipe.json .
|
||||||
|
RUN cargo chef cook --release
|
||||||
|
COPY . .
|
||||||
|
RUN cargo build --release
|
||||||
|
RUN mv ./target/release/saleor-app-template ./app
|
||||||
|
|
||||||
|
FROM debian:stable-slim AS runtime
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder /app/app /usr/local/bin/
|
||||||
|
ENTRYPOINT ["/usr/local/bin/app"]
|
4
simple-payment-gateway/README.md
Normal file
4
simple-payment-gateway/README.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Unofficial Saleor App Template
|
||||||
|
|
||||||
|
To update the saleor schema, you can download it from [here](https://raw.githubusercontent.com/saleor/saleor/main/saleor/graphql/schema.graphql) and put into schema/schema.graphql
|
||||||
|
To generate typings for events and gql queries, use: https://generator.cynic-rs.dev/
|
7
simple-payment-gateway/build.rs
Normal file
7
simple-payment-gateway/build.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
fn main() {
|
||||||
|
cynic_codegen::register_schema("saleor")
|
||||||
|
.from_sdl_file("schema/schema.graphql")
|
||||||
|
.unwrap()
|
||||||
|
.as_default()
|
||||||
|
.unwrap();
|
||||||
|
}
|
BIN
simple-payment-gateway/public/logo.png
Normal file
BIN
simple-payment-gateway/public/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
46
simple-payment-gateway/src/app.rs
Normal file
46
simple-payment-gateway/src/app.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
http::StatusCode,
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
};
|
||||||
|
|
||||||
|
use saleor_app_sdk::{config::Config, manifest::AppManifest, SaleorApp};
|
||||||
|
// Make our own error that wraps `anyhow::Error`.
|
||||||
|
pub struct AppError(anyhow::Error);
|
||||||
|
|
||||||
|
// Tell axum how to convert `AppError` into a response.
|
||||||
|
impl IntoResponse for AppError {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
format!("Something went wrong: {}", self.0),
|
||||||
|
)
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This enables using `?` on functions that return `Result<_, anyhow::Error>` to turn them into
|
||||||
|
// `Result<_, AppError>`. That way you don't need to do that manually.
|
||||||
|
impl<E> From<E> for AppError
|
||||||
|
where
|
||||||
|
E: Into<anyhow::Error>,
|
||||||
|
{
|
||||||
|
fn from(err: E) -> Self {
|
||||||
|
Self(err.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trace_to_std(config: &Config) {
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_max_level(config.log_level)
|
||||||
|
.with_target(false)
|
||||||
|
.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AppState {
|
||||||
|
pub saleor_app: Arc<tokio::sync::Mutex<SaleorApp>>,
|
||||||
|
pub config: Config,
|
||||||
|
pub manifest: AppManifest,
|
||||||
|
}
|
|
@ -1,3 +1,94 @@
|
||||||
fn main() {
|
#![allow(non_upper_case_globals)]
|
||||||
println!("Hello, world!");
|
#![feature(let_chains)]
|
||||||
|
#![deny(clippy::unwrap_used, clippy::expect_used)]
|
||||||
|
mod app;
|
||||||
|
mod queries;
|
||||||
|
mod routes;
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use saleor_app_sdk::{
|
||||||
|
config::Config,
|
||||||
|
manifest::{AppManifest, AppPermission},
|
||||||
|
webhooks::{SyncWebhookEventType, WebhookManifest},
|
||||||
|
SaleorApp,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app::{trace_to_std, AppState},
|
||||||
|
queries::event_transactions::{
|
||||||
|
sub_payment_gateway_initialize_session, sub_transaction_charge_requested,
|
||||||
|
sub_transaction_initialize_session, sub_transaction_process_session,
|
||||||
|
sub_transaction_refund_requested,
|
||||||
|
},
|
||||||
|
routes::create_routes,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
let config = Config::load()?;
|
||||||
|
trace_to_std(&config);
|
||||||
|
|
||||||
|
let saleor_app = SaleorApp::new(&config)?;
|
||||||
|
|
||||||
|
let app_manifest = AppManifest::new(&config)
|
||||||
|
.add_webhook(
|
||||||
|
WebhookManifest::new(&config)
|
||||||
|
.set_query(sub_transaction_process_session)
|
||||||
|
.add_sync_event(SyncWebhookEventType::TransactionProcessSession)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.add_webhook(
|
||||||
|
WebhookManifest::new(&config)
|
||||||
|
.set_query(sub_transaction_charge_requested)
|
||||||
|
.add_sync_event(SyncWebhookEventType::TransactionChargeRequested)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.add_webhook(
|
||||||
|
WebhookManifest::new(&config)
|
||||||
|
.set_query(sub_transaction_refund_requested)
|
||||||
|
.add_sync_event(SyncWebhookEventType::TransactionRefundRequested)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.add_webhook(
|
||||||
|
WebhookManifest::new(&config)
|
||||||
|
.set_query(sub_transaction_initialize_session)
|
||||||
|
.add_sync_event(SyncWebhookEventType::TransactionInitializeSession)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.add_webhook(
|
||||||
|
WebhookManifest::new(&config)
|
||||||
|
.set_query(sub_payment_gateway_initialize_session)
|
||||||
|
.add_sync_event(SyncWebhookEventType::PaymentGatewayInitializeSession)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.add_permissions(vec![
|
||||||
|
AppPermission::HandlePayments,
|
||||||
|
AppPermission::ManageOrders,
|
||||||
|
AppPermission::ManageCheckouts,
|
||||||
|
AppPermission::HandleCheckouts,
|
||||||
|
])
|
||||||
|
.build();
|
||||||
|
let app_state = AppState {
|
||||||
|
manifest: app_manifest,
|
||||||
|
config: config.clone(),
|
||||||
|
saleor_app: Arc::new(Mutex::new(saleor_app)),
|
||||||
|
};
|
||||||
|
let app = create_routes(app_state);
|
||||||
|
|
||||||
|
let listener = tokio::net::TcpListener::bind(
|
||||||
|
&config
|
||||||
|
.app_api_base_url
|
||||||
|
.split("//")
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.get(1)
|
||||||
|
.context("APP_API_BASE_URL invalid format")?,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
tracing::debug!("listening on {}", listener.local_addr().unwrap());
|
||||||
|
match axum::serve(listener, app).await {
|
||||||
|
Ok(o) => Ok(o),
|
||||||
|
Err(e) => anyhow::bail!(e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
180
simple-payment-gateway/src/queries/event_transactions.rs
Normal file
180
simple-payment-gateway/src/queries/event_transactions.rs
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
use const_format::concatcp;
|
||||||
|
#[cynic::schema("saleor")]
|
||||||
|
mod schema {}
|
||||||
|
|
||||||
|
pub const fragment_transaction_details: &str = r#"
|
||||||
|
fragment TransactionDetails on TransactionItem {
|
||||||
|
id
|
||||||
|
actions
|
||||||
|
externalUrl
|
||||||
|
message
|
||||||
|
authorizedAmount {
|
||||||
|
currency
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
authorizePendingAmount {
|
||||||
|
currency
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
canceledAmount {
|
||||||
|
currency
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
cancelPendingAmount {
|
||||||
|
currency
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
chargedAmount {
|
||||||
|
currency
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
chargePendingAmount {
|
||||||
|
currency
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
refundedAmount {
|
||||||
|
currency
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
pub const fragment_order_details: &str = r#"
|
||||||
|
fragment OrderDetails on Order {
|
||||||
|
checkoutId
|
||||||
|
id
|
||||||
|
status
|
||||||
|
isPaid
|
||||||
|
paymentStatus
|
||||||
|
chargeStatus
|
||||||
|
canFinalize
|
||||||
|
totalBalance {
|
||||||
|
currency
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
pub const sub_payment_gateway_initialize_session: &str = concatcp!(
|
||||||
|
r#"
|
||||||
|
subscription PaymentGatewayInitializeSession {
|
||||||
|
event {
|
||||||
|
... on PaymentGatewayInitializeSession {
|
||||||
|
data
|
||||||
|
amount
|
||||||
|
sourceObject {
|
||||||
|
...OrderDetails
|
||||||
|
}
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
fragment_order_details
|
||||||
|
);
|
||||||
|
|
||||||
|
pub const sub_transaction_initialize_session: &str = concatcp!(
|
||||||
|
r#"
|
||||||
|
subscription transactionInitializeSession {
|
||||||
|
event {
|
||||||
|
... on TransactionInitializeSession {
|
||||||
|
data
|
||||||
|
sourceObject {
|
||||||
|
...OrderDetails
|
||||||
|
}
|
||||||
|
transaction {
|
||||||
|
...TransactionDetails
|
||||||
|
}
|
||||||
|
action {
|
||||||
|
amount
|
||||||
|
currency
|
||||||
|
actionType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
fragment_order_details,
|
||||||
|
fragment_transaction_details
|
||||||
|
);
|
||||||
|
|
||||||
|
pub const sub_transaction_process_session: &str = concatcp!(
|
||||||
|
r#"
|
||||||
|
subscription transactionProcessSession {
|
||||||
|
event {
|
||||||
|
... on TransactionProcessSession {
|
||||||
|
action {
|
||||||
|
amount
|
||||||
|
actionType
|
||||||
|
}
|
||||||
|
sourceObject {
|
||||||
|
...OrderDetails
|
||||||
|
}
|
||||||
|
transaction {
|
||||||
|
...TransactionDetails
|
||||||
|
}
|
||||||
|
data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
fragment_order_details,
|
||||||
|
fragment_transaction_details
|
||||||
|
);
|
||||||
|
|
||||||
|
pub const sub_transaction_charge_requested: &str = concatcp!(
|
||||||
|
r#"
|
||||||
|
subscription transactionChargeRequested {
|
||||||
|
event {
|
||||||
|
... on TransactionChargeRequested {
|
||||||
|
action {
|
||||||
|
amount
|
||||||
|
actionType
|
||||||
|
}
|
||||||
|
transaction {
|
||||||
|
...TransactionDetails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
fragment_transaction_details
|
||||||
|
);
|
||||||
|
|
||||||
|
pub const sub_transaction_refund_requested: &str = concatcp!(
|
||||||
|
r#"
|
||||||
|
subscription transactionRefundRequested {
|
||||||
|
event {
|
||||||
|
... on TransactionRefundRequested {
|
||||||
|
action {
|
||||||
|
amount
|
||||||
|
actionType
|
||||||
|
}
|
||||||
|
transaction {
|
||||||
|
...TransactionDetails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
fragment_transaction_details
|
||||||
|
);
|
||||||
|
|
||||||
|
pub const sub_transaction_cancelation_requested: &str = concatcp!(
|
||||||
|
r#"
|
||||||
|
subscription transactionCancelationRequested {
|
||||||
|
event {
|
||||||
|
... on TransactionCancelationRequested {
|
||||||
|
action {
|
||||||
|
amount
|
||||||
|
actionType
|
||||||
|
}
|
||||||
|
transaction {
|
||||||
|
...TransactionDetails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
fragment_transaction_details
|
||||||
|
);
|
3
simple-payment-gateway/src/queries/mod.rs
Normal file
3
simple-payment-gateway/src/queries/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod event_transactions;
|
||||||
|
pub mod mutation_transaction_update;
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
/*
|
||||||
|
mutation transactionUpdate($id: ID!, $transaction: TransactionUpdateInput) {
|
||||||
|
transactionUpdate(id: $id, transaction: $transaction) {
|
||||||
|
transaction {
|
||||||
|
id
|
||||||
|
actions
|
||||||
|
externalUrl
|
||||||
|
message
|
||||||
|
}
|
||||||
|
errors {
|
||||||
|
field
|
||||||
|
message
|
||||||
|
code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
8
simple-payment-gateway/src/routes/manifest.rs
Normal file
8
simple-payment-gateway/src/routes/manifest.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
use axum::{extract::State, Json};
|
||||||
|
use saleor_app_sdk::{manifest::AppManifest};
|
||||||
|
|
||||||
|
use crate::app::{AppError, AppState};
|
||||||
|
|
||||||
|
pub async fn manifest(State(state): State<AppState>) -> Result<Json<AppManifest>, AppError> {
|
||||||
|
Ok(Json(state.manifest))
|
||||||
|
}
|
40
simple-payment-gateway/src/routes/mod.rs
Normal file
40
simple-payment-gateway/src/routes/mod.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
use axum::{
|
||||||
|
handler::HandlerWithoutStateExt,
|
||||||
|
http::StatusCode,
|
||||||
|
middleware,
|
||||||
|
routing::{get, post},
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
use saleor_app_sdk::middleware::verify_webhook_signature::webhook_signature_verifier;
|
||||||
|
use tower_http::services::ServeDir;
|
||||||
|
|
||||||
|
use crate::app::AppState;
|
||||||
|
|
||||||
|
pub mod manifest;
|
||||||
|
pub mod register;
|
||||||
|
pub mod webhooks;
|
||||||
|
use manifest::manifest;
|
||||||
|
use register::register;
|
||||||
|
use webhooks::webhooks;
|
||||||
|
|
||||||
|
pub fn create_routes(state: AppState) -> Router {
|
||||||
|
async fn handle_404() -> (StatusCode, &'static str) {
|
||||||
|
(StatusCode::NOT_FOUND, "Not found")
|
||||||
|
}
|
||||||
|
let service = handle_404.into_service();
|
||||||
|
let serve_dir = ServeDir::new("saleor-app-template/public").not_found_service(service);
|
||||||
|
|
||||||
|
Router::new()
|
||||||
|
.layer(middleware::from_fn(webhook_signature_verifier))
|
||||||
|
//handles just path, eg. localhost:3000/
|
||||||
|
.route("/api/webhooks", post(webhooks))
|
||||||
|
.route(
|
||||||
|
"/",
|
||||||
|
get(|| async { "Your app got installed successfully!" }),
|
||||||
|
)
|
||||||
|
//handles files, eg. localhost:3000/logo.png
|
||||||
|
.fallback_service(serve_dir)
|
||||||
|
.route("/api/manifest", get(manifest))
|
||||||
|
.route("/api/register", post(register))
|
||||||
|
.with_state(state)
|
||||||
|
}
|
40
simple-payment-gateway/src/routes/register.rs
Normal file
40
simple-payment-gateway/src/routes/register.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
use anyhow::Context;
|
||||||
|
use axum::{
|
||||||
|
extract::Json,
|
||||||
|
extract::State,
|
||||||
|
http::{HeaderMap, StatusCode},
|
||||||
|
};
|
||||||
|
use saleor_app_sdk::{AuthData, AuthToken};
|
||||||
|
use tracing::{debug, info};
|
||||||
|
|
||||||
|
use crate::app::{AppError, AppState};
|
||||||
|
|
||||||
|
pub async fn register(
|
||||||
|
headers: HeaderMap,
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Json(auth_token): Json<AuthToken>,
|
||||||
|
) -> Result<StatusCode, AppError> {
|
||||||
|
debug!(
|
||||||
|
"/api/register:\nsaleor_api_url:{:?}\nauth_token:{:?}",
|
||||||
|
headers.get("saleor-api-url"),
|
||||||
|
auth_token
|
||||||
|
);
|
||||||
|
|
||||||
|
if auth_token.auth_token.is_empty() {
|
||||||
|
return Err(anyhow::anyhow!("missing auth_token").into());
|
||||||
|
}
|
||||||
|
let app = state.saleor_app.lock().await;
|
||||||
|
let saleor_api_url = headers.get("saleor-api-url").context("missing api field")?;
|
||||||
|
let saleor_api_url = saleor_api_url.to_str()?.to_owned();
|
||||||
|
let auth_data = AuthData {
|
||||||
|
jwks: None,
|
||||||
|
token: auth_token.auth_token,
|
||||||
|
domain: Some(state.config.app_api_base_url),
|
||||||
|
app_id: state.manifest.id,
|
||||||
|
saleor_api_url: saleor_api_url.clone(),
|
||||||
|
};
|
||||||
|
app.apl.set(auth_data).await?;
|
||||||
|
|
||||||
|
info!("registered app for{:?}", &saleor_api_url);
|
||||||
|
Ok(StatusCode::OK)
|
||||||
|
}
|
58
simple-payment-gateway/src/routes/webhooks.rs
Normal file
58
simple-payment-gateway/src/routes/webhooks.rs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
use anyhow::Context;
|
||||||
|
use axum::{
|
||||||
|
extract::State,
|
||||||
|
http::{HeaderMap, StatusCode},
|
||||||
|
};
|
||||||
|
use saleor_app_sdk::{
|
||||||
|
headers::SALEOR_API_URL_HEADER,
|
||||||
|
webhooks::{
|
||||||
|
utils::{get_webhook_event_type, EitherWebhookType},
|
||||||
|
SyncWebhookEventType,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use tracing::{debug, error, info};
|
||||||
|
|
||||||
|
use crate::app::{AppError, AppState};
|
||||||
|
|
||||||
|
pub async fn webhooks(
|
||||||
|
headers: HeaderMap,
|
||||||
|
State(state): State<AppState>,
|
||||||
|
body: String,
|
||||||
|
) -> Result<StatusCode, AppError> {
|
||||||
|
debug!("/api/webhooks");
|
||||||
|
debug!("req: {:?}", body);
|
||||||
|
debug!("headers: {:?}", headers);
|
||||||
|
|
||||||
|
let 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)?;
|
||||||
|
match event_type {
|
||||||
|
EitherWebhookType::Sync(a) => match a {
|
||||||
|
SyncWebhookEventType::PaymentGatewayInitializeSession => {
|
||||||
|
initialize_gateway(&state, &url).await?;
|
||||||
|
}
|
||||||
|
SyncWebhookEventType::TransactionProcessSession
|
||||||
|
| SyncWebhookEventType::TransactionChargeRequested
|
||||||
|
| SyncWebhookEventType::TransactionRefundRequested
|
||||||
|
| SyncWebhookEventType::TransactionInitializeSession => {
|
||||||
|
update_transaction_response(&state, &url).await?;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("got webhooks!");
|
||||||
|
Ok(StatusCode::OK)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn initialize_gateway(state: &AppState, saleor_api_url: &str) -> anyhow::Result<()> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_transaction_response(state: &AppState, saleor_api_url: &str) -> anyhow::Result<()> {
|
||||||
|
todo!()
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ repository = "https://github.com/djkato/saleor-apps-rs"
|
||||||
documentation = "https://github.com/djkato/saleor-apps-rs"
|
documentation = "https://github.com/djkato/saleor-apps-rs"
|
||||||
keywords = ["saleor", "plugin"]
|
keywords = ["saleor", "plugin"]
|
||||||
categories = ["web-programming::http-server"]
|
categories = ["web-programming::http-server"]
|
||||||
license = "PolyForm Noncommercial License 1.0.0"
|
license = "PolyForm-Noncommercial-1.0.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 11 KiB |
Loading…
Reference in a new issue