Class: Spree::Kashflow::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/spree/kashflow/client.rb

Overview

Stateless SOAP client for the KashFlow API. Only this class may reference Savon — every public method returns plain Ruby (Integer, Array, TrueClass) so no SOAP object crosses the gem boundary. KashFlow has no session token: credentials are sent on every call, so nothing is cached between calls.

Constant Summary collapse

WSDL =

Returns the WSDL endpoint KashFlow publishes for the SOAP API.

Returns:

  • (String)

    the WSDL endpoint KashFlow publishes for the SOAP API

"https://securedwebapp.com/api/service.asmx?WSDL"
AUTH_FAULT =

Returns matches SOAP faults caused by bad credentials.

Returns:

  • (Regexp)

    matches SOAP faults caused by bad credentials

/invalid.*(username|password)|not authori[sz]ed/i
SUCCESS_STATUS =

The WSDL declares Status as a bare s:string on every *Response element (minOccurs="0"), with no <s:enumeration> and no <wsdl:documentation> naming the success value — so this constant is KashFlow's documented convention ("OK"), not something the schema pins down. Comparison is case-insensitive, and an absent Status (legal, since minOccurs="0") is treated as success; only a present Status that is not "OK" is a business-level rejection.

Returns:

  • (String)

    the in-band Status value KashFlow returns on success.

"OK"

Instance Method Summary collapse

Constructor Details

#initialize(username:, password:) ⇒ Client

Returns a new instance of Client.

Parameters:

  • username (String)

    the KashFlow API username

  • password (String)

    the KashFlow API password



35
36
37
38
# File 'lib/spree/kashflow/client.rb', line 35

def initialize(username:, password:)
  @username = username
  @password = password
end

Instance Method Details

#apply_credit_note(credit_note_number:, invoice_number:) ⇒ TrueClass

Applies a credit note to an invoice in KashFlow.

The WSDL declares applyCreditNoteToInvoiceResult as an s:boolean. A false is a refusal to link, and swallowing it produces exactly the orphan credit note SyncRefundJob's precondition guard exists to prevent — a credit note sitting in KashFlow attached to nothing.

Parameters:

  • credit_note_number (Integer)

    the KashFlow credit note id

  • invoice_number (Integer)

    the KashFlow invoice id

Returns:

  • (TrueClass)

    true when the credit note was applied

Raises:



165
166
167
168
169
170
# File 'lib/spree/kashflow/client.rb', line 165

def apply_credit_note(credit_note_number:, invoice_number:)
  response = call(:apply_credit_note_to_invoice, {"InvoiceID" => invoice_number, "CreditNoteID" => credit_note_number})
  result = response.dig("applyCreditNoteToInvoiceResponse", "applyCreditNoteToInvoiceResult")
  assert_accepted!(result, "did not apply credit note #{credit_note_number} to invoice #{invoice_number}")
  true
end

#bank_accountsArray<Hash>

Returns bank accounts as {id:, name:} hashes.

Returns:

  • (Array<Hash>)

    bank accounts as {id:, name:} hashes

Raises:



75
76
77
78
# File 'lib/spree/kashflow/client.rb', line 75

def bank_accounts
  rows = extract_rows(call(:get_bank_accounts), "GetBankAccountsResponse", "GetBankAccountsResult", "BankAccount")
  rows.map { |row| {id: row["AccountID"].to_i, name: row["AccountName"]} }
end

#create_invoice(payload) ⇒ Integer

Creates an invoice in KashFlow.

Parameters:

  • payload (Hash)

    a KashFlow Invoice_TypeDefined structure

Returns:

  • (Integer)

    the invoice number KashFlow assigned

Raises:



125
126
127
128
129
# File 'lib/spree/kashflow/client.rb', line 125

def create_invoice(payload)
  response = call(:insert_invoice_type_defined, {"Inv_TD" => payload})
  result = response.dig("InsertInvoice_TypeDefinedResponse", "InsertInvoice_TypeDefinedResult")
  assert_identifier!(result, "invoice number")
