Documentation
¶
Overview ¶
Package dominaite is the server-side Go client for the Dominaite merchant API.
One call from your backend opens a hosted checkout session; a two-line script tag renders the payment widget on your page. Card details go straight from your customer's browser into the widget. They never touch your server or this SDK, which keeps your PCI scope minimal (SAQ A).
Keep your API secret on the server. Never ship it to a browser, never commit it, never log it.
client, err := dominaite.New(
os.Getenv("DOMINAITE_KEY_ID"),
os.Getenv("DOMINAITE_SECRET"),
dominaite.WithBaseURL(dominaite.SandboxBaseURL), // required
)
if err != nil {
return err
}
key, err := dominaite.OrderIdempotencyKey("checkout", "order-1042", 2500, "EUR")
if err != nil {
return err
}
session, err := client.CreateCheckoutSession(ctx, dominaite.CreateCheckoutSessionParams{
Amount: 2500, // minor units: 25.00 EUR
Currency: "EUR",
OrderReference: "order-1042",
IdempotencyKey: key, // "checkout-order-1042-2500-EUR"
})
Hand session.CashierKey and session.CashierToken to the embed snippet.
Index ¶
- Constants
- Variables
- func CurrencyExponent(currency string) (int, error)
- func IsPaid(status string) bool
- func IsRefundTerminal(status string) bool
- func IsTerminal(status string) bool
- func OrderIdempotencyKey(scope, orderID string, amountMinor int64, currency string) (string, error)
- func Sign(in SignInput) string
- func ToMinorUnits(amount, currency string) (int64, error)
- type APIError
- type Agreement
- type AgreementConsent
- type AgreementWebhookData
- type AuthError
- type ChargeError
- type ChargePaymentMethodParams
- type ChargeWebhookData
- type ChargeWebhookPaymentMethod
- type CheckoutSession
- type CheckoutStatus
- type CheckoutStatusAgreement
- type Client
- func (c *Client) CancelAgreement(ctx context.Context, agreementID string) error
- func (c *Client) ChargePaymentMethod(ctx context.Context, paymentMethodID string, params ChargePaymentMethodParams) (*PaymentMethodCharge, error)
- func (c *Client) CreateAgreement(ctx context.Context, params CreateAgreementParams) (*Agreement, error)
- func (c *Client) CreateCheckoutSession(ctx context.Context, params CreateCheckoutSessionParams) (*CheckoutSession, error)
- func (c *Client) CreateCheckoutSessionWithRetry(ctx context.Context, params CreateCheckoutSessionParams, opts RetryOptions) (*CheckoutSession, error)deprecated
- func (c *Client) CreatePlan(ctx context.Context, params CreatePlanParams) (*Plan, error)
- func (c *Client) CreateRefund(ctx context.Context, transactionID string, params CreateRefundParams) (*Refund, error)
- func (c *Client) CreateWebhookEndpoint(ctx context.Context, params CreateWebhookEndpointParams) (*WebhookEndpoint, error)
- func (c *Client) DeletePlan(ctx context.Context, planID string) error
- func (c *Client) DeleteWebhookEndpoint(ctx context.Context, webhookID string) error
- func (c *Client) GetAgreement(ctx context.Context, agreementID string) (*Agreement, error)
- func (c *Client) GetIntegrationContract(ctx context.Context) (*IntegrationContract, error)
- func (c *Client) GetPlan(ctx context.Context, planID string) (*Plan, error)
- func (c *Client) GetRefund(ctx context.Context, transactionID, refundID string) (*Refund, error)
- func (c *Client) GetStatus(ctx context.Context, transactionID string) (*CheckoutStatus, error)
- func (c Client) GoString() string
- func (c *Client) ListPlans(ctx context.Context) ([]Plan, error)
- func (c *Client) ListRefunds(ctx context.Context, transactionID string) (*RefundList, error)
- func (c *Client) ListTransactions(ctx context.Context, params ListTransactionsParams) (*TransactionList, error)
- func (c *Client) ListWebhookEndpoints(ctx context.Context) ([]WebhookEndpoint, error)
- func (c *Client) Ping(ctx context.Context) (*Ping, error)
- func (c *Client) RevokePaymentMethod(ctx context.Context, paymentMethodID string) error
- func (c Client) String() string
- func (c *Client) UpdatePlan(ctx context.Context, planID string, params UpdatePlanParams) (*Plan, error)
- type Consent
- type CreateAgreementParams
- type CreateCheckoutSessionParams
- type CreatePlanParams
- type CreateRefundParams
- type CreateWebhookEndpointParams
- type Customer
- type Error
- type Integration
- type IntegrationContract
- type ListTransactionsParams
- type Option
- type PaymentMethodCharge
- type Ping
- type Plan
- type RateLimitError
- type Refund
- type RefundError
- type RefundList
- type RefusalError
- type RetryOptionsdeprecated
- type RevokeError
- type SignInput
- type StoredPaymentMethod
- type Transaction
- type TransactionList
- type TransportError
- type UpdatePlanParams
- type ValidationError
- type VerifyOption
- type WebhookData
- type WebhookEndpoint
- type WebhookEvent
- type WebhookVerificationError
Examples ¶
Constants ¶
const ( AgreementStatusPending = "pending" AgreementStatusActive = "active" AgreementStatusPastDue = "past_due" AgreementStatusCancelled = "cancelled" AgreementStatusCompleted = "completed" AgreementStatusFailed = "failed" )
Agreement status values, in the gateway's own order. pending: created, not paid yet. active: with scheduled charges on, the platform charges the card each period. past_due: the platform gave up on the current period on this card (PastDueReason says why); it stays past_due until the period is paid on another card, or until you cancel it. cancelled, completed and failed are final.
const ( PastDueReasonCardExpired = "card_expired" PastDueReasonCardRevoked = "card_revoked" PastDueReasonHardDecline = "hard_decline" PastDueReasonDunningExhausted = "dunning_exhausted" PastDueReasonSCARequired = "sca_required" PastDueReasonUnprocessable = "unprocessable" )
Why an agreement is past_due, on Agreement.PastDueReason, in the gateway's own order. PastDueReasonSCARequired: the issuer asked for customer authentication, which no unattended retry can pass.
const ( // SandboxBaseURL is the sandbox merchant API. Use it with your sandbox keys. SandboxBaseURL = "https://api.staging.dominaite.com/payments" // ProductionBaseURL is the production merchant API. Use it with your live keys. ProductionBaseURL = "https://api.dominaite.com/payments" // SessionsPath is the canonical path that gets signed. POST creates a // session; GET SessionsPath + "/{transactionId}" reads its status. SessionsPath = "/merchant-api/checkout/sessions" // PaymentMethodsPath is the canonical path of stored payment methods. POST // PaymentMethodsPath + "/{paymentMethodId}/charges" charges one; DELETE // PaymentMethodsPath + "/{paymentMethodId}" revokes it. PaymentMethodsPath = "/merchant-api/payment-methods" // PaymentsPath is the canonical path of payments. POST PaymentsPath + // "/{transactionId}/refunds" refunds one, GET on the same path lists its // refunds, and GET PaymentsPath + "/{transactionId}/refunds/{refundId}" // reads a refund back. PaymentsPath = "/merchant-api/payments" // PlansPath is the canonical path of subscription plans. POST creates a // plan, GET lists them; GET, PATCH and DELETE PlansPath + "/{planId}" // read, change and archive one. PlansPath = "/merchant-api/plans" // AgreementsPath is the canonical path of subscription agreements. POST // creates an agreement; GET and DELETE AgreementsPath + "/{agreementId}" // read and cancel one. AgreementsPath = "/merchant-api/agreements" // WebhooksPath is the canonical path of your webhook endpoints. POST // registers one, GET lists them, DELETE WebhooksPath + "/{webhookId}" // deletes one. WebhooksPath = "/merchant-api/webhooks" // TransactionsPath is the canonical path of your transaction list. GET // reads one page. The query string is never part of the signature. TransactionsPath = "/merchant-api/transactions" // PingPath is the credentials-and-clock smoke test. It creates nothing. PingPath = "/merchant-api/ping" // IntegrationContractPath is the API's machine-readable contract. GET // reads it. The route is public, so it needs no scope. IntegrationContractPath = "/merchant-api/integration/contract" // Version is this SDK's version, reported in the User-Agent. Version = "0.7.0" )
const ( // ErrorCodeStorefrontNotWhitelisted (HTTP 409): the storefront's domain is // not whitelisted with the payment provider yet, and the environment // requires it. Nothing was minted. Ask Dominaite support to finish the // domain whitelisting; retrying will not help until then. ErrorCodeStorefrontNotWhitelisted = "STOREFRONT_NOT_WHITELISTED" // ErrorCodeStorefrontInactive (HTTP 409): the storefront was deactivated // or deleted. Reactivate it in the dashboard or use another one. ErrorCodeStorefrontInactive = "STOREFRONT_INACTIVE" // ErrorCodeStorefrontMismatch (HTTP 400): the API key is bound to one // storefront and the request named another. A replay of a key minted for // a different storefront answers the same code as a *RefusalError. ErrorCodeStorefrontMismatch = "STOREFRONT_MISMATCH" // ErrorCodeAlreadyProcessed (refusal): the payment for this idempotency // key already moved money. Mark the order paid; the key is spent. ErrorCodeAlreadyProcessed = "ALREADY_PROCESSED" // ErrorCodePriorAttemptFailed (refusal): the attempt for this key ended // failed, cancelled or abandoned. The key is spent; try again under a // new one. ErrorCodePriorAttemptFailed = "PRIOR_ATTEMPT_FAILED" // ErrorCodeDuplicateRequest (refusal): a session for this key exists but // cannot be handed back yet. Re-POST the SAME key after about a second. ErrorCodeDuplicateRequest = "DUPLICATE_REQUEST" // a charge): card payments are off right now. Nothing was minted or // charged; retry later with the SAME key. ErrorCodePaymentProcessingUnavailable = "PAYMENT_PROCESSING_UNAVAILABLE" // ErrorCodeIdempotencyKeyReused (refusal): this key was first used for a // different amount, currency or card-saving choice. A re-priced order // needs a new key; OrderIdempotencyKey derives one. ErrorCodeIdempotencyKeyReused = "IDEMPOTENCY_KEY_REUSED" )
Error codes the checkout session route answers with, for branching on the ErrorCode of a *RefusalError or an *APIError instead of on message text. The same strings can also arrive on a *ChargeError (see the ChargeError* constants for what they mean there).
The storefront codes mean the session was refused because of the online location (storefront) it would be filed under. They arrive as an *APIError with HTTPStatus set, not as a refusal, and a retry will not change them:
var apiErr *dominaite.APIError
if errors.As(err, &apiErr) && apiErr.ErrorCode == dominaite.ErrorCodeStorefrontNotWhitelisted {
// the site's domain is not whitelisted with the payment provider yet
}
const ( // ErrorCodeAgreementNotFound: no agreement with this id under your // account. The agr_ id is case-sensitive; send it exactly as it was // returned. The agreement routes answer the same code as a 404 *APIError. ErrorCodeAgreementNotFound = "AGREEMENT_NOT_FOUND" // ErrorCodeAgreementNotPending: the agreement is already active, // cancelled, completed or failed, so there is no first payment left to // take. ErrorCodeAgreementNotPending = "AGREEMENT_NOT_PENDING" // ErrorCodeAgreementAmountMismatch: the amount or the currency is not the // agreement's own. On a plan without a trial the session must charge // exactly the agreement's amount; the currency must always match. ErrorCodeAgreementAmountMismatch = "AGREEMENT_AMOUNT_MISMATCH" // ErrorCodeAgreementConsentRequired: neither the session nor the // agreement carries consent to a version of the platform text. Send // Consent, with all three fields, on the session. ErrorCodeAgreementConsentRequired = "AGREEMENT_CONSENT_REQUIRED" // ErrorCodeAgreementSessionOpen: a session for this agreement is still // open, and TransactionID names it. Mint a new session with a new key // only after that one has expired. ErrorCodeAgreementSessionOpen = "AGREEMENT_SESSION_OPEN" // ErrorCodeAgreementAlreadyPaid: the session of this agreement was paid, // and the agreement is still pending. Read the status of TransactionID. // If the agreement stays pending, contact support with both ids. Never // mint again: that would charge the customer twice. ErrorCodeAgreementAlreadyPaid = "AGREEMENT_ALREADY_PAID" // ErrorCodeAgreementVariableAmountUnsupported: the agreement has a // MaxAmountPerPeriod, and a session can take the first payment of a // fixed-amount agreement only. ErrorCodeAgreementVariableAmountUnsupported = "AGREEMENT_VARIABLE_AMOUNT_UNSUPPORTED" // ErrorCodeSubscriptionsNotEnabled: subscriptions are not switched on for // your account yet. Plans and agreements can already be created. Contact // support; do not retry on a schedule. ErrorCodeSubscriptionsNotEnabled = "SUBSCRIPTIONS_NOT_ENABLED" )
Subscription session refusal codes, in the gateway's own order: the RefusalError.ErrorCode of a session created with AgreementID. They arrive as HTTP 200 with success false, like every session refusal. Nothing was created under any of them, and none changes on a retry with the same body.
const ( // ErrorCodePlanNotFound (404): no plan with this id under your account. // The plan_ id is case-sensitive; send it exactly as it was returned. ErrorCodePlanNotFound = "PLAN_NOT_FOUND" // ErrorCodePlanNotActive (409, CreateAgreement): the plan is archived. ErrorCodePlanNotActive = "PLAN_NOT_ACTIVE" // ErrorCodeAgreementEnded (409, CancelAgreement): the agreement already // completed or failed, so there is nothing left to cancel. ErrorCodeAgreementEnded = "AGREEMENT_ENDED" // ErrorCodePaymentMethodNotFound (404, CreateAgreement): no stored // payment method with this id under your account. ErrorCodePaymentMethodNotFound = "PAYMENT_METHOD_NOT_FOUND" // ErrorCodePaymentMethodNotActive (409, CreateAgreement): the stored // payment method is revoked, expired or retired. Leave it out and let the // subscription session save a fresh card. ErrorCodePaymentMethodNotActive = "PAYMENT_METHOD_NOT_ACTIVE" // ErrorCodePaymentMethodCurrencyMismatch (422, CreateAgreement): the card // was saved from a payment in another currency than the plan's. ErrorCodePaymentMethodCurrencyMismatch = "PAYMENT_METHOD_CURRENCY_MISMATCH" // ErrorCodePaymentMethodCurrencyUnknown (422, CreateAgreement): the // currency of the card cannot be established, so it cannot back an // agreement. Leave it out and let the subscription session save a fresh // card. ErrorCodePaymentMethodCurrencyUnknown = "PAYMENT_METHOD_CURRENCY_UNKNOWN" // ErrorCodeMaxAmountBelowPlanAmount (422, CreateAgreement): // MaxAmountPerPeriod is below the plan's amount. ErrorCodeMaxAmountBelowPlanAmount = "MAX_AMOUNT_BELOW_PLAN_AMOUNT" )
Codes of the plan and agreement routes. They arrive on APIError.ErrorCode, with the status on APIError.HTTPStatus. ErrorCodeAgreementNotFound (404), ErrorCodeAgreementConsentRequired (422: CreateAgreement names a stored payment method or a MaxAmountPerPeriod without Consent) and ErrorCodeIdempotencyKeyReused (422: the key was first used for a different plan or agreement) arrive there too. The list is additive: treat a 4xx code you do not know as not retryable. A 5xx is a *TransportError, with the code on TransportError.ErrorCode when there was one: nothing was created, so retry with the same key.
const ( // ErrorCodeWebhookURLRejected (400): the URL is not https, is malformed, // does not resolve, or points at a private, loopback or link-local host. // The message says which. ErrorCodeWebhookURLRejected = "WEBHOOK_URL_REJECTED" // ErrorCodeWebhookLimitReached (409): you already have the maximum of 25 // active endpoints. Delete one first. ErrorCodeWebhookLimitReached = "WEBHOOK_LIMIT_REACHED" // ErrorCodeWebhookAlreadyRegistered (409): this key registered an endpoint // more than a day ago, and that endpoint still exists. Its id is at // error.params.webhookId in APIError.Raw. Use a fresh key for a new // endpoint. ErrorCodeWebhookAlreadyRegistered = "WEBHOOK_ALREADY_REGISTERED" // ErrorCodeWebhookNotFound (404, DeleteWebhookEndpoint): no endpoint with // this id under your account. After a lost answer, treat it as done. ErrorCodeWebhookNotFound = "WEBHOOK_NOT_FOUND" )
Codes of the webhook endpoint routes. They arrive on APIError.ErrorCode, with the status on APIError.HTTPStatus. ErrorCodeIdempotencyKeyReused (422: the key was first used for a different URL or events) and ErrorCodeDuplicateRequest (409: a request with this key is in progress; retry with the same key after a second) arrive there too. Nothing was registered under any of them.
const ( // ErrorCodeInvalidAPIKey: the key id is unknown, malformed or revoked, or // it belongs to another environment. ErrorCodeInvalidAPIKey = "INVALID_API_KEY" // ErrorCodeInvalidSignature: the signature does not match. Check the // secret and that the signed body is the body sent. ErrorCodeInvalidSignature = "INVALID_SIGNATURE" // ErrorCodeTimestampOutOfRange: X-Timestamp is more than 300 seconds // from the server clock. Sync this server's clock. ErrorCodeTimestampOutOfRange = "TIMESTAMP_OUT_OF_RANGE" // ErrorCodeIPNotAllowed: the caller's IP address is not on the key's // allowlist. ErrorCodeIPNotAllowed = "IP_NOT_ALLOWED" )
Authentication codes (HTTP 401), on AuthError.ErrorCode. None is retryable: fix the key id, the secret, the server clock or the allowlist.
const ( // behind it is unavailable. A *TransportError carries it on ErrorCode; a // revoke carries it on a *RevokeError. ErrorCodeMerchantAPIUnavailable = "MERCHANT_API_UNAVAILABLE" // ErrorCodeInternalError (HTTP 500): the API failed before it could // answer. A *TransportError carries it on ErrorCode. ErrorCodeInternalError = "INTERNAL_ERROR" )
Transient codes: nothing was created and a retry with the SAME key can succeed.
const ( ErrorCodeRequired = "REQUIRED" ErrorCodeMinValue = "MIN_VALUE" ErrorCodeMaxLength = "MAX_LENGTH" ErrorCodeInvalidFormat = "INVALID_FORMAT" ErrorCodeInvalidSelection = "INVALID_SELECTION" ErrorCodeDateInFuture = "DATE_IN_FUTURE" ErrorCodeInvalidEmail = "INVALID_EMAIL" ErrorCodeInvalidCurrency = "INVALID_CURRENCY" )
Field codes of a validation failure: the code of each entry in error.validationErrors on APIError.Raw. A plan create also answers ErrorCodeInvalidCurrency and ErrorCodeInvalidSelection as the envelope code.
const ( // ErrorCodeRateLimitExceeded: the key or the IP address sent more // requests than it may. Retry-After says how long to wait. The API sends // no Retry-After when it locks an address out after repeated failed // sign-ins; fix the credentials before you retry. ErrorCodeRateLimitExceeded = "RATE_LIMIT_EXCEEDED" // ErrorCodeChargeVelocityExceeded (ChargePaymentMethod): a volume limit // refused the charge before anything was placed. error.params.window and // error.params.limit on Raw say which limit. A charge that keeps hitting // it is a pattern to look into, not to retry around. ErrorCodeChargeVelocityExceeded = "CHARGE_VELOCITY_EXCEEDED" // ErrorCodeChargeDeclineRateExceeded (ChargePaymentMethod): the key // collected too many declined charges in a short window, so its charge // route is locked for a while. The other routes keep working. If this is // a genuine billing run with many declines, contact support. ErrorCodeChargeDeclineRateExceeded = "CHARGE_DECLINE_RATE_EXCEEDED" )
Rate limit codes, on RateLimitError.ErrorCode. Nothing was created or charged under any of them; retry with the SAME idempotency key once the wait is over.
const ( // ChargeErrorPaymentMethodNotActive (409): the method is revoked, expired // or retired; ask the customer for another card via a hosted session with // SaveCard. ChargeErrorPaymentMethodNotActive = "PAYMENT_METHOD_NOT_ACTIVE" // ChargeErrorDuplicateRequest (409): a request with this key is still in // flight; retry with the SAME key in a moment. ChargeErrorDuplicateRequest = "DUPLICATE_REQUEST" // ChargeErrorIdempotencyKeyReused (422): same key, different body or // method; a bug on your side. ChargeErrorIdempotencyKeyReused = "IDEMPOTENCY_KEY_REUSED" // ChargeErrorOutcomeUnknown (502): the provider gave no verdict and the // charge MAY have happened. Charge is set: poll // GetStatus(Charge.TransactionID) or wait for the webhook. Never retry // under a new key. ChargeErrorOutcomeUnknown = "CHARGE_OUTCOME_UNKNOWN" // ChargeErrorFailed (502): nothing was charged. Charge is set when a row // exists (its DeclineClass and DeclineCode are empty), nil when the // provider refused before one. ChargeErrorFailed = "CHARGE_FAILED" // ChargeErrorChargesDisabled (503): nothing was charged. The API answers // it until we switch saved-card charges on for your account, and when we // pause charges for a short time. The code is the same in both cases. If // we never switched saved-card charges on for your account, a retry does // not help: ask us. If they are on, retry later with the SAME key. ChargeErrorChargesDisabled = "PAYMENT_METHOD_CHARGES_DISABLED" // with the SAME key. ChargeErrorProcessingUnavailable = "PAYMENT_PROCESSING_UNAVAILABLE" // ChargeErrorPaymentMethodNotFound (404): no stored payment method with // this id under your account. ChargeErrorPaymentMethodNotFound = "PAYMENT_METHOD_NOT_FOUND" // ChargeErrorInProgress (409): another charge of this payment method is // still running. Retry with the SAME key in a moment. ChargeErrorInProgress = "CHARGE_IN_PROGRESS" // ChargeErrorCurrencyMismatch (422): the currency is not the currency the // card was saved in. ChargeErrorCurrencyMismatch = "CHARGE_CURRENCY_MISMATCH" // ChargeErrorAgreementNotFound (404): no agreement with the AgreementID // under your account. ChargeErrorAgreementNotFound = "AGREEMENT_NOT_FOUND" // ChargeErrorAgreementMethodMismatch (422): the payment method is not the // card of the agreement. ChargeErrorAgreementMethodMismatch = "AGREEMENT_METHOD_MISMATCH" // ChargeErrorAgreementConsentMissing (422): the agreement has no recorded // consent, so it cannot be charged. ChargeErrorAgreementConsentMissing = "AGREEMENT_CONSENT_MISSING" // ChargeErrorAgreementCurrencyMismatch (422): the currency is not the // agreement's currency. ChargeErrorAgreementCurrencyMismatch = "AGREEMENT_CURRENCY_MISMATCH" // ChargeErrorAgreementAmountMismatch (422): the amount is not the amount // of a fixed agreement. ChargeErrorAgreementAmountMismatch = "AGREEMENT_AMOUNT_MISMATCH" // ChargeErrorAmountAboveAgreement (422): the amount is above the // MaxAmountPerPeriod of a variable agreement. ChargeErrorAmountAboveAgreement = "CHARGE_AMOUNT_ABOVE_AGREEMENT" // ChargeErrorAgreementPeriodAlreadyCharged (422): this period of the // agreement was already charged. ChargeErrorAgreementPeriodAlreadyCharged = "AGREEMENT_PERIOD_ALREADY_CHARGED" // ChargeErrorAgreementEnded (422): the agreement is cancelled, completed or // failed. ChargeErrorAgreementEnded = "AGREEMENT_ENDED" // ChargeErrorAgreementNotStarted (422): the agreement is not active yet. ChargeErrorAgreementNotStarted = "AGREEMENT_NOT_STARTED" // ChargeErrorAmountAboveCap (422): the amount is above the per-charge cap // of your account. error.params.amount and error.params.cap on Raw say // both values. ChargeErrorAmountAboveCap = "CHARGE_AMOUNT_ABOVE_CAP" )
Charge error codes, in the gateway's own order: the codes ChargePaymentMethod returns as a *ChargeError. CHARGE_DECLINED (HTTP 402) is deliberately not one of them: a decline is a charge result with Status ChargeStatusFailed, not an error.
const ( // RevokeErrorUpstreamContract (502): the provider refused the deletion for // a reason a retry will not fix; contact support with the payment method id. RevokeErrorUpstreamContract = "UPSTREAM_CONTRACT_ERROR" // throttling; retry later. RevokeErrorMerchantAPIUnavailable = "MERCHANT_API_UNAVAILABLE" )
Revoke error codes, in the gateway's own order: the codes RevokePaymentMethod returns as a *RevokeError. Nothing changed under either.
const ( // RefundErrorPaymentNotFound (404): no card-not-present payment with this // id under your account. Not retryable. RefundErrorPaymentNotFound = "PAYMENT_NOT_FOUND" // RefundErrorRefundNotFound (404, GetRefund only): no refund with this id // on this payment. Right after CreateRefund the refund may not be picked // up yet: poll again for up to 60 seconds, after that the id is unknown. RefundErrorRefundNotFound = "REFUND_NOT_FOUND" // RefundErrorPaymentNotRefundable (422): the payment is not paid, already // fully refunded, or everything left on it is already being refunded. // Nothing was queued and the key is not burnt. RefundErrorPaymentNotRefundable = "PAYMENT_NOT_REFUNDABLE" // RefundErrorAmountExceeded (422): the amount is more than what is left to // refund, counting refunds still in progress; the message names the // amount left. Nothing was queued and the key is not burnt. RefundErrorAmountExceeded = "REFUND_AMOUNT_EXCEEDED" // RefundErrorIdempotencyKeyReused (422): the key was first used for a // different amount, reason or payment. Use a fresh key for a genuinely // new refund. RefundErrorIdempotencyKeyReused = "IDEMPOTENCY_KEY_REUSED" // RefundErrorDuplicateRequest (409): a request with this key is being // processed right now. Retry with the SAME key after a second, for up to // 120 seconds. RefundErrorDuplicateRequest = "DUPLICATE_REQUEST" // RefundErrorIdempotencyKeyRequired (400): the Idempotency-Key header was // missing or too long. The SDK refuses such a key before sending, so this // only arrives if something between you and the API dropped the header. RefundErrorIdempotencyKeyRequired = "IDEMPOTENCY_KEY_REQUIRED" )
Refund error codes, in the gateway's own order: the codes CreateRefund and GetRefund return as a *RefundError.
const ( // RefundFailureAmountExceeded: by the time the refund ran, less was left // to refund than it asked for. RefundFailureAmountExceeded = "REFUND_AMOUNT_EXCEEDED" // RefundFailurePaymentNotRefundable: by the time the refund ran, the // payment could no longer be refunded. RefundFailurePaymentNotRefundable = "PAYMENT_NOT_REFUNDABLE" // RefundFailureFailed: the refund could not be completed. Retry with a new // idempotency key if it is still wanted. RefundFailureFailed = "REFUND_FAILED" )
Refund failure codes, in the gateway's own order: the values of Refund.FailureCode on a failed refund. They are not errors: CreateRefund and GetRefund return the refund with Status RefundStatusFailed. Treat a value you do not recognise as RefundFailureFailed.
const ( // WebhookReasonMalformedSignature means the signature header was missing, // empty, or not in the "t=...,v1=..." shape (no t, no v1, or a v1 that is // not hex). Nothing could be checked. WebhookReasonMalformedSignature = "MALFORMED_SIGNATURE" // WebhookReasonSignatureMismatch means the MAC did not match. Either the // secret is wrong or the payload is not the bytes that were signed. Treat // the request as hostile. WebhookReasonSignatureMismatch = "SIGNATURE_MISMATCH" // WebhookReasonTimestampOutOfTolerance means the MAC was valid but the // timestamp is too far from now: a replay of a genuine old delivery, or // your server clock has drifted. Check NTP before widening the tolerance. WebhookReasonTimestampOutOfTolerance = "TIMESTAMP_OUT_OF_TOLERANCE" // WebhookReasonMalformedPayload means the signature was good but the body // was not valid JSON. Surprising - the bytes are authentic - so log it // rather than silently dropping it. WebhookReasonMalformedPayload = "MALFORMED_PAYLOAD" // WebhookReasonInvalidTolerance means the caller passed a negative // tolerance to WithWebhookTolerance. That is your bug, not the sender's: // nothing about the delivery was checked. Use 0 to require an exact // timestamp, or a positive window. WebhookReasonInvalidTolerance = "INVALID_TOLERANCE" )
Reasons reported on WebhookVerificationError.Reason.
const ( // NoticeHeader is the response header in which the API tells an SDK that // it is deprecated: deprecated; current="0.7.0"; sunset="2027-01-31"; info="https://...". NoticeHeader = "Dominaite-Sdk-Notice" // RequestIDHeader names the API's id for one call. Quote it to support. RequestIDHeader = "Dominaite-Request-Id" )
const ( PlanStatusActive = "active" PlanStatusArchived = "archived" )
Plan status values, in the gateway's own order. Only an active plan accepts new agreements.
const ( IntervalUnitDay = "day" IntervalUnitWeek = "week" IntervalUnitMonth = "month" IntervalUnitYear = "year" )
Interval units of a plan's billing period. The interval is at most one year: 365 days, 52 weeks, 12 months or 1 year.
const ( TransactionTypePayment = "payment" TransactionTypeCharge = "charge" TransactionTypeVerification = "verification" )
Transaction types on Transaction.Type, in the gateway's own order. TransactionTypePayment: the payer paid on the checkout. TransactionTypeCharge: a saved card was charged, by you or by the subscription scheduler. TransactionTypeVerification: a verify-only session saved a card. It is not a purchase, so leave it out of revenue.
const ( StatusPending = "pending" StatusProcessing = "processing" StatusSucceeded = "succeeded" StatusFailed = "failed" StatusRefunded = "refunded" StatusPartiallyRefunded = "partially_refunded" StatusCancelled = "cancelled" StatusDisputed = "disputed" StatusRequiresCapture = "requires_capture" StatusAbandoned = "abandoned" )
Transaction status values returned by GetStatus.
const ( PaymentMethodCard = "card" PaymentMethodWallet = "wallet" PaymentMethodBankTransfer = "bank_transfer" PaymentMethodSepa = "sepa" )
Payment method categories reported in CheckoutStatus.PaymentMethod.
const ( WalletTypeApplePay = "apple_pay" WalletTypeGooglePay = "google_pay" WalletTypeSamsungPay = "samsung_pay" )
Wallets reported in CheckoutStatus.WalletType.
const ( StoredPaymentMethodStatusActive = "active" StoredPaymentMethodStatusRevoked = "revoked" StoredPaymentMethodStatusExpired = "expired" StoredPaymentMethodStatusRetired = "retired" )
Stored payment method status values, in the gateway's own order.
Only active methods can be charged. StoredPaymentMethodStatusRevoked is what Client.RevokePaymentMethod leaves behind; StoredPaymentMethodStatusExpired means the card's expiry date has passed; StoredPaymentMethodStatusRetired means the platform stopped the card on its own (RetiredReason says why) and it never becomes active again, so ask the customer to save a card again.
const ( RetiredReasonHardDecline = "hard_decline" RetiredReasonChargeback = "chargeback" RetiredReasonSourceSaleReversed = "source_sale_reversed" )
Why the platform retired a stored payment method, in the gateway's own order.
RetiredReasonHardDecline: a charge on the card was declined as final. RetiredReasonChargeback: a charge on it was disputed. RetiredReasonSourceSaleReversed: the payment that saved it was fully refunded or disputed.
const ( ChargeStatusSucceeded = "succeeded" ChargeStatusFailed = "failed" ChargeStatusPending = "pending" ChargeStatusCancelled = "cancelled" )
Charge status values returned by ChargePaymentMethod, in the gateway's own order. ChargeStatusSucceeded: the money moved. ChargeStatusFailed: it did not; DeclineClass says why, when the class is known. ChargeStatusPending is not terminal: keep polling GetStatus on the charge's TransactionID. ChargeStatusCancelled: an authorization voided before capture, no money moved.
const ( DeclineClassHard = "hard" DeclineClassSoftFunds = "soft_funds" DeclineClassSoftSCARequired = "soft_sca_required" DeclineClassSoftOther = "soft_other" )
Decline classes, coarse enough to act on without reading the issuer's code:
- DeclineClassHard: do not retry this card, ask the customer for another one.
- DeclineClassSoftFunds: insufficient funds; retry later (after the customer's payday, not in a loop).
- DeclineClassSoftSCARequired: the issuer wants the customer present; send them through a hosted checkout session with SaveCard instead of charging off-session again.
- DeclineClassSoftOther: a transient issuer or network condition; one retry later is reasonable.
const ( RefundStatusPending = "pending" RefundStatusProcessing = "processing" RefundStatusSucceeded = "succeeded" RefundStatusFailed = "failed" )
Refund status values, in the gateway's own order. RefundStatusPending: queued. RefundStatusProcessing: with the payment provider now. RefundStatusSucceeded: the money was returned to the payer. RefundStatusFailed: the refund did not happen, read FailureCode; final for that idempotency key, so a new attempt needs a new key.
const ( EventPaymentSucceeded = "payment.succeeded" EventPaymentFailed = "payment.failed" EventPaymentRequiresCapture = "payment.requires_capture" EventPaymentCancelled = "payment.cancelled" EventPaymentAbandoned = "payment.abandoned" EventPaymentRefunded = "payment.refunded" EventPaymentDisputed = "payment.disputed" )
Webhook event types of payments: the contract's webhookEventCatalog. With the agreement, charge and verification types below, they are every type that endpoint registration accepts. It refuses any other type, and the match is case-sensitive. The list can grow: treat a type you do not know as a no-op.
EventPaymentSucceeded is the ONLY signal that means money is in hand. The in-flight states (pending, processing) are deliberately not webhooked; poll GetStatus if you want to drive UX off them.
const ( EventAgreementActivated = "agreement.activated" EventAgreementPastDue = "agreement.past_due" EventAgreementCancelled = "agreement.cancelled" EventChargeSucceeded = "charge.succeeded" EventChargeFailed = "charge.failed" EventChargeRetrying = "charge.retrying" )
Webhook event types of agreements and saved-card charges. They carry Data.Sequence: order them by it, never by CreatedAt.
EventAgreementPastDue: the platform gave up on the current period on the agreement's card. Nothing cancels the agreement automatically. EventChargeFailed: nothing more is tried for this charge. For a one-off charge it can arrive again for the same chargeId with a new decline class; keep the latest. EventChargeRetrying: an agreement period was declined softly, and the platform tries the same card again.
const ( EventVerificationSucceeded = "verification.succeeded" EventVerificationFailed = "verification.failed" )
Webhook event types of verify-only sessions. A verify-only session never fires payment.* events. EventVerificationFailed fires when the issuer declined or the payer abandoned the checkout; no card is saved.
const ( ChargeOutcomeSucceeded = "succeeded" ChargeOutcomeRetrying = "retrying" ChargeOutcomeFailed = "failed" )
Outcomes of a charge on charge.* events, on ChargeWebhookData.Outcome, in the gateway's own order. ChargeOutcomeRetrying: an agreement period was declined softly and the platform tries the same card again, at NextAttemptAt.
const ( WebhookEndpointStatusActive = "active" WebhookEndpointStatusInactive = "inactive" )
Webhook endpoint status values, in the gateway's own order. inactive means deliveries are paused: the endpoint was switched off in the dashboard, or after repeated failed deliveries. An inactive endpoint does not count toward the limit of active endpoints.
const ConsentTextVersionRecurring202609 = "recurring-2026-09"
ConsentTextVersionRecurring202609 is the version of the platform's recurring payment consent text that the subscription session accepts. Show this version's text on your page. Older versions stay accepted for agreements that were shown them.
const DefaultWebhookTolerance = 5 * time.Minute
DefaultWebhookTolerance is how far a delivery's timestamp may sit from your clock before VerifyWebhook rejects it. It matches the gateway's own window.
const ErrorCodeIdempotencyKeyRequired = "IDEMPOTENCY_KEY_REQUIRED"
ErrorCodeIdempotencyKeyRequired (HTTP 400, every keyed write): the Idempotency-Key header was missing or too long. The SDK refuses such a key before sending, so this only arrives if something between you and the API dropped the header.
const ErrorCodeInsufficientScope = "INSUFFICIENT_SCOPE"
ErrorCodeInsufficientScope (HTTP 403, on every route that needs a scope): the request was signed correctly, but the API key does not hold the scope the route needs. It arrives as an *AuthError with HTTPStatus 403. A key's scopes are fixed when it is issued, so a retry with the same key gets the same answer: call the route with a key that holds its scope.
The scopes are payments (sessions, charges, revokes, refunds, plans and agreements), transactions:read (ListTransactions) and webhooks:manage (the webhook endpoint routes). A key holds either payments, or one or both of the other two: use a separate reporting key for transactions and webhook endpoints.
const ErrorCodeValidationError = "VALIDATION_ERROR"
ErrorCodeValidationError (HTTP 400) is the envelope code of a request that failed validation. error.validationErrors on APIError.Raw names each field and its field code (the ValidationErrorCodes).
const ErrorCodeVerifyOnlyNotEnabled = "VERIFY_ONLY_NOT_ENABLED"
ErrorCodeVerifyOnlyNotEnabled (HTTP 403, CreateCheckoutSession): the session asked for VerifyOnly, and verify-only sessions are not enabled for your account. It arrives as an *AuthError with HTTPStatus 403. Nothing was created and the idempotency key is not spent. We switch it on for your account: ask Dominaite support.
const WebhookSignatureHeader = "X-Webhook-Signature"
WebhookSignatureHeader is the header Dominaite signs each delivery with. An endpoint can be configured to use a different header name, but this is the default and the one the dashboard shows.
Variables ¶
var AgreementStatuses = []string{ AgreementStatusPending, AgreementStatusActive, AgreementStatusPastDue, AgreementStatusCancelled, AgreementStatusCompleted, AgreementStatusFailed, }
AgreementStatuses is the agreement status vocabulary, in the gateway's order.
var AuthenticationErrorCodes = []string{ ErrorCodeInvalidAPIKey, ErrorCodeInvalidSignature, ErrorCodeTimestampOutOfRange, ErrorCodeIPNotAllowed, }
AuthenticationErrorCodes are the HTTP 401 codes, in the gateway's own order.
var AuthorizationErrorCodes = []string{ ErrorCodeInsufficientScope, }
AuthorizationErrorCodes are the codes a correctly signed request can be refused with because of what the key may do, in the gateway's own order. None is retryable.
var ChargeErrorCodes = []string{ ChargeErrorPaymentMethodNotActive, ChargeErrorDuplicateRequest, ChargeErrorIdempotencyKeyReused, ChargeErrorOutcomeUnknown, ChargeErrorFailed, ChargeErrorChargesDisabled, ChargeErrorProcessingUnavailable, ChargeErrorPaymentMethodNotFound, ChargeErrorInProgress, ChargeErrorCurrencyMismatch, ChargeErrorAgreementNotFound, ChargeErrorAgreementMethodMismatch, ChargeErrorAgreementConsentMissing, ChargeErrorAgreementCurrencyMismatch, ChargeErrorAgreementAmountMismatch, ChargeErrorAmountAboveAgreement, ChargeErrorAgreementPeriodAlreadyCharged, ChargeErrorAgreementEnded, ChargeErrorAgreementNotStarted, ChargeErrorAmountAboveCap, }
ChargeErrorCodes is the complete v1 vocabulary of ChargeError.ErrorCode: the seven codes of the published contract first, then the 404, 409 and 422 codes of the charge route. Unknown 4xx codes still arrive as a *ChargeError; branch on the ones you know.
var ChargeOutcomes = []string{ ChargeOutcomeSucceeded, ChargeOutcomeRetrying, ChargeOutcomeFailed, }
ChargeOutcomes is the charge outcome vocabulary, in the gateway's order. Treat a value you do not know as not final.
var ChargeStatuses = []string{ ChargeStatusSucceeded, ChargeStatusFailed, ChargeStatusPending, ChargeStatusCancelled, }
ChargeStatuses is the complete v1 charge status vocabulary. Treat a value outside this list as still open.
var DeclineClasses = []string{ DeclineClassHard, DeclineClassSoftFunds, DeclineClassSoftSCARequired, DeclineClassSoftOther, }
DeclineClasses is the complete v1 decline class vocabulary.
var ErrDominaite = errors.New("dominaite")
ErrDominaite matches every error this SDK returns, for a single errors.Is catch-all:
if errors.Is(err, dominaite.ErrDominaite) { ... }
For the specific kind, use errors.As with *RefusalError, *ChargeError, *RevokeError, *RefundError, *AuthError, *RateLimitError, *APIError, *TransportError or *ValidationError, or errors.As with the Error interface to catch any of them while keeping the message.
var IdempotencyErrorCodes = []string{ ErrorCodeIdempotencyKeyRequired, ErrorCodeDuplicateRequest, ErrorCodeAlreadyProcessed, ErrorCodePriorAttemptFailed, ErrorCodeIdempotencyKeyReused, }
IdempotencyErrorCodes are the idempotency codes, in the gateway's own order: ErrorCodeIdempotencyKeyRequired (HTTP 400), then the replay refusals of a session create (HTTP 200 with success false).
var Integrations = []Integration{ IntegrationWidget, IntegrationFields, }
Integrations is the complete v1 integration vocabulary.
var IntervalUnits = []string{IntervalUnitDay, IntervalUnitWeek, IntervalUnitMonth, IntervalUnitYear}
IntervalUnits is the interval unit vocabulary, in the gateway's order.
var PastDueReasons = []string{ PastDueReasonCardExpired, PastDueReasonCardRevoked, PastDueReasonHardDecline, PastDueReasonDunningExhausted, PastDueReasonSCARequired, PastDueReasonUnprocessable, }
PastDueReasons is the past-due reason vocabulary, in the gateway's order.
var PaymentMethodCategories = []string{ PaymentMethodCard, PaymentMethodWallet, PaymentMethodBankTransfer, PaymentMethodSepa, }
PaymentMethodCategories is every payment method category the merchant API reports, in the gateway's own order.
Reporting data, not a money-flow switch: a wallet payment refunds, captures and disputes exactly like a card payment.
var PlanStatuses = []string{PlanStatusActive, PlanStatusArchived}
PlanStatuses is the plan status vocabulary, in the gateway's order.
var RecurringErrorCodes = []string{ ErrorCodePlanNotFound, ErrorCodePlanNotActive, ErrorCodeAgreementNotFound, ErrorCodeAgreementEnded, ErrorCodePaymentMethodNotFound, ErrorCodePaymentMethodNotActive, ErrorCodePaymentMethodCurrencyMismatch, ErrorCodePaymentMethodCurrencyUnknown, ErrorCodeMaxAmountBelowPlanAmount, ErrorCodeAgreementConsentRequired, ErrorCodeIdempotencyKeyReused, }
RecurringErrorCodes are the codes of the plan and agreement routes, in the gateway's own order. They arrive on APIError.ErrorCode.
var RefundErrorCodes = []string{ RefundErrorPaymentNotFound, RefundErrorRefundNotFound, RefundErrorPaymentNotRefundable, RefundErrorAmountExceeded, RefundErrorIdempotencyKeyReused, RefundErrorDuplicateRequest, RefundErrorIdempotencyKeyRequired, }
RefundErrorCodes is the complete v1 vocabulary of RefundError.ErrorCode. Unknown codes still arrive as a *RefundError; branch on the ones you know.
var RefundFailureCodes = []string{ RefundFailureAmountExceeded, RefundFailurePaymentNotRefundable, RefundFailureFailed, }
RefundFailureCodes is the complete v1 vocabulary of Refund.FailureCode.
var RefundStatuses = []string{ RefundStatusPending, RefundStatusProcessing, RefundStatusSucceeded, RefundStatusFailed, }
RefundStatuses is the complete v1 refund status vocabulary. Treat a value outside this list as still open.
var RevokeErrorCodes = []string{ RevokeErrorUpstreamContract, RevokeErrorMerchantAPIUnavailable, }
RevokeErrorCodes is the complete v1 vocabulary of RevokeError.ErrorCode.
var SessionRefusalErrorCodes = []string{ ErrorCodePaymentProcessingUnavailable, ErrorCodeDuplicateRequest, ErrorCodeAlreadyProcessed, ErrorCodeIdempotencyKeyReused, ErrorCodePriorAttemptFailed, ErrorCodeStorefrontMismatch, ErrorCodeAgreementNotFound, ErrorCodeAgreementNotPending, ErrorCodeAgreementAmountMismatch, ErrorCodeAgreementConsentRequired, ErrorCodeAgreementSessionOpen, ErrorCodeAgreementAlreadyPaid, ErrorCodeAgreementVariableAmountUnsupported, ErrorCodeSubscriptionsNotEnabled, }
SessionRefusalErrorCodes are every RefusalError.ErrorCode a session create can answer with HTTP 200 and success false: the five replay and availability codes of the published contract, ErrorCodeStorefrontMismatch (a replay of a key minted for another storefront), then the SubscriptionSessionRefusalCodes. Treat a code you do not know as not retryable.
var Statuses = []string{ StatusPending, StatusProcessing, StatusSucceeded, StatusFailed, StatusRefunded, StatusPartiallyRefunded, StatusCancelled, StatusDisputed, StatusRequiresCapture, StatusAbandoned, }
Statuses is the complete v1 status vocabulary. A value outside this list means the gateway grew a status this SDK does not know yet: keep polling, never treat it as terminal.
var StoredPaymentMethodRetiredReasons = []string{ RetiredReasonHardDecline, RetiredReasonChargeback, RetiredReasonSourceSaleReversed, }
StoredPaymentMethodRetiredReasons is the complete v1 retired-reason vocabulary. Treat a value outside this list as retired for an unknown reason.
var StoredPaymentMethodStatuses = []string{ StoredPaymentMethodStatusActive, StoredPaymentMethodStatusRevoked, StoredPaymentMethodStatusExpired, StoredPaymentMethodStatusRetired, }
StoredPaymentMethodStatuses is the complete v1 stored-payment-method status vocabulary. Treat a value outside this list as not chargeable.
var StorefrontErrorCodes = []string{ ErrorCodeStorefrontMismatch, ErrorCodeStorefrontInactive, ErrorCodeStorefrontNotWhitelisted, }
StorefrontErrorCodes are the storefront codes a session create can be refused with, in the gateway's own order: ErrorCodeStorefrontMismatch (HTTP 400), ErrorCodeStorefrontInactive (409) and ErrorCodeStorefrontNotWhitelisted (409). None is retryable. They arrive as an *APIError, not as the HTTP 200 refusal shape, so they are not session refusal codes.
var SubscriptionSessionRefusalCodes = []string{ ErrorCodeAgreementNotFound, ErrorCodeAgreementNotPending, ErrorCodeAgreementAmountMismatch, ErrorCodeAgreementConsentRequired, ErrorCodeAgreementSessionOpen, ErrorCodeAgreementAlreadyPaid, ErrorCodeAgreementVariableAmountUnsupported, ErrorCodeSubscriptionsNotEnabled, }
SubscriptionSessionRefusalCodes are the refusal codes of a session created with AgreementID, in the gateway's own order. Treat a code you do not know as not retryable.
var TransactionTypes = []string{TransactionTypePayment, TransactionTypeCharge, TransactionTypeVerification}
TransactionTypes is the transaction type vocabulary, in the gateway's order.
var TransientErrorCodes = []string{ ErrorCodeMerchantAPIUnavailable, ErrorCodePaymentProcessingUnavailable, }
TransientErrorCodes are the codes the gateway asks you to retry, in its own order. The client retries both for you (see WithMaxRetries).
var ValidationErrorCodes = []string{ ErrorCodeRequired, ErrorCodeMinValue, ErrorCodeMaxLength, ErrorCodeInvalidFormat, ErrorCodeInvalidSelection, ErrorCodeDateInFuture, ErrorCodeInvalidEmail, ErrorCodeInvalidCurrency, }
ValidationErrorCodes are the field codes of a validation failure, in the gateway's own order. The list can grow: treat a code you do not know as a rejected field.
var WalletTypes = []string{ WalletTypeApplePay, WalletTypeGooglePay, WalletTypeSamsungPay, }
WalletTypes is the wallets the gateway names today, pinned against the published contract. The field can carry a lower-cased identifier not in this list yet: treat an unknown value as a valid wallet, not an error.
var WebhookEndpointStatuses = []string{WebhookEndpointStatusActive, WebhookEndpointStatusInactive}
WebhookEndpointStatuses is the endpoint status vocabulary, in the gateway's order.
var WebhookErrorCodes = []string{ ErrorCodeWebhookNotFound, ErrorCodeWebhookLimitReached, ErrorCodeWebhookAlreadyRegistered, ErrorCodeWebhookURLRejected, ErrorCodeIdempotencyKeyReused, ErrorCodeDuplicateRequest, }
WebhookErrorCodes are the codes of the webhook endpoint routes, in the gateway's own order. They arrive on APIError.ErrorCode.
Functions ¶
func CurrencyExponent ¶ added in v0.3.0
CurrencyExponent returns the number of minor-unit digits the gateway uses for a currency: 2 for EUR (1 EUR is 100 cents), 0 for JPY and HUF, 3 for KWD. HUF differs from ISO 4217 on purpose; see ToMinorUnits. The code is case-insensitive. ISK, KRW, OMR, JOD and TND are refused as not supported, and an unknown currency is refused too, both with a *ValidationError.
func IsPaid ¶ added in v0.3.0
IsPaid reports whether a status means the payment is complete: true for StatusSucceeded only. StatusRequiresCapture is not paid yet (the funds are held, not captured), and neither is anything this SDK does not recognise.
func IsRefundTerminal ¶ added in v0.3.1
IsRefundTerminal reports whether a refund status is final, so polling can stop: succeeded and failed. pending, processing and any status this SDK does not recognise are still open.
func IsTerminal ¶ added in v0.3.0
IsTerminal reports whether a status is final, so polling can stop: succeeded, failed, cancelled, abandoned, refunded and partially_refunded. Everything else is still open and worth polling, including pending, processing, requires_capture, disputed and any status this SDK does not recognise: a value the API adds later must never close a live order.
func OrderIdempotencyKey ¶ added in v0.3.0
OrderIdempotencyKey builds the idempotency key for one payment of one order: "{scope}-{orderID}-{amountMinor}-{CURRENCY}", for example OrderIdempotencyKey("checkout", "order-1042", 2500, "eur") is "checkout-order-1042-2500-EUR".
Derive the key from the order, never per call. Your payment page will be re-entered: a reload, the Back button, a second tab, a retry after a timeout. The same order at the same amount produces the same key every time, so each of those replays the session already open (same TransactionID, same cashier handles) instead of opening a second payment. A changed amount or currency (the cart was edited) produces a new key and therefore a fresh session; the old key replayed with a new amount would be refused with IDEMPOTENCY_KEY_REUSED.
scope is a fixed label for the call site ("checkout", "renewal"), so two different kinds of payment for the same order never share a key. SaveCard is part of a session's identity too: if the payer can flip it after a session was opened, use a different scope for each choice (for example "checkout-save"). Keep scope free of values that vary per request.
amountMinor is in MINOR units and must be positive. currency is ISO 4217 and is uppercased here, so "eur" and "EUR" give the same key. Returns a *ValidationError when scope or orderID is blank, the amount is not positive, the currency is not three ASCII letters, or the key breaks the key rules: at most 100 characters, visible ASCII only (0x21 to 0x7E), so an order id with a space or a non-ASCII letter is refused rather than silently rewritten.
func Sign ¶
Sign builds the X-Signature value for one request: lowercase hex HMAC-SHA256 over
"{timestamp}\n{METHOD}\n{path}\n{idempotencyKey}\n{sha256hex(body)}"
The idempotency key is INSIDE the signature, so a captured request cannot be replayed with a different key to mint extra sessions. The server rejects timestamps more than 5 minutes off, so keep your server clock on NTP.
The client signs for you. Sign is exported so you can pin the recipe against the offline test vector before calling the live API, and so you can debug an INVALID_SIGNATURE without reading this package's source.
func ToMinorUnits ¶ added in v0.3.0
ToMinorUnits converts a decimal amount string to the integer minor units Amount takes, by the exponent the gateway uses for the currency:
ToMinorUnits("0.30", "EUR") // 30
ToMinorUnits("25", "EUR") // 2500
ToMinorUnits("1500", "JPY") // 1500
ToMinorUnits("1500", "HUF") // 1500: whole forints, NOT ISO 4217's 150000
ToMinorUnits("1.250", "KWD") // 1250
Known currencies: EUR, USD, GBP, CAD, AUD, CHF, BGN, RON, PLN, CZK, SEK, DKK, NOK (2 decimals), JPY, HUF (0) and BHD, KWD (3).
The conversion is exact, done on the digits, never through a float (19.99 as a float64 times 100 is 1998.9999999999998, which truncates to 1998). The amount is plain digits with an optional point: no sign, no thousands separators, no exponent. More fractional digits than the currency has is refused, zeros included ("0.305" and "25.000" EUR, "100.0" JPY): nothing is rounded or trimmed, because rounding a price is a business decision, not the SDK's. Surrounding whitespace is ignored.
Returns a *ValidationError for a malformed amount, an unknown or unsupported currency, or an amount too large for int64.
Types ¶
type APIError ¶
type APIError struct {
HTTPStatus int
ErrorCode string
// Raw is the whole envelope the API sent, for fields not modelled above.
Raw json.RawMessage
// contains filtered or unexported fields
}
APIError means the API answered, but with an unexpected or rejecting response. HTTPStatus carries the code. A 404 from GetStatus means an unknown transaction id; a 404 from ChargePaymentMethod or RevokePaymentMethod an id that is not yours. A 3xx means something in front of the API answered with a redirect: the SDK never follows one and never treats its body as a real response, because the API itself does not redirect.
A key replayed with a different body does NOT arrive here. The API answers HTTP 200 with success false and IDEMPOTENCY_KEY_REUSED, so it reaches you as a *RefusalError. A 429 does not arrive here either: it is a *RateLimitError.
ErrorCode is the machine-readable code when the API sent one, so input rejections can be branched on rather than string-matched. A 400 carrying IDEMPOTENCY_KEY_REQUIRED means the Idempotency-Key header was missing or empty. A 409 carrying ErrorCodeStorefrontNotWhitelisted or ErrorCodeStorefrontInactive, or a 400 carrying ErrorCodeStorefrontMismatch, means the storefront refused the session. Empty when the API named no code.
Raw is the whole envelope the API sent with a rejection, for fields not modelled above: error.params, error.validationErrors and error.traceId. It is nil when there was no JSON envelope, and on an unexpected 2xx.
type Agreement ¶ added in v0.5.0
type Agreement struct {
// ID is agr_ followed by 32 hex characters. It is case-sensitive: store it
// and send it back exactly as it is.
ID string `json:"id"`
PlanID string `json:"planId"`
CustomerReference string `json:"customerReference"`
// StoredPaymentMethodID is the pm_ id of the card on file, empty until one
// is saved.
StoredPaymentMethodID string `json:"storedPaymentMethodId,omitempty"`
// Status is one of the AgreementStatus* values.
Status string `json:"status"`
// Amount is the charge per period, in MINOR units of Currency.
Amount int64 `json:"amount"`
// MaxAmountPerPeriod is the most one charge may be on a variable
// agreement, in MINOR units. nil on a fixed agreement.
MaxAmountPerPeriod *int64 `json:"maxAmountPerPeriod,omitempty"`
Currency string `json:"currency"`
// IntervalUnit is one of the IntervalUnit* values.
IntervalUnit string `json:"intervalUnit"`
IntervalCount int `json:"intervalCount"`
// PeriodCount is nil when the agreement bills until it is cancelled.
PeriodCount *int `json:"periodCount,omitempty"`
TrialDays int `json:"trialDays"`
// NextChargeAt is ISO 8601 UTC, empty while pending and once the
// agreement has ended.
NextChargeAt string `json:"nextChargeAt,omitempty"`
// PastDueReason is one of the PastDueReason* values while the agreement
// is past_due, empty otherwise.
PastDueReason string `json:"pastDueReason,omitempty"`
// PastDueAt is ISO 8601 UTC, when the agreement went past_due.
PastDueAt string `json:"pastDueAt,omitempty"`
// Consent is nil until consent is captured.
Consent *AgreementConsent `json:"consent,omitempty"`
// Version starts at 1 and rises when the price changes for a future
// period.
Version int `json:"version"`
// Sequence is the agreement's transition counter as of this read: the
// Data.Sequence of the latest agreement.* event, 0 before the first
// transition. Compare it with the highest sequence you processed for the
// agreement.
Sequence int64 `json:"sequence"`
// CreatedAt is ISO 8601 UTC.
CreatedAt string `json:"createdAt"`
// ActivatedAt is ISO 8601 UTC, empty while pending.
ActivatedAt string `json:"activatedAt,omitempty"`
// CancelledAt is ISO 8601 UTC, empty unless cancelled.
CancelledAt string `json:"cancelledAt,omitempty"`
// RequestID is the API's id of the call that returned this agreement.
// Quote it to support. Empty when the API sent none.
RequestID string `json:"-"`
// Raw is the unparsed agreement object, for fields this struct does not
// model yet.
Raw json.RawMessage `json:"-"`
}
Agreement is one customer's subscription to a plan. The price and the interval are copied from the plan when the agreement is created; a later plan change never re-prices a running agreement.
type AgreementConsent ¶ added in v0.5.0
type AgreementConsent struct {
TextVersion string `json:"textVersion"`
AcceptedAt string `json:"acceptedAt"`
IP string `json:"ip"`
ReceivedAt string `json:"receivedAt,omitempty"`
ReceivedFromIP string `json:"receivedFromIp,omitempty"`
}
AgreementConsent is the consent recorded on an agreement. TextVersion, AcceptedAt and IP are stored exactly as they were sent, on CreateAgreement or on the subscription session. ReceivedAt and ReceivedFromIP are the platform's own receipt: when the consent reached the platform, and from which address.
type AgreementWebhookData ¶ added in v0.7.0
type AgreementWebhookData struct {
// ID is the agr_ id: the ordering key of agreement.* events.
ID string `json:"id"`
PlanID string `json:"planId"`
CustomerReference string `json:"customerReference"`
// StoredPaymentMethodID is the pm_ id of the card on file, empty until one
// is saved.
StoredPaymentMethodID string `json:"storedPaymentMethodId"`
// Status is one of the AgreementStatus* values; PreviousStatus is the
// status it moved from.
Status string `json:"status"`
PreviousStatus string `json:"previousStatus"`
// Amount is the charge per period, in MINOR units of Currency.
Amount int64 `json:"amount"`
Currency string `json:"currency"`
// IntervalUnit is one of the IntervalUnit* values.
IntervalUnit string `json:"intervalUnit"`
IntervalCount int `json:"intervalCount"`
// PeriodCount is nil when the agreement bills until it is cancelled.
PeriodCount *int `json:"periodCount"`
TrialDays int `json:"trialDays"`
// NextChargeAt, ActivatedAt and CancelledAt are as the API sent them,
// empty when null.
NextChargeAt string `json:"nextChargeAt"`
ActivatedAt string `json:"activatedAt"`
CancelledAt string `json:"cancelledAt"`
// Version starts at 1 and rises when the price changes for a future
// period.
Version int `json:"version"`
// Sequence is the same value as WebhookData.Sequence.
Sequence int64 `json:"sequence"`
// Raw is the unparsed data object, for fields not modelled above.
Raw json.RawMessage `json:"-"`
}
AgreementWebhookData is the data of an agreement.* event: the agreement after the change. The fields are the ones GetAgreement returns, plus PreviousStatus. Webhooks send every field, null when empty: a null string reads as "", a null number as nil.
type AuthError ¶
type AuthError struct {
// HTTPStatus is 401 or 403.
HTTPStatus int
ErrorCode string
// contains filtered or unexported fields
}
AuthError means the API refused the request before it did anything: HTTP 401 or 403, on HTTPStatus. A retry with the same key and config gets the same answer.
HTTP 401: the API could not authenticate the request. ErrorCode is one of INVALID_API_KEY, INVALID_SIGNATURE, TIMESTAMP_OUT_OF_RANGE or IP_NOT_ALLOWED. Fix the key id, the secret, the server clock or the caller allowlist.
HTTP 403: the request was signed correctly, so the key id, the secret and the clock are fine, but the key may not do this. ErrorCode is ErrorCodeInsufficientScope (the key does not hold the scope the route needs) or ErrorCodeVerifyOnlyNotEnabled (a verify-only session on an account without verify-only sessions). Message says what to do.
type ChargeError ¶ added in v0.3.0
type ChargeError struct {
HTTPStatus int
ErrorCode string
// Charge is the charge row the gateway attached to its answer, when it did.
Charge *PaymentMethodCharge
// TransactionID is Charge.TransactionID, for polling GetStatus. Empty
// without a charge.
TransactionID string
// Raw is the whole envelope the gateway sent, for fields not modelled above.
Raw json.RawMessage
// contains filtered or unexported fields
}
ChargeError means the gateway answered a charge with an error code instead of a charge result: HTTP 404, 409, 422, 502 or 503 with one of the ChargeError* codes on ErrorCode (see each constant for what to do). HTTPStatus carries the status, Message the gateway's own text.
Charge is the charge row the gateway attached to its answer, when it did: always for CHARGE_OUTCOME_UNKNOWN, sometimes for CHARGE_FAILED, never for the rest. TransactionID is a shortcut for Charge.TransactionID, empty when there is no charge:
charge, err := client.ChargePaymentMethod(ctx, id, params)
var chargeErr *dominaite.ChargeError
if errors.As(err, &chargeErr) && chargeErr.ErrorCode == dominaite.ChargeErrorOutcomeUnknown {
// poll client.GetStatus(ctx, chargeErr.TransactionID); do not retry under a new key
}
Only authentication (401/403, *AuthError), validation (400, *APIError), rate limiting (429, *RateLimitError), network failures and a 5xx without one of the charge route's own 5xx codes (*TransportError, with the code when there was one) keep their generic errors on this route. A 404 with a code, PAYMENT_METHOD_NOT_FOUND or AGREEMENT_NOT_FOUND, is a *ChargeError.
type ChargePaymentMethodParams ¶ added in v0.3.0
type ChargePaymentMethodParams struct {
// Amount is in MINOR units: 2500 is 25.00 EUR. Integers only.
Amount int64 `json:"amount"`
// Currency is ISO 4217, e.g. "EUR".
Currency string `json:"currency"`
// OrderReference is your own order id, at most 100 characters. It shows up
// in your dashboard.
OrderReference string `json:"orderReference"`
Description string `json:"description,omitempty"`
// AgreementID is the agr_ id of the agreement this charge is made under.
// Empty charges the card on your own authority, as with any card on file:
// only the per-charge cap and the volume limits apply. When set, the
// charge must be on the agreement's terms: its payment method, its
// consent, its currency and its amount rule. The gateway refuses anything
// else with a 422 *ChargeError, or a 404 *APIError (AGREEMENT_NOT_FOUND)
// for an agreement that is not yours. The charge records the agreement,
// so a dispute can be answered with the consent the customer gave.
AgreementID string `json:"agreementId,omitempty"`
// IdempotencyKey is required. Derive it from what you are charging for
// (the order, the billing period), never per attempt: retrying with the
// same key never charges the card twice, so on a timeout retry with the
// same key. It travels in the header and in the signature, never in the
// body. 1 to 100 visible ASCII characters; anything else is refused with a
// *ValidationError before anything is sent.
IdempotencyKey string `json:"-"`
// Extra carries any additional field the API accepts that this struct does
// not model yet. Keys here are merged into the JSON body and replace a
// modelled field of the same name.
Extra map[string]any `json:"-"`
}
ChargePaymentMethodParams are the parameters for Client.ChargePaymentMethod. Amount, Currency and OrderReference are required.
func (ChargePaymentMethodParams) MarshalJSON ¶ added in v0.5.0
func (p ChargePaymentMethodParams) MarshalJSON() ([]byte, error)
MarshalJSON emits the request body: the modelled fields in declaration order, with Extra merged in. IdempotencyKey is excluded; it is a header, not a body field.
type ChargeWebhookData ¶ added in v0.7.0
type ChargeWebhookData struct {
// ChargeID is ch_ followed by 32 hex characters: the same id
// ChargePaymentMethod returned for a one-off charge.
ChargeID string `json:"chargeId"`
// TransactionID is the transaction the charge created; readable with
// GetStatus.
TransactionID string `json:"transactionId"`
// StoredPaymentMethodID is the pm_ id of the card that was charged.
StoredPaymentMethodID string `json:"storedPaymentMethodId"`
// AgreementID is the agr_ id the charge belongs to. Empty for a one-off
// charge without an agreement.
AgreementID string `json:"agreementId"`
// CustomerReference is the agreement's customer reference. Empty without
// an agreement.
CustomerReference string `json:"customerReference"`
// Outcome is one of the ChargeOutcome* values.
Outcome string `json:"outcome"`
// PeriodNumber is the agreement period the charge pays, from 1. nil for a
// one-off charge.
PeriodNumber *int `json:"periodNumber"`
// AttemptNumber counts the attempts on this period, from 1. nil when the
// gateway did not count one.
AttemptNumber *int `json:"attemptNumber"`
// Amount is in MINOR units of Currency.
Amount int64 `json:"amount"`
Currency string `json:"currency"`
// PaymentMethod is the brand and last four digits of the card. nil when
// the provider reported neither.
PaymentMethod *ChargeWebhookPaymentMethod `json:"paymentMethod"`
// OrderReference and Description are what the charge was sent with.
OrderReference string `json:"orderReference"`
Description string `json:"description"`
// DeclineClass is one of the DeclineClass* constants on charge.failed and
// charge.retrying, empty otherwise. DeclineCode is the issuer's code,
// verbatim, for your logs.
DeclineClass string `json:"declineClass"`
DeclineCode string `json:"declineCode"`
// NextAttemptAt is when the platform tries the card again, on
// charge.retrying. NextChargeAt is the agreement's next charge, when
// there is one. Both are as the API sent them, empty when null.
NextAttemptAt string `json:"nextAttemptAt"`
NextChargeAt string `json:"nextChargeAt"`
// Sequence is the same value as WebhookData.Sequence.
Sequence int64 `json:"sequence"`
// Raw is the unparsed data object, for fields not modelled above.
Raw json.RawMessage `json:"-"`
}
ChargeWebhookData is the data of a charge.* event: a charge on a stored card, one-off (ChargePaymentMethod) or placed by the platform for an agreement period. Webhooks send every field, null when empty: a null string reads as "", a null number as nil.
type ChargeWebhookPaymentMethod ¶ added in v0.7.0
type ChargeWebhookPaymentMethod struct {
// Brand is the lower-cased card brand (visa, mastercard, ...). Empty when
// the provider did not report one.
Brand string `json:"brand"`
// Last4 is the card's last four digits. Empty when unknown.
Last4 string `json:"last4"`
}
ChargeWebhookPaymentMethod is the card on a charge.* event.
type CheckoutSession ¶
type CheckoutSession struct {
TransactionID string `json:"transactionId"`
OrderID string `json:"orderId"`
// CashierKey feeds the widget's data-cashier-key. A per-payment value, not
// a credential.
CashierKey string `json:"cashierKey"`
// CashierToken feeds the widget's data-cashier-token. A per-payment value,
// not a credential.
CashierToken string `json:"cashierToken"`
// Amount is in MINOR units.
Amount int64 `json:"amount"`
Currency string `json:"currency"`
// ExpiresAt is ISO 8601. Sessions are valid for 2 hours.
ExpiresAt string `json:"expiresAt"`
// Integration echoes what the session was created for: IntegrationWidget or
// IntegrationFields. For fields, CashierKey and CashierToken are the card
// fields key and session token.
Integration Integration `json:"integration"`
// ClientSecret is set only for IntegrationFields: the browser credential
// the checkout.js drop-in sends for this session. Opaque, at most 128
// characters, the same on every replay. Hand it to the payer's page with the
// other session values; never log it or keep it past the session.
ClientSecret string `json:"clientSecret,omitempty"`
// VerifyOnly is true on a verify-only session: Amount is the verification
// hold, which is released once the card is saved. False on a payment
// session.
VerifyOnly bool `json:"verifyOnly,omitempty"`
// AgreementID is the agr_ id of the agreement this subscription session
// takes the first payment for. Empty on a one-off checkout.
AgreementID string `json:"agreementId,omitempty"`
// RequestID is the API's id of the call that returned this: the
// Dominaite-Request-Id response header, or metadata.requestId in the body
// when the header is absent. Quote it to support. Empty when the API sent
// neither.
RequestID string `json:"-"`
// Raw is the unparsed payload, for fields this struct does not model yet.
Raw json.RawMessage `json:"-"`
}
CheckoutSession is what CreateCheckoutSession returns.
type CheckoutStatus ¶
type CheckoutStatus struct {
TransactionID string `json:"transactionId"`
OrderID string `json:"orderId"`
OrderReference string `json:"orderReference,omitempty"`
// PSPReference is the payment processor's reference for the transaction.
// Empty (null or absent on the wire) until known, and on a sale settled by
// reconciliation without a processor webhook. Older gateways omit it.
PSPReference string `json:"pspReference,omitempty"`
// Status is one of the Status* constants.
Status string `json:"status"`
// Amount is in MINOR units.
Amount int64 `json:"amount"`
Currency string `json:"currency"`
RefundedAmount int64 `json:"refundedAmount,omitempty"`
// PaymentMethod is how the payer paid, one of PaymentMethodCategories.
// Empty (null or absent on the wire) while the payment is still open, with
// no method chosen yet, and on older transactions. Reporting data only.
PaymentMethod string `json:"paymentMethod,omitempty"`
// WalletType is which wallet, when PaymentMethod is PaymentMethodWallet.
// A value outside WalletTypes is a valid wallet this SDK does not name yet,
// not an error. Empty (null or absent on the wire) for non-wallet payments.
WalletType string `json:"walletType,omitempty"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt,omitempty"`
// ExpiresAt is present while the session is still payable.
ExpiresAt string `json:"expiresAt,omitempty"`
// StoredPaymentMethod is the card kept on file by a session created with
// SaveCard. Present once the payment is approved, and it stays present
// after a revoke with Status StoredPaymentMethodStatusRevoked; nil (absent
// on the wire) until then, for sessions without SaveCard, and for declined
// or abandoned ones. Store StoredPaymentMethod.ID against your customer -
// it is what Client.ChargePaymentMethod takes.
//
// Not to be confused with PaymentMethod, which is the string category of
// how the payer paid ("card", "wallet", ...).
StoredPaymentMethod *StoredPaymentMethod `json:"storedPaymentMethod,omitempty"`
// VerifyOnly is true on a verify-only session: a card check, not a
// payment. Status stays the raw status (requires_capture, then cancelled
// once the hold is released). A non-nil StoredPaymentMethod is what says
// the card is verified. False on every other session.
VerifyOnly bool `json:"verifyOnly,omitempty"`
// DeclineClass is one of the DeclineClass* constants on a failed payment.
// Empty while the payment is open and on every other status. It can be
// empty on a failed payment too, for example when nobody declined the
// payment. A value outside DeclineClasses is a class this SDK does not name
// yet, not an error.
DeclineClass string `json:"declineClass,omitempty"`
// IsRecurring is true for a charge on a stored card without the payer
// present (a recurring charge), false for a checkout payment.
IsRecurring bool `json:"isRecurring,omitempty"`
// Agreement is the agreement the session took the first payment for, or
// the agreement a charge was placed under. nil on a one-off checkout or
// charge.
Agreement *CheckoutStatusAgreement `json:"agreement,omitempty"`
// Sequence is the data.sequence of the latest charge.* event about this
// transaction: compare it with the events you processed. 0 on a checkout
// payment, which sends payment.* events only, and before the first
// charge.* event.
Sequence int64 `json:"sequence,omitempty"`
// RequestID is the API's id of the call that returned this: the
// Dominaite-Request-Id response header, or metadata.requestId in the body
// when the header is absent. Quote it to support. Empty when the API sent
// neither.
RequestID string `json:"-"`
// Raw is the unparsed payload, for fields this struct does not model yet.
Raw json.RawMessage `json:"-"`
}
CheckoutStatus is what GetStatus returns.
type CheckoutStatusAgreement ¶ added in v0.5.0
type CheckoutStatusAgreement struct {
// ID is the agr_ id, case-sensitive.
ID string `json:"id"`
// Status is one of the AgreementStatus* values. It stays pending after a
// declined or abandoned subscription session: the subscription did not
// start.
Status string `json:"status"`
// NextChargeAt is ISO 8601 UTC. Empty while pending and once the
// agreement has ended.
NextChargeAt string `json:"nextChargeAt,omitempty"`
}
CheckoutStatusAgreement is the agreement on a status read: enough to know whether the subscription started. Read the whole agreement with GetAgreement.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a server-side client for the Dominaite merchant API. It is safe for concurrent use.
func New ¶
New builds a client from your API key id (dmk_...) and secret (dms_...), both from the dashboard's Website integration tab. It returns a *ValidationError when either credential has the wrong prefix, which catches a swapped key id and secret before anything is sent. It also returns one when WithBaseURL is missing, empty or whitespace, or is not https:// outside loopback.
func (*Client) CancelAgreement ¶ added in v0.5.0
CancelAgreement cancels an agreement from pending, active or past_due: no further charge is scheduled. A charge already placed is not reversed. Cancelling a cancelled agreement succeeds again, so a retry after a timeout is safe.
Errors it returns: as GetAgreement, and an *APIError with HTTPStatus 409 and ErrorCode AGREEMENT_ENDED when the agreement already completed or failed.
func (*Client) ChargePaymentMethod ¶ added in v0.3.0
func (c *Client) ChargePaymentMethod(ctx context.Context, paymentMethodID string, params ChargePaymentMethodParams) (*PaymentMethodCharge, error)
ChargePaymentMethod charges a card kept on file, off-session: no widget, no payer present.
paymentMethodID is the ID from GetStatus().StoredPaymentMethod of a session you created with SaveCard. The charge is signed like a session and carries an Idempotency-Key, which you must set (derive it from the billing period, never per attempt), so retrying after a timeout WITH THE SAME KEY never charges the card twice: the gateway replays its first answer, HTTP status included.
The HTTP status is the contract on this route. 201 (200 on a replay) returns the charge, Status ChargeStatusSucceeded, ChargeStatusPending or ChargeStatusCancelled. 402 returns the charge too: a decline is not an error. The charge has Status ChargeStatusFailed, and DeclineClass, when it is set, tells you whether to give up on the card (hard), wait (soft_funds, soft_other) or bring the customer back for a hosted session (soft_sca_required). ChargeStatusPending is not terminal - poll GetStatus(charge.TransactionID).
params.AgreementID charges under an agreement, on its terms.
Errors it returns:
- *ValidationError: bad arguments; nothing was sent.
- *ChargeError: 404, 409, 422, 502 or 503 with a code; branch on ErrorCode. 404 PAYMENT_METHOD_NOT_FOUND is an id that is not yours. CHARGE_OUTCOME_UNKNOWN carries the charge row (Charge, TransactionID): poll GetStatus with it, never retry under a new key.
- *AuthError: wrong credentials, bad signature, clock off, IP not allowlisted.
- *APIError: 400 validation, or an unexpected response; inspect HTTPStatus.
- *RateLimitError: HTTP 429; back off and reschedule with the same key.
- *TransportError: network failure or a 5xx without a charge code (ErrorCode keeps the code it had). Safe to retry WITH the same key.
func (*Client) CreateAgreement ¶ added in v0.5.0
func (c *Client) CreateAgreement(ctx context.Context, params CreateAgreementParams) (*Agreement, error)
CreateAgreement creates a pending agreement that subscribes one customer to one of your active plans. Nothing is charged. To take the first payment, save the card and activate the agreement, open a subscription session: CreateCheckoutSession with AgreementID.
params.IdempotencyKey is required and signed. The same key with the same body answers the agreement it created, with HTTP 200 instead of 201. The same key with a different body is refused with IDEMPOTENCY_KEY_REUSED.
Errors it returns:
- *ValidationError: a missing plan id or customer reference, or a bad IdempotencyKey; nothing was sent.
- *APIError: a 4xx with ErrorCode, e.g. 404 PLAN_NOT_FOUND, 409 PLAN_NOT_ACTIVE, 422 AGREEMENT_CONSENT_REQUIRED (see the codes next to ErrorCodePlanNotFound). Nothing was created.
- *AuthError, *RateLimitError: as everywhere else.
- *TransportError: network failure or 5xx. Nothing was created on a 503; retry WITH the same key.
func (*Client) CreateCheckoutSession ¶
func (c *Client) CreateCheckoutSession(ctx context.Context, params CreateCheckoutSessionParams) (*CheckoutSession, error)
CreateCheckoutSession opens a hosted checkout session for one payment. With VerifyOnly it saves the payer's card without a payment; with AgreementID it takes the first payment of a subscription.
Errors it returns:
- *ValidationError: bad arguments, including a missing IdempotencyKey; nothing was sent.
- *AuthError: 401 for wrong credentials, a bad signature, a clock that is off or an IP that is not allowlisted. 403 for a key without the payments scope, or VerifyOnly on an account without verify-only sessions. Fix the config, do not retry.
- *RefusalError: the gateway refused the session; inspect ErrorCode.
- *APIError: an unexpected or rejecting response; inspect HTTPStatus.
- *RateLimitError: HTTP 429; back off and reschedule with the same idempotency key. Never retried for you.
- *TransportError: network failure or 5xx, after the client's retries (WithMaxRetries) ran out. Safe to retry WITH the same idempotency key.
The client retries a *TransportError and PAYMENT_PROCESSING_UNAVAILABLE with the same idempotency key and body; see WithMaxRetries. A retry after an attempt that DID land gets that session back while it is still open. When the earlier attempt already moved money, ended, or cannot be handed back yet, the API answers with a replay refusal (*RefusalError, ErrorCode ALREADY_PROCESSED, PRIOR_ATTEMPT_FAILED or DUPLICATE_REQUEST). Recover through RefusalError.TransactionID with GetStatus when the API named one.
func (*Client) CreateCheckoutSessionWithRetry
deprecated
func (c *Client) CreateCheckoutSessionWithRetry(ctx context.Context, params CreateCheckoutSessionParams, opts RetryOptions) (*CheckoutSession, error)
CreateCheckoutSessionWithRetry is CreateCheckoutSession with its own retry budget. It retries the same failures, with THE SAME idempotency key across every attempt.
Deprecated: CreateCheckoutSession retries on its own; set the budget with WithMaxRetries. This method stays for existing callers.
func (*Client) CreatePlan ¶ added in v0.5.0
CreatePlan creates a subscription plan. Nothing is charged: an agreement subscribes a customer to the plan, and the hosted subscription session activates the agreement.
params.IdempotencyKey is required and signed. The same key with the same body answers the plan it created, with HTTP 200 instead of 201, so a create whose answer you lost is safe to repeat with the same key. The same key with a different body is refused with IDEMPOTENCY_KEY_REUSED. Keys are not case-sensitive: two keys that differ only in letter case are one key.
Errors it returns:
- *ValidationError: a missing name, currency or interval unit, a non-positive amount, or a bad IdempotencyKey; nothing was sent.
- *APIError: a 4xx with ErrorCode. 400 is a field the API refused (Raw carries error.validationErrors); 422 is IDEMPOTENCY_KEY_REUSED.
- *AuthError, *RateLimitError: as everywhere else.
- *TransportError: network failure or 5xx. Nothing was created on a 503; retry WITH the same key.
func (*Client) CreateRefund ¶ added in v0.3.1
func (c *Client) CreateRefund(ctx context.Context, transactionID string, params CreateRefundParams) (*Refund, error)
CreateRefund refunds a payment, in full or in part. transactionID is the id CreateCheckoutSession or ChargePaymentMethod returned; only card-not-present payments of your own account can be refunded.
params.IdempotencyKey is required and signed, exactly as on a charge. Derive it from your refund (the return or credit-note id): replaying the same key answers the same refund and never refunds twice, so on a timeout retry with the same key. The same key with a different amount, reason or payment is refused with IDEMPOTENCY_KEY_REUSED.
Leave params.Amount nil to refund everything still refundable. Partial refunds add up: the amount may not exceed what is left after earlier refunds and refunds still in progress.
The gateway answers HTTP 202: the refund is queued, not done. Read it back with GetRefund, or wait for the payment.refunded webhook, which fires once the money has moved. A failed refund sends no webhook, so poll GetRefund if you need to know about failures. A replay of the same key returns the refund as it stands now, so it doubles as a status read.
Errors it returns:
- *ValidationError: bad arguments, including a missing IdempotencyKey; nothing was sent.
- *RefundError: 400, 404, 409 or 422 with a RefundError* code; branch on ErrorCode, retry with the same key only when Retryable.
- *AuthError: wrong credentials, bad signature, clock off, IP not allowlisted.
- *APIError: an unexpected response; inspect HTTPStatus.
- *RateLimitError: HTTP 429; back off and reschedule with the same key.
- *TransportError: network failure or 5xx. Nothing was queued on a 5xx; retry WITH the same key.
func (*Client) CreateWebhookEndpoint ¶ added in v0.5.0
func (c *Client) CreateWebhookEndpoint(ctx context.Context, params CreateWebhookEndpointParams) (*WebhookEndpoint, error)
CreateWebhookEndpoint registers an endpoint that receives the events you name. The answer carries the endpoint's Secret, once: store it, and pass it to VerifyWebhook for every delivery. Lost the secret? Delete the endpoint and create it again.
The key needs the webhooks:manage scope. A key with the payments scope cannot hold it, so use a separate key for webhook endpoints.
params.IdempotencyKey is required and signed. The same key with the same URL and events answers the same endpoint without its secret, and registers nothing new. After no answer, a timeout or a 503, retry with the SAME key. When the first attempt never finished, the retry replaces the endpoint it left and answers a new id and a new secret. When the endpoint was registered and only its answer was lost, the retry answers that endpoint with an empty Secret: delete it, then create it again with a new key. The client never retries this call on its own, whatever WithMaxRetries says: a retry can answer without the secret, so you decide when to send one.
Errors it returns:
- *ValidationError: an empty URL, no events, or a bad IdempotencyKey; nothing was sent.
- *APIError: a 4xx with ErrorCode, e.g. 400 WEBHOOK_URL_REJECTED, 409 WEBHOOK_LIMIT_REACHED, 409 WEBHOOK_ALREADY_REGISTERED (Raw carries the existing endpoint's id at error.params.webhookId), 422 IDEMPOTENCY_KEY_REUSED, 409 DUPLICATE_REQUEST (retry with the same key after a second). Nothing was registered.
- *AuthError: also 403 INSUFFICIENT_SCOPE for a key without webhooks:manage.
- *RateLimitError: as everywhere else.
- *TransportError: network failure or 5xx. Retry WITH the same key.
func (*Client) DeletePlan ¶ added in v0.5.0
DeletePlan archives a plan: it accepts no new agreements, and the agreements it already has continue unchanged. Deleting an archived plan succeeds again, so a retry after a timeout is safe. An archived plan stays readable.
Errors it returns: as GetPlan.
func (*Client) DeleteWebhookEndpoint ¶ added in v0.5.0
DeleteWebhookEndpoint deletes one of your webhook endpoints. It stops receiving deliveries at once. The key needs the webhooks:manage scope.
An id that is unknown, not yours or already deleted answers an *APIError with HTTPStatus 404 and ErrorCode WEBHOOK_NOT_FOUND. A retry after a lost answer gets that 404 too: treat it as done.
Errors it returns: as ListWebhookEndpoints, and a *ValidationError for an id that is not a UUID.
func (*Client) GetAgreement ¶ added in v0.5.0
GetAgreement reads one of your agreements.
Errors it returns: an *APIError with HTTPStatus 404 and ErrorCode AGREEMENT_NOT_FOUND for an id that is not yours, or 400 INVALID_FORMAT for an id that is not an agreement id; *ValidationError for an id that cannot be a path segment; the generic errors otherwise.
func (*Client) GetIntegrationContract ¶ added in v0.5.0
func (c *Client) GetIntegrationContract(ctx context.Context) (*IntegrationContract, error)
GetIntegrationContract reads the API's machine-readable contract: its routes, request and response fields, error codes and vocabularies, as the API serves them now. The route is public, so it needs no scope; the SDK signs the call anyway, like every call. Use it to check what the API supports, for example a field this SDK does not model yet. Version and the vocabularies are typed; everything else is on Raw.
Errors it returns: as Ping.
func (*Client) GetPlan ¶ added in v0.5.0
GetPlan reads one of your plans.
Errors it returns: an *APIError with HTTPStatus 404 and ErrorCode PLAN_NOT_FOUND for an id that is not yours, or 400 INVALID_FORMAT for an id that is not a plan id; *ValidationError for an id that cannot be a path segment; the generic errors otherwise.
func (*Client) GetRefund ¶ added in v0.3.1
GetRefund reads one refund of a payment: its Status, the Amount refunded once it succeeded, and FailureCode once it failed. Stop polling when IsRefundTerminal(refund.Status) is true.
Right after CreateRefund the refund may not be picked up yet: GetRefund then returns a *RefundError with ErrorCode REFUND_NOT_FOUND and Retryable set. Poll again for up to 60 seconds; after that the id is unknown.
Errors it returns: as CreateRefund, without the idempotency key rules. The read is signed with an empty idempotency key and an empty body.
func (*Client) GetStatus ¶
GetStatus reads the payment status of one of your checkout sessions.
Status is one of the Status* constants: pending, processing, succeeded, failed, refunded, partially_refunded, cancelled, disputed, requires_capture, abandoned. While a session is still payable the response also carries ExpiresAt; after that instant a pending session can only become abandoned. Amounts are integers in MINOR units. An unknown transaction id returns an *APIError with HTTPStatus 404.
StatusSucceeded is the only value that means the payment is complete. Keep polling on StatusPending, StatusProcessing and StatusRequiresCapture - none of them is terminal.
StatusRequiresCapture is NOT "unpaid": the payer has already paid and the funds are held awaiting capture. Never treat it as an abandoned order.
Treat any status you do not recognise as still-open too: a value the API adds later should make you keep polling, never silently close a live order.
StoredPaymentMethod is the card kept on file by a session created with SaveCard: present once the payment is approved (and it stays after a revoke, with Status revoked), nil until then, for sessions without SaveCard and for declined or abandoned ones. Its ID is what ChargePaymentMethod and RevokePaymentMethod take.
Poll after the payer returns to you, or on your order timeout. Not in a tight loop: the endpoint is rate limited per key (60/min per key, 120/min per IP), and going over returns a *RateLimitError.
func (*Client) ListPlans ¶ added in v0.5.0
ListPlans lists your plans: at most 200, active first, newest first within a status. The list has no pages; read a plan by id with GetPlan when you have more.
Errors it returns: *AuthError, *RateLimitError, *TransportError, and an *APIError for any other rejection.
func (*Client) ListRefunds ¶ added in v0.5.0
ListRefunds lists the refunds of one payment that were created through this API: pending first, then processing, then succeeded and failed, newest first within each group. Each refund has the fields GetRefund returns. A refund made in the dashboard or at the provider is not in the list, but RefundedAmount counts it. A new refund shows up once it is picked up, the same moment GetRefund stops answering REFUND_NOT_FOUND.
RefundableAmount is what a new refund could take right now, so read it before a partial refund to size it.
Errors it returns: as GetRefund. An unknown payment is a *RefundError with ErrorCode RefundErrorPaymentNotFound.
func (*Client) ListTransactions ¶ added in v0.5.0
func (c *Client) ListTransactions(ctx context.Context, params ListTransactionsParams) (*TransactionList, error)
ListTransactions reads one page of your card-not-present payments created in the window [From, To): checkout payments, saved-card charges, subscription charges and verify-only sessions. The rows come oldest first. Page with NextCursor until it is empty: paging never repeats or skips a row.
A row has its status at the time of the read. For a statement, end the window a few minutes in the past, and read again later to pick up changes such as refunds. A key bound to a storefront lists that storefront only.
The key needs the transactions:read scope. A key with the payments scope cannot hold it, so use a separate reporting key. The call is signed on the path only, with an empty idempotency key and an empty body: the query string is not part of the signature.
Errors it returns:
- *ValidationError: an empty From or To; nothing was sent.
- *APIError: 400 VALIDATION_ERROR for a window, settlement window, status, limit or cursor the API refused. Raw carries error.validationErrors, one per field. Do not retry it unchanged.
- *AuthError: also 403 INSUFFICIENT_SCOPE for a key without transactions:read.
- *RateLimitError, *TransportError: as everywhere else.
func (*Client) ListWebhookEndpoints ¶ added in v0.5.0
func (c *Client) ListWebhookEndpoints(ctx context.Context) ([]WebhookEndpoint, error)
ListWebhookEndpoints lists your webhook endpoints, newest first: every active endpoint and your newest 1000 inactive ones. The list has no pages and never carries a secret. The key needs the webhooks:manage scope.
Errors it returns: as CreateWebhookEndpoint, without the validation and idempotency rules.
func (*Client) Ping ¶ added in v0.1.2
Ping verifies your credentials, your signing and your clock without creating anything. Make this your first live call: it isolates the setup problems from the payment ones, so a failure here is the key id, the secret, the signature, the clock or an IP allowlist and nothing else.
Check Ping.ClockSkewSeconds. Requests start failing at 300.
Errors it returns:
- *AuthError: wrong credentials, bad signature, clock off, IP not allowlisted.
- *APIError: an unexpected or rejecting response; inspect HTTPStatus.
- *RateLimitError: HTTP 429; back off, the SDK never retries it for you.
- *TransportError: network failure or 5xx.
func (*Client) RevokePaymentMethod ¶ added in v0.3.0
RevokePaymentMethod revokes a card kept on file. The saved credential is deleted at the payment provider and the method's status becomes StoredPaymentMethodStatusRevoked; a later ChargePaymentMethod on it is refused with PAYMENT_METHOD_NOT_ACTIVE. Returns nil on success (HTTP 204), and again for an already revoked method, so retrying a timed-out revoke is safe. Not a payment operation: no idempotency key is signed.
Errors it returns:
- *RevokeError: the gateway refused and nothing changed; MERCHANT_API_UNAVAILABLE (503, retry later) or UPSTREAM_CONTRACT_ERROR (502, the provider refused for good - contact support with the id).
- *APIError: 404 for an id that is not yours, or an unexpected response.
- *AuthError, *RateLimitError, *TransportError: as everywhere else.
func (Client) String ¶ added in v0.2.0
String keeps the secret out of your logs. Printing a Client with %v, %+v or %#v (value or pointer) shows the key id and the base URL, never the secret.
func (*Client) UpdatePlan ¶ added in v0.5.0
func (c *Client) UpdatePlan(ctx context.Context, planID string, params UpdatePlanParams) (*Plan, error)
UpdatePlan changes a plan's name, its trial for agreements created from now on, or its cancel page, and returns the plan. A nil field keeps its value. Amount, currency and interval never change.
The route takes no idempotency key, and the SDK sends none: the same update twice leaves the same plan.
Errors it returns: as GetPlan. A field the API refused is an *APIError with HTTPStatus 400.
type Consent ¶ added in v0.5.0
type Consent struct {
// TextVersion is the version of the consent text the customer accepted,
// at most 50 characters. On a subscription session it must be a version
// of the platform text, e.g. ConsentTextVersionRecurring202609.
TextVersion string `json:"textVersion,omitempty"`
// AcceptedAt is the ISO 8601 UTC instant the customer accepted it, e.g.
// "2026-10-01T09:30:00Z". Not in the future.
AcceptedAt string `json:"acceptedAt,omitempty"`
// IP is the IPv4 or IPv6 address the customer accepted it from.
IP string `json:"ip,omitempty"`
}
Consent records that your customer accepted recurring payment terms: which version of the text, when, and from which IP address. Send all three together.
type CreateAgreementParams ¶ added in v0.5.0
type CreateAgreementParams struct {
// PlanID is the plan_ id of one of your active plans.
PlanID string `json:"planId"`
// CustomerReference is your own id for the customer, 1 to 100
// characters, stable per customer.
CustomerReference string `json:"customerReference"`
// StoredPaymentMethodID is the pm_ id of an active card this customer
// already saved, to bill it instead of saving a new card in the
// subscription session. The card must have been saved from a payment in
// the plan's currency. Consent is required with it.
StoredPaymentMethodID string `json:"storedPaymentMethodId,omitempty"`
// Consent is the customer's acceptance of your recurring terms: your own
// label for the wording as TextVersion, when, and from which IP address.
// It is required with StoredPaymentMethodID or MaxAmountPerPeriod.
// Without them, leave it nil and let the subscription session capture it.
Consent *Consent `json:"consent,omitempty"`
// MaxAmountPerPeriod is for a variable-amount subscription only: the most
// one charge may be, in MINOR units, at least the plan's amount. Consent
// is required with it. 0 sends nothing: a fixed agreement, where every
// charge is exactly the plan's amount.
MaxAmountPerPeriod int64 `json:"maxAmountPerPeriod,omitempty"`
// IdempotencyKey is required. It is sent as the Idempotency-Key header and
// signed. Derive it from your own subscription record, never per attempt.
IdempotencyKey string `json:"-"`
// Extra holds request fields this struct does not model yet. They are
// sent as given, and a key in Extra replaces the modelled field of the
// same name.
Extra map[string]any `json:"-"`
}
CreateAgreementParams is what CreateAgreement sends.
func (CreateAgreementParams) MarshalJSON ¶ added in v0.5.0
func (p CreateAgreementParams) MarshalJSON() ([]byte, error)
MarshalJSON writes the modelled fields, then the Extra fields.
type CreateCheckoutSessionParams ¶
type CreateCheckoutSessionParams struct {
// Amount is in MINOR units: 2500 is 25.00 EUR. Integers only. It must be
// positive, except with VerifyOnly, where it must be 0: the platform sets
// the verification amount.
Amount int64 `json:"amount"`
// Currency is ISO 4217, e.g. "EUR".
Currency string `json:"currency"`
// OrderReference is your own order id, at most 100 characters. It shows up
// in your dashboard.
OrderReference string `json:"orderReference"`
Customer *Customer `json:"customer,omitempty"`
// Country is ISO 3166-1 alpha-2.
Country string `json:"country,omitempty"`
// Language is the ISO 639-1 widget UI language.
Language string `json:"language,omitempty"`
// Theme is "light", "dark" or "bright".
Theme string `json:"theme,omitempty"`
Description string `json:"description,omitempty"`
// SaveCard asks the gateway to keep the card on file once this payment is
// approved, so you can charge it again later with Client.ChargePaymentMethod.
// The stored method shows up on CheckoutStatus.StoredPaymentMethod after the
// payment succeeds; a declined first payment stores nothing. The card details
// themselves never reach you: you get an id, a brand and the last four digits.
// With VerifyOnly or AgreementID the card is always saved.
SaveCard bool `json:"saveCard,omitempty"`
// Integration picks how the payer enters the card: IntegrationWidget (the
// default when empty) or IntegrationFields, card fields in your own page.
// Card fields are enabled per merchant on request; asking for them on an
// account without them is a 400 INVALID_SELECTION *APIError. It is part of
// the idempotency identity: replaying a key with a different Integration is
// IDEMPOTENCY_KEY_REUSED. Empty sends no integration field.
Integration Integration `json:"integration,omitempty"`
// LocationID is the id of the storefront (online location) this payment
// belongs to. Empty uses your default storefront, or your only active one
// when no default is set. An id that is not one of your storefronts is
// refused with a 404 *APIError. An API key bound to one storefront refuses
// another id with ErrorCodeStorefrontMismatch.
LocationID string `json:"locationId,omitempty"`
// VerifyOnly saves the payer's card without a payment. The checkout places
// a small authorization to check the card and saves the card. The platform
// releases the authorization within about 15 minutes, so nobody is
// charged. Leave Amount 0. SaveCard is implied, Integration must be empty
// or IntegrationWidget, and AgreementID must be empty.
//
// Verify-only sessions are off until we switch them on for your account.
// Until then the session is refused with a 403 *AuthError, ErrorCode
// ErrorCodeVerifyOnlyNotEnabled. The status read of a verify-only session
// has VerifyOnly true, and a non-nil StoredPaymentMethod says the card is
// verified. It fires verification.* webhook events, never payment.* ones.
VerifyOnly bool `json:"verifyOnly,omitempty"`
// AgreementID is the agr_ id of a PENDING agreement (CreateAgreement). It
// makes this the subscription session: it takes the agreement's first
// payment, saves the card and activates the agreement. Amount and Currency
// must be the agreement's own; on a plan with a trial only the currency is
// held. A refusal arrives as a *RefusalError with one of the
// SubscriptionSessionRefusalCodes.
AgreementID string `json:"agreementId,omitempty"`
// Consent records that the payer accepted the platform's recurring payment
// terms on your page. Send it with AgreementID only. Consent.TextVersion
// must be a version of the platform text, e.g.
// ConsentTextVersionRecurring202609.
Consent *Consent `json:"consent,omitempty"`
// IdempotencyKey is required. Derive it from the order with
// OrderIdempotencyKey: the same order at the same amount must produce the
// same key, so a reload, a Back button or a retry after a timeout replays
// the open session instead of opening a second payment. It travels in the
// header and in the signature, never in the body. 1 to 100 visible ASCII
// characters; anything else is refused with a *ValidationError before
// anything is sent.
IdempotencyKey string `json:"-"`
// Extra carries any additional field the API accepts that this struct does
// not model yet. Keys here are merged into the JSON body.
Extra map[string]any `json:"-"`
}
CreateCheckoutSessionParams are the parameters for Client.CreateCheckoutSession. Amount, Currency and OrderReference are required. With VerifyOnly, leave Amount 0.
func (CreateCheckoutSessionParams) MarshalJSON ¶
func (p CreateCheckoutSessionParams) MarshalJSON() ([]byte, error)
MarshalJSON emits the request body: the modelled fields in declaration order, with Extra merged in. IdempotencyKey is excluded; it is a header, not a body field.
type CreatePlanParams ¶ added in v0.5.0
type CreatePlanParams struct {
// Name is the display name, 1 to 100 characters. UpdatePlan can change it.
Name string `json:"name"`
// Amount is the charge per period, in MINOR units of Currency (2500 is
// 25.00 EUR). Convert with ToMinorUnits.
Amount int64 `json:"amount"`
// Currency is the ISO 4217 code, three upper-case letters.
Currency string `json:"currency"`
// IntervalUnit is one of the IntervalUnit* values.
IntervalUnit string `json:"intervalUnit"`
// IntervalCount is the number of units per billing period: every 3 months
// is IntervalUnitMonth with IntervalCount 3. 0 sends nothing, and the API
// then uses 1.
IntervalCount int `json:"intervalCount,omitempty"`
// PeriodCount is the number of periods after which an agreement completes
// on its own, at least 1. nil bills until the agreement is cancelled. It
// is a pointer so that a count that computed to zero is refused instead
// of turning into "bill forever".
PeriodCount *int `json:"periodCount,omitempty"`
// TrialDays is the number of days between activation and the first
// charge, 0 to 365. 0 is no trial. UpdatePlan can change it for agreements
// created later.
TrialDays int `json:"trialDays,omitempty"`
// CancelURL is your page where the customer cancels the subscription: an
// absolute https URL. The platform prints it as the cancel link in every
// receipt and decline notice it emails your customer. Empty sends nothing.
CancelURL string `json:"cancelUrl,omitempty"`
// IdempotencyKey is required. It is sent as the Idempotency-Key header and
// signed. Derive it from your own plan record, never per attempt.
IdempotencyKey string `json:"-"`
// Extra holds request fields this struct does not model yet. They are
// sent as given, and a key in Extra replaces the modelled field of the
// same name.
Extra map[string]any `json:"-"`
}
CreatePlanParams is what CreatePlan sends. Amount, Currency and the interval never change once the plan exists: to change a price, create a new plan and archive the old one.
func (CreatePlanParams) MarshalJSON ¶ added in v0.5.0
func (p CreatePlanParams) MarshalJSON() ([]byte, error)
MarshalJSON writes the modelled fields, then the Extra fields.
type CreateRefundParams ¶ added in v0.3.1
type CreateRefundParams struct {
// Amount is what to refund, in MINOR units of the payment's currency (2500
// is 25.00 EUR, 1500 is 1,500 HUF), at least 1. nil refunds everything still
// refundable: no amount is sent at all. A pointer, so a partial refund that
// computed to zero is refused as a *ValidationError instead of quietly
// turning into a full refund. ToMinorUnits converts a decimal amount.
Amount *int64 `json:"amount,omitempty"`
// Reason is free text stored with the refund, at most 500 characters.
Reason string `json:"reason,omitempty"`
// IdempotencyKey is required. Derive it from YOUR refund (the return or
// credit-note id), never per attempt: the same key always names the same
// refund and never refunds twice, so on a timeout retry with the same key.
// It travels in the header and in the signature, never in the body. 1 to
// 100 visible ASCII characters; anything else is refused with a
// *ValidationError before anything is sent.
IdempotencyKey string `json:"-"`
// Extra carries any additional field the API accepts that this struct does
// not model yet. Keys here are merged into the JSON body and replace a
// modelled field of the same name.
Extra map[string]any `json:"-"`
}
CreateRefundParams are the parameters for Client.CreateRefund. Only IdempotencyKey is required.
func (CreateRefundParams) MarshalJSON ¶ added in v0.5.0
func (p CreateRefundParams) MarshalJSON() ([]byte, error)
MarshalJSON emits the request body: the modelled fields, with Extra merged in. A nil Amount is left out. IdempotencyKey is excluded; it is a header, not a body field.
type CreateWebhookEndpointParams ¶ added in v0.5.0
type CreateWebhookEndpointParams struct {
// URL receives the deliveries: https on a public host, at most 500
// characters.
URL string `json:"url"`
// Events are the event types to deliver, 1 to 50 entries: the Event*
// constants. Duplicates count once.
Events []string `json:"events"`
// IdempotencyKey is required. It is sent as the Idempotency-Key header and
// signed. Use a fresh key for each new endpoint.
IdempotencyKey string `json:"-"`
// Extra holds request fields this struct does not model yet. They are
// sent as given, and a key in Extra replaces the modelled field of the
// same name.
Extra map[string]any `json:"-"`
}
CreateWebhookEndpointParams is what CreateWebhookEndpoint sends.
func (CreateWebhookEndpointParams) MarshalJSON ¶ added in v0.5.0
func (p CreateWebhookEndpointParams) MarshalJSON() ([]byte, error)
MarshalJSON writes the modelled fields, then the Extra fields.
type Customer ¶
type Customer struct {
FirstName string `json:"firstName,omitempty"`
LastName string `json:"lastName,omitempty"`
Email string `json:"email,omitempty"`
Phone string `json:"phone,omitempty"`
// Address is the billing street address, one line, at most 200
// characters. The widget asks the payer for address, city and postal
// code when the payment processor needs them, and skips a field you send.
Address string `json:"address,omitempty"`
// City is the billing city, at most 100 characters.
City string `json:"city,omitempty"`
// PostalCode is the billing postal code, at most 20 characters.
PostalCode string `json:"postalCode,omitempty"`
// State is the billing state or province code, at most 50 characters,
// e.g. "CA" or "NSW". Only used for the United States, Canada and
// Australia.
State string `json:"state,omitempty"`
}
Customer holds optional payer details. Prefilled fields are hidden from the payer in the widget, so the checkout form stays short.
type Error ¶
type Error interface {
error
// contains filtered or unexported methods
}
Error is implemented by every error this SDK returns. It is sealed: only the types in this package satisfy it, so a type switch over them is exhaustive.
var apiErr dominaite.Error
if errors.As(err, &apiErr) { ... }
type Integration ¶ added in v0.4.0
type Integration string
Integration is how the payer enters the card on a checkout session.
const ( // IntegrationWidget is the hosted cashier widget. The default. IntegrationWidget Integration = "widget" // IntegrationFields is card fields rendered in your own page by the // checkout.js drop-in. Enabled per merchant on request. IntegrationFields Integration = "fields" )
Integration values, in the contract's order.
type IntegrationContract ¶ added in v0.5.0
type IntegrationContract struct {
// Version is the dated version of the contract, e.g. "2026-09-16".
Version string `json:"version"`
// Statuses is the transaction status vocabulary, in the gateway's order.
Statuses []string `json:"statuses"`
// WebhookEventCatalog is the payment.* webhook event types. The recurring
// and verification event types are in their own sections of Raw.
WebhookEventCatalog []string `json:"webhookEventCatalog"`
// RequestID is the API's id of the call that returned this: the
// Dominaite-Request-Id response header, or metadata.requestId in the body
// when the header is absent. Quote it to support. Empty when the API sent
// neither.
RequestID string `json:"-"`
// Raw is the whole contract as the API sent it.
Raw json.RawMessage `json:"-"`
}
IntegrationContract is what GetIntegrationContract returns: the API's own description of its routes, fields, codes and vocabularies.
type ListTransactionsParams ¶ added in v0.5.0
type ListTransactionsParams struct {
// From is the start of the window, inclusive: ISO 8601 UTC in the Z form,
// e.g. "2026-10-01T00:00:00Z" (t.UTC().Format(time.RFC3339)), or a date,
// which is midnight UTC. Required.
From string
// To is the end of the window, exclusive, in the same form. Required. It
// must be later than From, at most 31 days after it.
To string
// SettledFrom is the start of the settlement window, inclusive: a date in
// the form "2026-10-13". Optional, but send it together with SettledTo.
// With it, the list has only payments the acquirer settled in the window.
// From and To still apply on CreatedAt.
SettledFrom string
// SettledTo is the end of the settlement window, exclusive, a date in the
// same form. It must be later than SettledFrom, at most 31 days after it.
SettledTo string
// Status keeps only payments now in this status: one of Statuses. Empty
// lists every status. The SDK does not check it: the API refuses an
// unknown status with a 400.
Status string
// Limit is the number of items per page, 1 to 100. 0 sends nothing, and
// the API then uses 50.
Limit int
// Cursor is the NextCursor of the previous page, unchanged. Empty reads
// the first page.
Cursor string
}
ListTransactionsParams selects one page of ListTransactions.
type Option ¶
type Option func(*Client)
Option configures a Client. Pass options to New.
func WithBaseURL ¶
WithBaseURL sets the API the client calls: SandboxBaseURL, ProductionBaseURL or your own value. It is required: New returns a *ValidationError without it, and when the value is empty or whitespace. Whitespace and trailing slashes are trimmed.
The URL must be https://. Plain http:// is accepted only for localhost, 127.0.0.1 and ::1, so a local mock server still works; anything else is refused by New with a *ValidationError. Over http:// your API secret's signature, key id and the whole request body travel in clear text.
func WithHTTPClient ¶
WithHTTPClient supplies your own *http.Client: a proxy-aware transport, custom TLS, or a test double. It replaces WithTimeout, so set the timeout on the client you pass.
The client is shallow-copied, so the SDK can pin its own redirect policy without changing the behaviour of the client you keep using elsewhere.
func WithLogger ¶ added in v0.6.0
WithLogger sends the SDK's warnings to logger instead of slog.Default(), and turns on the debug log: one line per request at slog.LevelDebug, which your handler's level then shows or drops. The default logger never gets debug lines.
Warnings are logged at slog.LevelWarn: an SDK notice from the API (once per distinct value), each retry of the client's retry policy, and a Ping clock skew above 60 seconds. No line ever holds the secret, the signature, the cashier token, a query string or a request or response body.
func WithMaxRetries ¶ added in v0.7.0
WithMaxRetries sets how many times the client retries a failed call after the first attempt. Defaults to 2, so a call is sent at most 3 times. 0 disables retries. A negative value makes New return a *ValidationError.
The client retries reads (every GET) and every POST that carries an idempotency key, except CreateWebhookEndpoint, which is never retried. It retries only a *TransportError (a network failure or a 5xx) and PAYMENT_PROCESSING_UNAVAILABLE, on the refusal and on the charge error. The charge route's 503 PAYMENT_METHOD_CHARGES_DISABLED is not retried: it lasts until saved-card charges are switched on for your account. Every retry sends the same idempotency key and the same body, signed again with a fresh timestamp. A 429 is never retried. Neither is a DELETE or a PATCH.
The wait before retry n (0 for the first) is a random time between 0 and 500ms * 2^n, capped at 8 seconds. The timeout applies to each attempt, so one call can take up to (maxRetries + 1) times the timeout plus those waits. Bound the whole call with a context deadline when that matters.
func WithTimeout ¶
WithTimeout sets the per-request timeout on the default HTTP client. Defaults to 45 seconds. A context deadline still wins when it is shorter.
func WithUserAgent ¶
WithUserAgent appends your own identifier to the SDK's User-Agent, which helps when Dominaite support reads the access logs for your integration. The SDK sends "dominaite-go/<version> (go <go version>) <your suffix>". A suffix with a control character makes New return a *ValidationError.
type PaymentMethodCharge ¶ added in v0.3.0
type PaymentMethodCharge struct {
// ChargeID is ch_ followed by 32 hex characters. Store it against the
// order; it is what support asks for.
ChargeID string `json:"chargeId"`
// Status is one of the ChargeStatus* constants. Treat anything you do not
// recognise as still open.
Status string `json:"status"`
// DeclineClass is one of the DeclineClass* constants on a failed charge. It
// can be empty, for example when nobody declined the payment. An empty
// class is not hard: if you retry, retry as for DeclineClassSoftOther. The
// status read (GetStatus on TransactionID) carries the latest class. Empty
// on every status other than failed.
DeclineClass string `json:"declineClass,omitempty"`
// DeclineCode is the issuer's decline code, verbatim, for your logs;
// branch on DeclineClass instead. Empty unless the charge failed.
DeclineCode string `json:"declineCode,omitempty"`
// TransactionID is the transaction the charge created; readable with
// GetStatus.
TransactionID string `json:"transactionId"`
// AgreementID is the agr_ id the charge was placed under, when the request
// named one. Empty otherwise.
AgreementID string `json:"agreementId,omitempty"`
// Sequence is the charge's outcome counter as of this answer: on a charge
// decided at once, the data.sequence of its charge.succeeded or
// charge.failed event. 0 while pending. A replay returns the first answer
// unchanged, so it can be lower than a later event's; GetStatus carries
// the live value.
Sequence int64 `json:"sequence,omitempty"`
// RequestID is the API's id of the call that returned this: the
// Dominaite-Request-Id response header, or metadata.requestId in the body
// when the header is absent. Quote it to support. Empty when the API sent
// neither.
RequestID string `json:"-"`
// Raw is the unparsed charge object, for fields this struct does not model yet.
Raw json.RawMessage `json:"-"`
}
PaymentMethodCharge is what ChargePaymentMethod returns, for a placed charge (HTTP 201) and for a provider decline (HTTP 402, Status ChargeStatusFailed) alike. Also carried on a *ChargeError when the gateway attached the charge row to its answer.
type Ping ¶ added in v0.1.2
type Ping struct {
// Pong is always true on a 200.
Pong bool `json:"pong"`
// MerchantID is the merchant your key authenticated as.
MerchantID string `json:"merchantId"`
// ServerTime is ISO 8601.
ServerTime string `json:"serverTime,omitempty"`
// ServerUnixTime is the server clock in unix seconds.
ServerUnixTime int64 `json:"serverUnixTime,omitempty"`
// ClockSkewSeconds is server time minus your X-Timestamp. If its absolute
// value creeps toward 300, fix NTP now - requests start failing at 300.
ClockSkewSeconds int64 `json:"clockSkewSeconds"`
// RequestID is the API's id of the call that returned this: the
// Dominaite-Request-Id response header, or metadata.requestId in the body
// when the header is absent. Quote it to support. Empty when the API sent
// neither.
RequestID string `json:"-"`
// Raw is the unparsed payload, for fields this struct does not model yet.
Raw json.RawMessage `json:"-"`
}
Ping is what Ping returns: proof that your key, secret, signing and clock are all good, without creating anything.
type Plan ¶ added in v0.5.0
type Plan struct {
// ID is plan_ followed by 32 hex characters. It is case-sensitive: store
// it and send it back exactly as it is.
ID string `json:"id"`
Name string `json:"name"`
// Amount is the charge per period, in MINOR units of Currency.
Amount int64 `json:"amount"`
Currency string `json:"currency"`
// IntervalUnit is one of the IntervalUnit* values.
IntervalUnit string `json:"intervalUnit"`
IntervalCount int `json:"intervalCount"`
// PeriodCount is nil when agreements bill until they are cancelled.
PeriodCount *int `json:"periodCount,omitempty"`
// TrialDays applies to agreements created from now on. 0 is no trial.
TrialDays int `json:"trialDays"`
// Status is one of the PlanStatus* values.
Status string `json:"status"`
// CancelURL is empty when the plan has no cancel page.
CancelURL string `json:"cancelUrl,omitempty"`
// CreatedAt is ISO 8601 UTC.
CreatedAt string `json:"createdAt"`
// ArchivedAt is ISO 8601 UTC, empty while the plan is active.
ArchivedAt string `json:"archivedAt,omitempty"`
// RequestID is the API's id of the call that returned this plan. Quote it
// to support. Empty when the API sent none.
RequestID string `json:"-"`
// Raw is the unparsed plan object, for fields this struct does not model yet.
Raw json.RawMessage `json:"-"`
}
Plan is a subscription plan: a price and a billing interval you sell. An agreement subscribes one customer to it.
type RateLimitError ¶ added in v0.2.0
type RateLimitError struct {
RetryAfterSeconds int
HasRetryAfter bool
ErrorCode string
// Raw is the whole envelope the gateway sent, for fields not modelled
// above. Empty when the 429 had no body.
Raw json.RawMessage
// contains filtered or unexported fields
}
RateLimitError means the API answered HTTP 429: you sent requests faster than your allowance. Nothing was created and nothing is wrong with the request.
The platform limits are 60 requests per minute per API key and 120 per minute per IP address. Both are enforced, so several keys behind one egress IP share the second budget.
The SDK does NOT retry this for you, on purpose. A client that retries into a rate limit makes the queue longer for everyone, and only your code knows whether this payment can wait. The client's retry policy (WithMaxRetries) returns it at once. When you do retry, reuse the SAME idempotency key: the request may have been rejected at the edge, but that is not guaranteed.
RetryAfterSeconds carries the Retry-After header when the API sent one as integer seconds. HasRetryAfter distinguishes "wait 0 seconds" from "the API did not say" - a Retry-After in the HTTP-date form is reported as absent.
var limited *dominaite.RateLimitError
if errors.As(err, &limited) {
wait := 5 * time.Second
if limited.HasRetryAfter {
wait = time.Duration(limited.RetryAfterSeconds) * time.Second
}
// reschedule with the same idempotency key
}
ErrorCode is the machine-readable code when the API named one, empty otherwise: ErrorCodeRateLimitExceeded for the request limits, and ErrorCodeChargeVelocityExceeded or ErrorCodeChargeDeclineRateExceeded on ChargePaymentMethod. Raw is the whole envelope; the charge limits put their window and limit under error.params.
type Refund ¶ added in v0.3.1
type Refund struct {
// RefundID is re_ followed by 32 hex characters. The same idempotency key
// on the same payment always names the same refund.
RefundID string `json:"refundId"`
// TransactionID is the payment being refunded.
TransactionID string `json:"transactionId"`
// Status is one of the RefundStatus* constants.
Status string `json:"status"`
// Amount is in MINOR units. On pending it is the amount requested, nil for a
// full refund; on processing it is the amount being refunded, nil until a
// full refund has been sized; on succeeded it is the amount actually
// refunded; on failed it is always nil.
Amount *int64 `json:"amount,omitempty"`
// Currency is the ISO 4217 code of the payment. A refund is always in the
// payment's currency.
Currency string `json:"currency"`
// FailureCode is set on a failed refund only: one of the RefundFailure*
// constants. Treat a value you do not recognise as RefundFailureFailed.
FailureCode string `json:"failureCode,omitempty"`
// FailureMessage is a fixed English explanation of FailureCode, for your
// logs. Empty unless the refund failed.
FailureMessage string `json:"failureMessage,omitempty"`
// CompletedAt is the ISO 8601 UTC instant the refund reached succeeded or
// failed. Empty before that.
CompletedAt string `json:"completedAt,omitempty"`
// RequestID is the API's id of the call that returned this: the
// Dominaite-Request-Id response header, or metadata.requestId in the body
// when the header is absent. Quote it to support. Empty when the API sent
// neither.
RequestID string `json:"-"`
// Raw is the unparsed refund object, for fields this struct does not model yet.
Raw json.RawMessage `json:"-"`
}
Refund is what CreateRefund and GetRefund return. The gateway omits null fields on the wire; they read as nil or empty here.
type RefundError ¶ added in v0.3.1
type RefundError struct {
HTTPStatus int
ErrorCode string
// Retryable reports whether retrying with the same idempotency key can
// succeed: DUPLICATE_REQUEST and REFUND_NOT_FOUND.
Retryable bool
// Raw is the whole envelope the gateway sent, for fields not modelled above.
Raw json.RawMessage
// contains filtered or unexported fields
}
RefundError means a refund route answered with one of the RefundError* codes instead of a refund: HTTP 400, 404, 409 or 422 (see each constant for what to do). HTTPStatus carries the status, Message the gateway's own text.
Retryable is true for the two codes the gateway asks you to retry with the SAME idempotency key: DUPLICATE_REQUEST (for up to 120 seconds) and, on GetRefund, REFUND_NOT_FOUND (for up to 60 seconds after CreateRefund). Everything else will not change on a retry.
refund, err := client.CreateRefund(ctx, transactionID, params)
var refundErr *dominaite.RefundError
if errors.As(err, &refundErr) && refundErr.ErrorCode == dominaite.RefundErrorAmountExceeded {
// less is left to refund than you asked for; nothing was queued
}
A failed refund is not a *RefundError: it comes back as a Refund with Status RefundStatusFailed and a FailureCode. Authentication (401/403, *AuthError), rate limiting (429, *RateLimitError) and network failures or any 5xx (*TransportError: nothing was queued, retry with the SAME key; a 503 MERCHANT_API_UNAVAILABLE keeps its code on TransportError.ErrorCode) keep their generic errors on these routes.
type RefundList ¶ added in v0.5.0
type RefundList struct {
// TransactionID is the payment whose refunds these are.
TransactionID string `json:"transactionId"`
// Currency is the ISO 4217 code of the payment and of every amount here.
Currency string `json:"currency"`
// RefundedAmount is what was refunded so far, from every source: this
// API, the dashboard and the provider.
RefundedAmount int64 `json:"refundedAmount"`
// RefundableAmount is what a new refund could take right now: what is not
// refunded yet, minus refunds still in progress. 0 when the payment
// cannot be refunded.
RefundableAmount int64 `json:"refundableAmount"`
// Refunds are the refunds created through this API: pending first, then
// processing, then succeeded and failed, newest first within each group.
// Empty when there are none.
Refunds []Refund `json:"refunds"`
// RequestID is the API's id of the call that returned this: the
// Dominaite-Request-Id response header, or metadata.requestId in the body
// when the header is absent. Quote it to support. Empty when the API sent
// neither.
RequestID string `json:"-"`
// Raw is the unparsed refund list, for fields this struct does not model yet.
Raw json.RawMessage `json:"-"`
}
RefundList is what ListRefunds returns: the refunds of one payment created through this API, and the amounts that decide the next refund. Every amount is in MINOR units of Currency.
type RefusalError ¶
type RefusalError struct {
ErrorCode string
// TransactionID is the payment this idempotency key collided with, when the
// API named one. Empty otherwise.
TransactionID string
// Raw is the unparsed refusal payload, for fields not modelled above.
Raw json.RawMessage
// contains filtered or unexported fields
}
RefusalError means the gateway understood the request but refused to open a checkout session. The API answered with success: false.
Branch on ErrorCode (the ErrorCode* constants):
- PAYMENT_PROCESSING_UNAVAILABLE: card payments are off right now; retry later.
- DUPLICATE_REQUEST: a session for this idempotency key exists but cannot be handed back yet; re-POST the same key shortly.
- ALREADY_PROCESSED: this idempotency key's payment already completed.
- PRIOR_ATTEMPT_FAILED: a prior attempt with this key failed terminally; use a fresh key.
- IDEMPOTENCY_KEY_REUSED: same key sent with a different amount, currency or card-saving choice; a re-priced order needs a new key.
- STOREFRONT_MISMATCH: the key was first used for a different storefront.
On a replay refusal the API also names WHICH payment your key collided with, on TransactionID. That is the recovery path - read it back with GetStatus to find out what the earlier attempt did, instead of minting a second payment for the same order:
session, err := client.CreateCheckoutSession(ctx, params)
var refusal *dominaite.RefusalError
if errors.As(err, &refusal) && refusal.TransactionID != "" {
status, err := client.GetStatus(ctx, refusal.TransactionID)
}
TransactionID is empty when the API did not name one - notably the concurrent-race DUPLICATE_REQUEST, which knows a key was taken but not yet by which row. Always check before using it.
Never blind-retry a refusal. It will not change on its own.
type RetryOptions
deprecated
type RetryOptions struct {
// Attempts is the total number of attempts including the first. Zero means
// the client's policy: WithMaxRetries plus the first attempt, 3 by default.
Attempts int
// BaseDelay bounds the wait before the first retry, which is a random time
// between 0 and BaseDelay; the bound doubles each retry, up to 8 seconds.
// Zero means the default of 500ms.
BaseDelay time.Duration
}
RetryOptions tunes CreateCheckoutSessionWithRetry.
Deprecated: set the retry budget on the client with WithMaxRetries; CreateCheckoutSession retries on its own.
type RevokeError ¶ added in v0.3.0
type RevokeError struct {
HTTPStatus int
ErrorCode string
// Raw is the whole envelope the gateway sent, for fields not modelled above.
Raw json.RawMessage
// contains filtered or unexported fields
}
RevokeError means the gateway refused to revoke a stored payment method: HTTP 502 or 503 with one of the RevokeError* codes on ErrorCode. Nothing changed either way. An id that is not yours is still the generic *APIError with HTTPStatus 404. Any other 5xx is a *TransportError, with the code when there was one.
type SignInput ¶
type SignInput struct {
// Secret is your API secret (dms_...). json:"-" keeps it out of every
// structured JSON logger: those encode the struct rather than calling
// String, so a tag is the only thing that stops them.
Secret string `json:"-"`
// Timestamp is unix SECONDS, as sent in X-Timestamp.
Timestamp string
// Method is the HTTP method. Uppercased before signing.
Method string
// Path is the canonical path only: no host, no query string.
Path string
// IdempotencyKey is the Idempotency-Key header value. Empty string for GET.
IdempotencyKey string
// Body is the exact request body string that will be sent. Empty string for GET.
Body string
}
SignInput is everything that goes into one request signature.
type StoredPaymentMethod ¶ added in v0.3.0
type StoredPaymentMethod struct {
// ID is opaque: pm_ followed by 32 hex characters, case-sensitive. The
// handle you charge and revoke with.
ID string `json:"id"`
// Brand is the card brand as the gateway reports it, e.g. "visa", "mastercard".
Brand string `json:"brand,omitempty"`
// Last4 is the last four digits of the card number, for display only.
Last4 string `json:"last4,omitempty"`
// ExpiryMonth is 1 to 12.
ExpiryMonth int `json:"expiryMonth,omitempty"`
// ExpiryYear is four digits, e.g. 2029.
ExpiryYear int `json:"expiryYear,omitempty"`
// Status is one of the StoredPaymentMethodStatus* constants. Treat any value
// you do not recognise as not chargeable.
Status string `json:"status"`
// RetiredReason is one of the RetiredReason* constants when Status is
// StoredPaymentMethodStatusRetired, and kept if the card is revoked
// afterwards. Empty on every other card (the gateway sends null or omits it).
RetiredReason string `json:"retiredReason,omitempty"`
}
StoredPaymentMethod is a card kept on file. Never the card number, never the PSP token - only what you may show a customer. Brand, Last4 and the expiry are zero when the provider did not report them (the gateway omits null fields on the wire).
type Transaction ¶ added in v0.5.0
type Transaction struct {
// TransactionID is the payment's id, as the session or the charge
// returned it.
TransactionID string `json:"transactionId"`
// OrderReference is your own order id from the session, empty when none
// was sent.
OrderReference string `json:"orderReference,omitempty"`
// Type is one of the TransactionType* values.
Type string `json:"type"`
// Status is one of Statuses, the same value GetStatus answers.
Status string `json:"status"`
// Amount is in MINOR units of Currency.
Amount int64 `json:"amount"`
Currency string `json:"currency"`
// CreatedAt is ISO 8601 UTC. The window and the order use it.
CreatedAt string `json:"createdAt"`
// CompletedAt is ISO 8601 UTC, when the payment first succeeded. Empty
// when it never succeeded, and on older payments that succeeded before
// this time was recorded.
CompletedAt string `json:"completedAt,omitempty"`
// StoredPaymentMethodID is the pm_ id of the card a charge was made on.
// Empty on a checkout payment, and on an old charge that did not record
// its card.
StoredPaymentMethodID string `json:"storedPaymentMethodId,omitempty"`
// RefundedAmount is what was refunded so far, in MINOR units. 0 when
// nothing was refunded.
RefundedAmount int64 `json:"refundedAmount,omitempty"`
// Fee is the acquirer fee of this payment in MINOR units of Currency. nil
// until the payment is settled, usually the next business day. It can
// change, or go back to nil, when the settlement is matched again.
Fee *int64 `json:"fee,omitempty"`
// NetAmount is Amount minus Fee, in MINOR units. nil when Fee is nil. It
// does not include deductions the acquirer makes on the whole payout.
NetAmount *int64 `json:"netAmount,omitempty"`
// SettledAt is the date the acquirer settled the payment, as ISO 8601 UTC
// at midnight, e.g. "2026-10-13T00:00:00Z". Empty until settled.
SettledAt string `json:"settledAt,omitempty"`
// PayoutID is the id of the payout that paid this money to you, the id the
// dashboard shows. Empty until the money is paid out.
PayoutID string `json:"payoutId,omitempty"`
// RequestID is the API's id of the call that read this page, the same as
// TransactionList.RequestID. Quote it to support. Empty when the API sent
// none.
RequestID string `json:"-"`
// Raw is the unparsed item, for fields this struct does not model yet.
Raw json.RawMessage `json:"-"`
}
Transaction is one payment in ListTransactions. A refund is not a row of its own: it shows on its payment as RefundedAmount and Status.
type TransactionList ¶ added in v0.5.0
type TransactionList struct {
// Items are the payments of this page, oldest first. Empty when the
// window has none.
Items []Transaction `json:"items"`
// NextCursor reads the next page: send it back as Cursor with the same
// From, To, SettledFrom, SettledTo and Status. Empty on the last page.
NextCursor string `json:"nextCursor,omitempty"`
// RequestID is the API's id of the call that read this page. Quote it to
// support. Empty when the API sent none.
RequestID string `json:"-"`
// Raw is the unparsed page, for fields this struct does not model yet.
Raw json.RawMessage `json:"-"`
}
TransactionList is one page of ListTransactions.
type TransportError ¶
type TransportError struct {
Err error
HTTPStatus int
ErrorCode string
// Raw is the whole envelope the API sent with the 5xx, for fields not
// modelled above.
Raw json.RawMessage
// contains filtered or unexported fields
}
TransportError means a network-level failure, a timeout, or a 5xx. The request may or may not have reached the API, so retry WITH THE SAME idempotency key: a retried key never creates a second payment. The client retries it for you on the keyed writes and the reads (see WithMaxRetries) before you see it.
Err is the underlying cause when there was one, reachable with errors.Unwrap or errors.As.
HTTPStatus is the 5xx status, 0 when no response arrived. ErrorCode is the API's code when the 5xx carried one, for example ErrorCodeMerchantAPIUnavailable or ErrorCodeInternalError, and empty otherwise. Raw is the envelope of that 5xx, nil when it was not JSON.
func (*TransportError) Unwrap ¶
func (e *TransportError) Unwrap() error
Unwrap exposes the underlying network error, if any.
type UpdatePlanParams ¶ added in v0.5.0
type UpdatePlanParams struct {
// Name is the new display name, 1 to 100 characters.
Name *string `json:"name,omitempty"`
// TrialDays is the new trial, 0 to 365 days, for agreements created from
// now on. A pointer to 0 removes the trial.
TrialDays *int `json:"trialDays,omitempty"`
// CancelURL is the new cancel page, shown on the next email. A pointer to
// "" removes it.
CancelURL *string `json:"cancelUrl,omitempty"`
// Extra holds request fields this struct does not model yet. They are
// sent as given, and a key in Extra replaces the modelled field of the
// same name.
Extra map[string]any `json:"-"`
}
UpdatePlanParams is what UpdatePlan sends. A nil field keeps its value. Amount, currency and interval cannot change.
func (UpdatePlanParams) MarshalJSON ¶ added in v0.5.0
func (p UpdatePlanParams) MarshalJSON() ([]byte, error)
MarshalJSON writes the modelled fields, then the Extra fields.
type ValidationError ¶
type ValidationError struct {
// contains filtered or unexported fields
}
ValidationError means the SDK rejected the call before sending anything. Nothing reached the network; fix the arguments.
type VerifyOption ¶ added in v0.2.0
type VerifyOption func(*verifyConfig)
VerifyOption tunes VerifyWebhook. The defaults are correct for production; you should not need any of these outside tests.
func WithWebhookClock ¶ added in v0.2.0
func WithWebhookClock(now func() time.Time) VerifyOption
WithWebhookClock replaces the clock VerifyWebhook compares against. This exists so tests can pin a fixed instant against a recorded delivery; it has no use in production code. A nil function is ignored.
func WithWebhookTolerance ¶ added in v0.2.0
func WithWebhookTolerance(tolerance time.Duration) VerifyOption
WithWebhookTolerance overrides how far the delivery timestamp may sit from your clock. Defaults to DefaultWebhookTolerance (5 minutes).
Zero is the strictest setting, not an off switch: it requires the timestamp to equal your clock to the second, which in practice rejects everything that spent any time in flight. There is no way to disable the freshness check, because doing so accepts a replay of any delivery ever captured. A negative duration is refused outright - VerifyWebhook returns WebhookReasonInvalidTolerance without checking anything.
If you are reaching for this because deliveries keep failing, your server clock is wrong. Fix NTP rather than widening the window.
type WebhookData ¶ added in v0.2.0
type WebhookData struct {
TransactionID string `json:"transactionId"`
// OrderReference is your own order reference as stored on the
// transaction: what you sent on create session, unless you replaced it
// later. Match deliveries to your orders on it. Refund and cancel events
// carry the original payment's. Nullable on the wire; empty string when
// absent, which includes payments not started through the API.
OrderReference string `json:"orderReference"`
// OrderID is the hosted checkout order id, the same value as
// CheckoutStatus.OrderID. Nullable on the wire; empty string on refund and
// cancel events and for payments taken outside hosted checkout.
OrderID string `json:"orderId"`
// Description is the description you sent on create session. Nullable on
// the wire; empty string when none was given and on refund and cancel
// events.
Description string `json:"description"`
// Status is the wire status of the row, one of the Status* constants.
Status string `json:"status"`
// PreviousStatus is the status the row moved from. Nullable on the wire;
// empty string when absent.
PreviousStatus string `json:"previousStatus"`
// Kind is the movement kind, e.g. "sale". Nullable on the wire; empty
// string when absent.
Kind string `json:"kind"`
// Amount is in MINOR units. For payment.* it is what the merchant is PAID
// (the base amount). For payment.refunded it is what was returned to the
// customer. It is NOT the card movement - that is GrossAmount.
Amount int64 `json:"amount"`
// GrossAmount is the card movement in MINOR units: what the customer's card
// was actually charged.
GrossAmount int64 `json:"grossAmount"`
// SurchargeAmount is in MINOR units and nil when no surcharge is known.
// A pointer, so "no surcharge information" stays distinct from "zero".
SurchargeAmount *int64 `json:"surchargeAmount"`
// Currency is ISO 4217.
Currency string `json:"currency"`
// PSPReference is the payment processor's reference for the transaction,
// on payment.* events. Empty (null or absent on the wire) until known, and
// on a sale settled by reconciliation without a processor webhook. Refund
// and cancel events carry the original sale's reference. charge.* events do
// not have it. Older gateways omit it.
PSPReference string `json:"pspReference"`
// OriginalTransactionID is the parent transaction for refunds and
// reversals. Empty on an original payment.
OriginalTransactionID string `json:"originalTransactionId"`
// IdempotencyKey is your own mint key when the gateway knows it - the
// cheapest way to match a delivery back to your order without a lookup.
// Empty when unknown, which today includes every refund.
IdempotencyKey string `json:"idempotencyKey"`
// PaymentMethod is how the payer paid, one of PaymentMethodCategories, on
// payment.* and verification.* events: the same value as
// CheckoutStatus.PaymentMethod. Nullable on the wire; empty string while
// no method is known. On charge.* events the wire field is an object
// instead: read it from Charge.PaymentMethod.
PaymentMethod string `json:"paymentMethod"`
// WalletType is which wallet, when PaymentMethod is PaymentMethodWallet. A
// value outside WalletTypes is a valid wallet this SDK does not name yet.
// Nullable on the wire; empty string for non-wallet payments.
WalletType string `json:"walletType"`
// PaymentMethodBrand is the lower-cased card brand (visa, mastercard, ...)
// once a card payment has been attempted. Nullable on the wire; empty
// string while the payment is open, for non-card methods, and when the
// provider did not report a brand.
PaymentMethodBrand string `json:"paymentMethodBrand"`
// PaymentMethodLast4 is the card's last four digits once a card payment
// has been attempted. Empty under the same conditions as
// PaymentMethodBrand.
PaymentMethodLast4 string `json:"paymentMethodLast4"`
// StoredPaymentMethod is the card a SaveCard session stored, on payment.*
// events: the same object as CheckoutStatus.StoredPaymentMethod, so its ID
// is what ChargePaymentMethod takes. Set on payment.succeeded (and
// payment.requires_capture for an authorization) when the card was stored
// together with the approval.
//
// nil (null or absent on the wire) when no card was saved, and on every
// other event. It can ALSO be nil when a card was saved: on server-to-server
// sales that succeeded synchronously and on sales settled by the
// background sweep, the card is stored after the approval was announced.
// GetStatus is the source of truth: on a SaveCard session whose event has
// StoredPaymentMethod nil, read the status to pick the card up.
//
// charge.* events carry the id instead, on Charge.StoredPaymentMethodID.
StoredPaymentMethod *StoredPaymentMethod `json:"storedPaymentMethod,omitempty"`
// VerifyOnly is true on every event of a verify-only session. False (or
// absent on the wire) on every other event.
VerifyOnly bool `json:"verifyOnly"`
// DeclineClass is one of the DeclineClass* constants on payment.failed,
// verification.failed and the charge.failed and charge.retrying events.
// It can be empty, for example when nobody declined the payment. Empty
// (null or absent on the wire) on every other event. A value outside
// DeclineClasses is a class this SDK does not name yet, not an error.
DeclineClass string `json:"declineClass"`
// Sequence orders agreement.* and charge.* events for one object: it counts
// the announced changes of that object, only ever rises, and a redelivery
// carries the same number. The object is the agreement (Agreement.ID) for
// agreement.*, the agreement period (Charge.AgreementID plus
// Charge.PeriodNumber) for a charge the platform placed for an agreement,
// and Charge.ChargeID for a one-off charge. Keep the highest Sequence you
// have processed per object and discard any event whose Sequence is not
// higher. Zero when absent (payment.* events do not carry it) and on
// events recorded before the counter existed; treat zero as older than any
// positive number.
Sequence int64 `json:"sequence"`
// Charge is the typed data of a charge.* event, nil on every other event,
// and nil when the data does not fit ChargeWebhookData; read Raw then.
// The fields above that a charge event shares (TransactionID, Amount,
// Currency, OrderReference, Description, DeclineClass, Sequence) are set
// too; Status is empty, because a charge event says Charge.Outcome.
Charge *ChargeWebhookData `json:"-"`
// Agreement is the typed data of an agreement.* event, nil on every other
// event, and nil when the data does not fit AgreementWebhookData; read Raw
// then. Status, PreviousStatus, Amount, Currency and Sequence above are
// set too, with the agreement's values.
Agreement *AgreementWebhookData `json:"-"`
// Raw is the unparsed data object, for fields not modelled above.
Raw json.RawMessage `json:"-"`
}
WebhookData is the payload of a WebhookEvent.
func (*WebhookData) UnmarshalJSON ¶ added in v0.7.0
func (d *WebhookData) UnmarshalJSON(raw []byte) error
UnmarshalJSON reads the data object of any event type. The paymentMethod field is a string on payment.* and verification.* events and an object on charge.* events; only the string lands on PaymentMethod.
type WebhookEndpoint ¶ added in v0.5.0
type WebhookEndpoint struct {
// ID is the endpoint's UUID, lowercase.
ID string `json:"id"`
URL string `json:"url"`
Events []string `json:"events"`
// Status is one of the WebhookEndpointStatus* values.
Status string `json:"status"`
// CreatedAt is ISO 8601 UTC.
CreatedAt string `json:"createdAt"`
// Secret is the endpoint's signing secret (whsec_...), for VerifyWebhook.
// Only the first answer to CreateWebhookEndpoint carries it. A replay of
// the same key and ListWebhookEndpoints leave it empty, so store it when
// you receive it. json:"-" keeps it out of structured JSON loggers, and
// String keeps it out of %v.
Secret string `json:"-"`
// RequestID is the API's id of the call that returned this endpoint.
// Quote it to support. Empty when the API sent none.
RequestID string `json:"-"`
// Raw is the unparsed endpoint object, for fields this struct does not
// model yet. On the first answer to CreateWebhookEndpoint it holds the
// secret too: never log it.
Raw json.RawMessage `json:"-"`
}
WebhookEndpoint is one registered webhook endpoint.
func (WebhookEndpoint) GoString ¶ added in v0.5.0
func (e WebhookEndpoint) GoString() string
GoString covers %#v, which does not use String.
func (WebhookEndpoint) String ¶ added in v0.5.0
func (e WebhookEndpoint) String() string
String keeps the secret out of your logs: printing a WebhookEndpoint with %v, %+v or %#v (value or pointer) shows every field except the secret and Raw.
type WebhookEvent ¶ added in v0.2.0
type WebhookEvent struct {
// ID is the delivery id and YOUR DEDUPE KEY. Delivery is at-least-once, so
// the same ID can arrive more than once and you must ignore the repeats.
ID string `json:"id"`
// Type is one of the Event* constants. Treat an unrecognised type as a
// no-op rather than an error; the catalog can grow.
Type string `json:"type"`
// APIVersion is the dated version of the payload shape the event was
// rendered in, e.g. "2026-09-25". Fields are only ever added under a
// version, never renamed or removed. A retry resends the bytes of the first
// attempt, so a redelivered event keeps its original APIVersion. Empty on
// deliveries from a gateway that predates the field.
APIVersion string `json:"apiVersion"`
// CreatedAt is the ISO 8601 UTC instant of the transition, not of delivery.
// On a retry it still carries the original transition time. It can repeat
// across events, so never order agreement.* or charge.* events by it; use
// Data.Sequence.
CreatedAt string `json:"createdAt"`
Data WebhookData `json:"data"`
// Raw is the exact verified payload, byte for byte as signed. Use it for
// fields this struct does not model yet, or to store the delivery verbatim.
Raw json.RawMessage `json:"-"`
}
WebhookEvent is one verified webhook delivery. VerifyWebhook returns it; there is no other way to get one, which is the point - the signature is checked before the body is parsed.
The envelope is FLAT. There is no ApiResponse wrapper and no `success` field, so do not branch on one.
func VerifyWebhook ¶ added in v0.2.0
func VerifyWebhook(payload []byte, signatureHeader, secret string, opts ...VerifyOption) (*WebhookEvent, error)
VerifyWebhook authenticates one webhook delivery and returns the parsed event. Verification happens BEFORE parsing, and the only way to obtain a WebhookEvent is through this function, so an unverified payload cannot reach your business logic by accident.
payload must be the RAW request body, exactly the bytes that arrived. Reading the body into a struct and re-encoding it will change them and the signature will not match. In net/http that means io.ReadAll(r.Body) before any json.Decode.
signatureHeader is the WebhookSignatureHeader value: "t={unix_seconds},v1={hex}". secret is the endpoint's whsec_ secret, shown once when the endpoint was created.
Every failure returns a *WebhookVerificationError carrying a WebhookReason* on .Reason. Nothing panics and no other error type comes out, so a malformed or hostile header is an ordinary rejection path:
body, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
event, err := dominaite.VerifyWebhook(body, r.Header.Get(dominaite.WebhookSignatureHeader), secret)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
if !alreadySeen(event.ID) { // delivery is at-least-once: dedupe on ID
enqueue(event) // do the work off the request path
}
w.WriteHeader(http.StatusOK) // answer fast; a slow 2xx counts as a failure
Example ¶
The doc example must stay compilable and correct.
event, err := VerifyWebhook(
[]byte(webhookVector.Body),
webhookVector.Header,
webhookVector.Secret,
atVectorTime(),
)
if err != nil {
fmt.Println("rejected:", err)
return
}
fmt.Println(event.Type, event.Data.Amount, event.Data.Currency)
Output: payment.succeeded 8440 EUR
type WebhookVerificationError ¶ added in v0.2.0
type WebhookVerificationError struct {
// Reason is one of the WebhookReason* constants.
Reason string
// contains filtered or unexported fields
}
WebhookVerificationError means VerifyWebhook rejected a delivery. Nothing was parsed and nothing should be acted on.
Answer the HTTP request with 400 and no detail. Never echo the reason back to the caller: whoever sent an unverified request does not get to learn whether their secret or their timestamp was the problem.
event, err := dominaite.VerifyWebhook(body, r.Header.Get("X-Webhook-Signature"), secret)
var verr *dominaite.WebhookVerificationError
if errors.As(err, &verr) {
log.Printf("rejected webhook: %s", verr.Reason) // your logs only
w.WriteHeader(http.StatusBadRequest)
return
}
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
create-session
command
Command create-session mints a Dominaite checkout session and reads its status back.
|
Command create-session mints a Dominaite checkout session and reads its status back. |
|
webhook-handler
command
Command webhook-handler is a runnable webhook receiver.
|
Command webhook-handler is a runnable webhook receiver. |