feat: extract cms from mp/cms — initial libreshop/cms
Build and publish / build (push) Failing after 17s
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:
127 files changed
+44618
No files matched your search
@@ -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;
|
||||
};
|
||||
Reference in new issue
Block a user