end

#currenciesArray<Hash>

Returns currencies as {code:, id:} hashes.

Returns:

  • (Array<Hash>)

    currencies as {code:, id:} hashes

Raises:



57
58
59
60
# File 'lib/spree/kashflow/client.rb', line 57

def currencies
  rows = extract_rows(call(:get_currencies), "GetCurrenciesResponse", "GetCurrenciesResult", "Currencies")
  rows.map { |row| {code: row["CurrencyCode"], id: row["CurrencyId"].to_i} }
end

#nominal_codesArray<Hash>

Returns nominal codes as {id:, name:} hashes.

Returns:

  • (Array<Hash>)

    nominal codes as {id:, name:} hashes

Raises:



66
67
68
69
# File 'lib/spree/kashflow/client.rb', line 66

def nominal_codes
  rows = extract_rows(call(:get_nominal_codes), "GetNominalCodesResponse", "GetNominalCodesResult", "NominalCode")
  rows.map { |row| {id: row["id"].to_i, name: row["Name"]} }
end

#payment_methodsArray<Hash>

Returns invoice payment methods as {id:, name:} hashes.

Returns:

  • (Array<Hash>)

    invoice payment methods as {id:, name:} hashes

Raises:



84
85
86
87
# File 'lib/spree/kashflow/client.rb', line 84

def payment_methods
  rows = extract_rows(call(:get_inv_pay_methods), "GetInvPayMethodsResponse", "GetInvPayMethodsResult", "PaymentMethod")
  rows.map { |row| {id: row["MethodID"].to_i, name: row["MethodName"]} }
end

#record_invoice_payment(payload) ⇒ TrueClass

Records a payment against an invoice in KashFlow.

The WSDL declares InsertInvoicePaymentResult as an s:int — the id of the payment KashFlow created. A 0 is therefore a rejection reported without a Status, and returning true regardless would leave the invoice permanently unpaid while the caller recorded a successful sync.

Parameters:

  • payload (Hash)

    a KashFlow Payment structure

Returns:

  • (TrueClass)

    true when the payment was recorded

Raises:



144
145
146
147
148
149
# File 'lib/spree/kashflow/client.rb', line 144

def record_invoice_payment(payload)
  response = call(:insert_invoice_payment, {"InvoicePayment" => payload})
  result = response.dig("InsertInvoicePaymentResponse", "InsertInvoicePaymentResult")
  assert_identifier!(result, "payment id")
  true
end

#upsert_customer(payload) ⇒ Integer

Creates or updates a customer in KashFlow, keyed on the customer Code.

KashFlow publishes no InsertOrUpdateCustomer, so an upsert is a lookup followed by an UpdateCustomer or an InsertCustomer. The lookup is not optional: customer codes are stable per customer (that is what keeps one KashFlow customer per Spree customer rather than one per order), so an unconditional insert succeeds exactly once and every subsequent order for that customer is rejected with "Customer Code is not unique" — with no invoice posted.

Parameters:

  • payload (Hash)

    a KashFlow Customer structure, whose "Code" identifies the customer

Returns:

  • (Integer)

    the KashFlow customer id, existing or newly assigned

Raises:



108
109
110
111
112
113
114
115
# File 'lib/spree/kashflow/client.rb', line 108

def upsert_customer(payload)
  existing_id = customer_id_for_code(payload["Code"])
  return update_customer(existing_id, payload) if existing_id

  response = call(:insert_customer, {"custr" => payload})
  result = response.dig("InsertCustomerResponse", "InsertCustomerResult")
  assert_identifier!(result, "customer id")
end

#verify_credentialsTrueClass, FalseClass

Verifies the configured credentials against the KashFlow API. Does not rescue — the caller decides how to handle a raised error.

Returns:

  • (TrueClass, FalseClass)

    true when KashFlow accepts the credentials

Raises:



48
49
50
51
# File 'lib/spree/kashflow/client.rb', line 48

def verify_credentials
  call(:get_currencies)
  true
end