feat: Add KasperoPay payment app for Kaspa cryptocurrency#27954
feat: Add KasperoPay payment app for Kaspa cryptocurrency#27954kasperolabs wants to merge 1 commit intocalcom:mainfrom
Conversation
There was a problem hiding this comment.
9 issues found across 28 files
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/app-store/kasperopay/api/webhook.ts">
<violation number="1" location="packages/app-store/kasperopay/api/webhook.ts:106">
P1: Rule violated: **Avoid Logging Sensitive Information**
Logging the raw Zod parse error from credential validation can expose sensitive `webhook_secret` and `merchant_id` values in application logs. Zod errors include received input values in their output. Log only a generic message instead.
(Based on your team's feedback about not logging raw ZodError objects as they may expose sensitive data.) [FEEDBACK_USED]</violation>
</file>
<file name="packages/app-store/kasperopay/lib/PaymentService.ts">
<violation number="1" location="packages/app-store/kasperopay/lib/PaymentService.ts:16">
P3: Environment variable access is inlined here instead of going through the centralized config/constants layer required by project policy, which reduces consistency and validation of env-derived values.</violation>
<violation number="2" location="packages/app-store/kasperopay/lib/PaymentService.ts:83">
P1: Rule violated: **Avoid Logging Sensitive Information**
The full KasperoPay API response is logged including the `token` field, which is a sensitive payment session authentication token. Only log non-sensitive fields (e.g., `session_id`, `error`) or sanitize the response before logging to avoid exposing secrets in logs.</violation>
</file>
<file name="apps/web/components/apps/kasperopay/KasperoPayPaymentComponent.tsx">
<violation number="1" location="apps/web/components/apps/kasperopay/KasperoPayPaymentComponent.tsx:98">
P2: Fallback flow opens a new tab after setting `isPaying(true)` but never clears it, leaving the UI stuck in a loading spinner if the user closes or abandons the payment tab.</violation>
</file>
<file name="packages/app-store/kasperopay/components/KasperoPriceComponent.tsx">
<violation number="1" location="packages/app-store/kasperopay/components/KasperoPriceComponent.tsx:12">
P3: User-facing tooltip strings are hardcoded ("loading...", "Price unavailable", and the USD format), bypassing the app’s localization system. This will display English-only text in a multilingual UI.</violation>
<violation number="2" location="packages/app-store/kasperopay/components/KasperoPriceComponent.tsx:19">
P2: The KAS/USD rate fetch does not validate the HTTP response and falls back to a hardcoded 0.1 rate, which can display a misleading USD value when the API fails or returns an unexpected payload. In a financial context, this should error out instead of using an arbitrary rate.</violation>
</file>
<file name="packages/app-store/kasperopay/pages/setup/index.tsx">
<violation number="1" location="packages/app-store/kasperopay/pages/setup/index.tsx:2">
P2: Pages Router page imports `useRouter` from `next/navigation` (App Router API), which is unsupported in `pages/` and can break routing at runtime. Use `next/router` here.</violation>
<violation number="2" location="packages/app-store/kasperopay/pages/setup/index.tsx:34">
P2: User-facing strings are hardcoded instead of using the existing localization hook, which makes the setup page non-translatable.</violation>
</file>
<file name="packages/app-store/kasperopay/components/EventTypeAppSettingsInterface.tsx">
<violation number="1" location="packages/app-store/kasperopay/components/EventTypeAppSettingsInterface.tsx:77">
P2: The controlled input clears when price is 0, which prevents entering decimal values that start with 0 (e.g., `0.5`). Allow 0 to remain as the value instead of converting it to `undefined`.</violation>
</file>
Since this is your first cubic review, here's how it works:
- cubic automatically reviews your code and comments on bugs and improvements
- Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
- Add one-off context when rerunning by tagging
@cubic-dev-aiwith guidance or docs links (includingllms.txt) - Ask questions if you need clarification on any suggestion
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
|
|
||
| const parseCredentials = kasperopayCredentialKeysSchema.safeParse(key); | ||
| if (!parseCredentials.success) { | ||
| console.error("Invalid credentials:", parseCredentials.error); |
There was a problem hiding this comment.
P1: Rule violated: Avoid Logging Sensitive Information
Logging the raw Zod parse error from credential validation can expose sensitive webhook_secret and merchant_id values in application logs. Zod errors include received input values in their output. Log only a generic message instead.
(Based on your team's feedback about not logging raw ZodError objects as they may expose sensitive data.)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/app-store/kasperopay/api/webhook.ts, line 106:
<comment>Logging the raw Zod parse error from credential validation can expose sensitive `webhook_secret` and `merchant_id` values in application logs. Zod errors include received input values in their output. Log only a generic message instead.
(Based on your team's feedback about not logging raw ZodError objects as they may expose sensitive data.) </comment>
<file context>
@@ -0,0 +1,185 @@
+
+ const parseCredentials = kasperopayCredentialKeysSchema.safeParse(key);
+ if (!parseCredentials.success) {
+ console.error("Invalid credentials:", parseCredentials.error);
+ throw new HttpCode({ statusCode: 500, message: "Credentials not valid" });
+ }
</file context>
| const initData: KasperoPayInitResponse = await initResponse.json(); | ||
|
|
||
| if (!initData.success || !initData.session_id) { | ||
| log.error("KasperoPay: Failed to initialize payment", safeStringify(initData)); |
There was a problem hiding this comment.
P1: Rule violated: Avoid Logging Sensitive Information
The full KasperoPay API response is logged including the token field, which is a sensitive payment session authentication token. Only log non-sensitive fields (e.g., session_id, error) or sanitize the response before logging to avoid exposing secrets in logs.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/app-store/kasperopay/lib/PaymentService.ts, line 83:
<comment>The full KasperoPay API response is logged including the `token` field, which is a sensitive payment session authentication token. Only log non-sensitive fields (e.g., `session_id`, `error`) or sanitize the response before logging to avoid exposing secrets in logs.</comment>
<file context>
@@ -0,0 +1,192 @@
+ const initData: KasperoPayInitResponse = await initResponse.json();
+
+ if (!initData.success || !initData.session_id) {
+ log.error("KasperoPay: Failed to initialize payment", safeStringify(initData));
+ throw new Error(initData.error || "Failed to initialize KasperoPay session");
+ }
</file context>
| }); | ||
| } else { | ||
| // Fallback: open payment page directly | ||
| window.open( |
There was a problem hiding this comment.
P2: Fallback flow opens a new tab after setting isPaying(true) but never clears it, leaving the UI stuck in a loading spinner if the user closes or abandons the payment tab.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/components/apps/kasperopay/KasperoPayPaymentComponent.tsx, line 98:
<comment>Fallback flow opens a new tab after setting `isPaying(true)` but never clears it, leaving the UI stuck in a loading spinner if the user closes or abandons the payment tab.</comment>
<file context>
@@ -0,0 +1,212 @@
+ });
+ } else {
+ // Fallback: open payment page directly
+ window.open(
+ `${KASPEROPAY_API}/pay/session/${session.session_id}?token=${session.token}`,
+ "_blank"
</file context>
| try { | ||
| // Fetch current KAS/USD rate | ||
| const response = await fetch("https://kaspa-store.com/api/store/price/kas"); | ||
| const data = await response.json(); |
There was a problem hiding this comment.
P2: The KAS/USD rate fetch does not validate the HTTP response and falls back to a hardcoded 0.1 rate, which can display a misleading USD value when the API fails or returns an unexpected payload. In a financial context, this should error out instead of using an arbitrary rate.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/app-store/kasperopay/components/KasperoPriceComponent.tsx, line 19:
<comment>The KAS/USD rate fetch does not validate the HTTP response and falls back to a hardcoded 0.1 rate, which can display a misleading USD value when the API fails or returns an unexpected payload. In a financial context, this should error out instead of using an arbitrary rate.</comment>
<file context>
@@ -0,0 +1,51 @@
+ try {
+ // Fetch current KAS/USD rate
+ const response = await fetch("https://kaspa-store.com/api/store/price/kas");
+ const data = await response.json();
+ const kasToUsd = data.kasToUsd || 0.1;
+ const usdValue = price * kasToUsd;
</file context>
| e.preventDefault(); | ||
|
|
||
| if (!merchantId.startsWith("kpm_")) { | ||
| showToast("Merchant ID must start with 'kpm_'", "error"); |
There was a problem hiding this comment.
P2: User-facing strings are hardcoded instead of using the existing localization hook, which makes the setup page non-translatable.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/app-store/kasperopay/pages/setup/index.tsx, line 34:
<comment>User-facing strings are hardcoded instead of using the existing localization hook, which makes the setup page non-translatable.</comment>
<file context>
@@ -0,0 +1,139 @@
+ e.preventDefault();
+
+ if (!merchantId.startsWith("kpm_")) {
+ showToast("Merchant ID must start with 'kpm_'", "error");
+ return;
+ }
</file context>
| @@ -0,0 +1,139 @@ | |||
| import { useState } from "react"; | |||
| import { useRouter } from "next/navigation"; | |||
There was a problem hiding this comment.
P2: Pages Router page imports useRouter from next/navigation (App Router API), which is unsupported in pages/ and can break routing at runtime. Use next/router here.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/app-store/kasperopay/pages/setup/index.tsx, line 2:
<comment>Pages Router page imports `useRouter` from `next/navigation` (App Router API), which is unsupported in `pages/` and can break routing at runtime. Use `next/router` here.</comment>
<file context>
@@ -0,0 +1,139 @@
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+
+import { useLocale } from "@calcom/lib/hooks/useLocale";
</file context>
| setAppData("currency", currency); | ||
| } | ||
| }} | ||
| value={price && price > 0 ? price : undefined} |
There was a problem hiding this comment.
P2: The controlled input clears when price is 0, which prevents entering decimal values that start with 0 (e.g., 0.5). Allow 0 to remain as the value instead of converting it to undefined.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/app-store/kasperopay/components/EventTypeAppSettingsInterface.tsx, line 77:
<comment>The controlled input clears when price is 0, which prevents entering decimal values that start with 0 (e.g., `0.5`). Allow 0 to remain as the value instead of converting it to `undefined`.</comment>
<file context>
@@ -0,0 +1,129 @@
+ setAppData("currency", currency);
+ }
+ }}
+ value={price && price > 0 ? price : undefined}
+ />
+ </div>
</file context>
| @@ -0,0 +1,51 @@ | |||
| import React from "react"; | |||
There was a problem hiding this comment.
P3: User-facing tooltip strings are hardcoded ("loading...", "Price unavailable", and the USD format), bypassing the app’s localization system. This will display English-only text in a multilingual UI.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/app-store/kasperopay/components/KasperoPriceComponent.tsx, line 12:
<comment>User-facing tooltip strings are hardcoded ("loading...", "Price unavailable", and the USD format), bypassing the app’s localization system. This will display English-only text in a multilingual UI.</comment>
<file context>
@@ -0,0 +1,51 @@
+};
+
+export function KasperoPriceComponent({ displaySymbol, price, formattedPrice }: KasperoPriceComponentProps) {
+ const [fiatValue, setFiatValue] = React.useState<string>("loading...");
+
+ React.useEffect(() => {
</file context>
|
|
||
| const log = logger.getSubLogger({ prefix: ["payment-service:kasperopay"] }); | ||
|
|
||
| const KASPEROPAY_API_URL = process.env.KASPEROPAY_API_URL || "https://kaspa-store.com"; |
There was a problem hiding this comment.
P3: Environment variable access is inlined here instead of going through the centralized config/constants layer required by project policy, which reduces consistency and validation of env-derived values.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/app-store/kasperopay/lib/PaymentService.ts, line 16:
<comment>Environment variable access is inlined here instead of going through the centralized config/constants layer required by project policy, which reduces consistency and validation of env-derived values.</comment>
<file context>
@@ -0,0 +1,192 @@
+
+const log = logger.getSubLogger({ prefix: ["payment-service:kasperopay"] });
+
+const KASPEROPAY_API_URL = process.env.KASPEROPAY_API_URL || "https://kaspa-store.com";
+
+interface KasperoPayInitResponse {
</file context>
What does this PR do?
Adds KasperoPay as a payment app, enabling Cal.com users to accept Kaspa (KAS) cryptocurrency payments for paid bookings. This follows the same pattern as existing crypto payment apps (Alby, BTCPay Server).
KasperoPay (https://kaspa-store.com/) is a payment processor for the Kaspa blockchain — the fastest proof-of-work cryptocurrency with 1-second block times.
How it works
Visual Demo
Image Demo:
Mandatory Tasks (DO NOT REMOVE)
How should this be tested?
kpm_xxxxx)No special environment variables required beyond the standard Cal.com setup. The app communicates with the KasperoPay API at kaspa-store.com.
Checklist