@gopaycz/gopay-js-sdk 1.6.6 → 1.7.0

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/CHANGELOG.md CHANGED
@@ -1,3 +1,37 @@
1
+ # [1.7.0](https://bitbucket.org/gp-gopay/gp-gw-js-sdk/compare/1.6.7...1.7.0) (2026-06-29)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * use api from production link GPOMA-2375 ([51e1574](https://bitbucket.org/gp-gopay/gp-gw-js-sdk/commits/51e157458f0f54a18a1460f4754df3c5103a560f))
7
+ * **audit:** suppress git-raw-commits deprecation GPOMA-2375 ([35f2379](https://bitbucket.org/gp-gopay/gp-gw-js-sdk/commits/35f237916d8581427bb9b358ffa20e0c07e9fc82))
8
+ * **browser-sdk:** guard attachPayment() against re-call while card form mounted D1 GPOMA-2375 ([41fa86d](https://bitbucket.org/gp-gopay/gp-gw-js-sdk/commits/41fa86d19957c6e1b0c94c491f2dfb8db438330e))
9
+ * **cards,polling,wallets,http:** code bugs B1 B2 B3 B6 D3 GPOMA-2375 ([55228e3](https://bitbucket.org/gp-gopay/gp-gw-js-sdk/commits/55228e3c5e0de30fa0934fddb31ff53ef6f2b5a7))
10
+ * **cards:** use cardFormSessionActive flag to close race in isCardFormMounted (D1) GPOMA-2375 ([b1d85c5](https://bitbucket.org/gp-gopay/gp-gw-js-sdk/commits/b1d85c5531b512c2d7160c693fe22c92e40d1cf9))
11
+ * **polling:** forward abort signal to per-poll client.get() GPOMA-2375 ([5d36270](https://bitbucket.org/gp-gopay/gp-gw-js-sdk/commits/5d36270589fe92e6b690276e47dd474bf99d58e8))
12
+ * update push instructions GPOMA-2375 ([160ce7e](https://bitbucket.org/gp-gopay/gp-gw-js-sdk/commits/160ce7e98c1143abe34e081153cc5bec7c418027))
13
+
14
+
15
+ ### Features
16
+
17
+ * **api:** wire AbortSignal through public payment methods (D4) GPOMA-2375 ([a23bd08](https://bitbucket.org/gp-gopay/gp-gw-js-sdk/commits/a23bd08e6b99fa89901a7813f581adfdfae91a2d))
18
+ * **errors,polling:** attach chargeState to CHARGE_FAILED error for catch callers D2 GPOMA-2375 ([26f1f38](https://bitbucket.org/gp-gopay/gp-gw-js-sdk/commits/26f1f38470c47b008684de17eb742f0c820ba1a3))
19
+ * **http:** add AbortSignal to RequestOptions and thread into all fetch calls D4 GPOMA-2375 ([81c2721](https://bitbucket.org/gp-gopay/gp-gw-js-sdk/commits/81c27211fabefc5de47b7211aad9b73403bc5755))
20
+ * **payments:** add awaitPaymentStatus() polling helper for QR and bank-transfer flows M1 GPOMA-2375 ([10b447a](https://bitbucket.org/gp-gopay/gp-gw-js-sdk/commits/10b447a8202607afc9b737aa44f4ae7ed8a1fce3))
21
+ * **scopes:** export GoPayScopes constants and combineScopes helper M2 GPOMA-2375 ([74b94f8](https://bitbucket.org/gp-gopay/gp-gw-js-sdk/commits/74b94f84e2c815789c4842d088e7ddc43eba8e64))
22
+
23
+ ## [1.6.7](https://bitbucket.org/gp-gopay/gp-gw-js-sdk/compare/1.6.6...1.6.7) (2026-06-24)
24
+
25
+
26
+ ### Bug Fixes
27
+
28
+ * clarify theming link GPOMA-2364 ([dca521f](https://bitbucket.org/gp-gopay/gp-gw-js-sdk/commits/dca521f92fce78714d5ae5ee0ae2052f75f160f4))
29
+ * rename env filename GPOMA-2364 ([5eed047](https://bitbucket.org/gp-gopay/gp-gw-js-sdk/commits/5eed047b701a3350370c6da2467bdcb2bbacf356))
30
+ * rename remaining GP_GW_JS_SDK_* refs and make BASE_URL optional in e2e config GPOMA-2364 ([4e4774b](https://bitbucket.org/gp-gopay/gp-gw-js-sdk/commits/4e4774b1a8e4f5948f8674e3805568ba9aa4b445))
31
+ * run tests on local node GPOMA-2364 ([a54e7fa](https://bitbucket.org/gp-gopay/gp-gw-js-sdk/commits/a54e7facda750dcc423fdca0ae7142b93cc9344d))
32
+ * update env example GPOMA-2364 ([fe2a3ea](https://bitbucket.org/gp-gopay/gp-gw-js-sdk/commits/fe2a3eaa86ab5ef2c77ed3e2016f3069b5d5ff2e))
33
+ * update README env var names to GOPAY_PAYMENTS_V4_ prefix GPOMA-2364 ([cb3a07a](https://bitbucket.org/gp-gopay/gp-gw-js-sdk/commits/cb3a07a056a10fd3d90f0d20fa147e7ddfb4a34f))
34
+
1
35
  ## [1.6.6](https://bitbucket.org/gp-gopay/gp-gw-js-sdk/compare/1.6.5...1.6.6) (2026-06-17)
2
36
 
3
37
 
package/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # @gopaycz/gopay-js-sdk
2
2
 
3
- GoPay JavaScript SDK for server-side use (Node.js) — wraps the GoPay Payments API v4.0.
3
+ [![npm](https://img.shields.io/npm/v/@gopaycz/gopay-js-sdk)](https://www.npmjs.com/package/@gopaycz/gopay-js-sdk)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ GoPay JavaScript SDK for server-side use (Node.js) — wraps the new [GoPay Payments API v4](https://api-docs.gopay.com/).
4
7
 
5
8
  > **Building a browser integration?** Use [`@gopaycz/gopay-js-sdk-browser`](../browser-sdk/README.md) — it handles in-browser card encryption, direct-charge flows, and exposes an IIFE bundle for CDN use.
6
9
 
@@ -50,14 +53,15 @@ const payment = await sdk.createPayment('YOUR_GOID', {
50
53
  Once the customer completes the payment flow and returns to your site, charge the payment using the instrument they provided:
51
54
 
52
55
  ```ts
53
- // Card token (obtained via sdk.mountCardForm() returns an object, use .token)
56
+ // Card token: obtained via the browser SDK card form (see @gopaycz/gopay-js-sdk-browser)
57
+ // and forwarded to your server after tokenization via sdk.tokenizeEncryptedCard()
54
58
  const charge = await sdk.chargePayment(payment.id, {
55
59
  payment_instrument: {
56
60
  payment_instrument: 'PAYMENT_CARD',
57
61
  input: {
58
62
  input_type: 'CARD_TOKEN',
59
63
  card_token: cardToken.token,
60
- challenge_preferrence: 'AUTO',
64
+ challenge_preference: 'AUTO',
61
65
  },
62
66
  },
63
67
  });
@@ -82,7 +86,7 @@ The SDK uses OAuth2. **Client credentials (`client_id` / `client_secret`) must n
82
86
  Your backend holds the credentials. Call `authenticate()` once on startup — the SDK stores the token and refreshes it transparently before expiry:
83
87
 
84
88
  ```ts
85
- import { createGoPaySDK } from '@gopaycz/gopay-js-sdk';
89
+ import { createGoPaySDK, GoPayScopes } from '@gopaycz/gopay-js-sdk';
86
90
 
87
91
  const sdk = createGoPaySDK({ environment: 'production' });
88
92
 
@@ -90,13 +94,27 @@ await sdk.authenticate({
90
94
  grant_type: 'client_credentials',
91
95
  client_id: process.env.GOPAY_CLIENT_ID,
92
96
  client_secret: process.env.GOPAY_CLIENT_SECRET,
93
- scope: 'payment:write payment:read',
97
+ scope: GoPayScopes.PAYMENT_WRITE + ' ' + GoPayScopes.PAYMENT_READ,
98
+ // or: scope: combineScopes(GoPayScopes.PAYMENT_WRITE, GoPayScopes.PAYMENT_READ)
99
+ // or just a plain string: scope: 'payment:write payment:read'
94
100
  });
95
101
 
96
102
  // All subsequent calls attach the Bearer token automatically.
97
103
  const payment = await sdk.createPayment(goid, params);
98
104
  ```
99
105
 
106
+ ### Scopes reference
107
+
108
+ | Scope | Constant | Used by |
109
+ |---|---|---|
110
+ | `payment:write` | `GoPayScopes.PAYMENT_WRITE` | `createPayment`, `chargePayment`, `refundPayment`, recurrences, payment links |
111
+ | `payment:read` | `GoPayScopes.PAYMENT_READ` | `getPaymentStatus`, `getChargeState`, `listRefunds`, `getRefund`, recurrence status |
112
+ | `card:write` | `GoPayScopes.CARD_WRITE` | `getBrowserKeys()` — allows the browser SDK to present the card form |
113
+ | `card:read` | `GoPayScopes.CARD_READ` | `getCardDetails`, `deleteCard` |
114
+ | `payment:charge` | `GoPayScopes.PAYMENT_CHARGE` | Browser SDK only (`attachPayment` / `payment_credentials` grant) |
115
+
116
+ For `getBrowserKeys()` to work, include `card:write` in your server-side `authenticate()` call. `shareableKey` must also be set in the SDK config (see [Browser keys](#browser-keys) below). Include `payment:write` (and any other scopes) as needed for the rest of your integration.
117
+
100
118
  ---
101
119
 
102
120
  ## Configuration
@@ -153,6 +171,28 @@ The one exception is `startApplePaySession`: it returns `void` instead of a Prom
153
171
  | `isAuthenticated()` | Returns `true` if a token is currently stored. Does not check expiry — expired tokens are refreshed transparently on the next API call. |
154
172
  | `logout()` | Clear all stored tokens and credentials. All subsequent API calls will throw until the SDK is re-authenticated. |
155
173
 
174
+ #### Browser keys
175
+
176
+ `getBrowserKeys()` returns `{ shareable_key, client_id }` — pass both to the browser SDK via your own API endpoint.
177
+
178
+ **Prerequisites:**
179
+ - The `shareableKey` option must be set when creating the SDK:
180
+ ```ts
181
+ const sdk = createGoPaySDK({
182
+ environment: 'production',
183
+ shareableKey: process.env.GOPAY_SHAREABLE_KEY, // issued in GoPay admin alongside client_id
184
+ });
185
+ ```
186
+ - The token scope must include `card:write` (obtain `shareableKey` from the GoPay admin portal):
187
+ ```ts
188
+ await sdk.authenticate({
189
+ ...,
190
+ scope: 'payment:write payment:read card:write',
191
+ });
192
+ ```
193
+
194
+ Throws `AUTH_CREDENTIALS_MISSING` if either prerequisite is missing.
195
+
156
196
  ### Payments
157
197
 
158
198
  | Method | Description |
@@ -313,127 +353,72 @@ document.body.appendChild(img);
313
353
 
314
354
  ### Cards
315
355
 
356
+ > **SDK boundary:** card data encryption and the card form UI live in the **browser SDK** ([`@gopaycz/gopay-js-sdk-browser`](../browser-sdk/README.md)), not here. The browser encrypts the card inside a GoPay-hosted iframe and returns an `encryptedPayload`; your server calls `tokenizeEncryptedCard` to convert it to a card token, then charges the payment. Raw card data never touches your server or this SDK.
357
+
316
358
  | Method | Description |
317
359
  |---|---|
318
- | `mountCardForm(container, options?)` | Fetches the GoPay-hosted iframe URL from `GET /encryption/card-form-url`, mounts it into `container`, and returns `Promise<CardFormController>`. The controller exposes a `result` promise (resolves to the card token on submit), `setTheme()`, `setLocale()`, `submit()`, and `isValid` for runtime control. Handles iframe creation, internal communication, and `POST /cards/tokens` internally. Requires the `card:write` scope. |
319
- | `getCardDetails(cardId)` | Retrieve details of a stored permanent card token (`GET /cards/tokens/{cardId}`). Returns masked PAN, expiry, scheme, fingerprint, and the reusable token. Requires the `card:read` scope. |
320
- | `deleteCard(cardId)` | Delete a stored permanent card token (`DELETE /cards/tokens/{cardId}`). Returns `void`. |
321
-
322
- #### `mountCardForm` options
323
-
324
- | Option | Type | Default | Description |
325
- |---|---|---|---|
326
- | `theme` | `CardFormTheme` | `DEFAULT_CARD_FORM_THEME` | Visual theme (colors, font sizes, spacing, button styles). All fields are optional — the iframe applies built-in defaults for any omitted field. |
327
- | `locale` | `string` | `navigator.language` | BCP 47 language tag for form labels and placeholders. The region subtag is ignored (`'cs-CZ'` → `'cs'`). Unknown locales fall back to English. Supported: `bg` `cs` `de` `en` `es` `et` `fr` `hr` `hu` `it` `lt` `lv` `nl` `pl` `pt` `ro` `ru` `sk` `sl` `uk`. |
328
- | `submitMode` | `'internal' \| 'external'` | `'internal'` | `'internal'` — the iframe renders its own submit button. `'external'` — the iframe hides the button; the parent controls submission via `cardForm.submit()` and receives validity changes via `onValidityChange`. |
329
- | `permanent` | `boolean` | `false` | When `true`, a permanent (reusable) card token is created that can be charged again for future payments. When `false` (default), a single-use token is created. |
330
- | `onValidityChange` | `(isValid: boolean) => void` | — | Called whenever the overall form validity changes in external submit mode. Use this to enable/disable your external submit button. |
331
-
332
- > **Send theme and locale at init time.** Both options are applied before the form is first painted, avoiding a flash of unstyled content. Use `cardForm.setTheme()` and `cardForm.setLocale()` on the returned controller if you need to update them after mounting.
333
-
334
- `CardFormTheme` is fully typed — hover in your editor to see every available field. Notable fields:
360
+ | `tokenizeEncryptedCard(encryptedPayload)` | Convert the encrypted payload returned by the browser SDK into a card token (`POST /cards/tokens`). Requires `card:write` scope. Returns `{ token, card_id? }`. |
361
+ | `getCardDetails(cardId)` | Retrieve details of a stored permanent card token (`GET /cards/tokens/{cardId}`). Returns masked PAN, expiry, scheme, fingerprint, and the reusable token. Requires `card:read` scope. |
362
+ | `deleteCard(cardId)` | Delete a stored permanent card token (`DELETE /cards/tokens/{cardId}`). Returns `void`. Requires `card:read` scope. |
335
363
 
336
- | Field | Type | Default | Description |
337
- |---|---|---|---|
338
- | `fontFamily` | `string` | `system-ui, sans-serif` | CSS font-family stack applied to all form text (labels, inputs, errors, submit button). Use system font stacks — e.g. `"Inter, system-ui, sans-serif"` — to match your page without loading external fonts. |
364
+ ### Flow A server-side card charge
339
365
 
340
- > **Color format restriction.** All color fields in `CardFormTheme` must use one of the following CSS formats: `#rgb`, `#rrggbb`, `#rrggbbaa`, `rgb(...)`, `rgba(...)`, `hsl(...)`, `hsla(...)`, or `transparent`. Values that do not match are silently replaced with `unset` (browser default). Named colors (e.g. `red`) and other CSS values are not accepted.
341
-
342
- Theme presets are exported from the package:
366
+ The browser encrypts the card; your server tokenizes and charges. This is the recommended flow when your server handles the charge (e.g. to save the token for future use).
343
367
 
344
368
  ```ts
345
- import {
346
- GoPaySDK,
347
- DEFAULT_CARD_FORM_THEME,
348
- DARK_CARD_FORM_THEME,
349
- } from '@gopaycz/gopay-js-sdk';
350
-
351
- // Default theme, locale from navigator.language (options can be omitted entirely)
352
- const cardForm = await sdk.mountCardForm(container);
353
- const cardToken = await cardForm.result;
354
-
355
- // Dark theme with Czech locale
356
- const cardForm = await sdk.mountCardForm(container, {
357
- theme: DARK_CARD_FORM_THEME,
358
- locale: 'cs',
359
- });
360
- const cardToken = await cardForm.result;
361
-
362
- // Custom theme — override individual fields, keep the rest as defaults
363
- const cardForm = await sdk.mountCardForm(container, {
364
- theme: { ...DEFAULT_CARD_FORM_THEME, submitBackgroundColor: '#your-brand-color' },
365
- });
366
-
367
- // Update theme or locale at runtime (e.g. user toggles dark mode or language)
368
- darkModeToggle.addEventListener('change', () => cardForm.setTheme(DARK_CARD_FORM_THEME));
369
- langSelect.addEventListener('change', e => cardForm.setLocale(e.target.value));
369
+ // ── Browser (using @gopaycz/gopay-js-sdk-browser) ──────────────────────────
370
+ import { createGoPayBrowserSDK, collectBrowserData } from '@gopaycz/gopay-js-sdk-browser';
370
371
 
371
- const cardToken = await cardForm.result;
372
- ```
373
-
374
- #### External submit button
375
-
376
- Use `submitMode: 'external'` when you want the submit button to live outside the iframe — for example to match your own checkout UI. The iframe hides its built-in button, reports validity changes via `onValidityChange`, and waits for `cardForm.submit()` to trigger encryption.
372
+ const browserSdk = createGoPayBrowserSDK({ environment: 'production', shareableKey, clientId });
373
+ const controller = await browserSdk.mountCardForm(container, { flow: 'return-payload' });
374
+ const { encryptedPayload } = await controller.result;
377
375
 
378
- ```ts
379
- const payBtn = document.getElementById('pay-btn');
376
+ // Collect browser data for 3DS — must be done in the browser
377
+ const browserData = collectBrowserData();
380
378
 
381
- const cardForm = await sdk.mountCardForm(container, {
382
- submitMode: 'external',
383
- onValidityChange(isValid) {
384
- // Enable/disable your own submit button based on form validity.
385
- payBtn.disabled = !isValid;
386
- },
379
+ // Forward both to your server endpoint
380
+ await fetch('/api/charge', {
381
+ method: 'POST',
382
+ headers: { 'Content-Type': 'application/json' },
383
+ body: JSON.stringify({ paymentId, encryptedPayload, browserData }),
387
384
  });
388
385
 
389
- payBtn.addEventListener('click', () => {
390
- cardForm.submit(); // triggers encryption inside the iframe
391
- });
392
-
393
- // Await the result exactly the same as in internal submit.
394
- const cardToken = await cardForm.result;
395
- ```
396
-
397
- `cardForm.isValid` is also available as a synchronous getter in case you need to read the current validity state imperatively (e.g. inside a click handler before calling `submit()`).
398
-
399
- > Calling `submit()` in internal submit mode throws a `GoPaySDKError`. Calling it after the form completes or is unmounted is a no-op.
400
-
401
- ### Card Pay
386
+ // ── Server (using @gopaycz/gopay-js-sdk) ───────────────────────────────────
387
+ import { createGoPaySDK } from '@gopaycz/gopay-js-sdk';
402
388
 
403
- Card number encryption must never run in publicly reachable JavaScript — doing so would expose the raw PAN to any script on the merchant's page. The SDK uses a GoPay-hosted iframe for this step so that raw card data never touches merchant code.
389
+ const sdk = createGoPaySDK({ environment: 'production' });
390
+ await sdk.authenticate({ ..., scope: 'payment:write payment:read card:write' });
404
391
 
405
- ```ts
406
- // 1. Create a payment session first (server-side).
407
- const payment = await sdk.createPayment(goid, params);
392
+ // Handler for POST /api/charge
393
+ const { encryptedPayload, paymentId, browserData } = req.body;
408
394
 
409
- // 2. Mount the iframe the SDK fetches the hosted form URL from the API,
410
- // mounts the iframe, then awaits the user submitting the card form,
411
- // and calls POST /cards/tokens automatically.
412
- const container = document.getElementById('card-form-container');
413
- const cardForm = await sdk.mountCardForm(container);
414
- const cardToken = await cardForm.result;
395
+ // 1. Convert encrypted payload to a card token
396
+ const cardToken = await sdk.tokenizeEncryptedCard(encryptedPayload);
415
397
 
416
- // 3. Charge the payment with the resulting card token.
417
- const charge = await sdk.chargePayment(payment.id, {
398
+ // 2. Charge the payment always forward browserData from the browser for 3DS
399
+ const charge = await sdk.chargePayment(paymentId, {
418
400
  payment_instrument: {
419
401
  payment_instrument: 'PAYMENT_CARD',
420
402
  input: {
421
403
  input_type: 'CARD_TOKEN',
422
404
  card_token: cardToken.token,
423
- challenge_preferrence: 'AUTO',
405
+ challenge_preference: 'AUTO',
406
+ browser_data: browserData, // required for 3DS — do not omit
424
407
  },
425
408
  },
426
409
  });
427
410
 
428
411
  if (charge.action?.redirect_url) {
429
- // 3DS authentication required — redirect the customer.
430
- window.location.href = charge.action.redirect_url;
412
+ // 3DS authentication required — redirect the customer
413
+ res.json({ redirectUrl: charge.action.redirect_url });
431
414
  }
432
415
  ```
433
416
 
417
+ > **`browser_data` is required for 3DS.** The server SDK does not auto-collect it (only the browser SDK can). Always forward it from the browser via `collectBrowserData()`. Without it, 3DS context is absent and approval rates degrade.
418
+
434
419
  ### Saved cards
435
420
 
436
- When `mountCardForm` is called with `permanent: true`, the resulting `cardToken.token` is a permanent token tied to a card ID (`cardToken.card_id`). You can retrieve or delete it later:
421
+ To save a card for future payments, pass `permanent: true` to `mountCardForm` in the browser SDK. The resulting `cardToken.card_id` can be stored and used to charge again without re-entering card details:
437
422
 
438
423
  ```ts
439
424
  // Retrieve details of a stored card (masked PAN, expiry, scheme, etc.)
@@ -452,8 +437,6 @@ const charge = await sdk.chargePayment(payment.id, {
452
437
  await sdk.deleteCard(card.card_id);
453
438
  ```
454
439
 
455
- `GET /encryption/public-key` and the JWE construction it enables are **intentionally not part of this SDK's API surface**.
456
-
457
440
  ---
458
441
 
459
442
  ### Recurrences
@@ -600,6 +583,23 @@ try {
600
583
  | `status` | `number` | HTTP status code (e.g. `401`, `422`). |
601
584
  | `body` | `unknown` | Parsed JSON response body, or raw text if JSON parsing failed. |
602
585
 
586
+ A 422 error from `chargePayment` with an invalid amount looks like:
587
+
588
+ ```json
589
+ {
590
+ "date_time": "2024-03-15T14:22:00.000+0100",
591
+ "error_code": 400,
592
+ "error_name": "PAYMENT_VALIDATION_ERROR",
593
+ "message": [
594
+ {
595
+ "field": "amount",
596
+ "message": "INVALID",
597
+ "description": "Value 0 is out of allowed range [1, 99999999]"
598
+ }
599
+ ]
600
+ }
601
+ ```
602
+
603
603
  ### Centralised error handling with `onError`
604
604
 
605
605
  Pass an `onError` callback to the SDK config to intercept every error in one place — useful for logging or alerting — without replacing per-call `catch` blocks: