diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000..f94e5394 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,16 @@ +{ + "permissions": { + "allow": [ + "Bash(find:*)", + "Bash(bundle exec rspec:*)", + "Bash(mkdir:*)", + "Bash(rspec:*)", + "Bash(bundle install:*)", + "Bash(ruby:*)", + "Bash(gem which:*)", + "Bash(bundle:*)" + ], + "deny": [], + "ask": [] + } +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..4bd82459 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +name: CI + +on: + push: + branches: [ master, main ] + pull_request: + branches: [ master, main ] + +jobs: + rubocop: + runs-on: ubuntu-latest + + strategy: + matrix: + ruby-version: ['3.2'] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Ruby ${{ matrix.ruby-version }} + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + bundler-cache: true + + - name: Install dependencies + run: bundle install + + - name: Run RuboCop + run: bundle exec rubocop + + rspec: + runs-on: ubuntu-latest + + strategy: + matrix: + ruby-version: ['3.2'] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Ruby ${{ matrix.ruby-version }} + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + bundler-cache: true + + - name: Install dependencies + run: bundle install + + - name: Run RSpec tests + run: bundle exec rspec diff --git a/.rubocop.yml b/.rubocop.yml index abe1c659..cc32da4b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,29 +1 @@ inherit_from: .rubocop_todo.yml - -# Offense count: 1 -Security/Eval: - Exclude: - - 'bin/*' - -# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. -# URISchemes: http, https -Metrics/LineLength: - Max: 120 - -Metrics/BlockLength: - ExcludedMethods: ['describe', 'context'] - -Style/AsciiComments: - Enabled: false - -Style/Copyright: - Notice: 'Copyright (\(c\) )?2[0-9]{3} .+' - AutocorrectNotice: "# Copyright (c) 2018 FINFEX https://github.com/finfex\n" - Description: 'Include a copyright notice in each file before any code.' - Enabled: true - VersionAdded: '0.1' - -AllCops: - Exclude: - - utils/* - - vendor/bundle/**/* diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 33c1993d..2796008f 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,18 +1,284 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2018-12-18 21:28:41 +0300 using RuboCop version 0.61.1. +# on 2025-10-19 18:26:32 UTC using RuboCop version 1.81.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 9 +# Offense count: 4 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation. +Gemspec/OrderedDependencies: + Exclude: + - 'payment_services.gemspec' + +# Offense count: 1 +# Configuration parameters: Severity. +Gemspec/RequiredRubyVersion: + Exclude: + - 'payment_services.gemspec' + +# Offense count: 6 +# This cop supports safe autocorrection (--autocorrect). +Layout/EmptyLineAfterGuardClause: + Exclude: + - 'lib/payment_services/block_io/client.rb' + - 'lib/payment_services/crypto_apis_v2/transaction_repository.rb' + - 'lib/payment_services/obmenka/invoicer.rb' + - 'lib/payment_services/tronscan/transaction.rb' + - 'lib/payment_services/your_payments/invoicer.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Layout/EmptyLines: + Exclude: + - 'spec/rails_helper.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowAliasSyntax, AllowedMethods. +# AllowedMethods: alias_method, public, protected, private +Layout/EmptyLinesAroundAttributeAccessor: + Exclude: + - 'lib/payment_services/paylama/payout_adapter.rb' + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: empty_lines, no_empty_lines +Layout/EmptyLinesAroundBlockBody: + Exclude: + - 'spec/payment_services/rbk/payout_destination_spec.rb' + - 'spec/payment_services/rbk/payout_spec.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines, beginning_only, ending_only +Layout/EmptyLinesAroundClassBody: + Exclude: + - 'lib/payment_services/payeer/payout_adapter.rb' + +# Offense count: 12 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment. +Layout/ExtraSpacing: + Exclude: + - 'lib/payment_services/any_pay/invoice.rb' + - 'lib/payment_services/any_pay/payout.rb' + - 'lib/payment_services/binance/payout_adapter.rb' + - 'lib/payment_services/blockchair/client.rb' + - 'lib/payment_services/coin_payments_hub/invoice.rb' + - 'lib/payment_services/crypto_apis_v2/blockchain.rb' + - 'lib/payment_services/crypto_apis_v2/client.rb' + - 'lib/payment_services/master_processing/client.rb' + - 'lib/payment_services/one_crypto/invoice.rb' + - 'lib/payment_services/transfera/invoicer.rb' + +# Offense count: 13 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: consistent, consistent_relative_to_receiver, special_for_inner_method_call, special_for_inner_method_call_in_parentheses +Layout/FirstArgumentIndentation: + Exclude: + - 'lib/payment_services/any_pay/client.rb' + - 'lib/payment_services/best_api/client.rb' + - 'lib/payment_services/block_io/client.rb' + - 'lib/payment_services/crypto_apis/payout_clients/base_client.rb' + - 'lib/payment_services/crypto_apis/payout_clients/ethereum_client.rb' + - 'lib/payment_services/crypto_apis/payout_clients/omni_client.rb' + - 'lib/payment_services/crypto_apis_v2/client.rb' + - 'lib/payment_services/ex_pay/client.rb' + - 'lib/payment_services/one_crypto/client.rb' + - 'lib/payment_services/tronscan/client.rb' + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: IndentationWidth. +# SupportedStyles: special_inside_parentheses, consistent, align_braces +Layout/FirstHashElementIndentation: + EnforcedStyle: consistent + +# Offense count: 162 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. +# SupportedHashRocketStyles: key, separator, table +# SupportedColonStyles: key, separator, table +# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit +Layout/HashAlignment: + Enabled: false + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: aligned, indented, indented_relative_to_receiver +Layout/MultilineMethodCallIndentation: + Exclude: + - 'lib/payment_services/crypto_apis_v2/client.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: aligned, indented +Layout/MultilineOperationIndentation: + Exclude: + - 'lib/payment_services/base/p2p_bank_resolver.rb' + +# Offense count: 7 +# This cop supports safe autocorrection (--autocorrect). +Layout/SpaceAfterComma: + Exclude: + - 'lib/payment_services/binance/invoicer.rb' + - 'lib/payment_services/crypto_apis/invoicer.rb' + - 'lib/payment_services/crypto_apis_v2/transaction_repository.rb' + - 'lib/payment_services/exmo/invoicer.rb' + - 'lib/payment_services/paylama_crypto/transaction.rb' + +# Offense count: 14 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowForAlignment, EnforcedStyleForExponentOperator, EnforcedStyleForRationalLiterals. +# SupportedStylesForExponentOperator: space, no_space +# SupportedStylesForRationalLiterals: space, no_space +Layout/SpaceAroundOperators: + Exclude: + - 'lib/payment_services/any_pay/invoice.rb' + - 'lib/payment_services/any_pay/payout.rb' + - 'lib/payment_services/base/p2p_bank_resolver.rb' + - 'lib/payment_services/blockchair/client.rb' + - 'lib/payment_services/bridgex/client.rb' + - 'lib/payment_services/coin_payments_hub/client.rb' + - 'lib/payment_services/coin_payments_hub/invoice.rb' + - 'lib/payment_services/crypto_apis_v2/blockchain.rb' + - 'lib/payment_services/crypto_apis_v2/client.rb' + - 'lib/payment_services/exmo/client.rb' + - 'lib/payment_services/master_processing/client.rb' + - 'lib/payment_services/payeer/client.rb' + - 'lib/payment_services/transfera/invoicer.rb' + - 'lib/payment_services/tronscan/transaction_matcher.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: space, compact, no_space +Layout/SpaceInsideParens: + Exclude: + - 'lib/payment_services/cryptomus/payout_adapter.rb' + +# Offense count: 18 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: final_newline, final_blank_line +Layout/TrailingEmptyLines: + Enabled: false + +# Offense count: 23 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowInHeredoc. +Layout/TrailingWhitespace: + Enabled: false + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +Lint/DeprecatedOpenSSLConstant: + Exclude: + - 'lib/payment_services/merchant_alikassa/client.rb' + - 'lib/payment_services/merchant_alikassa_virtual/client.rb' + +# Offense count: 4 +Lint/FloatComparison: + Exclude: + - 'lib/payment_services/payeer/invoice.rb' + - 'lib/payment_services/tronscan/transaction_matcher.rb' + - 'lib/payment_services/x_pay_pro/invoicer.rb' + - 'lib/payment_services/x_pay_pro_virtual/invoicer.rb' + +# Offense count: 2 +Lint/IneffectiveAccessModifier: + Exclude: + - 'lib/payment_services/master_processing/response.rb' + +# Offense count: 35 +# Configuration parameters: AllowedParentClasses. +Lint/MissingSuper: + Enabled: false + +# Offense count: 5 +# This cop supports safe autocorrection (--autocorrect). +Lint/RedundantStringCoercion: + Exclude: + - 'lib/payment_services/block_io/client.rb' + - 'lib/payment_services/erapay/invoicer.rb' + - 'lib/payment_services/fire_kassa/invoicer.rb' + - 'lib/payment_services/liquid/payout_adapter.rb' + +# Offense count: 2 +Lint/RescueException: + Exclude: + - 'lib/payment_services/block_io/client.rb' + +# Offense count: 1 +Lint/ShadowedException: + Exclude: + - 'lib/payment_services/block_io/client.rb' + +# Offense count: 112 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions. +# NotImplementedExceptions: NotImplementedError +Lint/UnusedMethodArgument: + Enabled: false + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: ContextCreatingMethods, MethodCreatingMethods. +Lint/UselessAccessModifier: + Exclude: + - 'lib/payment_services/manual_by_group/invoicer.rb' + - 'lib/payment_services/master_processing/response.rb' + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +Lint/UselessAssignment: + Exclude: + - 'lib/payment_services/base/client.rb' + - 'spec/unit/base_client_spec.rb' + +# Offense count: 57 +# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. Metrics/AbcSize: - Max: 52 + Max: 77 + +# Offense count: 23 +# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. +# AllowedMethods: refine +Metrics/BlockLength: + Max: 152 + +# Offense count: 4 +# Configuration parameters: CountComments, CountAsOne. +Metrics/ClassLength: + Max: 487 + +# Offense count: 1 +# Configuration parameters: AllowedMethods, AllowedPatterns. +Metrics/CyclomaticComplexity: + Max: 20 -# Configuration parameters: CountComments, ExcludedMethods. +# Offense count: 68 +# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. Metrics/MethodLength: - Max: 25 + Max: 24 + +# Offense count: 1 +# Configuration parameters: CountKeywordArgs, MaxOptionalParameters. +Metrics/ParameterLists: + Max: 6 + +# Offense count: 1 +# Configuration parameters: AllowedMethods, AllowedPatterns. +Metrics/PerceivedComplexity: + Max: 20 # Offense count: 2 Naming/AccessorMethodName: @@ -20,11 +286,369 @@ Naming/AccessorMethodName: - 'lib/payment_services/qiwi/client.rb' - 'lib/payment_services/qiwi/importer.rb' -# Offense count: 40 +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyleForLeadingUnderscores. +# SupportedStylesForLeadingUnderscores: disallowed, required, optional +Naming/MemoizedInstanceVariableName: + Exclude: + - 'lib/payment_services/appex_money/payout_adapter.rb' + +# Offense count: 1 +# Configuration parameters: NamePrefix, ForbiddenPrefixes, AllowedMethods, MethodDefinitionMacros, UseSorbetSigs. +# NamePrefix: is_, has_, have_, does_ +# ForbiddenPrefixes: is_, has_, have_, does_ +# AllowedMethods: is_a? +# MethodDefinitionMacros: define_method, define_singleton_method +Naming/PredicatePrefix: + Exclude: + - 'spec/**/*' + - 'lib/payment_services/binance/payout.rb' + +# Offense count: 15 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: PreferredName. +Naming/RescuedExceptionsVariableName: + Exclude: + - 'lib/payment_services/adv_cash/client.rb' + - 'lib/payment_services/ali_kassa/client.rb' + - 'lib/payment_services/any_money/client.rb' + - 'lib/payment_services/appex_money/client.rb' + - 'lib/payment_services/base/client.rb' + - 'lib/payment_services/block_io/client.rb' + - 'lib/payment_services/crypto_apis/clients/base_client.rb' + - 'lib/payment_services/kuna/client.rb' + - 'lib/payment_services/liquid/client.rb' + - 'lib/payment_services/obmenka/client.rb' + - 'lib/payment_services/perfect_money/client.rb' + - 'lib/payment_services/qiwi/payment_order_support.rb' + - 'lib/payment_services/rbk/client.rb' + - 'spec/unit/base_client_spec.rb' + +# Offense count: 5 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods. +# SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces +# ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object +# FunctionalMethods: let, let!, subject, watch +# AllowedMethods: lambda, proc, it +Style/BlockDelimiters: + Exclude: + - 'spec/payment_services/base/client_spec.rb' + - 'spec/payment_services/rbk/payout_destination_spec.rb' + - 'spec/payment_services/rbk/payout_spec.rb' + - 'spec/unit/base_client_spec.rb' + +# Offense count: 2 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: MinBranchesCount. +Style/CaseLikeIf: + Exclude: + - 'lib/payment_services/base/client.rb' + - 'spec/unit/base_client_spec.rb' + +# Offense count: 251 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle, EnforcedStyleForClasses, EnforcedStyleForModules. +# SupportedStyles: nested, compact +# SupportedStylesForClasses: ~, nested, compact +# SupportedStylesForModules: ~, nested, compact +Style/ClassAndModuleChildren: + Enabled: false + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +Style/ColonMethodCall: + Exclude: + - 'lib/payment_services/merchant_alikassa/client.rb' + - 'lib/payment_services/merchant_alikassa_virtual/client.rb' + +# Offense count: 5 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: Keywords, RequireColon. +# Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW, NOTE +Style/CommentAnnotation: + Exclude: + - 'lib/blockchain_com_client.rb' + - 'lib/payment_services/rbk/customer.rb' + - 'lib/payment_services/rbk/invoicer.rb' + - 'lib/payment_services/rbk/payment_card.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions. +# SupportedStyles: assign_to_condition, assign_inside_condition +Style/ConditionalAssignment: + Exclude: + - 'lib/payment_services/tronscan/client.rb' + +# Offense count: 315 +# Configuration parameters: AllowedConstants. Style/Documentation: Enabled: false -# Offense count: 24 -# Configuration parameters: MinBodyLength. -Style/ClassAndModuleChildren: +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: format, sprintf, percent +Style/FormatString: + Exclude: + - 'lib/payment_services/merchant_alikassa/payout_adapter.rb' + +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +Style/GlobalStdStream: + Exclude: + - 'lib/payment_services/auto_logger.rb' + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. +Style/GuardClause: + Exclude: + - 'lib/payment_services/pay_for_u/invoicer.rb' + - 'lib/payment_services/pay_for_u_h2h/invoicer.rb' + +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: AllowedReceivers. +# AllowedReceivers: Thread.current +Style/HashEachMethods: + Exclude: + - 'lib/payment_services/blockchair/transaction_matcher.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Style/KeywordParametersOrder: + Exclude: + - 'lib/payment_services/payeer/client.rb' + +# Offense count: 5 +Style/MissingRespondToMissing: + Exclude: + - 'lib/payment_services/blockchair/transaction.rb' + - 'lib/payment_services/blockchair/transaction_matcher.rb' + - 'lib/payment_services/crypto_apis_v2/transaction.rb' + - 'lib/payment_services/crypto_apis_v2/transaction_repository.rb' + - 'lib/payment_services/paylama_crypto/transaction.rb' + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +Style/MultilineIfModifier: + Exclude: + - 'lib/payment_services/crypto_apis/invoicer.rb' + - 'lib/payment_services/crypto_apis/payout_adapter.rb' + +# Offense count: 10 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowMethodComparison, ComparisonsThreshold. +Style/MultipleComparison: + Exclude: + - 'lib/payment_services/any_money/payout.rb' + - 'lib/payment_services/binance/payout.rb' + - 'lib/payment_services/exmo/invoice.rb' + - 'lib/payment_services/ff/transaction.rb' + - 'lib/payment_services/kuna/payout.rb' + - 'lib/payment_services/master_processing/invoice.rb' + - 'lib/payment_services/master_processing/payout.rb' + - 'lib/payment_services/obmenka/invoice.rb' + - 'lib/payment_services/obmenka/payout.rb' + - 'lib/payment_services/paylama_crypto/transaction.rb' + +# Offense count: 28 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: literals, strict +Style/MutableConstant: + Enabled: false + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: both, prefix, postfix +Style/NegatedIf: + Exclude: + - 'lib/payment_services/bridgex/invoicer.rb' + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: Strict, AllowedNumbers, AllowedPatterns. +Style/NumericLiterals: + MinDigits: 10 + +# Offense count: 2 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns. +# SupportedStyles: predicate, comparison +Style/NumericPredicate: + Exclude: + - 'spec/**/*' + - 'lib/payment_services/crypto_apis_v2/transaction.rb' + - 'lib/payment_services/paylama_crypto/transaction.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Style/ParallelAssignment: + Exclude: + - 'lib/payment_services/pay_for_u_h2h/invoicer.rb' + +# Offense count: 19 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: PreferredDelimiters. +Style/PercentLiteralDelimiters: Enabled: false + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +Style/RedundantAssignment: + Exclude: + - 'lib/payment_services/ali_kassa/invoicer.rb' + - 'spec/unit/base_client_spec.rb' + +# Offense count: 4 +# This cop supports safe autocorrection (--autocorrect). +Style/RedundantBegin: + Exclude: + - 'lib/payment_services/appex_money/payout_adapter.rb' + - 'lib/payment_services/exmo/payout_adapter.rb' + - 'lib/payment_services/kuna/payout_adapter.rb' + - 'lib/payment_services/perfect_money/payout_adapter.rb' + +# Offense count: 3 +# This cop supports unsafe autocorrection (--autocorrect-all). +Style/RedundantInterpolation: + Exclude: + - 'lib/payment_services/merchant_alikassa/client.rb' + - 'lib/payment_services/merchant_alikassa_virtual/client.rb' + - 'lib/payment_services/panda_pay/client.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Style/RedundantParentheses: + Exclude: + - 'lib/payment_services/rbk/customer.rb' + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +Style/RescueModifier: + Exclude: + - 'lib/payment_services/rbk/invoice.rb' + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: implicit, explicit +Style/RescueStandardError: + Exclude: + - 'lib/payment_services/perfect_money/client.rb' + - 'lib/payment_services/rbk/invoice.rb' + +# Offense count: 6 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. +# AllowedMethods: present?, blank?, presence, try, try! +Style/SafeNavigation: + Exclude: + - 'lib/payment_services/crypto_apis/invoicer.rb' + - 'lib/payment_services/ex_pay/invoicer.rb' + - 'lib/payment_services/manual_by_group/invoicer.rb' + - 'lib/payment_services/master_processing/invoicer.rb' + - 'lib/payment_services/merchant_alikassa_virtual/invoicer.rb' + - 'lib/payment_services/obmenka/invoicer.rb' + +# Offense count: 7 +# This cop supports unsafe autocorrection (--autocorrect-all). +Style/SingleArgumentDig: + Exclude: + - 'lib/payment_services/any_money/payout_adapter.rb' + - 'lib/payment_services/any_pay/client.rb' + - 'lib/payment_services/appex_money/payout_adapter.rb' + - 'lib/payment_services/pay_for_u_h2h/invoicer.rb' + +# Offense count: 2 +# This cop supports unsafe autocorrection (--autocorrect-all). +Style/SlicingWithRange: + Exclude: + - 'lib/payment_services/any_pay/payout_adapter.rb' + - 'lib/payment_services/merchant_alikassa/payout_adapter.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowModifier. +Style/SoleNestedConditional: + Exclude: + - 'lib/payment_services/cryptomus/invoicer.rb' + +# Offense count: 11 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: Mode. +Style/StringConcatenation: + Exclude: + - 'lib/payment_services/appex_money/client.rb' + - 'lib/payment_services/base.rb' + - 'lib/payment_services/kuna/client.rb' + - 'lib/payment_services/liquid/client.rb' + - 'lib/payment_services/payeer/client.rb' + +# Offense count: 116 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. +# SupportedStyles: single_quotes, double_quotes +Style/StringLiterals: + Exclude: + - 'lib/payment_services/any_money/invoicer.rb' + - 'lib/payment_services/any_pay/client.rb' + - 'lib/payment_services/coin_payments_hub/client.rb' + - 'lib/payment_services/exmo/client.rb' + - 'lib/payment_services/kuna/client.rb' + - 'lib/payment_services/liquid/client.rb' + - 'lib/payment_services/master_processing/client.rb' + - 'lib/payment_services/master_processing/invoicer.rb' + - 'lib/payment_services/merchant_alikassa/payout_adapter.rb' + - 'spec/support/schema.rb' + +# Offense count: 3 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: . +# SupportedStyles: percent, brackets +Style/SymbolArray: + EnforcedStyle: percent + MinSize: 8 + +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: AllowMethodsWithArguments, AllowedMethods, AllowedPatterns, AllowComments. +# AllowedMethods: define_method +Style/SymbolProc: + Exclude: + - 'lib/payment_services/blockchair/transaction_matcher.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, AllowSafeAssignment. +# SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex +Style/TernaryParentheses: + Exclude: + - 'lib/payment_services/paylama/invoicer.rb' + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyleForMultiline. +# SupportedStylesForMultiline: comma, consistent_comma, diff_comma, no_comma +Style/TrailingCommaInHashLiteral: + Exclude: + - 'lib/payment_services/blockchair/blockchain.rb' + - 'lib/payment_services/bovapay/invoicer.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Style/UnlessElse: + Exclude: + - 'lib/payment_services/crypto_apis_v2/payout_adapter.rb' + +# Offense count: 88 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings. +# URISchemes: http, https +Layout/LineLength: + Max: 199 diff --git a/.ruby-version b/.ruby-version index 59aa62c1..f092941a 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.4.5 +3.2.8 diff --git a/Gemfile.lock b/Gemfile.lock index 6e87d787..6bf1feba 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,85 +2,307 @@ PATH remote: . specs: payment_services (0.1.0) + activerecord activesupport + auto_logger (~> 0.1.4) + block_io jwt + money-rails virtus - workflow + workflow-activerecord GEM remote: https://rubygems.org/ specs: - activesupport (5.2.2) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - ast (2.4.0) + actionpack (8.0.3) + actionview (= 8.0.3) + activesupport (= 8.0.3) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actionview (8.0.3) + activesupport (= 8.0.3) + builder (~> 3.1) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activemodel (8.0.3) + activesupport (= 8.0.3) + activerecord (8.0.3) + activemodel (= 8.0.3) + activesupport (= 8.0.3) + timeout (>= 0.4.0) + activesupport (8.0.3) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + ast (2.4.3) + auto_logger (0.1.7) + activesupport + beautiful-log + awesome_print (1.8.0) axiom-types (0.1.1) descendants_tracker (~> 0.0.4) ice_nine (~> 0.11.0) thread_safe (~> 0.3, >= 0.3.1) + base32 (0.3.4) + base64 (0.3.0) + beautiful-log (0.2.2) + awesome_print (~> 1.8.0) + colorize (~> 0.8.1) + bech32 (1.5.0) + thor (>= 1.1.0) + benchmark (0.4.1) + bigdecimal (3.3.1) + bip-schnorr (0.7.0) + ecdsa_ext (~> 0.5.0) + bitcoinrb (1.7.0) + base32 (>= 0.3.4) + bech32 (>= 1.3.0) + bip-schnorr (>= 0.7.0) + daemon-spawn + ecdsa_ext (~> 0.5.1) + eventmachine + eventmachine_httpserver + ffi + iniparse + json_pure (>= 2.3.1) + leb128 (~> 1.0.0) + murmurhash3 (~> 0.1.7) + siphash + thor + block_io (3.1.0) + bitcoinrb (>= 1.2.1, < 2) + oj (~> 3.0, < 4) + typhoeus (~> 1.0, < 2) + builder (3.3.0) coercible (1.0.0) descendants_tracker (~> 0.0.1) - concurrent-ruby (1.1.4) + colorize (0.8.1) + concurrent-ruby (1.3.5) + connection_pool (2.5.4) + crass (1.0.6) + daemon-spawn (0.4.2) + database_cleaner-active_record (2.2.2) + activerecord (>= 5.a) + database_cleaner-core (~> 2.0) + database_cleaner-core (2.0.1) + date (3.4.1) descendants_tracker (0.0.4) thread_safe (~> 0.3, >= 0.3.1) - diff-lcs (1.3) - equalizer (0.0.11) - i18n (1.2.0) + diff-lcs (1.6.2) + drb (2.2.3) + ecdsa (1.2.0) + ecdsa_ext (0.5.1) + ecdsa (~> 1.2.0) + erb (5.1.1) + erubi (1.13.1) + ethon (0.15.0) + ffi (>= 1.15.0) + eventmachine (1.2.7) + eventmachine_httpserver (0.2.1) + ffi (1.17.2) + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-aarch64-linux-musl) + ffi (1.17.2-arm-linux-gnu) + ffi (1.17.2-arm-linux-musl) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-darwin) + ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) + i18n (1.14.7) concurrent-ruby (~> 1.0) ice_nine (0.11.2) - jaro_winkler (1.5.1) - jwt (2.1.0) - minitest (5.11.3) - parallel (1.12.1) - parser (2.5.3.0) - ast (~> 2.4.0) - powerpack (0.1.2) - rainbow (3.0.0) - rake (10.5.0) - rspec (3.8.0) - rspec-core (~> 3.8.0) - rspec-expectations (~> 3.8.0) - rspec-mocks (~> 3.8.0) - rspec-core (3.8.0) - rspec-support (~> 3.8.0) - rspec-expectations (3.8.1) + iniparse (1.5.0) + io-console (0.8.1) + irb (1.15.2) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + json (2.15.1) + json_pure (2.8.1) + jwt (3.1.2) + base64 + language_server-protocol (3.17.0.5) + leb128 (1.0.0) + lint_roller (1.1.0) + logger (1.7.0) + loofah (2.24.1) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + minitest (5.26.0) + monetize (1.13.0) + money (~> 6.12) + money (6.19.0) + i18n (>= 0.6.4, <= 2) + money-rails (1.15.0) + activesupport (>= 3.0) + monetize (~> 1.9) + money (~> 6.13) + railties (>= 3.0) + murmurhash3 (0.1.7) + nokogiri (1.18.10-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.10-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.18.10-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.10-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.18.10-arm64-darwin) + racc (~> 1.4) + nokogiri (1.18.10-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.18.10-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.10-x86_64-linux-musl) + racc (~> 1.4) + oj (3.16.11) + bigdecimal (>= 3.0) + ostruct (>= 0.2) + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.9.0) + ast (~> 2.4.1) + racc + pp (0.6.3) + prettyprint + prettyprint (0.2.0) + prism (1.6.0) + psych (5.2.6) + date + stringio + racc (1.8.1) + rack (3.2.3) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.2.1) + rack (>= 3) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (8.0.3) + actionpack (= 8.0.3) + activesupport (= 8.0.3) + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.3.0) + rdoc (6.15.0) + erb + psych (>= 4.0.0) + tsort + regexp_parser (2.11.3) + reline (0.6.2) + io-console (~> 0.5) + rspec (3.13.1) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.5) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-mocks (3.8.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.6) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-support (3.8.0) - rubocop (0.61.1) - jaro_winkler (~> 1.5.1) + rspec-support (~> 3.13.0) + rspec-support (3.13.6) + rubocop (1.81.1) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) parallel (~> 1.10) - parser (>= 2.5, != 2.5.1.1) - powerpack (~> 0.1) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.47.1, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (~> 1.4.0) - ruby-progressbar (1.10.0) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.47.1) + parser (>= 3.3.7.2) + prism (~> 1.4) + ruby-progressbar (1.13.0) + securerandom (0.4.1) + shoulda-matchers (6.5.0) + activesupport (>= 5.2.0) + siphash (0.0.1) + sqlite3 (2.7.4-aarch64-linux-gnu) + sqlite3 (2.7.4-aarch64-linux-musl) + sqlite3 (2.7.4-arm-linux-gnu) + sqlite3 (2.7.4-arm-linux-musl) + sqlite3 (2.7.4-arm64-darwin) + sqlite3 (2.7.4-x86_64-darwin) + sqlite3 (2.7.4-x86_64-linux-gnu) + sqlite3 (2.7.4-x86_64-linux-musl) + stringio (3.1.7) + thor (1.4.0) thread_safe (0.3.6) - tzinfo (1.2.5) - thread_safe (~> 0.1) - unicode-display_width (1.4.0) - virtus (1.0.5) + timeout (0.4.3) + tsort (0.2.0) + typhoeus (1.5.0) + ethon (>= 0.9.0, < 0.16.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.1.0) + uri (1.0.4) + useragent (0.16.11) + virtus (2.0.0) axiom-types (~> 0.1) coercible (~> 1.0) descendants_tracker (~> 0.0, >= 0.0.3) - equalizer (~> 0.0, >= 0.0.9) - workflow (1.2.0) + workflow (3.1.1) + workflow-activerecord (6.0.1) + activerecord (>= 6.0) + workflow (~> 3.0) + zeitwerk (2.7.3) PLATFORMS - ruby + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86_64-darwin + x86_64-linux + x86_64-linux-gnu + x86_64-linux-musl DEPENDENCIES - bundler (~> 1.16) + bundler + database_cleaner-active_record payment_services! - rake (~> 10.0) - rspec (~> 3.0) - rubocop (~> 0.61) + rake + rspec + rubocop + shoulda-matchers + sqlite3 BUNDLED WITH - 1.17.1 + 2.7.2 diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 00000000..75b1f136 --- /dev/null +++ b/config/database.yml @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +test: + adapter: sqlite3 + database: ':memory:' + pool: 5 + timeout: 5000 \ No newline at end of file diff --git a/lib/payment_services.rb b/lib/payment_services.rb index 58680b6d..f5c010a9 100644 --- a/lib/payment_services.rb +++ b/lib/payment_services.rb @@ -4,13 +4,20 @@ require 'payment_services/version' require 'payment_services/configuration' +require 'payment_services/auto_logger' +require 'workflow-activerecord' +require 'money' module PaymentServices class << self attr_reader :configuration end + # Set explicit rounding mode to avoid deprecation warning + Money.rounding_mode = BigDecimal::ROUND_HALF_UP + require 'payment_services/base' + require 'payment_services/application_record' require 'payment_services/base/invoicer' require 'payment_services/base/payout_adapter' require 'payment_services/base/client' diff --git a/lib/payment_services/adv_cash/payout.rb b/lib/payment_services/adv_cash/payout.rb index 9a66d1ef..6617c9aa 100644 --- a/lib/payment_services/adv_cash/payout.rb +++ b/lib/payment_services/adv_cash/payout.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class PaymentServices::AdvCash - class Payout < ApplicationRecord - include Workflow + class Payout < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'advcash_payouts' diff --git a/lib/payment_services/ali_kassa/invoice.rb b/lib/payment_services/ali_kassa/invoice.rb index 471e5e3f..2db3d16f 100644 --- a/lib/payment_services/ali_kassa/invoice.rb +++ b/lib/payment_services/ali_kassa/invoice.rb @@ -3,8 +3,8 @@ # Copyright (c) 2018 FINFEX https://github.com/finfex class PaymentServices::AliKassa - class Invoice < ApplicationRecord - include Workflow + class Invoice < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'ali_kassa_invoices' scope :ordered, -> { order(id: :desc) } diff --git a/lib/payment_services/ali_kassa_peer_to_peer/invoice.rb b/lib/payment_services/ali_kassa_peer_to_peer/invoice.rb index 83b5cf5b..d9c85cd7 100644 --- a/lib/payment_services/ali_kassa_peer_to_peer/invoice.rb +++ b/lib/payment_services/ali_kassa_peer_to_peer/invoice.rb @@ -3,8 +3,8 @@ # Copyright (c) 2018 FINFEX https://github.com/finfex class PaymentServices::AliKassaPeerToPeer - class Invoice < ApplicationRecord - include Workflow + class Invoice < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'ali_kassa_p2p_invoices' scope :ordered, -> { order(id: :desc) } diff --git a/lib/payment_services/any_money/invoice.rb b/lib/payment_services/any_money/invoice.rb index 4b574993..6eb682c6 100644 --- a/lib/payment_services/any_money/invoice.rb +++ b/lib/payment_services/any_money/invoice.rb @@ -3,8 +3,8 @@ # Copyright (c) 2018 FINFEX https://github.com/finfex class PaymentServices::AnyMoney - class Invoice < ApplicationRecord - include Workflow + class Invoice < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'any_money_invoices' scope :ordered, -> { order(id: :desc) } diff --git a/lib/payment_services/any_money/payout.rb b/lib/payment_services/any_money/payout.rb index 71b67d4d..ed3ec2fb 100644 --- a/lib/payment_services/any_money/payout.rb +++ b/lib/payment_services/any_money/payout.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class PaymentServices::AnyMoney - class Payout < ApplicationRecord - include Workflow + class Payout < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'any_money_payouts' scope :ordered, -> { order(id: :desc) } diff --git a/lib/payment_services/appex_money/payout.rb b/lib/payment_services/appex_money/payout.rb index 7fd090e2..474b238a 100644 --- a/lib/payment_services/appex_money/payout.rb +++ b/lib/payment_services/appex_money/payout.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class PaymentServices::AppexMoney - class Payout < ApplicationRecord - include Workflow + class Payout < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'appex_money_payouts' scope :ordered, -> { order(id: :desc) } diff --git a/lib/payment_services/application_record.rb b/lib/payment_services/application_record.rb new file mode 100644 index 00000000..98a83371 --- /dev/null +++ b/lib/payment_services/application_record.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'active_record' +require 'money-rails' +require 'money-rails/active_record/monetizable' +require 'money-rails/active_model/validator' + +module PaymentServices + class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true + include MoneyRails::ActiveRecord::Monetizable + end +end diff --git a/lib/payment_services/auto_logger.rb b/lib/payment_services/auto_logger.rb new file mode 100644 index 00000000..2d805d47 --- /dev/null +++ b/lib/payment_services/auto_logger.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'logger' + +module AutoLogger + def logger + @logger ||= Logger.new(STDOUT) + end +end \ No newline at end of file diff --git a/lib/payment_services/base/crypto_invoice.rb b/lib/payment_services/base/crypto_invoice.rb index 745a62d1..6377d13f 100644 --- a/lib/payment_services/base/crypto_invoice.rb +++ b/lib/payment_services/base/crypto_invoice.rb @@ -1,8 +1,10 @@ # frozen_string_literal: true class PaymentServices::Base - class CryptoInvoice < ActiveRecord::Base - include Workflow + class CryptoInvoice < PaymentServices::ApplicationRecord + self.abstract_class = true + + include WorkflowActiverecord scope :ordered, -> { order(id: :desc) } diff --git a/lib/payment_services/base/crypto_payout.rb b/lib/payment_services/base/crypto_payout.rb index e4a1c489..45f951d6 100644 --- a/lib/payment_services/base/crypto_payout.rb +++ b/lib/payment_services/base/crypto_payout.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class PaymentServices::Base - class CryptoPayout < ActiveRecord::Base - include Workflow + class CryptoPayout < PaymentServices::ApplicationRecord + include WorkflowActiverecord scope :ordered, -> { order(id: :desc) } diff --git a/lib/payment_services/base/fiat_invoice.rb b/lib/payment_services/base/fiat_invoice.rb index 162985a8..032c658d 100644 --- a/lib/payment_services/base/fiat_invoice.rb +++ b/lib/payment_services/base/fiat_invoice.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true class PaymentServices::Base - class FiatInvoice < ActiveRecord::Base + class FiatInvoice < PaymentServices::ApplicationRecord self.abstract_class = true - include Workflow + include WorkflowActiverecord scope :ordered, -> { order(id: :desc) } diff --git a/lib/payment_services/base/fiat_payout.rb b/lib/payment_services/base/fiat_payout.rb index e27d767c..d54a2d0a 100644 --- a/lib/payment_services/base/fiat_payout.rb +++ b/lib/payment_services/base/fiat_payout.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true class PaymentServices::Base - class FiatPayout < ActiveRecord::Base + class FiatPayout < PaymentServices::ApplicationRecord self.abstract_class = true - include Workflow + include WorkflowActiverecord scope :ordered, -> { order(id: :desc) } diff --git a/lib/payment_services/binance/invoice.rb b/lib/payment_services/binance/invoice.rb index 4b1ca5ea..e7d6ecf0 100644 --- a/lib/payment_services/binance/invoice.rb +++ b/lib/payment_services/binance/invoice.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class PaymentServices::Binance - class Invoice < ApplicationRecord - include Workflow + class Invoice < PaymentServices::ApplicationRecord + include WorkflowActiverecord BINANCE_SUCCESS = [1, 6] BINANCE_FAILED = 3 diff --git a/lib/payment_services/binance/payout.rb b/lib/payment_services/binance/payout.rb index 61bdabd9..42c53e48 100644 --- a/lib/payment_services/binance/payout.rb +++ b/lib/payment_services/binance/payout.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class PaymentServices::Binance - class Payout < ApplicationRecord - include Workflow + class Payout < PaymentServices::ApplicationRecord + include WorkflowActiverecord BINANCE_SUCCESS = 6 BINANCE_REJECTED = 3 diff --git a/lib/payment_services/block_io/invoice.rb b/lib/payment_services/block_io/invoice.rb index 1222e333..0ce2991e 100644 --- a/lib/payment_services/block_io/invoice.rb +++ b/lib/payment_services/block_io/invoice.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class PaymentServices::BlockIo - class Invoice < ApplicationRecord - include Workflow + class Invoice < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'block_io_invoices' scope :ordered, -> { order(id: :desc) } diff --git a/lib/payment_services/block_io/payout.rb b/lib/payment_services/block_io/payout.rb index 47a5635e..bc7d9e44 100644 --- a/lib/payment_services/block_io/payout.rb +++ b/lib/payment_services/block_io/payout.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class PaymentServices::BlockIo - class Payout < ApplicationRecord - include Workflow + class Payout < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'block_io_payouts' scope :ordered, -> { order(id: :desc) } diff --git a/lib/payment_services/crypto_apis/invoice.rb b/lib/payment_services/crypto_apis/invoice.rb index 39357fe1..a727d96e 100644 --- a/lib/payment_services/crypto_apis/invoice.rb +++ b/lib/payment_services/crypto_apis/invoice.rb @@ -3,9 +3,9 @@ # Copyright (c) 2020 FINFEX https://github.com/finfex class PaymentServices::CryptoApis - class Invoice < ApplicationRecord + class Invoice < PaymentServices::ApplicationRecord CONFIRMATIONS_FOR_COMPLETE = 1 - include Workflow + include WorkflowActiverecord self.table_name = 'crypto_apis_invoices' scope :ordered, -> { order(id: :desc) } diff --git a/lib/payment_services/crypto_apis/payout.rb b/lib/payment_services/crypto_apis/payout.rb index 6cfd44ec..09e9933d 100644 --- a/lib/payment_services/crypto_apis/payout.rb +++ b/lib/payment_services/crypto_apis/payout.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true class PaymentServices::CryptoApis - class Payout < ApplicationRecord + class Payout < PaymentServices::ApplicationRecord CONFIRMATIONS_FOR_COMPLETE = 1 - include Workflow + include WorkflowActiverecord self.table_name = 'crypto_apis_payouts' scope :ordered, -> { order(id: :desc) } diff --git a/lib/payment_services/crypto_apis_v2/invoice.rb b/lib/payment_services/crypto_apis_v2/invoice.rb index 9f2dcbb1..175e1753 100644 --- a/lib/payment_services/crypto_apis_v2/invoice.rb +++ b/lib/payment_services/crypto_apis_v2/invoice.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class PaymentServices::CryptoApisV2 - class Invoice < ApplicationRecord - include Workflow + class Invoice < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'crypto_apis_invoices' scope :ordered, -> { order(id: :desc) } diff --git a/lib/payment_services/crypto_apis_v2/payout.rb b/lib/payment_services/crypto_apis_v2/payout.rb index 648fd4f9..348930b3 100644 --- a/lib/payment_services/crypto_apis_v2/payout.rb +++ b/lib/payment_services/crypto_apis_v2/payout.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class PaymentServices::CryptoApisV2 - class Payout < ApplicationRecord - include Workflow + class Payout < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'crypto_apis_payouts' scope :ordered, -> { order(id: :desc) } diff --git a/lib/payment_services/exmo/invoice.rb b/lib/payment_services/exmo/invoice.rb index 3d17ab47..f291a991 100644 --- a/lib/payment_services/exmo/invoice.rb +++ b/lib/payment_services/exmo/invoice.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class PaymentServices::Exmo - class Invoice < ApplicationRecord - include Workflow + class Invoice < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'exmo_invoices' diff --git a/lib/payment_services/exmo/payout.rb b/lib/payment_services/exmo/payout.rb index b2c801c3..53719a35 100644 --- a/lib/payment_services/exmo/payout.rb +++ b/lib/payment_services/exmo/payout.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class PaymentServices::Exmo - class Payout < ApplicationRecord - include Workflow + class Payout < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'exmo_payouts' diff --git a/lib/payment_services/kuna/invoice.rb b/lib/payment_services/kuna/invoice.rb index 604ca7ca..2529c74a 100644 --- a/lib/payment_services/kuna/invoice.rb +++ b/lib/payment_services/kuna/invoice.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true class PaymentServices::Kuna - class Invoice < ApplicationRecord + class Invoice < PaymentServices::ApplicationRecord FEE_PERCENT = 0.5 UAH_FEE_PERCENT = 1.0 UAH_FEE_REGULAR = 5 KOPECK_EPSILON = 1 - include Workflow + include WorkflowActiverecord self.table_name = 'kuna_invoices' diff --git a/lib/payment_services/kuna/payout.rb b/lib/payment_services/kuna/payout.rb index 1ce3400a..181a7063 100644 --- a/lib/payment_services/kuna/payout.rb +++ b/lib/payment_services/kuna/payout.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class PaymentServices::Kuna - class Payout < ApplicationRecord - include Workflow + class Payout < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'kuna_payouts' diff --git a/lib/payment_services/liquid/invoice.rb b/lib/payment_services/liquid/invoice.rb index 51dea9c0..34a3f8e8 100644 --- a/lib/payment_services/liquid/invoice.rb +++ b/lib/payment_services/liquid/invoice.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class PaymentServices::Liquid - class Invoice < ApplicationRecord - include Workflow + class Invoice < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'liquid_invoices' scope :ordered, -> { order(id: :desc) } diff --git a/lib/payment_services/liquid/payout.rb b/lib/payment_services/liquid/payout.rb index 96507819..755bbcc3 100644 --- a/lib/payment_services/liquid/payout.rb +++ b/lib/payment_services/liquid/payout.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class PaymentServices::Liquid - class Payout < ApplicationRecord - include Workflow + class Payout < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'liquid_payouts' SUCCESS_PAYOUT_STATE = 'processed' diff --git a/lib/payment_services/master_processing/invoice.rb b/lib/payment_services/master_processing/invoice.rb index 39ed1bb3..45bcb444 100644 --- a/lib/payment_services/master_processing/invoice.rb +++ b/lib/payment_services/master_processing/invoice.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class PaymentServices::MasterProcessing - class Invoice < ApplicationRecord - include Workflow + class Invoice < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'master_processing_invoices' diff --git a/lib/payment_services/master_processing/payout.rb b/lib/payment_services/master_processing/payout.rb index ebc73b95..c98ecc28 100644 --- a/lib/payment_services/master_processing/payout.rb +++ b/lib/payment_services/master_processing/payout.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class PaymentServices::MasterProcessing - class Payout < ApplicationRecord - include Workflow + class Payout < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'master_processing_payouts' diff --git a/lib/payment_services/obmenka/invoice.rb b/lib/payment_services/obmenka/invoice.rb index ac028371..bd179613 100644 --- a/lib/payment_services/obmenka/invoice.rb +++ b/lib/payment_services/obmenka/invoice.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class PaymentServices::Obmenka - class Invoice < ApplicationRecord - include Workflow + class Invoice < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'obmenka_invoices' diff --git a/lib/payment_services/obmenka/payout.rb b/lib/payment_services/obmenka/payout.rb index 4b330384..47dfa7ed 100644 --- a/lib/payment_services/obmenka/payout.rb +++ b/lib/payment_services/obmenka/payout.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class PaymentServices::Obmenka - class Payout < ApplicationRecord - include Workflow + class Payout < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'obmenka_payouts' diff --git a/lib/payment_services/oko_otc/payout.rb b/lib/payment_services/oko_otc/payout.rb index 1a8728c2..301bbf3f 100644 --- a/lib/payment_services/oko_otc/payout.rb +++ b/lib/payment_services/oko_otc/payout.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true class PaymentServices::OkoOtc - class Payout < ApplicationRecord + class Payout < PaymentServices::ApplicationRecord SUCCESS_PROVIDER_STATE = 'Выплачена' FAILED_PROVIDER_STATE = 'Отмененная' - include Workflow + include WorkflowActiverecord self.table_name = 'oko_otc_payouts' diff --git a/lib/payment_services/payeer/payout.rb b/lib/payment_services/payeer/payout.rb index 2438720c..2c80d201 100644 --- a/lib/payment_services/payeer/payout.rb +++ b/lib/payment_services/payeer/payout.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class PaymentServices::Payeer - class Payout < ApplicationRecord - include Workflow + class Payout < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'payeer_payouts' diff --git a/lib/payment_services/paylama/invoice.rb b/lib/payment_services/paylama/invoice.rb index ac0e548f..6ad233aa 100644 --- a/lib/payment_services/paylama/invoice.rb +++ b/lib/payment_services/paylama/invoice.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true class PaymentServices::Paylama - class Invoice < ApplicationRecord + class Invoice < PaymentServices::ApplicationRecord SUCCESS_PROVIDER_STATE = 'Succeed' FAILED_PROVIDER_STATE = 'Failed' - include Workflow + include WorkflowActiverecord self.table_name = 'paylama_invoices' diff --git a/lib/payment_services/paylama/payout.rb b/lib/payment_services/paylama/payout.rb index 3ce3c396..973e4eb8 100644 --- a/lib/payment_services/paylama/payout.rb +++ b/lib/payment_services/paylama/payout.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true class PaymentServices::Paylama - class Payout < ApplicationRecord + class Payout < PaymentServices::ApplicationRecord SUCCESS_PROVIDER_STATE = 'Succeed' FAILED_PROVIDER_STATE = 'Failed' - include Workflow + include WorkflowActiverecord self.table_name = 'paylama_payouts' diff --git a/lib/payment_services/perfect_money/invoice.rb b/lib/payment_services/perfect_money/invoice.rb index fd6cb4fc..5401020e 100644 --- a/lib/payment_services/perfect_money/invoice.rb +++ b/lib/payment_services/perfect_money/invoice.rb @@ -3,8 +3,8 @@ # Copyright (c) 2018 FINFEX https://github.com/finfex class PaymentServices::PerfectMoney - class Invoice < ApplicationRecord - include Workflow + class Invoice < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'perfect_money_invoices' scope :ordered, -> { order(id: :desc) } diff --git a/lib/payment_services/perfect_money/payout.rb b/lib/payment_services/perfect_money/payout.rb index baa6b90f..99938ed4 100644 --- a/lib/payment_services/perfect_money/payout.rb +++ b/lib/payment_services/perfect_money/payout.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class PaymentServices::PerfectMoney - class Payout < ApplicationRecord - include Workflow + class Payout < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'perfect_money_payouts' diff --git a/lib/payment_services/qiwi/payment.rb b/lib/payment_services/qiwi/payment.rb index e7cd2bf0..ec077e44 100644 --- a/lib/payment_services/qiwi/payment.rb +++ b/lib/payment_services/qiwi/payment.rb @@ -9,9 +9,8 @@ require_relative 'payment_order_support' class PaymentServices::QIWI - class Payment < ApplicationRecord + class Payment < PaymentServices::ApplicationRecord include AutoLogger - extend Enumerize include PaymentOrderSupport self.table_name = :qiwi_payments @@ -21,11 +20,11 @@ class Payment < ApplicationRecord scope :ordered, -> { order 'id desc, date desc' } monetize :total_cents, as: :total - enum status: %i[UNKNOWN WAITING SUCCESS ERROR] - enumerize :direction_type, in: %w[IN OUT], predicates: { prefix: true } + enum :status, %i[UNKNOWN WAITING SUCCESS ERROR] + enum :direction_type, { in: 'IN', out: 'OUT' } def success_in? - SUCCESS? && direction_type_IN? + SUCCESS? && in? end end end diff --git a/lib/payment_services/qiwi/payout_adapter.rb b/lib/payment_services/qiwi/payout_adapter.rb index 05f7eab2..aa2db466 100644 --- a/lib/payment_services/qiwi/payout_adapter.rb +++ b/lib/payment_services/qiwi/payout_adapter.rb @@ -18,7 +18,6 @@ def make_payout!(amount:, payment_card_details:, transaction_id:, destination_ac private - # rubocop:disable Lint/UnusedMethodArgument def make_payout(amount:, payment_card_details:, transaction_id:, destination_account:) # rubocop:enable Lint/UnusedMethodArgument diff --git a/lib/payment_services/rbk.rb b/lib/payment_services/rbk.rb index ae60fd99..1f02a2c8 100644 --- a/lib/payment_services/rbk.rb +++ b/lib/payment_services/rbk.rb @@ -5,14 +5,20 @@ module PaymentServices class Rbk < Base CHECKOUT_URL = 'https://checkout.rbk.money/v1/checkout.html' + + # Используем autoload для всех классов + autoload :Identity, 'payment_services/rbk/identity' + autoload :Wallet, 'payment_services/rbk/wallet' + autoload :PayoutDestination, 'payment_services/rbk/payout_destination' + autoload :Payout, 'payment_services/rbk/payout' + autoload :Payment, 'payment_services/rbk/payment' + autoload :Invoice, 'payment_services/rbk/invoice' + autoload :Customer, 'payment_services/rbk/customer' + autoload :PaymentCard, 'payment_services/rbk/payment_card' autoload :PayoutAdapter, 'payment_services/rbk/payout_adapter' autoload :Invoicer, 'payment_services/rbk/invoicer' + register :invoicer, Invoicer register :payout_adapter, PayoutAdapter end end -# FIXME: не знаю как проще подгрузить все файлы из rbk -require_relative 'rbk/identity' -require_relative 'rbk/wallet' -require_relative 'rbk/payout_destination' -require_relative 'rbk/payout' diff --git a/lib/payment_services/rbk/customer.rb b/lib/payment_services/rbk/customer.rb index 237841aa..07f8ceff 100644 --- a/lib/payment_services/rbk/customer.rb +++ b/lib/payment_services/rbk/customer.rb @@ -3,11 +3,10 @@ # Copyright (c) 2018 FINFEX https://github.com/finfex require_relative 'customer_client' -require_relative 'payment_card' require 'jwt' class PaymentServices::Rbk - class Customer < ApplicationRecord + class Customer < PaymentServices::ApplicationRecord self.table_name = 'rbk_money_customers' belongs_to :user diff --git a/lib/payment_services/rbk/identity.rb b/lib/payment_services/rbk/identity.rb index 1544e36c..5f4e48f9 100644 --- a/lib/payment_services/rbk/identity.rb +++ b/lib/payment_services/rbk/identity.rb @@ -5,7 +5,7 @@ require_relative 'identity_client' class PaymentServices::Rbk - class Identity < ApplicationRecord + class Identity < PaymentServices::ApplicationRecord self.table_name = 'rbk_identities' has_many :rbk_wallets, diff --git a/lib/payment_services/rbk/invoice.rb b/lib/payment_services/rbk/invoice.rb index 49fb7fda..1ef8223e 100644 --- a/lib/payment_services/rbk/invoice.rb +++ b/lib/payment_services/rbk/invoice.rb @@ -2,12 +2,11 @@ # Copyright (c) 2018 FINFEX https://github.com/finfex -require_relative 'payment' require_relative 'invoice_client' class PaymentServices::Rbk - class Invoice < ApplicationRecord - include Workflow + class Invoice < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'rbk_money_invoices' scope :ordered, -> { order(id: :desc) } @@ -69,6 +68,9 @@ def make_refund! def fetch_payments! response = InvoiceClient.new.get_payments(self) response.map { |payment_json| find_or_create_payment!(payment_json) } + rescue => e + Rails.logger.warn "Failed to fetch payments for invoice #{id}: #{e.message}" if defined?(Rails) + raise e end private @@ -77,12 +79,12 @@ def find_or_create_payment!(payment_json) payment = payments.find_by(rbk_id: payment_json['id']) return payment if payment.present? - Payment.create!( + PaymentServices::Rbk::Payment.create!( rbk_id: payment_json['id'], invoice: self, order_public_id: order_public_id, amount_in_cents: payment_json['amount'], - state: Payment.rbk_state_to_state(payment_json['status']), + state: PaymentServices::Rbk::Payment.rbk_state_to_state(payment_json['status']), payload: payment_json ) end diff --git a/lib/payment_services/rbk/invoicer.rb b/lib/payment_services/rbk/invoicer.rb index e7b4092d..a77d65be 100644 --- a/lib/payment_services/rbk/invoicer.rb +++ b/lib/payment_services/rbk/invoicer.rb @@ -2,15 +2,14 @@ # Copyright (c) 2018 FINFEX https://github.com/finfex -require_relative 'invoice' require_relative 'invoice_client' -require_relative 'customer' +require_relative 'customer_client' class PaymentServices::Rbk class Invoicer < ::PaymentServices::Base::Invoicer def create_invoice(money) response = InvoiceClient.new.create_invoice(order_id: order.public_id, amount: money.cents) - Invoice.create!( + PaymentServices::Rbk::Invoice.create!( amount: money.to_f, order_public_id: order.public_id, rbk_invoice_id: response['invoice']['id'], diff --git a/lib/payment_services/rbk/payment.rb b/lib/payment_services/rbk/payment.rb index b820b2f5..f0acb924 100644 --- a/lib/payment_services/rbk/payment.rb +++ b/lib/payment_services/rbk/payment.rb @@ -5,8 +5,8 @@ require_relative 'payment_client' class PaymentServices::Rbk - class Payment < ApplicationRecord - include Workflow + class Payment < PaymentServices::ApplicationRecord + include WorkflowActiverecord self.table_name = 'rbk_money_payments' scope :ordered, -> { order(id: :desc) } @@ -50,7 +50,7 @@ def self.rbk_state_to_state(rbk_state) elsif PaymentClient::PENDING_STATES.include?(rbk_state) :pending elsif PaymentClient::REFUND_STATES.include?(rbk_state) - :fefunded + :refunded else raise("Такого статуса не существует: #{rbk_state}") end diff --git a/lib/payment_services/rbk/payment_card.rb b/lib/payment_services/rbk/payment_card.rb index 169ba8d5..5165772b 100644 --- a/lib/payment_services/rbk/payment_card.rb +++ b/lib/payment_services/rbk/payment_card.rb @@ -3,16 +3,19 @@ # Copyright (c) 2018 FINFEX https://github.com/finfex class PaymentServices::Rbk - class PaymentCard < ApplicationRecord + class PaymentCard < PaymentServices::ApplicationRecord self.table_name = 'rbk_payment_cards' - enum card_type: %i[bank_card applepay googlepay], _prefix: :card_type + enum :card_type, %i[bank_card applepay googlepay], prefix: :card_type belongs_to :rbk_customer, class_name: 'PaymentServices::Rbk::Customer', foreign_key: :rbk_customer_id def masked_number # NOTE dup нужен, т.к. insert изменяет исходный объект - "#{bin.dup.insert(4, ' ')}** **** #{last_digits}" + return ' ** **** ' if bin.empty? && last_digits.empty? + + bin_with_space = bin.dup.insert(4, ' ') + "#{bin_with_space}** **** #{last_digits}" end end end diff --git a/lib/payment_services/rbk/payout.rb b/lib/payment_services/rbk/payout.rb index 611ae054..921fac98 100644 --- a/lib/payment_services/rbk/payout.rb +++ b/lib/payment_services/rbk/payout.rb @@ -5,7 +5,7 @@ require_relative 'payout_client' class PaymentServices::Rbk - class Payout < ApplicationRecord + class Payout < PaymentServices::ApplicationRecord self.table_name = 'rbk_payouts' Error = Class.new StandardError @@ -17,9 +17,11 @@ class Payout < ApplicationRecord class_name: 'PaymentServices::Rbk::Wallet', foreign_key: :rbk_wallet_id - def self.create_from!(destinaion:, wallet:, amount_cents:) + validates :rbk_id, :rbk_payout_destination, :rbk_wallet, :amount_cents, :rbk_status, presence: true, on: :create + + def self.create_from!(destination:, wallet:, amount_cents:) response = PayoutClient.new.make_payout( - payout_destination: destinaion, + payout_destination: destination, wallet: wallet, amount_cents: amount_cents ) @@ -27,7 +29,7 @@ def self.create_from!(destinaion:, wallet:, amount_cents:) create!( rbk_id: response['id'], - rbk_payout_destination: destinaion, + rbk_payout_destination: destination, rbk_wallet: wallet, amount_cents: amount_cents, payload: response, diff --git a/lib/payment_services/rbk/payout_adapter.rb b/lib/payment_services/rbk/payout_adapter.rb index 77fe5076..bd15186c 100644 --- a/lib/payment_services/rbk/payout_adapter.rb +++ b/lib/payment_services/rbk/payout_adapter.rb @@ -8,7 +8,6 @@ class PaymentServices::Rbk class PayoutAdapter < ::PaymentServices::Base::PayoutAdapter # TODO: возможность передавть ID кошелька для списания - # rubocop:disable Lint/UnusedMethodArgument def make_payout!(amount:, payment_card_details:, transaction_id:, destination_account:) # rubocop:enable Lint/UnusedMethodArgument raise 'Можно делать выплаты только в рублях' unless amount.currency == RUB @@ -28,7 +27,7 @@ def make_payout!(amount:, payment_card_details:, transaction_id:, destination_ac end Payout.create_from!( - destinaion: payout_destination, + destination: payout_destination, wallet: identity.current_wallet, amount_cents: amount.cents ) diff --git a/lib/payment_services/rbk/payout_destination.rb b/lib/payment_services/rbk/payout_destination.rb index a38e753e..22e54ddd 100644 --- a/lib/payment_services/rbk/payout_destination.rb +++ b/lib/payment_services/rbk/payout_destination.rb @@ -5,12 +5,14 @@ require_relative 'payout_destination_client' class PaymentServices::Rbk - class PayoutDestination < ApplicationRecord + class PayoutDestination < PaymentServices::ApplicationRecord self.table_name = 'rbk_payout_destinations' Error = Class.new StandardError belongs_to :rbk_identity, class_name: 'PaymentServices::Rbk::Identity', foreign_key: :rbk_identity_id + validates :rbk_id, :public_id, :card_brand, :card_bin, :card_suffix, :payment_token, :rbk_status, :rbk_identity, presence: true, on: :create + def self.find_or_create_from_card_details(number:, name:, exp_date:, identity:) tokenized_card = tokenize_card!(number: number, name: name, exp_date: exp_date) @@ -29,7 +31,7 @@ def self.create_destination!(identity:, tokenized_card:) payment_token: tokenized_card['token'], destination_public_id: public_id ) - raise Error, "Rbk failed to create destinaion: #{response}" unless response['id'] + raise Error, "Rbk failed to create destination: #{response}" unless response['id'] create!( rbk_identity: identity, diff --git a/lib/payment_services/rbk/wallet.rb b/lib/payment_services/rbk/wallet.rb index c297ea2c..a413e052 100644 --- a/lib/payment_services/rbk/wallet.rb +++ b/lib/payment_services/rbk/wallet.rb @@ -5,7 +5,7 @@ require_relative 'wallet_client' class PaymentServices::Rbk - class Wallet < ApplicationRecord + class Wallet < PaymentServices::ApplicationRecord self.table_name = 'rbk_wallets' belongs_to :rbk_identity, class_name: 'PaymentServices::Rbk::Identity', foreign_key: :rbk_identity_id diff --git a/lib/payment_services/tronscan/transaction.rb b/lib/payment_services/tronscan/transaction.rb index f6599794..236ac924 100644 --- a/lib/payment_services/tronscan/transaction.rb +++ b/lib/payment_services/tronscan/transaction.rb @@ -21,6 +21,7 @@ def to_s end def successful? + return false if source.nil? || source.empty? source[:confirmed] end end diff --git a/payment_services.gemspec b/payment_services.gemspec index 2083d7ad..ac46e398 100644 --- a/payment_services.gemspec +++ b/payment_services.gemspec @@ -38,11 +38,17 @@ Gem::Specification.new do |spec| spec.add_dependency 'activesupport' spec.add_dependency 'jwt' spec.add_dependency 'virtus' - spec.add_dependency 'workflow' + spec.add_dependency 'workflow-activerecord' spec.add_dependency 'block_io' - - spec.add_development_dependency 'bundler', '~> 1.16' - spec.add_development_dependency 'rake', '~> 10.0' - spec.add_development_dependency 'rspec', '~> 3.0' - spec.add_development_dependency 'rubocop', '~> 0.61' + spec.add_dependency 'activerecord' + spec.add_dependency 'money-rails' + spec.add_dependency 'auto_logger', '~> 0.1.4' + + spec.add_development_dependency 'bundler' + spec.add_development_dependency 'rake' + spec.add_development_dependency 'rspec' + spec.add_development_dependency 'rubocop' + spec.add_development_dependency 'sqlite3' + spec.add_development_dependency 'database_cleaner-active_record' + spec.add_development_dependency 'shoulda-matchers' end diff --git a/spec/fixtures/rbk_identities.yml b/spec/fixtures/rbk_identities.yml new file mode 100644 index 00000000..887509a0 --- /dev/null +++ b/spec/fixtures/rbk_identities.yml @@ -0,0 +1,13 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +identity_1: + rbk_id: identity_123 + current: false + +identity_2: + rbk_id: identity_456 + current: true + +identity_3: + rbk_id: identity_789 + current: false \ No newline at end of file diff --git a/spec/fixtures/rbk_payout_destinations.yml b/spec/fixtures/rbk_payout_destinations.yml new file mode 100644 index 00000000..0ea52cd4 --- /dev/null +++ b/spec/fixtures/rbk_payout_destinations.yml @@ -0,0 +1,34 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +destination_1: + rbk_identity: identity_1 + rbk_id: dest_456 + public_id: dest_123 + payment_token: token_123 + card_brand: visa + card_bin: '411111' + card_suffix: '1111' + rbk_status: Authorized + payload: {"test": "data"} + +destination_2: + rbk_identity: identity_1 + rbk_id: dest_789 + public_id: dest_456 + payment_token: token_456 + card_brand: mastercard + card_bin: '555555' + card_suffix: '5555' + rbk_status: Pending + payload: {"test": "data2"} + +destination_3: + rbk_identity: identity_2 + rbk_id: dest_999 + public_id: dest_789 + payment_token: token_789 + card_brand: amex + card_bin: '378282' + card_suffix: '8282' + rbk_status: Completed + payload: {"test": "data3"} \ No newline at end of file diff --git a/spec/fixtures/rbk_payouts.yml b/spec/fixtures/rbk_payouts.yml new file mode 100644 index 00000000..d5ec7ffd --- /dev/null +++ b/spec/fixtures/rbk_payouts.yml @@ -0,0 +1,25 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +payout_1: + rbk_payout_destination: destination_1 + rbk_wallet: wallet_1 + rbk_id: payout_123 + amount_cents: 5000 + rbk_status: processed + payload: {"test": "data"} + +payout_2: + rbk_payout_destination: destination_2 + rbk_wallet: wallet_2 + rbk_id: payout_456 + amount_cents: 10000 + rbk_status: pending + payload: {"test": "data2"} + +payout_3: + rbk_payout_destination: destination_3 + rbk_wallet: wallet_3 + rbk_id: payout_789 + amount_cents: 7500 + rbk_status: completed + payload: {"test": "data3"} \ No newline at end of file diff --git a/spec/fixtures/rbk_wallets.yml b/spec/fixtures/rbk_wallets.yml new file mode 100644 index 00000000..03b24aa9 --- /dev/null +++ b/spec/fixtures/rbk_wallets.yml @@ -0,0 +1,16 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +wallet_1: + rbk_id: wallet_123 + rbk_identity: identity_1 + current: false + +wallet_2: + rbk_id: wallet_456 + rbk_identity: identity_1 + current: true + +wallet_3: + rbk_id: wallet_789 + rbk_identity: identity_2 + current: false \ No newline at end of file diff --git a/spec/payment_services/base/client_spec.rb b/spec/payment_services/base/client_spec.rb new file mode 100644 index 00000000..81830c6c --- /dev/null +++ b/spec/payment_services/base/client_spec.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +require_relative '../../../lib/payment_services/base/client' + +RSpec.describe PaymentServices::Base::Client do + let(:client) { described_class.new } + + describe '#http_request' do + let(:uri) { URI.parse('https://example.com/api') } + let(:http) { double('Net::HTTP') } + let(:request) { double('Request') } + let(:response) { double('Response') } + + before do + allow(client).to receive(:http).with(uri).and_return(http) + allow(client).to receive(:build_request).and_return(request) + allow(request).to receive(:body).and_return('test payload') + allow(http).to receive(:request).and_return(response) + end + + it 'sends HTTP request and returns response' do + result = client.http_request( + url: 'https://example.com/api', + method: :GET, + body: '{"test": "data"}', + headers: { 'Content-Type' => 'application/json' } + ) + + expect(result).to eq(response) + end + end + + describe '#build_request' do + let(:uri) { URI.parse('https://example.com/api') } + + context 'when method is POST' do + it 'creates Net::HTTP::Post request' do + request = client.build_request(uri: uri, method: :POST, body: 'test data') + + expect(request).to be_a(Net::HTTP::Post) + expect(request.body).to eq('test data') + end + end + + context 'when method is GET' do + it 'creates Net::HTTP::Get request' do + request = client.build_request(uri: uri, method: :GET) + + expect(request).to be_a(Net::HTTP::Get) + end + end + + context 'when method is PATCH' do + it 'creates Net::HTTP::Patch request' do + request = client.build_request(uri: uri, method: :PATCH, body: 'patch data') + + expect(request).to be_a(Net::HTTP::Patch) + expect(request.body).to eq('patch data') + end + end + + context 'when method is unsupported' do + it 'raises error' do + expect { + client.build_request(uri: uri, method: :DELETE) + }.to raise_error('Запрос DELETE не поддерживается!') + end + end + end + + describe '#safely_parse' do + let(:response) { double('Response') } + + context 'when response body is valid JSON' do + before do + allow(response).to receive(:body).and_return('{"status": "success", "data": [1, 2, 3]}') + end + + it 'parses JSON and returns hash' do + result = client.safely_parse(response) + + expect(result).to eq({ 'status' => 'success', 'data' => [1, 2, 3] }) + end + end + + context 'when response body is invalid JSON' do + before do + allow(response).to receive(:body).and_return('invalid json') + allow(response).to receive(:class).and_return(Net::HTTPBadRequest) + end + + it 'returns raw body and logs warning' do + result = client.safely_parse(response) + + expect(result).to eq('invalid json') + end + end + + context 'when response body is nil' do + before do + allow(response).to receive(:body).and_return(nil) + allow(response).to receive(:class).and_return(Net::HTTPBadRequest) + end + + it 'returns nil body' do + result = client.safely_parse(response) + + expect(result).to be_nil + end + end + end +end \ No newline at end of file diff --git a/spec/payment_services/exmo/transaction_spec.rb b/spec/payment_services/exmo/transaction_spec.rb new file mode 100644 index 00000000..7f932ebd --- /dev/null +++ b/spec/payment_services/exmo/transaction_spec.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require_relative '../../../lib/payment_services/exmo/transaction' + +RSpec.describe PaymentServices::Exmo::Transaction do + describe '.build_from' do + let(:raw_transaction) do + { + 'status' => 'Paid', + 'extra' => { + 'txid' => 'tx_12345' + }, + 'amount' => '100.5', + 'currency' => 'BTC' + } + end + + it 'creates transaction from raw data' do + transaction = described_class.build_from(raw_transaction: raw_transaction) + + expect(transaction.id).to eq('tx_12345') + expect(transaction.provider_state).to eq('Paid') + expect(transaction.source).to eq(raw_transaction) + end + end + + describe '#to_s' do + let(:source) { { 'id' => '123', 'status' => 'Paid' } } + let(:transaction) { described_class.new(source: source) } + + it 'returns source as string' do + expect(transaction.to_s).to eq(source.to_s) + end + end + + describe '#successful?' do + context 'when provider_state is Paid' do + it 'returns true' do + transaction = described_class.new(provider_state: 'Paid') + expect(transaction.successful?).to be true + end + end + + context 'when provider_state is not Paid' do + it 'returns false' do + transaction = described_class.new(provider_state: 'Pending') + expect(transaction.successful?).to be false + end + end + end + + describe '#failed?' do + context 'when provider_state is Cancelled' do + it 'returns true' do + transaction = described_class.new(provider_state: 'Cancelled') + expect(transaction.failed?).to be true + end + end + + context 'when provider_state is Error' do + it 'returns true' do + transaction = described_class.new(provider_state: 'Error') + expect(transaction.failed?).to be true + end + end + + context 'when provider_state is not in failed states' do + it 'returns false' do + transaction = described_class.new(provider_state: 'Paid') + expect(transaction.failed?).to be false + end + end + end +end \ No newline at end of file diff --git a/spec/payment_services/rbk/customer_spec.rb b/spec/payment_services/rbk/customer_spec.rb new file mode 100644 index 00000000..9a6b2f3e --- /dev/null +++ b/spec/payment_services/rbk/customer_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe PaymentServices::Rbk::Customer, type: :model do + describe 'associations' do + it { is_expected.to belong_to(:user) } + it { is_expected.to have_many(:payment_cards) } + end + + describe 'scopes' do + let!(:customer1) { described_class.create!(user_id: 1, rbk_id: 'cust_1', access_token: 'token_1') } + let!(:customer2) { described_class.create!(user_id: 2, rbk_id: 'cust_2', access_token: 'token_2') } + + it 'orders customers by id desc' do + expect(described_class.ordered).to eq([customer2, customer1]) + end + end + + describe '#access_token_valid?' do + it 'returns false when access_token_expired_at is nil' do + customer = described_class.new(access_token_expired_at: nil) + expect(customer.access_token_valid?).to be false + end + + it 'returns false when access_token is expired' do + customer = described_class.new(access_token_expired_at: 1.hour.ago) + expect(customer.access_token_valid?).to be false + end + + it 'returns true when access_token is not expired' do + customer = described_class.new(access_token_expired_at: 1.hour.from_now) + expect(customer.access_token_valid?).to be true + end + end +end \ No newline at end of file diff --git a/spec/payment_services/rbk/identity_spec.rb b/spec/payment_services/rbk/identity_spec.rb new file mode 100644 index 00000000..44a07836 --- /dev/null +++ b/spec/payment_services/rbk/identity_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe PaymentServices::Rbk::Identity, type: :model do + describe 'associations' do + it { is_expected.to have_many(:rbk_wallets) } + it { is_expected.to have_many(:rbk_payout_destinations) } + end + + describe '.current' do + it 'returns identity marked as current' do + current_identity = described_class.create!(rbk_id: 'id_1', current: true) + described_class.create!(rbk_id: 'id_2', current: false) + + expect(described_class.current).to eq(current_identity) + end + + it 'returns nil when no current identity exists' do + described_class.create!(rbk_id: 'id_1', current: false) + + expect(described_class.current).to be_nil + end + end + + describe '.create_sample!' do + let(:identity_client) { double('IdentityClient') } + let(:response) { { 'id' => 'identity_123', 'metadata' => 'sample' } } + + before do + allow(PaymentServices::Rbk::IdentityClient).to receive(:new).and_return(identity_client) + allow(identity_client).to receive(:create_sample_identity).and_return(response) + end + + it 'creates identity with response data' do + identity = described_class.create_sample! + + expect(identity.rbk_id).to eq('identity_123') + expect(identity.payload).to eq(response) + end + end + + describe '#current_wallet' do + let(:identity) { described_class.new(rbk_id: 'id_1') } + let!(:current_wallet) { double('CurrentWallet') } + let!(:other_wallet) { double('OtherWallet') } + + before do + allow(identity).to receive(:rbk_wallets).and_return(double('Wallets')) + allow(identity.rbk_wallets).to receive(:find_by).with(current: true).and_return(current_wallet) + end + + it 'returns the current wallet' do + expect(identity.current_wallet).to eq(current_wallet) + end + end +end \ No newline at end of file diff --git a/spec/payment_services/rbk/invoice_spec.rb b/spec/payment_services/rbk/invoice_spec.rb new file mode 100644 index 00000000..d97cb3c1 --- /dev/null +++ b/spec/payment_services/rbk/invoice_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe PaymentServices::Rbk::Invoice, type: :model do + describe 'associations' do + it { is_expected.to have_many(:payments).dependent(:destroy) } + end + + describe 'scopes' do + let!(:invoice1) { described_class.create!(amount_in_cents: 1000, order_public_id: 1, state: 'pending') } + let!(:invoice2) { described_class.create!(amount_in_cents: 2000, order_public_id: 2, state: 'pending') } + + it 'orders invoices by id desc' do + expect(described_class.ordered).to eq([invoice2, invoice1]) + end + end + + describe 'monetize' do + it 'monetizes amount_in_cents as amount' do + invoice = described_class.new(amount_in_cents: 1500) + expect(invoice.amount.cents).to eq(1500) + expect(invoice.amount.currency.iso_code).to eq('RUB') + end + end + + describe 'workflow states' do + let(:invoice) { described_class.create!(amount_in_cents: 1000, order_public_id: 1, state: 'pending') } + + before do + # Mock fetch_payments! to avoid HTTP calls + allow_any_instance_of(described_class).to receive(:fetch_payments!).and_return([]) + + # Mock order method to avoid external dependencies + order = double('Order') + allow(order).to receive(:auto_confirm!) + allow_any_instance_of(described_class).to receive(:order).and_return(order) + end + + it 'starts in pending state' do + expect(invoice).to be_pending + end + + it 'transitions from pending to paid with pay!' do + expect { invoice.pay! }.to change(invoice, :state).from('pending').to('paid') + end + + it 'transitions from pending to cancelled with cancel!' do + expect { invoice.cancel! }.to change(invoice, :state).from('pending').to('cancelled') + end + end +end \ No newline at end of file diff --git a/spec/payment_services/rbk/payment_card_spec.rb b/spec/payment_services/rbk/payment_card_spec.rb new file mode 100644 index 00000000..9f613bc4 --- /dev/null +++ b/spec/payment_services/rbk/payment_card_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe PaymentServices::Rbk::PaymentCard, type: :model do + describe 'associations' do + it { is_expected.to belong_to(:rbk_customer) } + end + + describe 'enums' do + it 'defines card_type enum' do + expect(described_class.card_types).to eq( + 'bank_card' => 0, + 'applepay' => 1, + 'googlepay' => 2 + ) + end + end + + describe '#masked_number' do + it 'formats the card number correctly' do + card = described_class.new(bin: '123456', last_digits: '7890') + expect(card.masked_number).to eq('1234 56** **** 7890') + end + + it 'handles empty bin and last_digits' do + card = described_class.new(bin: '', last_digits: '') + expect(card.masked_number).to eq(' ** **** ') + end + end +end \ No newline at end of file diff --git a/spec/payment_services/rbk/payment_spec.rb b/spec/payment_services/rbk/payment_spec.rb new file mode 100644 index 00000000..ac953d7f --- /dev/null +++ b/spec/payment_services/rbk/payment_spec.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe PaymentServices::Rbk::Payment, type: :model do + describe 'associations' do + it { is_expected.to belong_to(:invoice) } + end + + describe 'scopes' do + let!(:payment1) { described_class.create!(amount_in_cents: 1000, rbk_id: 'pay_1', state: 'pending', order_public_id: 1) } + let!(:payment2) { described_class.create!(amount_in_cents: 2000, rbk_id: 'pay_2', state: 'pending', order_public_id: 2) } + + it 'orders payments by id desc' do + expect(described_class.ordered).to eq([payment2, payment1]) + end + end + + describe 'monetize' do + it 'monetizes amount_in_cents as amount' do + payment = described_class.new(amount_in_cents: 1500) + expect(payment.amount.cents).to eq(1500) + expect(payment.amount.currency.iso_code).to eq('RUB') + end + end + + describe 'workflow states' do + let(:invoice) { PaymentServices::Rbk::Invoice.create!(amount_in_cents: 1000, order_public_id: 1, state: 'pending') } + let(:payment) { described_class.create!(amount_in_cents: 1000, rbk_id: 'pay_1', state: 'pending', order_public_id: 1, invoice: invoice) } + + before do + # Mock invoice methods to avoid recursive calls + allow(invoice).to receive(:pay!) + allow(invoice).to receive(:cancel!) + end + + it 'starts in pending state' do + expect(payment).to be_pending + end + + it 'transitions from pending to succeed with success!' do + expect { payment.success! }.to change(payment, :state).from('pending').to('succeed') + end + + it 'transitions from pending to failed with fail!' do + expect { payment.fail! }.to change(payment, :state).from('pending').to('failed') + end + + it 'transitions from pending to refunded with refund!' do + expect { payment.refund! }.to change(payment, :state).from('pending').to('refunded') + end + end + + describe '.rbk_state_to_state' do + let(:payment_client) { double('PaymentClient') } + + before do + allow(PaymentServices::Rbk::PaymentClient).to receive(:const_get).and_return(payment_client) + allow(payment_client).to receive(:const_get).and_return([]) + end + + it 'converts success states to :success' do + allow(PaymentServices::Rbk::PaymentClient).to receive(:const_get).with('SUCCESS_STATES').and_return(['processed']) + expect(described_class.rbk_state_to_state('processed')).to eq(:success) + end + + it 'converts fail states to :fail' do + allow(PaymentServices::Rbk::PaymentClient).to receive(:const_get).with('FAIL_STATES').and_return(['failed']) + expect(described_class.rbk_state_to_state('failed')).to eq(:fail) + end + + it 'raises error for unknown state' do + allow(PaymentServices::Rbk::PaymentClient).to receive(:const_get).and_return([]) + expect { described_class.rbk_state_to_state('unknown') }.to raise_error('Такого статуса не существует: unknown') + end + end + + describe '#payment_tool_info' do + let(:payment) { described_class.new } + let(:payload) do + { + 'payer' => { + 'paymentToolDetails' => { + 'cardNumberMask' => '**** **** **** 1234' + } + } + } + end + + before do + payment.payload = payload + end + + it 'extracts card number mask from payload' do + expect(payment.payment_tool_info).to eq('**** **** **** 1234') + end + + it 'returns nil when payload is empty' do + payment.payload = {} + expect(payment.payment_tool_info).to be_nil + end + end +end \ No newline at end of file diff --git a/spec/payment_services/rbk/payout_destination_spec.rb b/spec/payment_services/rbk/payout_destination_spec.rb new file mode 100644 index 00000000..da7edb4a --- /dev/null +++ b/spec/payment_services/rbk/payout_destination_spec.rb @@ -0,0 +1,187 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe PaymentServices::Rbk::PayoutDestination, type: :model do + + describe 'associations' do + it { is_expected.to belong_to(:rbk_identity) } + end + + describe '.find_or_create_from_card_details' do + let(:identity) { PaymentServices::Rbk::Identity.create!(rbk_id: 'identity_123') } + let(:tokenized_card) { { 'token' => 'token_123', 'paymentSystem' => 'visa' } } + + it 'returns existing destination if found' do + existing_destination = described_class.create!( + rbk_identity: identity, + rbk_id: 'dest_456', + payment_token: tokenized_card['token'], + public_id: 'existing_123', + card_brand: 'visa', + card_bin: '411111', + card_suffix: '1111', + rbk_status: 'Authorized', + payload: { 'test' => 'data' } + ) + allow(described_class).to receive(:tokenize_card!).and_return(tokenized_card) + + result = described_class.find_or_create_from_card_details( + number: '4111111111111111', + name: 'John Doe', + exp_date: '12/25', + identity: identity + ) + + expect(result).to eq(existing_destination) + end + + context 'when destination does not exist' do + it 'creates new destination' do + allow(described_class).to receive(:tokenize_card!).and_return(tokenized_card) + new_destination = double('NewDestination') + allow(described_class).to receive(:create_destination!).and_return(new_destination) + + expect(described_class).to receive(:create_destination!).with( + identity: identity, + tokenized_card: tokenized_card + ) + + result = described_class.find_or_create_from_card_details( + number: '4111111111111111', + name: 'John Doe', + exp_date: '12/25', + identity: identity + ) + + expect(result).to eq(new_destination) + end + end + end + + describe '.create_destination!' do + let(:identity) { PaymentServices::Rbk::Identity.create!(rbk_id: 'identity_123') } + let(:tokenized_card) do + { + 'token' => 'token_123', + 'paymentSystem' => 'visa', + 'bin' => '411111', + 'lastDigits' => '1111' + } + end + let(:client) { double('PayoutDestinationClient') } + let(:response) { { 'id' => 'dest_123', 'status' => 'authorized' } } + + before do + allow(PaymentServices::Rbk::PayoutDestinationClient).to receive(:new).and_return(client) + allow(client).to receive(:create_destination).and_return(response) + allow(SecureRandom).to receive(:hex).with(10).and_return('abc123def456') + end + + it 'creates destination with correct attributes' do + destination = described_class.create_destination!( + identity: identity, + tokenized_card: tokenized_card + ) + + expect(destination.rbk_identity).to eq(identity) + expect(destination.rbk_id).to eq('dest_123') + expect(destination.public_id).to eq('abc123def456') + expect(destination.card_brand).to eq('visa') + expect(destination.card_bin).to eq('411111') + expect(destination.card_suffix).to eq('1111') + expect(destination.payment_token).to eq('token_123') + expect(destination.rbk_status).to eq('authorized') + expect(destination.payload).to eq(response) + end + + it 'raises error when response has no id' do + allow(client).to receive(:create_destination).and_return({ 'status' => 'error' }) + + expect { + described_class.create_destination!(identity: identity, tokenized_card: tokenized_card) + }.to raise_error(described_class::Error, 'Rbk failed to create destination: {"status"=>"error"}') + end + end + + describe '.tokenize_card!' do + let(:client) { double('PayoutDestinationClient') } + let(:response) { { 'token' => 'token_123', 'paymentSystem' => 'visa' } } + + before do + allow(PaymentServices::Rbk::PayoutDestinationClient).to receive(:new).and_return(client) + allow(client).to receive(:tokenize_card).and_return(response) + end + + it 'returns tokenized card response' do + result = described_class.tokenize_card!( + number: '4111111111111111', + name: 'John Doe', + exp_date: '12/25' + ) + + expect(result).to eq(response) + end + + it 'raises error when tokenization fails' do + allow(client).to receive(:tokenize_card).and_return({ 'error' => 'invalid card' }) + + expect { + described_class.tokenize_card!( + number: '4111111111111111', + name: 'John Doe', + exp_date: '12/25' + ) + }.to raise_error(described_class::Error, 'Rbk tokenization error: {"error"=>"invalid card"}') + end + end + + describe '#authorized?' do + it 'returns true when status is Authorized' do + destination = described_class.new(rbk_status: 'Authorized') + expect(destination.authorized?).to be true + end + + it 'returns false when status is not Authorized' do + destination = described_class.new(rbk_status: 'Pending') + expect(destination.authorized?).to be false + end + end + + describe '#refresh_info!' do + let(:identity) { PaymentServices::Rbk::Identity.create!(rbk_id: 'identity_123') } + let(:destination) do + described_class.create!( + rbk_identity: identity, + rbk_id: 'dest_123', + public_id: 'dest_123', + payment_token: 'token_123', + card_brand: 'visa', + card_bin: '411111', + card_suffix: '1111', + rbk_status: 'Authorized', + payload: { 'test' => 'data' } + ) + end + let(:client) { double('PayoutDestinationClient') } + let(:response) { { 'id' => 'dest_456', 'status' => 'completed' } } + + before do + allow(PaymentServices::Rbk::PayoutDestinationClient).to receive(:new).and_return(client) + allow(client).to receive(:info).with(destination).and_return(response) + end + + it 'updates destination with response data' do + destination.refresh_info! + + expect(destination.rbk_status).to eq('completed') + expect(destination.payload).to eq(response) + end + + it 'does nothing when response has no status' do + allow(client).to receive(:info).and_return({ 'id' => 'dest_123' }) + + expect { destination.refresh_info! }.not_to change(destination, :rbk_status) + end + end +end \ No newline at end of file diff --git a/spec/payment_services/rbk/payout_spec.rb b/spec/payment_services/rbk/payout_spec.rb new file mode 100644 index 00000000..3aa376d3 --- /dev/null +++ b/spec/payment_services/rbk/payout_spec.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe PaymentServices::Rbk::Payout, type: :model do + + describe 'associations' do + it { is_expected.to belong_to(:rbk_payout_destination) } + it { is_expected.to belong_to(:rbk_wallet) } + end + + describe '.create_from!' do + let(:identity) { PaymentServices::Rbk::Identity.create!(rbk_id: 'identity_123') } + let(:destination) do + PaymentServices::Rbk::PayoutDestination.create!( + rbk_identity: identity, + rbk_id: 'dest_456', + public_id: 'dest_123', + payment_token: 'token_123', + card_brand: 'visa', + card_bin: '411111', + card_suffix: '1111', + rbk_status: 'Authorized', + payload: { 'test' => 'data' } + ) + end + let(:wallet) { PaymentServices::Rbk::Wallet.create!(rbk_identity: identity, rbk_id: 'wallet_123') } + let(:payout_client) { double('PayoutClient') } + let(:response) { { 'id' => 'payout_123', 'status' => 'processed' } } + + before do + allow(PaymentServices::Rbk::PayoutClient).to receive(:new).and_return(payout_client) + allow(payout_client).to receive(:make_payout).and_return(response) + end + + it 'creates payout with response data' do + payout = described_class.create_from!( + destination: destination, + wallet: wallet, + amount_cents: 5000 + ) + + expect(payout.rbk_id).to eq('payout_123') + expect(payout.rbk_payout_destination).to eq(destination) + expect(payout.rbk_wallet).to eq(wallet) + expect(payout.amount_cents).to eq(5000) + expect(payout.payload).to eq(response) + expect(payout.rbk_status).to eq('processed') + end + + it 'raises error when response status is missing' do + allow(payout_client).to receive(:make_payout).and_return({ 'id' => 'payout_123' }) + + expect { + described_class.create_from!( + destination: destination, + wallet: wallet, + amount_cents: 5000 + ) + }.to raise_error(described_class::Error, 'Rbk payout error: {"id"=>"payout_123"}') + end + end + + describe '#refresh_info!' do + let(:identity) { PaymentServices::Rbk::Identity.create!(rbk_id: 'identity_123') } + let(:destination) do + PaymentServices::Rbk::PayoutDestination.create!( + rbk_identity: identity, + rbk_id: 'dest_456', + public_id: 'dest_123', + payment_token: 'token_123', + card_brand: 'visa', + card_bin: '411111', + card_suffix: '1111', + rbk_status: 'Authorized', + payload: { 'test' => 'data' } + ) + end + let(:wallet) { PaymentServices::Rbk::Wallet.create!(rbk_identity: identity, rbk_id: 'wallet_123') } + let(:payout) do + described_class.create!( + rbk_id: 'payout_123', + rbk_payout_destination: destination, + rbk_wallet: wallet, + amount_cents: 5000, + rbk_status: 'processed', + payload: { 'test' => 'data' } + ) + end + let(:payout_client) { double('PayoutClient') } + let(:response) { { 'id' => 'payout_123', 'status' => 'completed' } } + + before do + allow(PaymentServices::Rbk::PayoutClient).to receive(:new).and_return(payout_client) + allow(payout_client).to receive(:info).with(payout).and_return(response) + end + + it 'updates payout with response data' do + payout.refresh_info! + + expect(payout.rbk_status).to eq('completed') + expect(payout.payload).to eq(response) + end + + it 'does nothing when response is empty' do + allow(payout_client).to receive(:info).and_return(nil) + + expect { payout.refresh_info! }.not_to change(payout, :rbk_status) + end + + it 'does nothing when response has no status' do + allow(payout_client).to receive(:info).and_return({ 'id' => 'payout_123' }) + + expect { payout.refresh_info! }.not_to change(payout, :rbk_status) + end + end +end \ No newline at end of file diff --git a/spec/payment_services/rbk/wallet_spec.rb b/spec/payment_services/rbk/wallet_spec.rb new file mode 100644 index 00000000..3e14f555 --- /dev/null +++ b/spec/payment_services/rbk/wallet_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe PaymentServices::Rbk::Wallet, type: :model do + describe 'associations' do + it { is_expected.to belong_to(:rbk_identity) } + end + + describe '.create_for_identity' do + let(:identity) { double('Identity', id: 1, rbk_wallets: double('wallets')) } + let(:wallet_client) { double('WalletClient') } + let(:response) { { 'id' => 'wallet_123', 'name' => 'Test Wallet' } } + + before do + allow(PaymentServices::Rbk::WalletClient).to receive(:new).and_return(wallet_client) + allow(wallet_client).to receive(:create_wallet).with(identity: identity).and_return(response) + end + + it 'creates a wallet with response data' do + expect(identity.rbk_wallets).to receive(:create!).with( + rbk_id: 'wallet_123', + payload: response + ) + + described_class.create_for_identity(identity) + end + end +end \ No newline at end of file diff --git a/spec/payment_services/tronscan/transaction_spec.rb b/spec/payment_services/tronscan/transaction_spec.rb new file mode 100644 index 00000000..ba7c6d9f --- /dev/null +++ b/spec/payment_services/tronscan/transaction_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require_relative '../../../lib/payment_services/tronscan/transaction' + +RSpec.describe PaymentServices::Tronscan::Transaction do + describe '.build_from' do + let(:raw_transaction) do + { + id: 'tx_12345', + created_at: Time.new(2023, 1, 1, 12, 0, 0), + source: { + 'amount' => '100.5', + 'currency' => 'TRX', + 'confirmed' => true + } + } + end + + it 'creates transaction from raw data' do + transaction = described_class.build_from(raw_transaction: raw_transaction) + + expect(transaction.id).to eq('tx_12345') + expect(transaction.created_at).to eq(Time.new(2023, 1, 1, 12, 0, 0)) + expect(transaction.source).to eq( + { + amount: '100.5', + currency: 'TRX', + confirmed: true + } + ) + end + + it 'symbolizes keys in source hash' do + transaction = described_class.build_from(raw_transaction: raw_transaction) + expect(transaction.source.keys).to all(be_a(Symbol)) + end + end + + describe '#to_s' do + let(:source) { { id: '123', amount: '100', confirmed: true } } + let(:transaction) { described_class.new(source: source) } + + it 'returns source as string' do + expect(transaction.to_s).to eq(source.to_s) + end + end + + describe '#successful?' do + context 'when source confirmed is true' do + it 'returns true' do + transaction = described_class.new(source: { confirmed: true }) + expect(transaction.successful?).to be true + end + end + + context 'when source confirmed is false' do + it 'returns false' do + transaction = described_class.new(source: { confirmed: false }) + expect(transaction.successful?).to be false + end + end + + context 'when source is empty' do + it 'returns false' do + transaction = described_class.new(source: {}) + expect(transaction.successful?).to be false + end + end + + context 'when source is nil' do + it 'returns false' do + transaction = described_class.new(source: nil) + expect(transaction.successful?).to be false + end + end + end +end \ No newline at end of file diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb new file mode 100644 index 00000000..730615ca --- /dev/null +++ b/spec/rails_helper.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +# Copyright (c) 2018 FINFEX https://github.com/finfex + +require 'spec_helper' + +# Configure ActiveRecord for testing +ENV['RAILS_ENV'] ||= 'test' + +# Load ActiveRecord and establish connection +require 'active_record' +require 'yaml' +require 'database_cleaner-active_record' +require 'active_support/time' +require 'active_record/fixtures' +require 'pathname' + +# Configure time zone +Time.zone = 'UTC' + +# Define Rails.root for fixtures +module Rails + def self.root + @root ||= Pathname.new(File.join(__dir__, '..')) + end +end + +db_config = YAML.load_file(File.join(__dir__, '..', 'config', 'database.yml')) +ActiveRecord::Base.establish_connection(db_config['test']) + +# Create tables needed for tests +require_relative 'support/schema' +require_relative 'support/models' + + +# Configure RSpec for Rails/ActiveRecord +RSpec.configure do |config| + # Clean up database before each test + config.before(:suite) do + DatabaseCleaner.strategy = :transaction + DatabaseCleaner.clean_with(:truncation) + end + + config.around(:each) do |example| + DatabaseCleaner.cleaning do + example.run + end + end +end \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 61f595dd..0f4f42ef 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -6,9 +6,19 @@ Bundler.require(:development, :test) require 'active_support/core_ext/module' require 'virtus' +require 'database_cleaner-active_record' require 'payment_services' +# Load shoulda-matchers +require 'shoulda/matchers' +Shoulda::Matchers.configure do |config| + config.integrate do |with| + with.test_framework :rspec + with.library :active_record + end +end + RSpec.configure do |config| # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = '.rspec_status' diff --git a/spec/support/models.rb b/spec/support/models.rb new file mode 100644 index 00000000..ab6356f7 --- /dev/null +++ b/spec/support/models.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +# Define minimal models needed for testing +class User < ActiveRecord::Base + # Minimal user model for association testing +end \ No newline at end of file diff --git a/spec/support/schema.rb b/spec/support/schema.rb new file mode 100644 index 00000000..a9fa1e5a --- /dev/null +++ b/spec/support/schema.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +# Create database tables needed for testing +ActiveRecord::Schema.define do + # User table for associations + create_table :users, force: true do |t| + t.integer "group_id", default: 1, null: false + t.string "login", limit: 100 + t.string "nickname", limit: 100, default: "", null: false + t.string "password", limit: 60, null: false + t.string "email", null: false + t.string "phone", limit: 100 + t.string "icq", limit: 15, default: "", null: false + t.datetime "regdate", null: false + t.datetime "logdate", null: false + t.date "birthdate", null: false + t.boolean "is_locked", default: false, null: false + t.integer "is_locked2", limit: 1, default: 0, null: false + t.boolean "is_logged_once", default: false, null: false + t.integer "rating", default: 0, null: false + t.integer "points", default: 0, null: false + t.string "last_ip", limit: 15, default: "", null: false + t.string "status", default: "", null: false + t.datetime "status_date" + t.integer "invited_by" + t.datetime "invdate" + t.timestamps + end + + # RBK Money tables + create_table :rbk_money_customers, force: true do |t| + t.string "rbk_id", null: false + t.integer "user_id", null: false + t.json "payload" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.text "access_token", null: false + t.datetime "access_token_expired_at" + t.index ["user_id"], name: "index_rbk_money_customers_on_user_id" + end + + create_table :rbk_payment_cards, force: true do |t| + t.string "rbk_id", null: false + t.string "bin", null: false + t.string "last_digits", null: false + t.string "rbk_customer_id", null: false + t.string "brand", null: false + t.integer "card_type", default: 0, null: false + t.json "payload", null: false + t.index ["rbk_customer_id"], name: "index_rbk_payment_cards_on_rbk_customer_id" + end + + create_table :rbk_identities, force: true do |t| + t.string "rbk_id", null: false + t.json "payload" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "current", default: false, null: false + end + + create_table :rbk_wallets, force: true do |t| + t.string "rbk_id", null: false + t.bigint "rbk_identity_id", null: false + t.json "payload" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "current", default: false, null: false + t.index ["rbk_identity_id"], name: "fk_rails_b1e89ecd83" + end + + create_table :rbk_money_invoices, force: true do |t| + t.integer "amount_in_cents", null: false + t.string "rbk_invoice_id" + t.string "description" + t.string "state" + t.json "payload" + t.bigint "order_public_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.text "access_token" + t.index ["order_public_id"], name: "index_rbk_money_invoices_on_order_public_id", unique: true + end + + create_table :rbk_money_payments, force: true do |t| + t.string "rbk_id", null: false + t.string "state", null: false + t.integer "amount_in_cents", null: false + t.json "payload" + t.json "refund_payload" + t.bigint "order_public_id", null: false + t.bigint "rbk_money_invoice_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["order_public_id"], name: "index_rbk_money_payments_on_order_public_id", unique: true + t.index ["rbk_money_invoice_id"], name: "index_rbk_money_payments_on_rbk_money_invoice_id" + end + + create_table :rbk_payouts, force: true do |t| + t.bigint "rbk_payout_destination_id", null: false + t.bigint "rbk_wallet_id", null: false + t.integer "amount_cents", null: false + t.json "payload" + t.string "rbk_status", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "rbk_id", null: false + t.index ["rbk_payout_destination_id"], name: "fk_rails_eb1172faa0" + t.index ["rbk_wallet_id"], name: "fk_rails_4be66d8283" + end + + create_table :rbk_payout_destinations, force: true do |t| + t.bigint "rbk_identity_id", null: false + t.string "rbk_id", null: false + t.string "public_id", null: false + t.string "card_brand", null: false + t.string "card_bin", null: false + t.string "card_suffix", null: false + t.string "payment_token", null: false + t.string "rbk_status", null: false + t.json "payload", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["rbk_identity_id"], name: "fk_rails_d31b9211b9" + end +end \ No newline at end of file diff --git a/spec/unit/base_client_spec.rb b/spec/unit/base_client_spec.rb new file mode 100644 index 00000000..ebf078eb --- /dev/null +++ b/spec/unit/base_client_spec.rb @@ -0,0 +1,128 @@ +# frozen_string_literal: true + +require 'net/http' +require 'uri' +require 'json' + +# Mock the base client class for testing +class TestBaseClient + TIMEOUT = 30 + + def http_request(url:, method:, body: nil, headers: nil) + uri = URI.parse(url) + https = http(uri) + request = build_request(uri: uri, method: method, body: body, headers: headers) + https.request(request) + end + + def build_request(uri:, method:, body: nil, headers: nil) + request = if method == :POST + Net::HTTP::Post.new(uri.request_uri, headers) + elsif method == :GET + Net::HTTP::Get.new(uri.request_uri, headers) + elsif method == :PATCH + Net::HTTP::Patch.new(uri.request_uri, headers) + else + raise "Запрос #{method} не поддерживается!" + end + request.body = body + request + end + + def http(uri) + Net::HTTP.start(uri.host, uri.port, + use_ssl: true, + verify_mode: OpenSSL::SSL::VERIFY_NONE, + open_timeout: TIMEOUT, + read_timeout: TIMEOUT) + end + + def safely_parse(response) + res = JSON.parse(response.body) + res + rescue JSON::ParserError, TypeError => err + response.body + end +end + +RSpec.describe TestBaseClient do + let(:client) { described_class.new } + + describe '#build_request' do + let(:uri) { URI.parse('https://example.com/api') } + + context 'when method is POST' do + it 'creates Net::HTTP::Post request' do + request = client.build_request(uri: uri, method: :POST, body: 'test data') + + expect(request).to be_a(Net::HTTP::Post) + expect(request.body).to eq('test data') + end + end + + context 'when method is GET' do + it 'creates Net::HTTP::Get request' do + request = client.build_request(uri: uri, method: :GET) + + expect(request).to be_a(Net::HTTP::Get) + end + end + + context 'when method is PATCH' do + it 'creates Net::HTTP::Patch request' do + request = client.build_request(uri: uri, method: :PATCH, body: 'patch data') + + expect(request).to be_a(Net::HTTP::Patch) + expect(request.body).to eq('patch data') + end + end + + context 'when method is unsupported' do + it 'raises error' do + expect { + client.build_request(uri: uri, method: :DELETE) + }.to raise_error('Запрос DELETE не поддерживается!') + end + end + end + + describe '#safely_parse' do + let(:response) { double('Response') } + + context 'when response body is valid JSON' do + before do + allow(response).to receive(:body).and_return('{"status": "success", "data": [1, 2, 3]}') + end + + it 'parses JSON and returns hash' do + result = client.safely_parse(response) + + expect(result).to eq({ 'status' => 'success', 'data' => [1, 2, 3] }) + end + end + + context 'when response body is invalid JSON' do + before do + allow(response).to receive(:body).and_return('invalid json') + end + + it 'returns raw body' do + result = client.safely_parse(response) + + expect(result).to eq('invalid json') + end + end + + context 'when response body is nil' do + before do + allow(response).to receive(:body).and_return(nil) + end + + it 'returns nil body' do + result = client.safely_parse(response) + + expect(result).to be_nil + end + end + end +end \ No newline at end of file diff --git a/spec/unit/exmo_transaction_spec.rb b/spec/unit/exmo_transaction_spec.rb new file mode 100644 index 00000000..646b3de0 --- /dev/null +++ b/spec/unit/exmo_transaction_spec.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +# Minimal test for Exmo Transaction model without loading full application +require 'virtus' + +# Mock the transaction class for testing +class TestExmoTransaction + include Virtus.model + + SUCCESSFULL_PROVIDER_STATE = 'Paid' + FAILED_PROVIDER_STATES = %w(Cancelled Error) + + attribute :id, String + attribute :provider_state, Integer + attribute :source, String + + def self.build_from(raw_transaction:) + new( + id: raw_transaction['extra']['txid'], + provider_state: raw_transaction['status'], + source: raw_transaction + ) + end + + def to_s + source.to_s + end + + def successful? + provider_state == SUCCESSFULL_PROVIDER_STATE + end + + def failed? + FAILED_PROVIDER_STATES.include?(provider_state) + end +end + +RSpec.describe TestExmoTransaction do + describe '.build_from' do + let(:raw_transaction) do + { + 'status' => 'Paid', + 'extra' => { + 'txid' => 'tx_12345' + }, + 'amount' => '100.5', + 'currency' => 'BTC' + } + end + + it 'creates transaction from raw data' do + transaction = described_class.build_from(raw_transaction: raw_transaction) + + expect(transaction.id).to eq('tx_12345') + expect(transaction.provider_state).to eq('Paid') + expect(transaction.source).to eq(raw_transaction) + end + end + + describe '#to_s' do + let(:source) { { 'id' => '123', 'status' => 'Paid' } } + let(:transaction) { described_class.new(source: source) } + + it 'returns source as string' do + expect(transaction.to_s).to eq(source.to_s) + end + end + + describe '#successful?' do + context 'when provider_state is Paid' do + it 'returns true' do + transaction = described_class.new(provider_state: 'Paid') + expect(transaction.successful?).to be true + end + end + + context 'when provider_state is not Paid' do + it 'returns false' do + transaction = described_class.new(provider_state: 'Pending') + expect(transaction.successful?).to be false + end + end + end + + describe '#failed?' do + context 'when provider_state is Cancelled' do + it 'returns true' do + transaction = described_class.new(provider_state: 'Cancelled') + expect(transaction.failed?).to be true + end + end + + context 'when provider_state is Error' do + it 'returns true' do + transaction = described_class.new(provider_state: 'Error') + expect(transaction.failed?).to be true + end + end + + context 'when provider_state is not in failed states' do + it 'returns false' do + transaction = described_class.new(provider_state: 'Paid') + expect(transaction.failed?).to be false + end + end + end +end \ No newline at end of file diff --git a/spec/unit/tronscan_transaction_spec.rb b/spec/unit/tronscan_transaction_spec.rb new file mode 100644 index 00000000..cbe95e5b --- /dev/null +++ b/spec/unit/tronscan_transaction_spec.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +# Minimal test for Tronscan Transaction model without loading full application +require 'virtus' + +# Mock the transaction class for testing +class TestTronscanTransaction + include Virtus.model + + attribute :id, String + attribute :created_at, DateTime + attribute :source, Hash + + def self.build_from(raw_transaction:) + new( + id: raw_transaction[:id], + created_at: raw_transaction[:created_at], + source: raw_transaction[:source].deep_symbolize_keys + ) + end + + def to_s + source.to_s + end + + def successful? + source&.dig(:confirmed) || false + end +end + +# Add deep_symbolize_keys method for hash +class Hash + def deep_symbolize_keys + each_with_object({}) do |(key, value), hash| + hash[key.to_sym] = value.is_a?(Hash) ? value.deep_symbolize_keys : value + end + end +end + +RSpec.describe TestTronscanTransaction do + describe '.build_from' do + let(:raw_transaction) do + { + id: 'tx_12345', + created_at: Time.new(2023, 1, 1, 12, 0, 0), + source: { + 'amount' => '100.5', + 'currency' => 'TRX', + 'confirmed' => true + } + } + end + + it 'creates transaction from raw data' do + transaction = described_class.build_from(raw_transaction: raw_transaction) + + expect(transaction.id).to eq('tx_12345') + expect(transaction.created_at).to eq(Time.new(2023, 1, 1, 12, 0, 0)) + expect(transaction.source).to eq( + { + amount: '100.5', + currency: 'TRX', + confirmed: true + } + ) + end + + it 'symbolizes keys in source hash' do + transaction = described_class.build_from(raw_transaction: raw_transaction) + expect(transaction.source.keys).to all(be_a(Symbol)) + end + end + + describe '#to_s' do + let(:source) { { id: '123', amount: '100', confirmed: true } } + let(:transaction) { described_class.new(source: source) } + + it 'returns source as string' do + expect(transaction.to_s).to eq(source.to_s) + end + end + + describe '#successful?' do + context 'when source confirmed is true' do + it 'returns true' do + transaction = described_class.new(source: { confirmed: true }) + expect(transaction.successful?).to be true + end + end + + context 'when source confirmed is false' do + it 'returns false' do + transaction = described_class.new(source: { confirmed: false }) + expect(transaction.successful?).to be false + end + end + + context 'when source is empty' do + it 'returns false' do + transaction = described_class.new(source: {}) + expect(transaction.successful?).to be false + end + end + + context 'when source is nil' do + it 'returns false' do + transaction = described_class.new(source: nil) + expect(transaction.successful?).to be false + end + end + end +end \ No newline at end of file