vonpay-medusa 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/bin/vonpay-medusa.cjs +15 -4
- package/package.json +2 -1
- package/storefront/src/context.tsx +2 -1
- package/storefront/src/csp.ts +1 -1
- package/storefront/src/offer-pass.ts +10 -0
- package/storefront/src/offer-prepare.ts +21 -0
- package/storefront/src/server.ts +28 -1
- package/storefront/src/vonpay-card-payment.tsx +12 -6
- package/storefront/src/vora-csp.json +85 -5
package/README.md
CHANGED
|
@@ -504,6 +504,18 @@ charges carry no address (the setup check WARNs; the store logs `shopper_ip_sig_
|
|
|
504
504
|
it cannot believe). **Upgrading from 1.0.1:** 1.0.1 sent the address unsigned; from 1.0.2 it is used only once
|
|
505
505
|
`VONPAY_SHOPPER_IP_SECRET` is set on both sides. The address is never logged or stored by the plugin.
|
|
506
506
|
|
|
507
|
+
**A repeated return from the bank still gets the offer (1.0.4).** The offer page needs a pass the storefront
|
|
508
|
+
gets while the cart is still open. From 1.0.4 the card step also gets it when the shopper presses Pay, before
|
|
509
|
+
they can leave for their bank's check, so a return that is repeated (a lost answer, a reload) still finds the
|
|
510
|
+
offer. **Upgrading a storefront set up before 1.0.4:** add this to `src/lib/vonpay/actions.ts` (the setup
|
|
511
|
+
command leaves an existing file as it is); without it the checkout works exactly as before.
|
|
512
|
+
|
|
513
|
+
```ts
|
|
514
|
+
export async function prepareVonpayOffer() {
|
|
515
|
+
return vonpay.prepareVonpayOffer()
|
|
516
|
+
}
|
|
517
|
+
```
|
|
518
|
+
|
|
507
519
|
Guest shoppers get offers too, by default: the checkout names them by the email they type, and their
|
|
508
520
|
card is kept for that purchase's offers only, then removed once the offer ends. A guest's card is never
|
|
509
521
|
added to an existing customer's account because the guest typed that customer's email. `guests: false`
|
package/bin/vonpay-medusa.cjs
CHANGED
|
@@ -50,6 +50,11 @@ export async function startVonpayPayment() {
|
|
|
50
50
|
return vonpay.startVonpayPayment()
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
// The offer pass, fetched at Pay (before the shopper can leave for the bank) and kept in an httpOnly cookie.
|
|
54
|
+
export async function prepareVonpayOffer() {
|
|
55
|
+
return vonpay.prepareVonpayOffer()
|
|
56
|
+
}
|
|
57
|
+
|
|
53
58
|
// "My saved cards": the signed-in customer's cards. Remove takes the card id; the store's server checks
|
|
54
59
|
// the card is this customer's before anything is removed.
|
|
55
60
|
export async function listVonpaySavedCards() {
|
|
@@ -476,7 +481,7 @@ function setupStorefront(root, opts = {}) {
|
|
|
476
481
|
note(step, "failed", err && err.message ? err.message : String(err))
|
|
477
482
|
}
|
|
478
483
|
}
|
|
479
|
-
const write = (file, content, step) =>
|
|
484
|
+
const write = (file, content, step, hint) =>
|
|
480
485
|
guarded(step, () => {
|
|
481
486
|
const rel = path.relative(root, file)
|
|
482
487
|
if (!insideRoot(file)) return note(step, "manual", `${rel} resolves outside this project (a symlinked folder) — create it yourself`)
|
|
@@ -486,8 +491,10 @@ function setupStorefront(root, opts = {}) {
|
|
|
486
491
|
} catch {}
|
|
487
492
|
if (isLink) return note(step, "manual", `${rel} is a symlink — left alone`)
|
|
488
493
|
if (fs.existsSync(file) && !force) {
|
|
489
|
-
const
|
|
490
|
-
|
|
494
|
+
const existing = fs.readFileSync(file, "utf8")
|
|
495
|
+
const same = existing === content
|
|
496
|
+
const extra = !same && hint ? hint(existing) : null
|
|
497
|
+
return note(step, same ? "present" : "manual", same ? rel : `${rel} exists and differs — left as is (re-run with --force to replace it)${extra ? `; ${extra}` : ""}`)
|
|
491
498
|
}
|
|
492
499
|
if (!dryRun) {
|
|
493
500
|
fs.mkdirSync(path.dirname(file), { recursive: true })
|
|
@@ -523,7 +530,11 @@ function setupStorefront(root, opts = {}) {
|
|
|
523
530
|
if (!fs.existsSync(path.join(root, f))) throw new Error(`${f} not found — this command expects Medusa's Next.js starter layout (src/lib/config.ts exporting \`sdk\`, src/lib/data/cookies.ts and cart.ts)`)
|
|
524
531
|
}
|
|
525
532
|
|
|
526
|
-
write(path.join(root, "src/lib/vonpay/actions.ts"), actionsFile(), "server actions (src/lib/vonpay/actions.ts)")
|
|
533
|
+
write(path.join(root, "src/lib/vonpay/actions.ts"), actionsFile(), "server actions (src/lib/vonpay/actions.ts)", (existing) =>
|
|
534
|
+
existing.includes("prepareVonpayOffer")
|
|
535
|
+
? null
|
|
536
|
+
: "it has no prepareVonpayOffer (1.0.4): add `export async function prepareVonpayOffer() { return vonpay.prepareVonpayOffer() }` so a repeated return from the bank still gets the offer (package README, upsells)"
|
|
537
|
+
)
|
|
527
538
|
const thumbnail = fs.existsSync(path.join(root, "src/modules/products/components/thumbnail/index.tsx")) ? "@modules/products/components/thumbnail" : null
|
|
528
539
|
write(path.join(root, "src/lib/vonpay/index.tsx"), componentsFile(detectUiImport(root, pkg), thumbnail), "card step + return page (src/lib/vonpay/index.tsx)")
|
|
529
540
|
const checkoutGroup = path.join(root, "src/app/[countryCode]/(checkout)")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vonpay-medusa",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Vonpay payment provider for Medusa v2",
|
|
5
5
|
"author": "Von Payments",
|
|
6
6
|
"license": "MIT",
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"check:money": "node scripts/ci/check-money-arithmetic.mjs src && node scripts/ci/check-money-arithmetic.mjs --self-test",
|
|
62
62
|
"check:release-install": "node scripts/ci/check-release-install.mjs && node scripts/ci/check-release-install.mjs --self-test",
|
|
63
63
|
"check:ci-scope": "node scripts/ci/check-ci-scope.mjs && node scripts/ci/check-ci-scope.mjs --self-test",
|
|
64
|
+
"check:bridge-alert": "node scripts/ci/bridge-alert.mjs --self-test",
|
|
64
65
|
"check:harness-isolation": "node scripts/ci/check-harness-isolation.mjs src apps && node scripts/ci/check-harness-isolation.mjs --self-test",
|
|
65
66
|
"check:pack": "node scripts/ci/check-pack.mjs && node scripts/ci/check-pack.mjs --self-test",
|
|
66
67
|
"check:pan-canary": "node scripts/ci/check-pan-canary.mjs && node scripts/ci/check-pan-canary.mjs --self-test",
|
|
@@ -4,7 +4,8 @@ import { type ReactNode, createContext, useContext } from "react"
|
|
|
4
4
|
import type { VonpayStorefrontServer } from "./server"
|
|
5
5
|
import { DEFAULT_UI, type VonpayUi } from "./ui"
|
|
6
6
|
|
|
7
|
-
export type VonpayActions = Pick<VonpayStorefrontServer, "completeVonpayCheckout" | "refreshVonpayPayment" | "reportStuckVonpayPayment" | "startVonpayPayment">
|
|
7
|
+
export type VonpayActions = Pick<VonpayStorefrontServer, "completeVonpayCheckout" | "refreshVonpayPayment" | "reportStuckVonpayPayment" | "startVonpayPayment"> &
|
|
8
|
+
Partial<Pick<VonpayStorefrontServer, "prepareVonpayOffer">>
|
|
8
9
|
|
|
9
10
|
interface VonpayContextValue {
|
|
10
11
|
actions: VonpayActions
|
package/storefront/src/csp.ts
CHANGED
|
@@ -2,7 +2,7 @@ import list from "./vora-csp.json"
|
|
|
2
2
|
import { VORA_JS_URL, type VonpayBrowserConfig } from "./config"
|
|
3
3
|
|
|
4
4
|
export const VORA_CSP_VERSION = list.version
|
|
5
|
-
export const VORA_CSP_SHA384 = "sha384-
|
|
5
|
+
export const VORA_CSP_SHA384 = "sha384-i1//4S+DUC1heRzIqlkwPggQhcxx/zzDp3RbtJrfKW721JphljXobflGiqV5VL99"
|
|
6
6
|
|
|
7
7
|
export const VONPAY_DEFAULT_API_ORIGIN = "https://checkout.vonpay.com"
|
|
8
8
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function offerPassCartId(token: string): string | null {
|
|
2
|
+
const parts = token.split(".")
|
|
3
|
+
if (parts.length !== 3 || parts[0] !== "v2" || !/^[A-Za-z0-9_-]+$/.test(parts[1])) return null
|
|
4
|
+
try {
|
|
5
|
+
const claims = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8")) as { cart_id?: unknown } | null
|
|
6
|
+
return typeof claims?.cart_id === "string" && claims.cart_id.length > 0 ? claims.cart_id : null
|
|
7
|
+
} catch {
|
|
8
|
+
return null
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { sleep } from "./sleep"
|
|
2
|
+
|
|
3
|
+
export const OFFER_PREPARE_WAIT_MS = 3_500
|
|
4
|
+
|
|
5
|
+
export function startOfferPrepare(actions: object): () => Promise<void> {
|
|
6
|
+
let prepare: unknown
|
|
7
|
+
try {
|
|
8
|
+
prepare = (actions as { prepareVonpayOffer?: unknown }).prepareVonpayOffer
|
|
9
|
+
} catch {
|
|
10
|
+
prepare = undefined
|
|
11
|
+
}
|
|
12
|
+
if (typeof prepare !== "function") return () => Promise.resolve()
|
|
13
|
+
let started: Promise<unknown>
|
|
14
|
+
try {
|
|
15
|
+
started = Promise.resolve((prepare as () => unknown)()).catch(() => undefined)
|
|
16
|
+
} catch {
|
|
17
|
+
started = Promise.resolve()
|
|
18
|
+
}
|
|
19
|
+
const done = started.then(() => undefined)
|
|
20
|
+
return () => Promise.race([done, sleep(OFFER_PREPARE_WAIT_MS)])
|
|
21
|
+
}
|
package/storefront/src/server.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { type CompletionResult, type VonpaySessionView, buyerReferenceOf, vonpay
|
|
|
6
6
|
import { STUCK_REPORT_GLOBAL_MAX, STUCK_REPORT_WINDOW_MS, stuckReportLimiter } from "./report-limit"
|
|
7
7
|
import { SAVED_CARDS_COPY, SAVED_CARD_ID, readSavedCards, type VonpayRemoveCardResult, type VonpaySavedCardsResult } from "./saved-cards"
|
|
8
8
|
import { SHOPPER_IP_SECRET_MIN, SHOPPER_IP_SIG_HEADER, signShopperIp } from "./shopper-ip"
|
|
9
|
+
import { offerPassCartId } from "./offer-pass"
|
|
9
10
|
import { sleep } from "./sleep"
|
|
10
11
|
import { withDeadline } from "./deadline"
|
|
11
12
|
import { readOfferAnswer, readOutcome, readWalletSession, type VonpayOffer, type VonpayOfferOutcome, type VonpayWalletSession } from "./upsell"
|
|
@@ -29,6 +30,7 @@ export interface VonpayStorefrontServer {
|
|
|
29
30
|
refreshVonpayPayment(): Promise<{ vpSessionId: string | null; status: string } | null>
|
|
30
31
|
reportStuckVonpayPayment(): Promise<{ reference: string | null }>
|
|
31
32
|
startVonpayPayment(): Promise<{ started: boolean }>
|
|
33
|
+
prepareVonpayOffer(): Promise<{ prepared: boolean }>
|
|
32
34
|
listSavedCards(): Promise<VonpaySavedCardsResult>
|
|
33
35
|
removeSavedCard(id: string): Promise<VonpayRemoveCardResult>
|
|
34
36
|
upsellOffer(orderId: string): Promise<VonpayOffer | null>
|
|
@@ -41,6 +43,7 @@ export interface VonpayStorefrontServer {
|
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
export const OFFER_PASS_COOKIE = "_vonpay_offer_pass"
|
|
46
|
+
export { offerPassCartId }
|
|
44
47
|
const passCookieName = () => (process.env.NODE_ENV === "production" ? `__Host-${OFFER_PASS_COOKIE}` : OFFER_PASS_COOKIE)
|
|
45
48
|
export const OFFER_PASS_MAX_AGE_S = 90 * 60
|
|
46
49
|
export const OFFER_PASS_REQUEST_MS = 3_000
|
|
@@ -61,7 +64,7 @@ async function completeVonpayCheckout(): Promise<CompletionResult> {
|
|
|
61
64
|
|
|
62
65
|
const headers = { ...(await getAuthHeaders()) }
|
|
63
66
|
|
|
64
|
-
const pass = await requestOfferPass(cartId, headers)
|
|
67
|
+
const pass = (await requestOfferPass(cartId, headers)) ?? (await keptOfferPass(cartId))
|
|
65
68
|
if (!pass) await endUpsellOffer()
|
|
66
69
|
|
|
67
70
|
let response: HttpTypes.StoreCompleteCartResponse
|
|
@@ -159,6 +162,19 @@ async function readSavedStatus(cartId: string, headers: Record<string, string>):
|
|
|
159
162
|
}
|
|
160
163
|
}
|
|
161
164
|
|
|
165
|
+
async function prepareVonpayOffer(): Promise<{ prepared: boolean }> {
|
|
166
|
+
try {
|
|
167
|
+
const cartId = await getCartId()
|
|
168
|
+
if (!cartId) return { prepared: false }
|
|
169
|
+
const pass = await requestOfferPass(cartId, { ...(await getAuthHeaders()) })
|
|
170
|
+
if (pass) await setOfferPass(pass)
|
|
171
|
+
else if (!(await keptOfferPass(cartId))) await endUpsellOffer()
|
|
172
|
+
return { prepared: pass !== null }
|
|
173
|
+
} catch {
|
|
174
|
+
return { prepared: false }
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
162
178
|
async function startVonpayPayment(): Promise<{ started: boolean }> {
|
|
163
179
|
const cartId = await getCartId()
|
|
164
180
|
if (!cartId) return { started: false }
|
|
@@ -216,6 +232,16 @@ async function readSavedStatus(cartId: string, headers: Record<string, string>):
|
|
|
216
232
|
}
|
|
217
233
|
}
|
|
218
234
|
|
|
235
|
+
async function keptOfferPass(cartId: string): Promise<string | null> {
|
|
236
|
+
let token: string | undefined
|
|
237
|
+
try {
|
|
238
|
+
token = (await cookies()).get(passCookieName())?.value
|
|
239
|
+
} catch {
|
|
240
|
+
return null
|
|
241
|
+
}
|
|
242
|
+
return token && offerPassCartId(token) === cartId ? token : null
|
|
243
|
+
}
|
|
244
|
+
|
|
219
245
|
async function setOfferPass(token: string): Promise<void> {
|
|
220
246
|
try {
|
|
221
247
|
;(await cookies()).set(passCookieName(), token, { httpOnly: true, sameSite: "lax", secure: process.env.NODE_ENV === "production", path: "/", maxAge: OFFER_PASS_MAX_AGE_S })
|
|
@@ -363,6 +389,7 @@ async function readSavedStatus(cartId: string, headers: Record<string, string>):
|
|
|
363
389
|
refreshVonpayPayment,
|
|
364
390
|
reportStuckVonpayPayment,
|
|
365
391
|
startVonpayPayment,
|
|
392
|
+
prepareVonpayOffer,
|
|
366
393
|
listSavedCards,
|
|
367
394
|
removeSavedCard,
|
|
368
395
|
upsellOffer,
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
takeDeclineNote,
|
|
38
38
|
} from "./reload-once"
|
|
39
39
|
import type { SaveUnavailable } from "./vonpay-payment-view"
|
|
40
|
+
import { startOfferPrepare } from "./offer-prepare"
|
|
40
41
|
import { sleep } from "./sleep"
|
|
41
42
|
import { useContactStore } from "./use-contact-store"
|
|
42
43
|
import VonpayPaymentView from "./vonpay-payment-view"
|
|
@@ -494,10 +495,12 @@ function CardPayment({
|
|
|
494
495
|
}
|
|
495
496
|
inFlight.current = true
|
|
496
497
|
setWalletNote(undefined)
|
|
497
|
-
void
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
498
|
+
void startOfferPrepare(actions)()
|
|
499
|
+
.then(() => confirm())
|
|
500
|
+
.finally(() => {
|
|
501
|
+
inFlight.current = false
|
|
502
|
+
})
|
|
503
|
+
}, [confirm, actions])
|
|
501
504
|
const afterAction = useCallback(() => {
|
|
502
505
|
if (!walletTapPending.current) return
|
|
503
506
|
walletTapPending.current = false
|
|
@@ -509,7 +512,7 @@ function CardPayment({
|
|
|
509
512
|
if (action.kind === "redirect" && !inFlight.current) {
|
|
510
513
|
inFlight.current = true
|
|
511
514
|
setPhase("redirecting")
|
|
512
|
-
window.location.assign(action.url)
|
|
515
|
+
void startOfferPrepare(actions)().then(() => window.location.assign(action.url))
|
|
513
516
|
return
|
|
514
517
|
}
|
|
515
518
|
askServerForWallet()
|
|
@@ -555,6 +558,7 @@ function CardPayment({
|
|
|
555
558
|
const collection = collectionRef.current
|
|
556
559
|
if (!collection) return false
|
|
557
560
|
if (saveUnavailableNow.current || (saveRequiredNow.current && !termsActiveNow.current && saveTickedNow.current === false)) return false
|
|
561
|
+
const offerReady = startOfferPrepare(actions)
|
|
558
562
|
setPhase("submitting")
|
|
559
563
|
let result: VoraSubmitResult | undefined
|
|
560
564
|
try {
|
|
@@ -577,14 +581,16 @@ function CardPayment({
|
|
|
577
581
|
return false
|
|
578
582
|
case "redirect":
|
|
579
583
|
setPhase("redirecting")
|
|
584
|
+
await offerReady()
|
|
580
585
|
window.location.assign(action.url)
|
|
581
586
|
return true
|
|
582
587
|
case "declined_ask_server":
|
|
583
588
|
case "complete":
|
|
589
|
+
await offerReady()
|
|
584
590
|
await confirm()
|
|
585
591
|
return false
|
|
586
592
|
}
|
|
587
|
-
}, [confirm])
|
|
593
|
+
}, [confirm, actions])
|
|
588
594
|
|
|
589
595
|
const startNewPayment = useCallback(async () => {
|
|
590
596
|
setPhase("loading")
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.
|
|
2
|
+
"version": "1.44.1",
|
|
3
3
|
"modes": {
|
|
4
4
|
"test": {
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.44.1",
|
|
6
6
|
"mode": "test",
|
|
7
7
|
"directives": {
|
|
8
8
|
"script-src": [
|
|
@@ -18,9 +18,11 @@
|
|
|
18
18
|
"https://embed.sandbox.portal.vonpay.com",
|
|
19
19
|
"https://applepay.cdn-apple.com",
|
|
20
20
|
"https://pay.sandbox.google.com",
|
|
21
|
+
"https://checkout.vonpay.com",
|
|
22
|
+
"https://hooks.stripe.com",
|
|
23
|
+
"'self'",
|
|
21
24
|
"https://js.stripe.com",
|
|
22
25
|
"https://*.js.stripe.com",
|
|
23
|
-
"https://hooks.stripe.com",
|
|
24
26
|
"https://link.com",
|
|
25
27
|
"https://*.link.com"
|
|
26
28
|
],
|
|
@@ -134,6 +136,36 @@
|
|
|
134
136
|
"feature": "google-pay",
|
|
135
137
|
"why": "Google Pay JS adds a <style> element for its button."
|
|
136
138
|
},
|
|
139
|
+
{
|
|
140
|
+
"directive": "frame-src",
|
|
141
|
+
"source": "https://embed.sandbox.vonpay.gr4vy.app",
|
|
142
|
+
"feature": "wallet-modal",
|
|
143
|
+
"why": "The bank check for a wallet payment, in a modal on your page."
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"directive": "frame-src",
|
|
147
|
+
"source": "https://checkout.vonpay.com",
|
|
148
|
+
"feature": "wallet-modal",
|
|
149
|
+
"why": "The page that reports the wallet bank check's result back to vora.js."
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"directive": "frame-src",
|
|
153
|
+
"source": "https://embed.sandbox.vonpay.gr4vy.app",
|
|
154
|
+
"feature": "bank-check",
|
|
155
|
+
"why": "The card-update bank check pop-up."
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"directive": "frame-src",
|
|
159
|
+
"source": "https://hooks.stripe.com",
|
|
160
|
+
"feature": "bank-check",
|
|
161
|
+
"why": "The same, on Stripe."
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"directive": "frame-src",
|
|
165
|
+
"source": "'self'",
|
|
166
|
+
"feature": "bank-check",
|
|
167
|
+
"why": "Your return page."
|
|
168
|
+
},
|
|
137
169
|
{
|
|
138
170
|
"directive": "script-src",
|
|
139
171
|
"source": "https://js.stripe.com",
|
|
@@ -200,10 +232,18 @@
|
|
|
200
232
|
"feature": "stripe-link",
|
|
201
233
|
"why": "Link artwork."
|
|
202
234
|
}
|
|
235
|
+
],
|
|
236
|
+
"ifNoUnsafeInline": [
|
|
237
|
+
{
|
|
238
|
+
"directive": "style-src",
|
|
239
|
+
"source": "'sha256-OuajJxe804doBGkJvwnBjnP1cpvNWFjHltumC6RbkcA='",
|
|
240
|
+
"feature": "default-look",
|
|
241
|
+
"why": "A one-line style element (@layer vora-defaults;) that puts the built-in default look below your own CSS layers. Without it the look still works, but your layered CSS (Tailwind v4 and similar) does not override it."
|
|
242
|
+
}
|
|
203
243
|
]
|
|
204
244
|
},
|
|
205
245
|
"live": {
|
|
206
|
-
"version": "1.
|
|
246
|
+
"version": "1.44.1",
|
|
207
247
|
"mode": "live",
|
|
208
248
|
"directives": {
|
|
209
249
|
"script-src": [
|
|
@@ -219,9 +259,11 @@
|
|
|
219
259
|
"https://embed.portal.vonpay.com",
|
|
220
260
|
"https://applepay.cdn-apple.com",
|
|
221
261
|
"https://pay.google.com",
|
|
262
|
+
"https://checkout.vonpay.com",
|
|
263
|
+
"https://hooks.stripe.com",
|
|
264
|
+
"'self'",
|
|
222
265
|
"https://js.stripe.com",
|
|
223
266
|
"https://*.js.stripe.com",
|
|
224
|
-
"https://hooks.stripe.com",
|
|
225
267
|
"https://link.com",
|
|
226
268
|
"https://*.link.com"
|
|
227
269
|
],
|
|
@@ -335,6 +377,36 @@
|
|
|
335
377
|
"feature": "google-pay",
|
|
336
378
|
"why": "Google Pay JS adds a <style> element for its button."
|
|
337
379
|
},
|
|
380
|
+
{
|
|
381
|
+
"directive": "frame-src",
|
|
382
|
+
"source": "https://embed.vonpay.gr4vy.app",
|
|
383
|
+
"feature": "wallet-modal",
|
|
384
|
+
"why": "The bank check for a wallet payment, in a modal on your page."
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
"directive": "frame-src",
|
|
388
|
+
"source": "https://checkout.vonpay.com",
|
|
389
|
+
"feature": "wallet-modal",
|
|
390
|
+
"why": "The page that reports the wallet bank check's result back to vora.js."
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
"directive": "frame-src",
|
|
394
|
+
"source": "https://embed.vonpay.gr4vy.app",
|
|
395
|
+
"feature": "bank-check",
|
|
396
|
+
"why": "The card-update bank check pop-up."
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
"directive": "frame-src",
|
|
400
|
+
"source": "https://hooks.stripe.com",
|
|
401
|
+
"feature": "bank-check",
|
|
402
|
+
"why": "The same, on Stripe."
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
"directive": "frame-src",
|
|
406
|
+
"source": "'self'",
|
|
407
|
+
"feature": "bank-check",
|
|
408
|
+
"why": "Your return page."
|
|
409
|
+
},
|
|
338
410
|
{
|
|
339
411
|
"directive": "script-src",
|
|
340
412
|
"source": "https://js.stripe.com",
|
|
@@ -401,6 +473,14 @@
|
|
|
401
473
|
"feature": "stripe-link",
|
|
402
474
|
"why": "Link artwork."
|
|
403
475
|
}
|
|
476
|
+
],
|
|
477
|
+
"ifNoUnsafeInline": [
|
|
478
|
+
{
|
|
479
|
+
"directive": "style-src",
|
|
480
|
+
"source": "'sha256-OuajJxe804doBGkJvwnBjnP1cpvNWFjHltumC6RbkcA='",
|
|
481
|
+
"feature": "default-look",
|
|
482
|
+
"why": "A one-line style element (@layer vora-defaults;) that puts the built-in default look below your own CSS layers. Without it the look still works, but your layered CSS (Tailwind v4 and similar) does not override it."
|
|
483
|
+
}
|
|
404
484
|
]
|
|
405
485
|
}
|
|
406
486
|
}
|