@gopaycz/gopay-js-sdk 1.6.6

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 ADDED
@@ -0,0 +1,633 @@
1
+ # @gopaycz/gopay-js-sdk
2
+
3
+ GoPay JavaScript SDK for server-side use (Node.js) — wraps the GoPay Payments API v4.0.
4
+
5
+ > **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
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @gopaycz/gopay-js-sdk
11
+ # or
12
+ yarn add @gopaycz/gopay-js-sdk
13
+ ```
14
+
15
+ ---
16
+
17
+ ## Quick start
18
+
19
+ ```ts
20
+ import { createGoPaySDK } from '@gopaycz/gopay-js-sdk';
21
+
22
+ const sdk = createGoPaySDK({ environment: 'production' });
23
+
24
+ // Authenticate (server-side only — never expose credentials in the browser)
25
+ await sdk.authenticate({
26
+ grant_type: 'client_credentials',
27
+ client_id: process.env.GOPAY_CLIENT_ID,
28
+ client_secret: process.env.GOPAY_CLIENT_SECRET,
29
+ scope: 'payment:write',
30
+ });
31
+
32
+ // Create a payment session
33
+ const payment = await sdk.createPayment('YOUR_GOID', {
34
+ amount: 1000, // CZK 10.00
35
+ currency: 'CZK',
36
+ order_number: 'ORDER-001',
37
+ customer: { email: 'customer@example.com' },
38
+ callback: {
39
+ notification_url: 'https://yourshop.com/notify',
40
+ return_url: 'https://yourshop.com/return',
41
+ },
42
+ });
43
+
44
+ // Use payment.id to charge via card token, Apple Pay, Google Pay, etc.
45
+ // See sdk.chargePayment() below.
46
+ ```
47
+
48
+ ### Charging a payment
49
+
50
+ Once the customer completes the payment flow and returns to your site, charge the payment using the instrument they provided:
51
+
52
+ ```ts
53
+ // Card token (obtained via sdk.mountCardForm() — returns an object, use .token)
54
+ const charge = await sdk.chargePayment(payment.id, {
55
+ payment_instrument: {
56
+ payment_instrument: 'PAYMENT_CARD',
57
+ input: {
58
+ input_type: 'CARD_TOKEN',
59
+ card_token: cardToken.token,
60
+ challenge_preferrence: 'AUTO',
61
+ },
62
+ },
63
+ });
64
+
65
+ // For bank account payments, use BANK_ACCOUNT with an account token:
66
+ // payment_instrument: { payment_instrument: 'BANK_ACCOUNT', input: { input_type: 'ACCOUNT_TOKEN', account_token: '...' } }
67
+
68
+ if (charge.action?.redirect_url) {
69
+ // 3DS or PSD2 authentication required — redirect the customer
70
+ res.redirect(charge.action.redirect_url);
71
+ }
72
+ ```
73
+
74
+ ---
75
+
76
+ ## Authentication
77
+
78
+ The SDK uses OAuth2. **Client credentials (`client_id` / `client_secret`) must never be exposed in browser JavaScript.** Choose the flow that matches your deployment:
79
+
80
+ ### Server-side (Node.js) — `authenticate()` must never run in the browser
81
+
82
+ Your backend holds the credentials. Call `authenticate()` once on startup — the SDK stores the token and refreshes it transparently before expiry:
83
+
84
+ ```ts
85
+ import { createGoPaySDK } from '@gopaycz/gopay-js-sdk';
86
+
87
+ const sdk = createGoPaySDK({ environment: 'production' });
88
+
89
+ await sdk.authenticate({
90
+ grant_type: 'client_credentials',
91
+ client_id: process.env.GOPAY_CLIENT_ID,
92
+ client_secret: process.env.GOPAY_CLIENT_SECRET,
93
+ scope: 'payment:write payment:read',
94
+ });
95
+
96
+ // All subsequent calls attach the Bearer token automatically.
97
+ const payment = await sdk.createPayment(goid, params);
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Configuration
103
+
104
+ | Option | Type | Default | Description |
105
+ |-----------------------|---------------------------------------------------|-------------|------------------------------------------------------------------------------------------|
106
+ | `environment` | `'sandbox' \| 'production'` | `'sandbox'` | Target environment. |
107
+ | `baseUrl` | `string` | _(see below)_ | Override the API base URL (e.g. for mock servers). Takes precedence over `environment`. |
108
+ | `requestTimeoutMs` | `number` | `10000` | Per-request timeout in milliseconds. Throws `GoPaySDKError` with `errorCode: 'NETWORK_TIMEOUT'` on expiry. |
109
+ | `debugLoggingEnabled` | `boolean` | `false` | Logs `→ METHOD URL` / `← STATUS URL` for every request via `console.debug`. |
110
+ | `onError` | `(error: GoPaySDKError \| GoPayHTTPError) => void` | — | Called synchronously for every error the SDK throws. Useful for centralised logging or alerting. |
111
+
112
+ ```ts
113
+ const sdk = createGoPaySDK({
114
+ environment: 'production',
115
+ requestTimeoutMs: 5000,
116
+ debugLoggingEnabled: true,
117
+ onError(err) {
118
+ Sentry.captureException(err);
119
+ },
120
+ });
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Promise-based API
126
+
127
+ All SDK methods return Promises. Use `async/await` or `.then()/.catch()` — there are no `onSuccess` / `onError` callback parameters on individual calls.
128
+
129
+ Handle errors with `try/catch`. The SDK throws two typed error classes (`GoPaySDKError`, `GoPayHTTPError`) that you can import and `instanceof`-check — see [Errors](#errors). TypeScript types for all request and response shapes are exported from the package; your editor will show them inline.
130
+
131
+ ```ts
132
+ try {
133
+ const payment = await sdk.createPayment(goid, params);
134
+ } catch (err) {
135
+ if (err instanceof GoPayHTTPError) { /* API error — check err.status and err.body */ }
136
+ if (err instanceof GoPaySDKError) { /* SDK lifecycle error — check err.errorCode */ }
137
+ }
138
+ ```
139
+
140
+ The `onError` config option (see [Configuration](#configuration)) intercepts every error centrally — useful for logging — but does not replace `try/catch`.
141
+
142
+ The one exception is `startApplePaySession`: it returns `void` instead of a Promise and accepts an optional `callbacks.oncancel`, because the Apple Pay cancel event fires natively on the `ApplePaySession` object rather than through promise rejection.
143
+
144
+ ---
145
+
146
+ ## API
147
+
148
+ ### Authentication
149
+
150
+ | Method | Description |
151
+ |---|---|
152
+ | `authenticate(params)` | Server-side: obtain an access/refresh token pair using `client_credentials`. Stores the token internally for automatic refresh. |
153
+ | `isAuthenticated()` | Returns `true` if a token is currently stored. Does not check expiry — expired tokens are refreshed transparently on the next API call. |
154
+ | `logout()` | Clear all stored tokens and credentials. All subsequent API calls will throw until the SDK is re-authenticated. |
155
+
156
+ ### Payments
157
+
158
+ | Method | Description |
159
+ |---|---|
160
+ | `createPayment(goid, params)` | Create a new payment session (`POST /eshops/{goid}/payments`). |
161
+ | `chargePayment(paymentId, params)` | Charge a payment using a payment instrument (`POST /payments/{paymentId}/charge`). |
162
+ | `getPaymentStatus(paymentId)` | Retrieve the current status of a payment (`GET /payments/{paymentId}`). Returns state, amount, currency, customer, and charge reference. |
163
+ | `getChargeState(paymentId)` | Retrieve the current state of a payment charge (`GET /payments/{paymentId}/charge`). Returns charge state, instrument details, and any 3DS action. |
164
+ | `getGooglePayInfo(paymentId)` | Retrieve Google Pay configuration for a payment. |
165
+ | `getApplePayInfo(paymentId)` | Retrieve Apple Pay configuration for a payment. Returns `applepayVersion`, `merchantIdentifier`, and `applePayPaymentRequest` needed to construct an `ApplePaySession`. |
166
+ | `startApplePaySession(paymentId, session, origin?, callbacks?)` | Wire merchant validation onto an `ApplePaySession` and call `begin()`. Handles `onvalidatemerchant` automatically; `origin` is the merchant HTTPS origin sent to Apple during validation — always pass it explicitly on Node.js (there is no `location.origin` to fall back to). Pass `{ oncancel }` in `callbacks` to be notified when the user dismisses the sheet. You must still wire `session.onpaymentauthorized` yourself. |
167
+ | `getQRPaymentInfo(paymentId, format?)` | Retrieve QR code and recipient info (`GET /payments/{paymentId}/qr-payment/info`). |
168
+
169
+ ### Google Pay
170
+
171
+ **SDK handles:** fetching the pre-filled `paymentDataRequest` config from the GoPay API.
172
+
173
+ **You must wire up:** creating the `PaymentsClient`, rendering the button via `paymentsClient.createButton()` (Google's UX requirement), calling `paymentsClient.loadPaymentData()`, and handling cancellation. When the user dismisses the sheet, `loadPaymentData` rejects with either `{ statusCode: 'CANCELED' }` (Google Pay JS SDK) or a `DOMException` with `name === 'AbortError'` (PaymentRequest API path in some browsers). Check both:
174
+
175
+ ```ts
176
+ } catch (err) {
177
+ const isCancel =
178
+ (err as any)?.statusCode === 'CANCELED' ||
179
+ (err instanceof DOMException && err.name === 'AbortError');
180
+ if (isCancel) { /* user dismissed — update your UI */ }
181
+ else { /* actual error */ }
182
+ }
183
+ ```
184
+
185
+ ```ts
186
+ // Fetch Google Pay configuration for this payment.
187
+ // googlePayInfo contains:
188
+ // environment — "TEST" or "PRODUCTION", passed to PaymentsClient
189
+ // paymentDataRequest — pre-filled PaymentRequest object (allowed networks,
190
+ // transaction info, merchant info, etc.) ready to pass
191
+ // to loadPaymentData()
192
+ const googlePayInfo = await sdk.getGooglePayInfo(payment.id);
193
+
194
+ // Initialise the Google Pay client and request payment data.
195
+ const paymentsClient = new google.payments.api.PaymentsClient({
196
+ environment: googlePayInfo.environment, // "TEST" | "PRODUCTION"
197
+ });
198
+ const paymentData = await paymentsClient.loadPaymentData(
199
+ googlePayInfo.paymentDataRequest,
200
+ );
201
+
202
+ // tokenizationData.token is a JSON string — parse it to extract the required fields.
203
+ const { protocolVersion, signature, signedMessage } = JSON.parse(
204
+ paymentData.paymentMethodData.tokenizationData.token,
205
+ );
206
+ const charge = await sdk.chargePayment(payment.id, {
207
+ payment_instrument: {
208
+ payment_instrument: 'PAYMENT_CARD',
209
+ input: {
210
+ input_type: 'GOOGLE_PAY',
211
+ protocolVersion,
212
+ signature,
213
+ signedMessage,
214
+ },
215
+ },
216
+ });
217
+ ```
218
+
219
+ ### Apple Pay
220
+
221
+ > **Safari only.** `ApplePaySession` is available exclusively in Safari on Apple
222
+ > devices. In other browsers the example page loads `apple-pay-polyfill.js`
223
+ > (a dev-only stub) so the flow can be exercised without a real device.
224
+
225
+ **SDK handles:** merchant validation (`onvalidatemerchant`) and `session.begin()`.
226
+
227
+ **You must wire up:** `session.onpaymentauthorized` — call `charge()` and then `session.completePayment(STATUS_SUCCESS / STATUS_FAILURE)`. Optionally pass `{ oncancel }` to `startApplePaySession` to update your UI when the user dismisses the sheet.
228
+
229
+ ```ts
230
+ // Fetch Apple Pay configuration for this payment.
231
+ // Returns applepayVersion and applePayPaymentRequest (networks, country,
232
+ // currency, total, etc.) needed to construct the ApplePaySession.
233
+ // Must be called before the user-gesture handler that creates the session.
234
+ const applePayInfo = await sdk.getApplePayInfo(payment.id);
235
+
236
+ // Create the ApplePaySession — must be called synchronously from a user-gesture
237
+ // handler (e.g. button click). Do not await anything between the gesture and
238
+ // this call or the browser will block it.
239
+ const session = new ApplePaySession(applePayInfo.applepayVersion, applePayInfo.applePayPaymentRequest);
240
+
241
+ // Step 1 — Payment authorisation.
242
+ // Fires after the user authenticates with Face ID / Touch ID.
243
+ // event.payment.token.paymentData is an Apple-encrypted blob — pass it to charge().
244
+ session.onpaymentauthorized = async (event) => {
245
+ const token = event.payment.token.paymentData;
246
+ const charge = await sdk.chargePayment(payment.id, {
247
+ payment_instrument: {
248
+ payment_instrument: 'PAYMENT_CARD',
249
+ input: {
250
+ input_type: 'APPLE_PAY',
251
+ data: token.data,
252
+ signature: token.signature,
253
+ version: token.version,
254
+ header: token.header,
255
+ },
256
+ },
257
+ });
258
+ // Tell the sheet to show a success or failure animation, then close.
259
+ session.completePayment(
260
+ charge.state === 'SUCCEEDED'
261
+ ? ApplePaySession.STATUS_SUCCESS
262
+ : ApplePaySession.STATUS_FAILURE,
263
+ );
264
+ };
265
+
266
+ // Wire merchant validation, then open the Apple Pay sheet.
267
+ // onvalidatemerchant and oncancel are handled automatically by the SDK.
268
+ // Pass the merchant origin explicitly — required on Node.js (no location.origin).
269
+ sdk.startApplePaySession(payment.id, session, 'https://yourshop.com', {
270
+ oncancel: () => {
271
+ // Update your UI to reflect the cancelled state.
272
+ },
273
+ });
274
+ ```
275
+
276
+ ### Payment status and charge state
277
+
278
+ Poll for payment completion on your server after the customer returns from a redirect or completes a 3DS challenge:
279
+
280
+ ```ts
281
+ // Check whether the payment has been paid, cancelled, or is still pending.
282
+ const status = await sdk.getPaymentStatus(payment.id);
283
+ // status.state: 'CREATED' | 'PAID' | 'CANCELED' | 'PAYMENT_METHOD_CHOSEN'
284
+ // | 'TIMEOUTED' | 'AUTHORIZED' | 'REFUNDED' | 'PARTIALLY_REFUNDED'
285
+
286
+ // Check the outcome of a specific charge attempt (e.g. after 3DS redirect).
287
+ const chargeState = await sdk.getChargeState(payment.id);
288
+ // chargeState.state: 'REQUESTED' | 'PROCESSING' | 'ACTION_REQUIRED'
289
+ // | 'SUCCEEDED' | 'FAILED'
290
+ if (chargeState.action?.redirect_url) {
291
+ // 3DS challenge still in progress — redirect the customer again
292
+ window.location.href = chargeState.action.redirect_url;
293
+ }
294
+ ```
295
+
296
+ ### QR Payment
297
+
298
+ ```ts
299
+ const qrInfo = await sdk.getQRPaymentInfo(payment.id);
300
+ // Optionally request SVG format instead of the default PNG:
301
+ // const qrInfo = await sdk.getQRPaymentInfo(payment.id, 'svg');
302
+
303
+ // Display the QR code (currency-specific)
304
+ const img = document.createElement('img');
305
+ img.src = `data:image/png;base64,${qrInfo.qr_code?.spayd}`; // CZK — Czech SPAYD
306
+ // img.src = `data:image/png;base64,${qrInfo.qr_code?.paybysquare}`; // EUR — Slovak PayBySquare
307
+ // img.src = `data:image/png;base64,${qrInfo.qr_code?.sepa}`; // EUR — SEPA (EPC)
308
+ // img.src = `data:image/png;base64,${qrInfo.qr_code?.mnb_qr}`; // HUF — Hungarian MNB
309
+ document.body.appendChild(img);
310
+ ```
311
+
312
+ ---
313
+
314
+ ### Cards
315
+
316
+ | Method | Description |
317
+ |---|---|
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:
335
+
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. |
339
+
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:
343
+
344
+ ```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));
370
+
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.
377
+
378
+ ```ts
379
+ const payBtn = document.getElementById('pay-btn');
380
+
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
+ },
387
+ });
388
+
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
402
+
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.
404
+
405
+ ```ts
406
+ // 1. Create a payment session first (server-side).
407
+ const payment = await sdk.createPayment(goid, params);
408
+
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;
415
+
416
+ // 3. Charge the payment with the resulting card token.
417
+ const charge = await sdk.chargePayment(payment.id, {
418
+ payment_instrument: {
419
+ payment_instrument: 'PAYMENT_CARD',
420
+ input: {
421
+ input_type: 'CARD_TOKEN',
422
+ card_token: cardToken.token,
423
+ challenge_preferrence: 'AUTO',
424
+ },
425
+ },
426
+ });
427
+
428
+ if (charge.action?.redirect_url) {
429
+ // 3DS authentication required — redirect the customer.
430
+ window.location.href = charge.action.redirect_url;
431
+ }
432
+ ```
433
+
434
+ ### Saved cards
435
+
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:
437
+
438
+ ```ts
439
+ // Retrieve details of a stored card (masked PAN, expiry, scheme, etc.)
440
+ const card = await sdk.getCardDetails(cardToken.card_id);
441
+ console.log(card.masked_pan, card.expiration_month, card.expiration_year);
442
+
443
+ // Charge a subsequent payment using the permanent token (no re-entry of card details)
444
+ const charge = await sdk.chargePayment(payment.id, {
445
+ payment_instrument: {
446
+ payment_instrument: 'PAYMENT_CARD',
447
+ input: { input_type: 'CARD_TOKEN', card_token: card.token },
448
+ },
449
+ });
450
+
451
+ // Delete the card when the customer removes it from their account
452
+ await sdk.deleteCard(card.card_id);
453
+ ```
454
+
455
+ `GET /encryption/public-key` and the JWE construction it enables are **intentionally not part of this SDK's API surface**.
456
+
457
+ ---
458
+
459
+ ### Recurrences
460
+
461
+ | Method | Description |
462
+ |---|---|
463
+ | `createRecurrence(goid, params)` | Create a recurring payment agreement (`POST /eshops/{goid}/recurrences`). Requires `payment:write` scope. |
464
+ | `recurrenceStatus(recId)` | Retrieve the current state of a recurrence (`GET /recurrences/{rec_id}`). Requires `payment:read` scope. |
465
+ | `stopRecurrence(recId)` | Stop a recurrence permanently (`DELETE /recurrences/{rec_id}`). Requires `payment:write` scope. |
466
+ | `startRecurrence(recId, params?)` | Trigger the first charge of a recurrence in `NEW` state (`POST /recurrences/{rec_id}/start`). Requires `payment:write` scope. |
467
+ | `recurrenceNext(recId, params?)` | Charge the next instalment of a `STARTED` recurrence (`POST /recurrences/{rec_id}/next`). Requires `payment:write` scope. |
468
+
469
+ ```ts
470
+ // Create an ON_DEMAND recurrence (customer can be charged any time)
471
+ const recurrence = await sdk.createRecurrence(goid, {
472
+ type: 'ON_DEMAND',
473
+ payment: {
474
+ amount: 1000,
475
+ currency: 'CZK',
476
+ order_number: 'SUB-001',
477
+ customer: { email: 'customer@example.com' },
478
+ callback: {
479
+ notification_url: 'https://yourshop.com/notify',
480
+ return_url: 'https://yourshop.com/return',
481
+ },
482
+ },
483
+ });
484
+
485
+ // Start the first charge (sets recurrence to STARTED)
486
+ await sdk.startRecurrence(recurrence.id);
487
+
488
+ // Charge subsequent instalments at will
489
+ await sdk.recurrenceNext(recurrence.id, { amount: 1000, order_number: 'SUB-002' });
490
+
491
+ // Stop the recurrence when the subscription ends
492
+ await sdk.stopRecurrence(recurrence.id);
493
+ ```
494
+
495
+ ---
496
+
497
+ ### Refunds
498
+
499
+ | Method | Description |
500
+ |---|---|
501
+ | `refundPayment(paymentId, params)` | Refund a payment fully or partially (`POST /payments/{paymentId}/refunds`). Requires `payment:write` scope. |
502
+ | `listRefunds(paymentId)` | List all refunds for a payment (`GET /payments/{paymentId}/refunds`). Requires `payment:read` scope. |
503
+ | `getRefund(refundId)` | Retrieve details of a single refund (`GET /refunds/{refundId}`). Requires `payment:read` scope. |
504
+
505
+ ---
506
+
507
+ ### Payment Links
508
+
509
+ | Method | Description |
510
+ |---|---|
511
+ | `createPaymentLink(goid, params)` | Create a shareable payment link (`POST /eshops/{goid}/links`). Requires `payment:write` scope. |
512
+ | `linkStatus(linkId)` | Retrieve the current state of a payment link (`GET /links/{link_id}`). Requires `payment:write` scope. |
513
+ | `disableLink(linkId)` | Disable a link so it can no longer be used (`DELETE /links/{link_id}`). Requires `payment:write` scope. |
514
+
515
+ ```ts
516
+ // Create a reusable link that expires at a set date
517
+ const link = await sdk.createPaymentLink(goid, {
518
+ reusable: true,
519
+ expires_at: '2027-12-31T23:59:59',
520
+ payment: {
521
+ amount: 500,
522
+ currency: 'CZK',
523
+ order_number: 'LINK-001',
524
+ customer: { email: 'customer@example.com' },
525
+ callback: {
526
+ notification_url: 'https://yourshop.com/notify',
527
+ return_url: 'https://yourshop.com/return',
528
+ },
529
+ },
530
+ });
531
+ console.log(link.url); // share this URL with the customer
532
+
533
+ // Check current state (active, stop_reason, etc.)
534
+ const status = await sdk.linkStatus(link.id);
535
+
536
+ // Disable the link when it should no longer accept payments
537
+ await sdk.disableLink(link.id);
538
+ ```
539
+
540
+ ---
541
+
542
+ ## Errors
543
+
544
+ The SDK throws two typed error classes so you can handle them precisely.
545
+
546
+ ### `GoPaySDKError`
547
+
548
+ Thrown for lifecycle and configuration errors — e.g. no token available, token refresh failed, or an invalid token response was received.
549
+
550
+ Every `GoPaySDKError` carries a machine-readable `errorCode` from the `GoPayErrorCodes` constant:
551
+
552
+ | `errorCode` | When thrown |
553
+ |---|---|
554
+ | `AUTH_TOKEN_MISSING` | A protected API call was made before authenticating. |
555
+ | `AUTH_REFRESH_FAILED` | Token re-authentication (client_credentials grant) failed. |
556
+ | `AUTH_INVALID_RESPONSE` | The OAuth2 endpoint returned an incomplete token response. |
557
+ | `AUTH_CREDENTIALS_MISSING` | No client credentials stored — call `authenticate()` first. |
558
+ | `AUTH_INVALID_TOKEN` | A JWT is malformed or missing the `sub` claim. |
559
+ | `AUTH_UNAUTHORIZED` | Still 401 after a successful token refresh — check OAuth2 scopes. |
560
+ | `NETWORK_TIMEOUT` | Request exceeded `requestTimeoutMs`. |
561
+ | `NETWORK_ERROR` | Network-level failure (no response received). |
562
+ | `CARD_FORM_ERROR` | The card form iframe encountered an error (e.g. URL unavailable, init timeout, or encryption failure). |
563
+
564
+ ```ts
565
+ import { GoPaySDKError, GoPayErrorCodes } from '@gopaycz/gopay-js-sdk';
566
+
567
+ try {
568
+ await sdk.createPayment(goid, params);
569
+ } catch (err) {
570
+ if (err instanceof GoPaySDKError) {
571
+ if (err.errorCode === GoPayErrorCodes.AUTH_TOKEN_MISSING) {
572
+ // redirect to login
573
+ } else if (err.errorCode === GoPayErrorCodes.NETWORK_TIMEOUT) {
574
+ // show retry UI
575
+ } else {
576
+ console.error('SDK error:', err.message);
577
+ }
578
+ }
579
+ }
580
+ ```
581
+
582
+ ### `GoPayHTTPError`
583
+
584
+ Thrown when the GoPay API returns a non-2xx response. Exposes the HTTP `status` code and the parsed response `body`.
585
+
586
+ ```ts
587
+ import { GoPayHTTPError } from '@gopaycz/gopay-js-sdk';
588
+
589
+ try {
590
+ await sdk.authenticate({ grant_type: 'client_credentials', ... });
591
+ } catch (err) {
592
+ if (err instanceof GoPayHTTPError) {
593
+ console.error(`HTTP ${err.status}`, err.body);
594
+ }
595
+ }
596
+ ```
597
+
598
+ | Property | Type | Description |
599
+ |---|---|---|
600
+ | `status` | `number` | HTTP status code (e.g. `401`, `422`). |
601
+ | `body` | `unknown` | Parsed JSON response body, or raw text if JSON parsing failed. |
602
+
603
+ ### Centralised error handling with `onError`
604
+
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:
606
+
607
+ ```ts
608
+ const sdk = createGoPaySDK({
609
+ environment: 'production',
610
+ onError(err) {
611
+ analytics.track('gopay_error', { code: err instanceof GoPaySDKError ? err.errorCode : err.status });
612
+ },
613
+ });
614
+ ```
615
+
616
+ The callback fires synchronously before the error propagates to the caller.
617
+
618
+ ---
619
+
620
+ ## Environments
621
+
622
+ | Environment | Base URL |
623
+ |---|---|
624
+ | `sandbox` | `https://api.sandbox.gopay.com/api/merchant/payments/4.0` |
625
+ | `production` | `https://api.gopay.com/api/merchant/payments/4.0` |
626
+
627
+ ---
628
+
629
+ ## Interactive example
630
+
631
+ An interactive developer page is included in the repository — it exercises every SDK method against the real API.
632
+
633
+ Browse the source at [github.com/gopaycommunity/gopay-js-sdk](https://github.com/gopaycommunity/gopay-js-sdk) (`example/` directory).