mirror of
https://github.com/bestbytes/datatrans.git
synced 2025-10-16 12:05:36 +00:00
additionally needed as sometimes orders are coming in without a specific internalID and we must fall back to the MerchantID
597 lines
20 KiB
Go
597 lines
20 KiB
Go
package datatrans
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/tls"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"hash/fnv"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
// https://docs.datatrans.ch/docs/api-endpoints
|
|
const (
|
|
endpointURLSandBox = `https://api.sandbox.datatrans.com`
|
|
endpointURLProduction = `https://api.datatrans.com`
|
|
|
|
pathBase = "/v1/transactions"
|
|
pathStatus = pathBase + "/%s"
|
|
pathCredit = pathBase + "/%s/credit"
|
|
pathCreditAuthorize = pathBase + "/credit"
|
|
pathCancel = pathBase + "/%s/cancel"
|
|
pathSettle = pathBase + "/%s/settle"
|
|
pathValidate = pathBase + "/validate"
|
|
pathAuthorizeTransaction = pathBase + "/%s/authorize"
|
|
pathAuthorize = pathBase + "/authorize"
|
|
pathInitialize = pathBase
|
|
pathSecureFields = pathBase + "/secureFields"
|
|
pathSecureFieldsUpdate = pathBase + "/secureFields/%s"
|
|
pathAliases = pathBase + "/aliases"
|
|
pathAliasesDelete = pathBase + "/aliases/%s"
|
|
pathReconciliationsSales = "/v1/reconciliations/sales"
|
|
pathReconciliationsSalesBulk = "/v1/reconciliations/sales/bulk"
|
|
)
|
|
|
|
type OptionMerchant struct {
|
|
InternalID string
|
|
EnableProduction bool
|
|
// https://docs.datatrans.ch/docs/api-endpoints#section-idempotency
|
|
// If your request failed to reach our servers, no idempotent result is saved
|
|
// because no API endpoint processed your request. In such cases, you can
|
|
// simply retry your operation safely. Idempotency keys remain stored for 3
|
|
// minutes. After 3 minutes have passed, sending the same request together
|
|
// with the previous idempotency key will create a new operation.
|
|
EnableIdempotency bool
|
|
DisableRawJSONBody bool
|
|
MerchantID string // basic auth user
|
|
Password string // basic auth pw
|
|
// Data contains merchant specific other IDs or configurations. Keys/Values
|
|
// from this map are not getting used in requests towards datatrans.
|
|
Data map[string]interface{}
|
|
}
|
|
|
|
func (m OptionMerchant) apply(c *Client) error {
|
|
if _, ok := c.merchants[m.InternalID]; ok {
|
|
return fmt.Errorf("InternalID %q already exists", m.InternalID)
|
|
}
|
|
c.merchants[m.InternalID] = m
|
|
if _, ok := c.merchants[m.MerchantID]; !ok {
|
|
c.merchants[m.MerchantID] = m
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type OptionHTTPRequestFn func(req *http.Request) (*http.Response, error)
|
|
|
|
func (fn OptionHTTPRequestFn) apply(c *Client) error {
|
|
c.doFn = fn
|
|
return nil
|
|
}
|
|
|
|
type Client struct {
|
|
doFn OptionHTTPRequestFn
|
|
merchants map[string]OptionMerchant // string = your custom merchant ID
|
|
currentInternalID string
|
|
internalIDFound bool
|
|
}
|
|
|
|
type Option interface {
|
|
apply(*Client) error
|
|
}
|
|
|
|
func MakeClient(opts ...Option) (Client, error) {
|
|
c := Client{
|
|
merchants: make(map[string]OptionMerchant, 3),
|
|
}
|
|
for _, opt := range opts {
|
|
if err := opt.apply(&c); err != nil {
|
|
return Client{}, err
|
|
}
|
|
}
|
|
if len(c.merchants) == 0 {
|
|
return Client{}, fmt.Errorf("no merchants applied")
|
|
}
|
|
if c.doFn == nil {
|
|
c.doFn = (&http.Client{
|
|
Timeout: 30 * time.Second,
|
|
Transport: &http.Transport{
|
|
TLSClientConfig: &tls.Config{
|
|
MinVersion: tls.VersionTLS12,
|
|
},
|
|
},
|
|
}).Do
|
|
}
|
|
// see if we have a default one, otherwise you always have to call WithMerchant.
|
|
_, c.internalIDFound = c.merchants[""]
|
|
return c, nil
|
|
}
|
|
|
|
// WithMerchant sets an ID and returns a shallow clone of the client.
|
|
func (c *Client) WithMerchant(internalID string) *Client {
|
|
c2 := *c
|
|
c2.currentInternalID = internalID
|
|
_, c2.internalIDFound = c2.merchants[internalID]
|
|
return &c2
|
|
}
|
|
|
|
func (c *Client) do(req *http.Request, v interface{}) error {
|
|
internalID := c.currentInternalID
|
|
if !c.internalIDFound {
|
|
return fmt.Errorf("ClientID %q not found in list of merchants", internalID)
|
|
}
|
|
|
|
req.SetBasicAuth(c.merchants[internalID].MerchantID, c.merchants[internalID].Password)
|
|
resp, err := c.doFn(req)
|
|
defer closeResponse(resp)
|
|
if err != nil {
|
|
return fmt.Errorf("ClientID:%q: failed to execute HTTP request: %w", internalID, err)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
body := io.TeeReader(resp.Body, &buf)
|
|
dec := json.NewDecoder(body)
|
|
|
|
if !c.isSuccess(resp.StatusCode) {
|
|
var errResp ErrorResponse
|
|
if err := dec.Decode(&errResp); err != nil {
|
|
return fmt.Errorf("ClientID:%q: failed to unmarshal HTTP error response: %w", internalID, err)
|
|
}
|
|
errResp.HTTPStatusCode = resp.StatusCode
|
|
return errResp
|
|
}
|
|
if v != nil {
|
|
if err := dec.Decode(v); err != nil {
|
|
return fmt.Errorf("ClientID:%q: failed to unmarshal HTTP error response: %w", internalID, err)
|
|
}
|
|
}
|
|
if ri, ok := v.(*ResponseInitialize); ok {
|
|
if loc := resp.Header.Get("Location"); loc != "" {
|
|
ri.Location = loc
|
|
}
|
|
}
|
|
if set, ok := v.(rawJSONBodySetter); !c.merchants[internalID].DisableRawJSONBody && ok {
|
|
set.setJSONRawBody(buf.Bytes())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) isSuccess(statusCode int) bool {
|
|
return statusCode >= http.StatusOK && statusCode < http.StatusMultipleChoices
|
|
}
|
|
|
|
func closeResponse(r *http.Response) {
|
|
if r == nil || r.Body == nil {
|
|
return
|
|
}
|
|
_, _ = io.Copy(ioutil.Discard, r.Body)
|
|
_ = r.Body.Close()
|
|
}
|
|
|
|
// MarshalJSON encodes the postData struct to json but also can merge custom
|
|
// settings into the final JSON. This function is called before sending the
|
|
// request to datatrans. Function exported for debug reasons.
|
|
func MarshalJSON(postData interface{}) ([]byte, error) {
|
|
jsonBytes, err := json.Marshal(postData)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal postData: %w", err)
|
|
}
|
|
|
|
// this steps merges two different Go types into one JS object.
|
|
if cfg, ok := postData.(customFieldsGetter); ok {
|
|
custFields := cfg.getCustomFields()
|
|
if len(custFields) == 0 {
|
|
return jsonBytes, nil
|
|
}
|
|
|
|
postDataMap := map[string]interface{}{}
|
|
if err := json.Unmarshal(jsonBytes, &postDataMap); err != nil {
|
|
return nil, fmt.Errorf("failed to Unmarshal postData raw bytes: %w", err)
|
|
}
|
|
for k, v := range custFields {
|
|
postDataMap[k] = v // overwrites existing data from postData struct
|
|
}
|
|
jsonBytes, err = json.Marshal(postDataMap)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal postDataMap: %w", err)
|
|
}
|
|
}
|
|
|
|
return jsonBytes, nil
|
|
}
|
|
|
|
func (c *Client) prepareJSONReq(ctx context.Context, method, path string, postData interface{}) (*http.Request, error) {
|
|
internalID := c.currentInternalID
|
|
|
|
var r io.Reader
|
|
var jsonBytes []byte
|
|
if postData != nil {
|
|
var err error
|
|
jsonBytes, err = MarshalJSON(postData)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ClientID:%q: failed to json marshal HTTP request: %w", internalID, err)
|
|
}
|
|
r = bytes.NewReader(jsonBytes)
|
|
}
|
|
host := endpointURLSandBox
|
|
if c.merchants[internalID].EnableProduction {
|
|
host = endpointURLProduction
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, method, host+path, r)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ClientID:%q: failed to create HTTP request: %w", internalID, err)
|
|
}
|
|
if postData != nil {
|
|
req.Header.Set("Content-Type", "application/json")
|
|
}
|
|
if method == http.MethodPost && c.merchants[internalID].EnableIdempotency {
|
|
// not quite happy with this
|
|
// https://docs.datatrans.ch/docs/api-endpoints#section-idempotency
|
|
fh := fnv.New64a()
|
|
_, _ = fh.Write([]byte(internalID + host + path))
|
|
_, _ = fh.Write(jsonBytes)
|
|
req.Header.Set("Idempotency-Key", hex.EncodeToString(fh.Sum(nil)))
|
|
}
|
|
|
|
return req, nil
|
|
}
|
|
|
|
// Status allows once a transactionId has been received the status can be checked
|
|
// with the Status API.
|
|
func (c *Client) Status(ctx context.Context, transactionID string) (*ResponseStatus, error) {
|
|
if transactionID == "" {
|
|
return nil, fmt.Errorf("transactionID cannot be empty")
|
|
}
|
|
internalID := c.currentInternalID
|
|
host := endpointURLSandBox
|
|
if c.merchants[internalID].EnableProduction {
|
|
host = endpointURLProduction
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf(host+pathStatus, transactionID), nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ClientID:%q: failed to create HTTP request: %w", internalID, err)
|
|
}
|
|
|
|
var respStatus ResponseStatus
|
|
if err := c.do(req, &respStatus); err != nil {
|
|
return nil, fmt.Errorf("ClientID:%q: failed to execute HTTP request: %w", internalID, err)
|
|
}
|
|
|
|
return &respStatus, nil
|
|
}
|
|
|
|
// Credit uses the credit API to credit a transaction which is in status settled.
|
|
// The previously settled amount must not be exceeded.
|
|
func (c *Client) Credit(ctx context.Context, transactionID string, rc RequestCredit) (*ResponseCardMasked, error) {
|
|
if transactionID == "" || rc.Currency == "" || rc.RefNo == "" {
|
|
return nil, fmt.Errorf("neither currency nor refno nor transactionID can be empty")
|
|
}
|
|
|
|
req, err := c.prepareJSONReq(ctx, http.MethodPost, fmt.Sprintf(pathCredit, transactionID), rc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var respRefund ResponseCardMasked
|
|
if err := c.do(req, &respRefund); err != nil {
|
|
return nil, fmt.Errorf("ClientID:%q: failed to execute HTTP request: %w", c.currentInternalID, err)
|
|
}
|
|
|
|
return &respRefund, nil
|
|
}
|
|
|
|
// CreditAuthorize allows to use this API to make a credit without referring to a
|
|
// previous authorization. This can be useful if you want to credit a cardholder
|
|
// when there was no debit.
|
|
func (c *Client) CreditAuthorize(ctx context.Context, rca RequestCreditAuthorize) (*ResponseCardMasked, error) {
|
|
if rca.Currency == "" || rca.RefNo == "" || rca.Amount == 0 {
|
|
return nil, fmt.Errorf("neither currency nor refno nor amount can be empty")
|
|
}
|
|
|
|
req, err := c.prepareJSONReq(ctx, http.MethodPost, pathCreditAuthorize, rca)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var respRefund ResponseCardMasked
|
|
if err := c.do(req, &respRefund); err != nil {
|
|
return nil, fmt.Errorf("ClientID:%q: failed to execute HTTP request: %w", c.currentInternalID, err)
|
|
}
|
|
return &respRefund, nil
|
|
}
|
|
|
|
// Cancel API can be used to release the blocked amount from an authorization.
|
|
// The transaction must either be in status authorized or settled. The
|
|
// transactionId is needed to cancel an authorization.
|
|
// https://api-reference.datatrans.ch/#operation/cancel
|
|
func (c *Client) Cancel(ctx context.Context, transactionID string, refno string) error {
|
|
if transactionID == "" || refno == "" {
|
|
return fmt.Errorf("neither transactionID nor refno can be empty")
|
|
}
|
|
req, err := c.prepareJSONReq(ctx, http.MethodPost, fmt.Sprintf(pathCancel, transactionID), struct {
|
|
Refno string `json:"refno"`
|
|
}{
|
|
Refno: refno,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := c.do(req, nil); err != nil {
|
|
return fmt.Errorf("ClientID:%q: failed to execute HTTP request: %w", c.currentInternalID, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Settle request is often also referred to as “Capture” or “Clearing”. It can be
|
|
// used for the settlement of previously authorized transactions. The
|
|
// transactionId is needed to settle an authorization. Note: This API call is not
|
|
// needed if "autoSettle": true was used when initializing a transaction.
|
|
// https://api-reference.datatrans.ch/#operation/settle
|
|
func (c *Client) Settle(ctx context.Context, transactionID string, rs RequestSettle) error {
|
|
if transactionID == "" || rs.Amount == 0 || rs.Currency == "" || rs.RefNo == "" {
|
|
return fmt.Errorf("neither transactionID nor refno nor amount nor currency can be empty")
|
|
}
|
|
req, err := c.prepareJSONReq(ctx, http.MethodPost, fmt.Sprintf(pathSettle, transactionID), rs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := c.do(req, nil); err != nil {
|
|
return fmt.Errorf("ClientID:%q: failed to execute HTTP request: %w", c.currentInternalID, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateAlias an existing alias can be validated at any time with the
|
|
// transaction validate API. No amount will be blocked on the customers account.
|
|
// Only credit cards (including Apple Pay and Google Pay), PFC, KLN and PAP
|
|
// support validation of an existing alias.
|
|
// https://api-reference.datatrans.ch/#operation/validate
|
|
func (c *Client) ValidateAlias(ctx context.Context, rva RequestValidateAlias) (*ResponseCardMasked, error) {
|
|
if rva.Currency == "" || rva.RefNo == "" {
|
|
return nil, fmt.Errorf("neither currency nor refno can be empty")
|
|
}
|
|
req, err := c.prepareJSONReq(ctx, http.MethodPost, pathValidate, rva)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var rcm ResponseCardMasked
|
|
if err := c.do(req, &rcm); err != nil {
|
|
return nil, fmt.Errorf("ClientID:%q: failed to execute HTTP request: %w", c.currentInternalID, err)
|
|
}
|
|
return &rcm, nil
|
|
}
|
|
|
|
// AuthorizeTransaction an authenticated transaction. If during the initialization of a
|
|
// transaction the parameter option.authenticationOnly was set to true, this API
|
|
// can be used to authorize an already authenticated (3D) transaction.
|
|
// https://api-reference.datatrans.ch/#operation/authorize-split
|
|
func (c *Client) AuthorizeTransaction(ctx context.Context, transactionID string, rva RequestAuthorizeTransaction) (*ResponseAuthorize, error) {
|
|
if transactionID == "" || rva.RefNo == "" {
|
|
return nil, fmt.Errorf("neither transactionID nor refno can be empty")
|
|
}
|
|
req, err := c.prepareJSONReq(ctx, http.MethodPost, fmt.Sprintf(pathAuthorizeTransaction, transactionID), rva)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var rcm ResponseAuthorize
|
|
if err := c.do(req, &rcm); err != nil {
|
|
return nil, fmt.Errorf("ClientID:%q: failed to execute HTTP request: %w", c.currentInternalID, err)
|
|
}
|
|
return &rcm, nil
|
|
}
|
|
|
|
// Authorize a transaction. Use this API to make an authorization without user
|
|
// interaction. (For example merchant initiated transactions with an alias)
|
|
// Depending on the payment method, different parameters are mandatory. Refer to
|
|
// the payment method specific objects (for example PAP) to see which parameters
|
|
// so send. For credit cards, the card object can be used.
|
|
// https://api-reference.datatrans.ch/#operation/authorize
|
|
func (c *Client) Authorize(ctx context.Context, rva RequestAuthorize) (*ResponseCardMasked, error) {
|
|
if rva.Amount == 0 || rva.Currency == "" || rva.RefNo == "" {
|
|
return nil, fmt.Errorf("neither transactionID nor amount nor currency nor refno can be empty")
|
|
}
|
|
req, err := c.prepareJSONReq(ctx, http.MethodPost, pathAuthorize, rva)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var rcm ResponseCardMasked
|
|
if err := c.do(req, &rcm); err != nil {
|
|
return nil, fmt.Errorf("ClientID:%q: failed to execute HTTP request: %w", c.currentInternalID, err)
|
|
}
|
|
return &rcm, nil
|
|
}
|
|
|
|
// Initialize a transaction. Securely send all the needed parameters to the
|
|
// transaction initialization API. The result of this API call is a HTTP 201
|
|
// status code with a transactionId in the response body and the Location header
|
|
// set. If you want to use the payment page redirect mode to collect the payment
|
|
// details, the browser needs to be redirected to this URL to continue with the
|
|
// transaction. Following the link provided in the Location header will raise the
|
|
// Datatrans Payment Page with all the payment methods available for the given
|
|
// merchantId. If you want to limit the number of payment methods, the
|
|
// paymentMethod array can be used.
|
|
func (c *Client) Initialize(ctx context.Context, rva RequestInitialize) (*ResponseInitialize, error) {
|
|
if rva.Amount == 0 || rva.Currency == "" || rva.RefNo == "" {
|
|
return nil, fmt.Errorf("neither amount nor currency nor refno can be empty")
|
|
}
|
|
req, err := c.prepareJSONReq(ctx, http.MethodPost, pathInitialize, rva)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var ri ResponseInitialize
|
|
if err := c.do(req, &ri); err != nil {
|
|
return nil, fmt.Errorf("ClientID:%q: failed to execute HTTP request: %w", c.currentInternalID, err)
|
|
}
|
|
return &ri, nil
|
|
}
|
|
|
|
// InitializeSecureFields initializes a Secure Fields transaction. Proceed with
|
|
// the steps below to process Secure Fields payment transactions.
|
|
// https://api-reference.datatrans.ch/#operation/secureFieldsInit
|
|
func (c *Client) SecureFieldsInit(ctx context.Context, rva RequestSecureFieldsInit) (*ResponseInitialize, error) {
|
|
if rva.Amount == 0 || rva.Currency == "" || rva.ReturnUrl == "" {
|
|
return nil, fmt.Errorf("neither amount nor currency nor returnURL can be empty")
|
|
}
|
|
req, err := c.prepareJSONReq(ctx, http.MethodPost, pathSecureFields, rva)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var ri ResponseInitialize
|
|
if err := c.do(req, &ri); err != nil {
|
|
return nil, fmt.Errorf("ClientID:%q: failed to execute HTTP request: %w", c.currentInternalID, err)
|
|
}
|
|
return &ri, nil
|
|
}
|
|
|
|
// SecureFieldsUpdate use this API to update the amount of a Secure Fields
|
|
// transaction. This action is only allowed before the 3D process. At least one
|
|
// property must be updated.
|
|
// https://api-reference.datatrans.ch/#operation/secure-fields-update
|
|
func (c *Client) SecureFieldsUpdate(ctx context.Context, transactionID string, rva RequestSecureFieldsUpdate) error {
|
|
if rva.Amount == 0 || rva.Currency == "" {
|
|
return fmt.Errorf("neither amount nor currency nor returnURL can be empty")
|
|
}
|
|
req, err := c.prepareJSONReq(ctx, http.MethodPatch, fmt.Sprintf(pathSecureFieldsUpdate, transactionID), rva)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := c.do(req, nil); err != nil {
|
|
return fmt.Errorf("ClientID:%q: failed to execute HTTP request: %w", c.currentInternalID, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AliasConvert converts a legacy (numeric or masked) alias to the most recent
|
|
// alias format.
|
|
func (c *Client) AliasConvert(ctx context.Context, legacyAlias string) (string, error) {
|
|
if legacyAlias == "" {
|
|
return "", fmt.Errorf("legacyAlias cannot be empty")
|
|
}
|
|
req, err := c.prepareJSONReq(ctx, http.MethodPost, pathAliases, struct {
|
|
LegacyAlias string `json:"legacyAlias"`
|
|
}{
|
|
LegacyAlias: legacyAlias,
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
var resp struct {
|
|
Alias string `json:"alias"`
|
|
}
|
|
if err := c.do(req, &resp); err != nil {
|
|
return "", fmt.Errorf("ClientID:%q: failed to execute HTTP request: %w", c.currentInternalID, err)
|
|
}
|
|
return resp.Alias, nil
|
|
}
|
|
|
|
// AliasDelete deletes an alias with immediate effect. The alias will no longer
|
|
// be recognized if used later with any API call.
|
|
func (c *Client) AliasDelete(ctx context.Context, alias string) error {
|
|
if alias == "" {
|
|
return fmt.Errorf("alias cannot be empty")
|
|
}
|
|
req, err := c.prepareJSONReq(ctx, http.MethodDelete, fmt.Sprintf(pathAliasesDelete, alias), nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := c.do(req, nil); err != nil {
|
|
return fmt.Errorf("ClientID:%q: failed to execute HTTP request: %w", c.currentInternalID, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ReconciliationsSales reports a sale. When using reconciliation, use this API
|
|
// to report a sale. The matching is based on the transactionId.
|
|
func (c *Client) ReconciliationsSales(ctx context.Context, sale RequestReconciliationsSale) (*ResponseReconciliationsSale, error) {
|
|
req, err := c.prepareJSONReq(ctx, http.MethodPost, pathReconciliationsSales, sale)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var rrs ResponseReconciliationsSale
|
|
if err := c.do(req, &rrs); err != nil {
|
|
return nil, fmt.Errorf("ClientID:%q: failed to execute HTTP request: %w", c.currentInternalID, err)
|
|
}
|
|
return &rrs, nil
|
|
}
|
|
|
|
// ReconciliationsSalesBulk reports bulk sales. When using reconciliation, use
|
|
// this API to report multiples sales with a single API call. The matching is
|
|
// based on the transactionId.
|
|
func (c *Client) ReconciliationsSalesBulk(ctx context.Context, sales RequestReconciliationsSales) (*ResponseReconciliationsSales, error) {
|
|
req, err := c.prepareJSONReq(ctx, http.MethodPost, pathReconciliationsSalesBulk, sales)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var rrs ResponseReconciliationsSales
|
|
if err := c.do(req, &rrs); err != nil {
|
|
return nil, fmt.Errorf("ClientID:%q: failed to execute HTTP request: %w", c.currentInternalID, err)
|
|
}
|
|
return &rrs, nil
|
|
}
|
|
|
|
// GetDataInt returns the int value from the data map or false if not found or failed to convert.
|
|
func (c *Client) GetDataInt(key string) (int, bool) {
|
|
internalID := c.currentInternalID
|
|
if !c.internalIDFound {
|
|
return 0, false
|
|
}
|
|
raw, ok := c.merchants[internalID].Data[key]
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
switch t := raw.(type) {
|
|
case string:
|
|
i, err := strconv.Atoi(t)
|
|
return i, err == nil
|
|
case int:
|
|
return t, true
|
|
case int64:
|
|
return int(t), true
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
// GetDataString returns the string value from the data map or false if not found or failed to convert.
|
|
func (c *Client) GetDataString(key string) (string, bool) {
|
|
internalID := c.currentInternalID
|
|
if !c.internalIDFound {
|
|
return "", false
|
|
}
|
|
raw, ok := c.merchants[internalID].Data[key]
|
|
if !ok {
|
|
return "", false
|
|
}
|
|
switch t := raw.(type) {
|
|
case string:
|
|
return t, true
|
|
case []byte:
|
|
return string(t), true
|
|
default:
|
|
return fmt.Sprintf("%v", t), true
|
|
}
|
|
}
|
|
|
|
func (c *Client) GetDataRaw(key string) (interface{}, bool) {
|
|
internalID := c.currentInternalID
|
|
if !c.internalIDFound {
|
|
return nil, false
|
|
}
|
|
raw, ok := c.merchants[internalID].Data[key]
|
|
return raw, ok
|
|
}
|