@easypayment/medusa-paypal 0.4.7 → 0.4.8
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/.medusa/server/src/admin/index.js +7 -7
- package/.medusa/server/src/admin/index.mjs +7 -7
- package/.medusa/server/src/api/store/paypal/create-order/route.d.ts.map +1 -1
- package/.medusa/server/src/api/store/paypal/create-order/route.js +62 -139
- package/.medusa/server/src/api/store/paypal/create-order/route.js.map +1 -1
- package/.medusa/server/src/modules/paypal/migrations/20260115120000_create_paypal_connection.js +22 -22
- package/.medusa/server/src/modules/paypal/migrations/20260123090000_create_paypal_settings.js +11 -11
- package/.medusa/server/src/modules/paypal/migrations/20260201090000_create_paypal_webhook_event.js +18 -18
- package/.medusa/server/src/modules/paypal/migrations/20260401090000_create_paypal_metric.js +16 -16
- package/.medusa/server/src/modules/paypal/migrations/20260701090000_add_paypal_webhook_event_processing.js +20 -20
- package/.medusa/server/src/modules/paypal/migrations/20261101090000_remove_paypal_reconciliation_status.js +14 -14
- package/.medusa/server/src/modules/paypal/migrations/20261201090000_remove_paypal_audit_log.js +15 -15
- package/README.md +142 -142
- package/package.json +75 -75
- package/src/admin/index.ts +7 -7
- package/src/admin/routes/settings/paypal/_components/Tabs.tsx +52 -52
- package/src/admin/routes/settings/paypal/_components/Toast.tsx +51 -51
- package/src/admin/routes/settings/paypal/additional-settings/page.tsx +200 -200
- package/src/admin/routes/settings/paypal/advanced-card-payments/page.tsx +183 -183
- package/src/admin/routes/settings/paypal/apple-pay/page.tsx +5 -5
- package/src/admin/routes/settings/paypal/connection/page.tsx +754 -754
- package/src/admin/routes/settings/paypal/google-pay/page.tsx +5 -5
- package/src/admin/routes/settings/paypal/pay-later-messaging/page.tsx +5 -5
- package/src/admin/routes/settings/paypal/paypal-settings/page.tsx +376 -376
- package/src/api/admin/payment-collections/[id]/payment-sessions/route.ts +24 -24
- package/src/api/admin/paypal/disconnect/route.ts +8 -8
- package/src/api/admin/paypal/environment/route.ts +25 -25
- package/src/api/admin/paypal/onboard-complete/route.ts +44 -44
- package/src/api/admin/paypal/onboarding-link/route.ts +45 -45
- package/src/api/admin/paypal/onboarding-status/route.ts +18 -18
- package/src/api/admin/paypal/rotate-credentials/route.ts +8 -8
- package/src/api/admin/paypal/save-credentials/route.ts +14 -14
- package/src/api/admin/paypal/settings/route.ts +14 -14
- package/src/api/admin/paypal/status/route.ts +12 -12
- package/src/api/store/payment-collections/[id]/payment-sessions/route.ts +65 -65
- package/src/api/store/paypal/capture-order/route.ts +276 -276
- package/src/api/store/paypal/config/route.ts +102 -102
- package/src/api/store/paypal/create-order/route.ts +77 -176
- package/src/api/store/paypal/settings/route.ts +19 -19
- package/src/api/store/paypal/webhook/route.ts +246 -246
- package/src/api/store/paypal-complete/route.ts +75 -75
- package/src/jobs/paypal-reconcile.ts +112 -112
- package/src/jobs/paypal-webhook-retry.ts +85 -85
- package/src/modules/paypal/clients/paypal-seller.client.ts +59 -59
- package/src/modules/paypal/index.ts +8 -8
- package/src/modules/paypal/migrations/20260115120000_create_paypal_connection.ts +33 -33
- package/src/modules/paypal/migrations/20260123090000_create_paypal_settings.ts +22 -22
- package/src/modules/paypal/migrations/20260201090000_create_paypal_webhook_event.ts +29 -29
- package/src/modules/paypal/migrations/20260401090000_create_paypal_metric.ts +27 -27
- package/src/modules/paypal/migrations/20260701090000_add_paypal_webhook_event_processing.ts +31 -31
- package/src/modules/paypal/migrations/20261101090000_remove_paypal_reconciliation_status.ts +25 -25
- package/src/modules/paypal/migrations/20261201090000_remove_paypal_audit_log.ts +26 -26
- package/src/modules/paypal/migrations/20270101090000_set_paypal_environment_default_live.ts +11 -11
- package/src/modules/paypal/models/paypal_connection.ts +21 -21
- package/src/modules/paypal/models/paypal_metric.ts +9 -9
- package/src/modules/paypal/models/paypal_settings.ts +8 -8
- package/src/modules/paypal/models/paypal_webhook_event.ts +19 -19
- package/src/modules/paypal/payment-provider/README.md +22 -22
- package/src/modules/paypal/payment-provider/card-service.ts +760 -760
- package/src/modules/paypal/payment-provider/index.ts +19 -19
- package/src/modules/paypal/payment-provider/service.ts +1121 -1121
- package/src/modules/paypal/payment-provider/webhook-utils.ts +88 -88
- package/src/modules/paypal/service.ts +1247 -1247
- package/src/modules/paypal/types/config.ts +47 -47
- package/src/modules/paypal/utils/amounts.ts +41 -41
- package/src/modules/paypal/utils/crypto.ts +51 -51
- package/src/modules/paypal/utils/currencies.ts +84 -84
- package/src/modules/paypal/utils/paypal-auth.ts +32 -32
- package/src/modules/paypal/utils/provider-ids.ts +15 -15
- package/src/modules/paypal/webhook-processor.ts +215 -215
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
export type PayPalModuleConfig = {
|
|
2
|
-
partnerServiceUrl: string
|
|
3
|
-
partnerJsUrl: string
|
|
4
|
-
backendUrl: string
|
|
5
|
-
sellerNonce: string
|
|
6
|
-
bnCode?: string
|
|
7
|
-
partnerMerchantIdSandbox: string
|
|
8
|
-
partnerMerchantIdLive: string
|
|
9
|
-
credentialsEncryptionKey?: string
|
|
10
|
-
credentialsEncryptionKeyPrevious?: string[]
|
|
11
|
-
alertWebhookUrls?: string[]
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const STATIC_CFG: PayPalModuleConfig = {
|
|
15
|
-
partnerServiceUrl: "https://mbjtechnolabs.com/ppcp-seller-onboarding/seller-onboarding.php",
|
|
16
|
-
partnerJsUrl: "https://www.paypal.com/webapps/merchantboarding/js/lib/lightbox/partner.js",
|
|
17
|
-
backendUrl: "http://localhost:9000",
|
|
18
|
-
sellerNonce: "a1233wtergfsdt4365tzrshgfbaewa36AGa1233wtergfsdt4365tzrshgfbaewa36AG",
|
|
19
|
-
bnCode: "",
|
|
20
|
-
partnerMerchantIdSandbox: "K6QLN2LPGQRHL",
|
|
21
|
-
partnerMerchantIdLive: "GT5R877JNBPLL",
|
|
22
|
-
credentialsEncryptionKey: "",
|
|
23
|
-
credentialsEncryptionKeyPrevious: [],
|
|
24
|
-
alertWebhookUrls: [],
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function getPayPalConfig(): PayPalModuleConfig {
|
|
28
|
-
const previousKeys = (process.env.PAYPAL_CREDENTIALS_ENCRYPTION_KEY_PREVIOUS || "")
|
|
29
|
-
.split(",")
|
|
30
|
-
.map((key) => key.trim())
|
|
31
|
-
.filter(Boolean)
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
...STATIC_CFG,
|
|
35
|
-
backendUrl: process.env.MEDUSA_BACKEND_URL || STATIC_CFG.backendUrl,
|
|
36
|
-
credentialsEncryptionKey:
|
|
37
|
-
process.env.PAYPAL_CREDENTIALS_ENCRYPTION_KEY ||
|
|
38
|
-
STATIC_CFG.credentialsEncryptionKey,
|
|
39
|
-
credentialsEncryptionKeyPrevious:
|
|
40
|
-
previousKeys.length > 0 ? previousKeys : STATIC_CFG.credentialsEncryptionKeyPrevious,
|
|
41
|
-
alertWebhookUrls:
|
|
42
|
-
(process.env.PAYPAL_ALERT_WEBHOOK_URLS || "")
|
|
43
|
-
.split(",")
|
|
44
|
-
.map((url) => url.trim())
|
|
45
|
-
.filter(Boolean) || STATIC_CFG.alertWebhookUrls,
|
|
46
|
-
}
|
|
47
|
-
}
|
|
1
|
+
export type PayPalModuleConfig = {
|
|
2
|
+
partnerServiceUrl: string
|
|
3
|
+
partnerJsUrl: string
|
|
4
|
+
backendUrl: string
|
|
5
|
+
sellerNonce: string
|
|
6
|
+
bnCode?: string
|
|
7
|
+
partnerMerchantIdSandbox: string
|
|
8
|
+
partnerMerchantIdLive: string
|
|
9
|
+
credentialsEncryptionKey?: string
|
|
10
|
+
credentialsEncryptionKeyPrevious?: string[]
|
|
11
|
+
alertWebhookUrls?: string[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const STATIC_CFG: PayPalModuleConfig = {
|
|
15
|
+
partnerServiceUrl: "https://mbjtechnolabs.com/ppcp-seller-onboarding/seller-onboarding.php",
|
|
16
|
+
partnerJsUrl: "https://www.paypal.com/webapps/merchantboarding/js/lib/lightbox/partner.js",
|
|
17
|
+
backendUrl: "http://localhost:9000",
|
|
18
|
+
sellerNonce: "a1233wtergfsdt4365tzrshgfbaewa36AGa1233wtergfsdt4365tzrshgfbaewa36AG",
|
|
19
|
+
bnCode: "",
|
|
20
|
+
partnerMerchantIdSandbox: "K6QLN2LPGQRHL",
|
|
21
|
+
partnerMerchantIdLive: "GT5R877JNBPLL",
|
|
22
|
+
credentialsEncryptionKey: "",
|
|
23
|
+
credentialsEncryptionKeyPrevious: [],
|
|
24
|
+
alertWebhookUrls: [],
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getPayPalConfig(): PayPalModuleConfig {
|
|
28
|
+
const previousKeys = (process.env.PAYPAL_CREDENTIALS_ENCRYPTION_KEY_PREVIOUS || "")
|
|
29
|
+
.split(",")
|
|
30
|
+
.map((key) => key.trim())
|
|
31
|
+
.filter(Boolean)
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
...STATIC_CFG,
|
|
35
|
+
backendUrl: process.env.MEDUSA_BACKEND_URL || STATIC_CFG.backendUrl,
|
|
36
|
+
credentialsEncryptionKey:
|
|
37
|
+
process.env.PAYPAL_CREDENTIALS_ENCRYPTION_KEY ||
|
|
38
|
+
STATIC_CFG.credentialsEncryptionKey,
|
|
39
|
+
credentialsEncryptionKeyPrevious:
|
|
40
|
+
previousKeys.length > 0 ? previousKeys : STATIC_CFG.credentialsEncryptionKeyPrevious,
|
|
41
|
+
alertWebhookUrls:
|
|
42
|
+
(process.env.PAYPAL_ALERT_WEBHOOK_URLS || "")
|
|
43
|
+
.split(",")
|
|
44
|
+
.map((url) => url.trim())
|
|
45
|
+
.filter(Boolean) || STATIC_CFG.alertWebhookUrls,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
const ZERO_DECIMAL_CURRENCIES = new Set([
|
|
2
|
-
"BIF",
|
|
3
|
-
"CLP",
|
|
4
|
-
"DJF",
|
|
5
|
-
"GNF",
|
|
6
|
-
"JPY",
|
|
7
|
-
"KMF",
|
|
8
|
-
"KRW",
|
|
9
|
-
"MGA",
|
|
10
|
-
"PYG",
|
|
11
|
-
"RWF",
|
|
12
|
-
"UGX",
|
|
13
|
-
"VND",
|
|
14
|
-
"VUV",
|
|
15
|
-
"XAF",
|
|
16
|
-
"XOF",
|
|
17
|
-
"XPF",
|
|
18
|
-
])
|
|
19
|
-
|
|
20
|
-
const THREE_DECIMAL_CURRENCIES = new Set(["BHD", "JOD", "KWD", "OMR", "TND"])
|
|
21
|
-
|
|
22
|
-
export function getCurrencyExponent(currencyCode: string) {
|
|
23
|
-
const code = currencyCode.toUpperCase()
|
|
24
|
-
if (ZERO_DECIMAL_CURRENCIES.has(code)) {
|
|
25
|
-
return 0
|
|
26
|
-
}
|
|
27
|
-
if (THREE_DECIMAL_CURRENCIES.has(code)) {
|
|
28
|
-
return 3
|
|
29
|
-
}
|
|
30
|
-
return 2
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function formatAmountForPayPal(
|
|
34
|
-
minorAmount: number,
|
|
35
|
-
currencyCode: string
|
|
36
|
-
) {
|
|
37
|
-
const exponent = getCurrencyExponent(currencyCode)
|
|
38
|
-
const factor = 10 ** exponent
|
|
39
|
-
const majorAmount = Number(minorAmount || 0) / factor
|
|
40
|
-
return majorAmount.toFixed(exponent)
|
|
41
|
-
}
|
|
1
|
+
const ZERO_DECIMAL_CURRENCIES = new Set([
|
|
2
|
+
"BIF",
|
|
3
|
+
"CLP",
|
|
4
|
+
"DJF",
|
|
5
|
+
"GNF",
|
|
6
|
+
"JPY",
|
|
7
|
+
"KMF",
|
|
8
|
+
"KRW",
|
|
9
|
+
"MGA",
|
|
10
|
+
"PYG",
|
|
11
|
+
"RWF",
|
|
12
|
+
"UGX",
|
|
13
|
+
"VND",
|
|
14
|
+
"VUV",
|
|
15
|
+
"XAF",
|
|
16
|
+
"XOF",
|
|
17
|
+
"XPF",
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
const THREE_DECIMAL_CURRENCIES = new Set(["BHD", "JOD", "KWD", "OMR", "TND"])
|
|
21
|
+
|
|
22
|
+
export function getCurrencyExponent(currencyCode: string) {
|
|
23
|
+
const code = currencyCode.toUpperCase()
|
|
24
|
+
if (ZERO_DECIMAL_CURRENCIES.has(code)) {
|
|
25
|
+
return 0
|
|
26
|
+
}
|
|
27
|
+
if (THREE_DECIMAL_CURRENCIES.has(code)) {
|
|
28
|
+
return 3
|
|
29
|
+
}
|
|
30
|
+
return 2
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function formatAmountForPayPal(
|
|
34
|
+
minorAmount: number,
|
|
35
|
+
currencyCode: string
|
|
36
|
+
) {
|
|
37
|
+
const exponent = getCurrencyExponent(currencyCode)
|
|
38
|
+
const factor = 10 ** exponent
|
|
39
|
+
const majorAmount = Number(minorAmount || 0) / factor
|
|
40
|
+
return majorAmount.toFixed(exponent)
|
|
41
|
+
}
|
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
import { createCipheriv, createDecipheriv, createHash, randomBytes } from "crypto"
|
|
2
|
-
|
|
3
|
-
const ENCRYPTED_PREFIX = "enc:"
|
|
4
|
-
const ALGORITHM = "aes-256-gcm"
|
|
5
|
-
const IV_LENGTH = 12
|
|
6
|
-
|
|
7
|
-
function deriveKey(secret: string) {
|
|
8
|
-
return createHash("sha256").update(secret).digest()
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function isEncryptedSecret(value?: string | null) {
|
|
12
|
-
return typeof value === "string" && value.startsWith(ENCRYPTED_PREFIX)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function encryptSecret(value: string, secret: string) {
|
|
16
|
-
if (!value || isEncryptedSecret(value)) {
|
|
17
|
-
return value
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const iv = randomBytes(IV_LENGTH)
|
|
21
|
-
const cipher = createCipheriv(ALGORITHM, deriveKey(secret), iv)
|
|
22
|
-
const encrypted = Buffer.concat([cipher.update(value, "utf8"), cipher.final()])
|
|
23
|
-
const tag = cipher.getAuthTag()
|
|
24
|
-
|
|
25
|
-
return [
|
|
26
|
-
ENCRYPTED_PREFIX.slice(0, -1),
|
|
27
|
-
iv.toString("base64"),
|
|
28
|
-
tag.toString("base64"),
|
|
29
|
-
encrypted.toString("base64"),
|
|
30
|
-
].join(":")
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function decryptSecret(value: string, secret: string) {
|
|
34
|
-
if (!isEncryptedSecret(value)) {
|
|
35
|
-
return value
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const [, ivBase64, tagBase64, payloadBase64] = value.split(":")
|
|
39
|
-
if (!ivBase64 || !tagBase64 || !payloadBase64) {
|
|
40
|
-
throw new Error("Invalid encrypted PayPal secret format.")
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const iv = Buffer.from(ivBase64, "base64")
|
|
44
|
-
const tag = Buffer.from(tagBase64, "base64")
|
|
45
|
-
const payload = Buffer.from(payloadBase64, "base64")
|
|
46
|
-
const decipher = createDecipheriv(ALGORITHM, deriveKey(secret), iv)
|
|
47
|
-
decipher.setAuthTag(tag)
|
|
48
|
-
const decrypted = Buffer.concat([decipher.update(payload), decipher.final()])
|
|
49
|
-
|
|
50
|
-
return decrypted.toString("utf8")
|
|
51
|
-
}
|
|
1
|
+
import { createCipheriv, createDecipheriv, createHash, randomBytes } from "crypto"
|
|
2
|
+
|
|
3
|
+
const ENCRYPTED_PREFIX = "enc:"
|
|
4
|
+
const ALGORITHM = "aes-256-gcm"
|
|
5
|
+
const IV_LENGTH = 12
|
|
6
|
+
|
|
7
|
+
function deriveKey(secret: string) {
|
|
8
|
+
return createHash("sha256").update(secret).digest()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function isEncryptedSecret(value?: string | null) {
|
|
12
|
+
return typeof value === "string" && value.startsWith(ENCRYPTED_PREFIX)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function encryptSecret(value: string, secret: string) {
|
|
16
|
+
if (!value || isEncryptedSecret(value)) {
|
|
17
|
+
return value
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const iv = randomBytes(IV_LENGTH)
|
|
21
|
+
const cipher = createCipheriv(ALGORITHM, deriveKey(secret), iv)
|
|
22
|
+
const encrypted = Buffer.concat([cipher.update(value, "utf8"), cipher.final()])
|
|
23
|
+
const tag = cipher.getAuthTag()
|
|
24
|
+
|
|
25
|
+
return [
|
|
26
|
+
ENCRYPTED_PREFIX.slice(0, -1),
|
|
27
|
+
iv.toString("base64"),
|
|
28
|
+
tag.toString("base64"),
|
|
29
|
+
encrypted.toString("base64"),
|
|
30
|
+
].join(":")
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function decryptSecret(value: string, secret: string) {
|
|
34
|
+
if (!isEncryptedSecret(value)) {
|
|
35
|
+
return value
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const [, ivBase64, tagBase64, payloadBase64] = value.split(":")
|
|
39
|
+
if (!ivBase64 || !tagBase64 || !payloadBase64) {
|
|
40
|
+
throw new Error("Invalid encrypted PayPal secret format.")
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const iv = Buffer.from(ivBase64, "base64")
|
|
44
|
+
const tag = Buffer.from(tagBase64, "base64")
|
|
45
|
+
const payload = Buffer.from(payloadBase64, "base64")
|
|
46
|
+
const decipher = createDecipheriv(ALGORITHM, deriveKey(secret), iv)
|
|
47
|
+
decipher.setAuthTag(tag)
|
|
48
|
+
const decrypted = Buffer.concat([decipher.update(payload), decipher.final()])
|
|
49
|
+
|
|
50
|
+
return decrypted.toString("utf8")
|
|
51
|
+
}
|
|
@@ -1,84 +1,84 @@
|
|
|
1
|
-
const PAYPAL_SUPPORTED_CURRENCIES = new Set([
|
|
2
|
-
"AUD",
|
|
3
|
-
"BRL",
|
|
4
|
-
"CAD",
|
|
5
|
-
"CHF",
|
|
6
|
-
"CZK",
|
|
7
|
-
"DKK",
|
|
8
|
-
"EUR",
|
|
9
|
-
"GBP",
|
|
10
|
-
"HKD",
|
|
11
|
-
"HUF",
|
|
12
|
-
"ILS",
|
|
13
|
-
"JPY",
|
|
14
|
-
"MXN",
|
|
15
|
-
"MYR",
|
|
16
|
-
"NOK",
|
|
17
|
-
"NZD",
|
|
18
|
-
"PHP",
|
|
19
|
-
"PLN",
|
|
20
|
-
"SEK",
|
|
21
|
-
"SGD",
|
|
22
|
-
"THB",
|
|
23
|
-
"TWD",
|
|
24
|
-
"USD",
|
|
25
|
-
])
|
|
26
|
-
|
|
27
|
-
type CurrencyCheck = {
|
|
28
|
-
currency: string
|
|
29
|
-
overrideCurrency?: string
|
|
30
|
-
supported: boolean
|
|
31
|
-
errors: string[]
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function normalizeCurrencyCode(code?: string, fallback = "EUR") {
|
|
35
|
-
const trimmed = String(code || "").trim()
|
|
36
|
-
return (trimmed || fallback).toUpperCase()
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function isPayPalCurrencySupported(currencyCode: string) {
|
|
40
|
-
return PAYPAL_SUPPORTED_CURRENCIES.has(normalizeCurrencyCode(currencyCode))
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function getPayPalCurrencyCompatibility(input: {
|
|
44
|
-
currencyCode?: string
|
|
45
|
-
paypalCurrencyOverride?: string
|
|
46
|
-
}): CurrencyCheck {
|
|
47
|
-
const currency = normalizeCurrencyCode(input.currencyCode)
|
|
48
|
-
const overrideCurrency = input.paypalCurrencyOverride
|
|
49
|
-
? normalizeCurrencyCode(input.paypalCurrencyOverride)
|
|
50
|
-
: undefined
|
|
51
|
-
const errors: string[] = []
|
|
52
|
-
|
|
53
|
-
if (!isPayPalCurrencySupported(currency)) {
|
|
54
|
-
errors.push(`PayPal does not support currency "${currency}".`)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (overrideCurrency && overrideCurrency !== currency) {
|
|
58
|
-
errors.push(
|
|
59
|
-
`PayPal is configured for "${overrideCurrency}", but the store cart uses "${currency}".`
|
|
60
|
-
)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
currency,
|
|
65
|
-
overrideCurrency,
|
|
66
|
-
supported: errors.length === 0,
|
|
67
|
-
errors,
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function assertPayPalCurrencySupported(input: {
|
|
72
|
-
currencyCode?: string
|
|
73
|
-
paypalCurrencyOverride?: string
|
|
74
|
-
}) {
|
|
75
|
-
const result = getPayPalCurrencyCompatibility(input)
|
|
76
|
-
if (!result.supported) {
|
|
77
|
-
throw new Error(result.errors.join(" "))
|
|
78
|
-
}
|
|
79
|
-
return result
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function getPayPalSupportedCurrencies() {
|
|
83
|
-
return Array.from(PAYPAL_SUPPORTED_CURRENCIES.values())
|
|
84
|
-
}
|
|
1
|
+
const PAYPAL_SUPPORTED_CURRENCIES = new Set([
|
|
2
|
+
"AUD",
|
|
3
|
+
"BRL",
|
|
4
|
+
"CAD",
|
|
5
|
+
"CHF",
|
|
6
|
+
"CZK",
|
|
7
|
+
"DKK",
|
|
8
|
+
"EUR",
|
|
9
|
+
"GBP",
|
|
10
|
+
"HKD",
|
|
11
|
+
"HUF",
|
|
12
|
+
"ILS",
|
|
13
|
+
"JPY",
|
|
14
|
+
"MXN",
|
|
15
|
+
"MYR",
|
|
16
|
+
"NOK",
|
|
17
|
+
"NZD",
|
|
18
|
+
"PHP",
|
|
19
|
+
"PLN",
|
|
20
|
+
"SEK",
|
|
21
|
+
"SGD",
|
|
22
|
+
"THB",
|
|
23
|
+
"TWD",
|
|
24
|
+
"USD",
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
type CurrencyCheck = {
|
|
28
|
+
currency: string
|
|
29
|
+
overrideCurrency?: string
|
|
30
|
+
supported: boolean
|
|
31
|
+
errors: string[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function normalizeCurrencyCode(code?: string, fallback = "EUR") {
|
|
35
|
+
const trimmed = String(code || "").trim()
|
|
36
|
+
return (trimmed || fallback).toUpperCase()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function isPayPalCurrencySupported(currencyCode: string) {
|
|
40
|
+
return PAYPAL_SUPPORTED_CURRENCIES.has(normalizeCurrencyCode(currencyCode))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getPayPalCurrencyCompatibility(input: {
|
|
44
|
+
currencyCode?: string
|
|
45
|
+
paypalCurrencyOverride?: string
|
|
46
|
+
}): CurrencyCheck {
|
|
47
|
+
const currency = normalizeCurrencyCode(input.currencyCode)
|
|
48
|
+
const overrideCurrency = input.paypalCurrencyOverride
|
|
49
|
+
? normalizeCurrencyCode(input.paypalCurrencyOverride)
|
|
50
|
+
: undefined
|
|
51
|
+
const errors: string[] = []
|
|
52
|
+
|
|
53
|
+
if (!isPayPalCurrencySupported(currency)) {
|
|
54
|
+
errors.push(`PayPal does not support currency "${currency}".`)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (overrideCurrency && overrideCurrency !== currency) {
|
|
58
|
+
errors.push(
|
|
59
|
+
`PayPal is configured for "${overrideCurrency}", but the store cart uses "${currency}".`
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
currency,
|
|
65
|
+
overrideCurrency,
|
|
66
|
+
supported: errors.length === 0,
|
|
67
|
+
errors,
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function assertPayPalCurrencySupported(input: {
|
|
72
|
+
currencyCode?: string
|
|
73
|
+
paypalCurrencyOverride?: string
|
|
74
|
+
}) {
|
|
75
|
+
const result = getPayPalCurrencyCompatibility(input)
|
|
76
|
+
if (!result.supported) {
|
|
77
|
+
throw new Error(result.errors.join(" "))
|
|
78
|
+
}
|
|
79
|
+
return result
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function getPayPalSupportedCurrencies() {
|
|
83
|
+
return Array.from(PAYPAL_SUPPORTED_CURRENCIES.values())
|
|
84
|
+
}
|
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared PayPal API authentication helpers.
|
|
3
|
-
* Import these instead of copying into each route.
|
|
4
|
-
*/
|
|
5
|
-
export function getPayPalApiBase(environment: string): string {
|
|
6
|
-
return environment === "live"
|
|
7
|
-
? "https://api-m.paypal.com"
|
|
8
|
-
: "https://api-m.sandbox.paypal.com"
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export async function getPayPalAccessToken(opts: {
|
|
12
|
-
environment: string
|
|
13
|
-
client_id: string
|
|
14
|
-
client_secret: string
|
|
15
|
-
}): Promise<{ accessToken: string; base: string }> {
|
|
16
|
-
const base = getPayPalApiBase(opts.environment)
|
|
17
|
-
const auth = Buffer.from(`${opts.client_id}:${opts.client_secret}`).toString("base64")
|
|
18
|
-
const resp = await fetch(`${base}/v1/oauth2/token`, {
|
|
19
|
-
method: "POST",
|
|
20
|
-
headers: {
|
|
21
|
-
Authorization: `Basic ${auth}`,
|
|
22
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
23
|
-
},
|
|
24
|
-
body: "grant_type=client_credentials",
|
|
25
|
-
})
|
|
26
|
-
const text = await resp.text()
|
|
27
|
-
if (!resp.ok) {
|
|
28
|
-
throw new Error(`PayPal token error (${resp.status}): ${text}`)
|
|
29
|
-
}
|
|
30
|
-
const json = JSON.parse(text)
|
|
31
|
-
return { accessToken: String(json.access_token), base }
|
|
32
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Shared PayPal API authentication helpers.
|
|
3
|
+
* Import these instead of copying into each route.
|
|
4
|
+
*/
|
|
5
|
+
export function getPayPalApiBase(environment: string): string {
|
|
6
|
+
return environment === "live"
|
|
7
|
+
? "https://api-m.paypal.com"
|
|
8
|
+
: "https://api-m.sandbox.paypal.com"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function getPayPalAccessToken(opts: {
|
|
12
|
+
environment: string
|
|
13
|
+
client_id: string
|
|
14
|
+
client_secret: string
|
|
15
|
+
}): Promise<{ accessToken: string; base: string }> {
|
|
16
|
+
const base = getPayPalApiBase(opts.environment)
|
|
17
|
+
const auth = Buffer.from(`${opts.client_id}:${opts.client_secret}`).toString("base64")
|
|
18
|
+
const resp = await fetch(`${base}/v1/oauth2/token`, {
|
|
19
|
+
method: "POST",
|
|
20
|
+
headers: {
|
|
21
|
+
Authorization: `Basic ${auth}`,
|
|
22
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
23
|
+
},
|
|
24
|
+
body: "grant_type=client_credentials",
|
|
25
|
+
})
|
|
26
|
+
const text = await resp.text()
|
|
27
|
+
if (!resp.ok) {
|
|
28
|
+
throw new Error(`PayPal token error (${resp.status}): ${text}`)
|
|
29
|
+
}
|
|
30
|
+
const json = JSON.parse(text)
|
|
31
|
+
return { accessToken: String(json.access_token), base }
|
|
32
|
+
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
export const PAYPAL_WALLET_PROVIDER_ID = "pp_paypal_paypal" as const
|
|
2
|
-
export const PAYPAL_CARD_PROVIDER_ID = "pp_paypal_card_paypal_card" as const
|
|
3
|
-
|
|
4
|
-
export const PAYPAL_PROVIDER_IDS = [
|
|
5
|
-
PAYPAL_WALLET_PROVIDER_ID,
|
|
6
|
-
PAYPAL_CARD_PROVIDER_ID,
|
|
7
|
-
] as const
|
|
8
|
-
|
|
9
|
-
export const isPayPalProviderId = (providerId?: string | null) => {
|
|
10
|
-
if (!providerId) return false
|
|
11
|
-
|
|
12
|
-
return PAYPAL_PROVIDER_IDS.includes(
|
|
13
|
-
providerId as (typeof PAYPAL_PROVIDER_IDS)[number]
|
|
14
|
-
)
|
|
15
|
-
}
|
|
1
|
+
export const PAYPAL_WALLET_PROVIDER_ID = "pp_paypal_paypal" as const
|
|
2
|
+
export const PAYPAL_CARD_PROVIDER_ID = "pp_paypal_card_paypal_card" as const
|
|
3
|
+
|
|
4
|
+
export const PAYPAL_PROVIDER_IDS = [
|
|
5
|
+
PAYPAL_WALLET_PROVIDER_ID,
|
|
6
|
+
PAYPAL_CARD_PROVIDER_ID,
|
|
7
|
+
] as const
|
|
8
|
+
|
|
9
|
+
export const isPayPalProviderId = (providerId?: string | null) => {
|
|
10
|
+
if (!providerId) return false
|
|
11
|
+
|
|
12
|
+
return PAYPAL_PROVIDER_IDS.includes(
|
|
13
|
+
providerId as (typeof PAYPAL_PROVIDER_IDS)[number]
|
|
14
|
+
)
|
|
15
|
+
}
|