feat: extract cms from mp/cms — initial libreshop/cms
Build and publish / build (push) Failing after 17s

Source moved verbatim from mp/cms/ on 2026-04-29; mp was the first
concrete adapter consuming the libreshop toolkit. Builds and publishes
git.librete.ch/libreshop/cms on every main / v* push via the standard
.gitea/workflows/build.yml shared across libreshop components.
This commit is contained in:
Michael Czechowski committed 2026-04-29 17:48:30 +02:00
commit 32a296baf2
127 files changed
+44618

No files matched your search

+165
View File
@@ -0,0 +1,165 @@
import {
Client,
Environment,
LogLevel,
OrdersController,
CheckoutPaymentIntent,
OrderRequest,
OrderApplicationContextShippingPreference,
Item
} from "@paypal/paypal-server-sdk";
import { paypalClientId, paypalClientSecret, paypalEnvironment, vatIncludedDecimal, vatDecimalExcluded } from "../../config/constants";
import { Order } from "../../types";
import { calculateTotalProductPrice } from "../api/product/services/product";
class PayPalApi {
private readonly client: Client;
private readonly ordersController: OrdersController;
constructor(clientId: string, clientSecret: string, environment: Environment) {
this.client = new Client({
clientCredentialsAuthCredentials: {
oAuthClientId: clientId,
oAuthClientSecret: clientSecret
},
timeout: 0,
environment,
logging: {
logLevel: LogLevel.Info,
logRequest: { logBody: true },
logResponse: { logHeaders: true }
}
});
this.ordersController = new OrdersController(this.client);
}
async createSessionOrThrow(returnUrl: string, order: Order) {
if (!order.total) throw new Error("Cart is empty or has no total");
const items = order.cart.map((item) => {
const totalProductPrice = calculateTotalProductPrice(item.product);
const amount = totalProductPrice / vatIncludedDecimal;
const tax = totalProductPrice - amount;
return {
name: item.product.name,
unitAmount: {
currencyCode: "EUR",
value: amount.toFixed(2)
},
tax: {
currencyCode: "EUR",
value: tax.toFixed(2)
},
quantity: item.count.toString()
} as Item;
});
try {
const collect = {
body: {
intent: CheckoutPaymentIntent.Capture,
purchaseUnits: [
{
// items,
referenceId: order.uuid,
amount: {
currencyCode: "EUR",
value: order.total.toFixed(2),
breakdown: {
itemTotal: {
currencyCode: "EUR",
value: (Math.round((order.subtotal / vatIncludedDecimal) * 100) / 100).toFixed(2)
},
taxTotal: {
currencyCode: "EUR",
value: order.VAT.toFixed(2)
},
shipping: order.delivery
? {
currencyCode: "EUR",
value: order.delivery.price.toFixed(2)
}
: undefined
}
},
shipping: {
name: {
fullName: order.deliveryAddress.split("\n")[0]
},
address: {
addressLine1: order.deliveryAddress.split("\n")[1],
postalCode: order.deliveryAddress.split("\n")[2].slice(0, 5),
adminArea2: order.deliveryAddress.split("\n")[2].slice(6),
countryCode: "DE"
}
},
customId: order.uuid,
invoiceId: order.invoiceNumber
}
],
applicationContext: {
returnUrl: returnUrl,
cancelUrl: returnUrl,
shippingPreference: OrderApplicationContextShippingPreference.SetProvidedAddress
}
} as OrderRequest,
prefer: "return=minimal"
};
strapi.log.silly("app:d:paypal-api: Creating PayPal order " + JSON.stringify({ collect }));
const { body, ...httpResponse } = await this.ordersController.ordersCreate(collect);
const sessionData = JSON.parse(body as string);
strapi.log.info("app:i:paypal-api: " + JSON.stringify({ session: sessionData }));
return {
id: sessionData.id,
links: sessionData.links
};
} catch (error) {
strapi.log.error("app:e:paypal-api: " + JSON.stringify({ error }));
throw error;
}
}
async checkPaymentStatus(redirect: { orderID: string }) {
try {
const collect = {
id: redirect.orderID,
prefer: "return=minimal"
};
const { body } = await this.ordersController.ordersCapture(collect);
const captureResult = JSON.parse(body as string);
strapi.log.info("app:i:paypal-api: " + JSON.stringify({ captureResult }));
return captureResult;
} catch (error) {
return "/result/error";
}
}
async handleWebhook(req: any) {
// Implement PayPal webhook validation and processing
strapi.log.verbose("app:i:paypal-api: " + JSON.stringify({ req }));
// Note: PayPal webhook handling requires specific implementation
// This is a placeholder and should be replaced with actual PayPal webhook validation
return { message: "Webhook received" };
}
getClientKey() {
// PayPal doesn't use a client key like Adyen, so we'll return the client ID
return paypalClientId;
}
}
export const paypalApi = new PayPalApi(paypalClientId, paypalClientSecret, paypalEnvironment);
export type PayPalApiOptions = {
clientId: string;
clientSecret: string;
environment: Environment;
};