{"id":50665486,"url":"https://github.com/fluvpay/fluvpay-ruby","last_synced_at":"2026-06-08T06:02:16.780Z","repository":{"id":363247708,"uuid":"1262534451","full_name":"fluvpay/fluvpay-ruby","owner":"fluvpay","description":"SDK Ruby oficial da FluvPay (v1): pagamentos PIX, saques, transferências internas e verificação de webhooks.","archived":false,"fork":false,"pushed_at":"2026-06-08T04:58:53.000Z","size":0,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-08T05:21:21.490Z","etag":null,"topics":["brazil","fluvpay","payment-gateway","payments","pix","ruby","sdk"],"latest_commit_sha":null,"homepage":"https://docs.fluvpay.com","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fluvpay.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-08T04:40:08.000Z","updated_at":"2026-06-08T04:58:22.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/fluvpay/fluvpay-ruby","commit_stats":null,"previous_names":["fluvpay/fluvpay-ruby"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/fluvpay/fluvpay-ruby","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluvpay%2Ffluvpay-ruby","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluvpay%2Ffluvpay-ruby/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluvpay%2Ffluvpay-ruby/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluvpay%2Ffluvpay-ruby/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fluvpay","download_url":"https://codeload.github.com/fluvpay/fluvpay-ruby/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluvpay%2Ffluvpay-ruby/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34050225,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-08T02:00:07.615Z","response_time":111,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["brazil","fluvpay","payment-gateway","payments","pix","ruby","sdk"],"created_at":"2026-06-08T06:02:16.046Z","updated_at":"2026-06-08T06:02:16.768Z","avatar_url":"https://github.com/fluvpay.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# FluvPay Ruby\n\nSDK oficial da FluvPay para Ruby. Pagamentos PIX, saques, transferências internas e verificação de webhooks, com erros tipados e tratamento idiomático.\n\n- Ruby 3.0+\n- Cliente HTTP sobre a biblioteca padrão (`net/http`), sem dependências de runtime\n- Retries automáticos (apenas em operações seguras), Idempotency-Key gerada sozinha, erros tipados\n\n## Instalação\n\nA gem ainda não está publicada no RubyGems. Por enquanto, instale direto do código-fonte no GitHub. Esse é o método que funciona hoje.\n\n### Via Bundler (recomendado)\n\nAdicione ao seu `Gemfile`, fixando uma versão pela tag:\n\n```ruby\ngem \"fluvpay\", git: \"https://github.com/fluvpay/fluvpay-ruby\", tag: \"v1.0.0\"\n```\n\nE rode:\n\n```bash\nbundle install\n```\n\nFixar a `tag` garante builds reproduzíveis. Se preferir acompanhar o desenvolvimento, troque `tag:` por `branch: \"main\"`, ciente de que a `main` pode mudar a qualquer momento.\n\n### Sem Bundler\n\nPara instalar a gem a partir do código-fonte sem um `Gemfile`:\n\n```bash\ngit clone --branch v1.0.0 https://github.com/fluvpay/fluvpay-ruby.git\ncd fluvpay-ruby\ngem build fluvpay.gemspec\ngem install ./fluvpay-1.0.0.gem\n```\n\n### Pelo RubyGems (em breve, quando publicado no RubyGems)\n\nAssim que a gem for publicada no RubyGems, a instalação passará a ser por um destes caminhos. Eles ainda NÃO funcionam hoje:\n\n```ruby\ngem \"fluvpay\"\n```\n\n```bash\ngem install fluvpay\n```\n\n## Autenticação\n\nUse sua API Key no construtor. O modo (produção ou sandbox) vem do prefixo da chave: `fluv_live_` para produção e `fluv_test_` para o sandbox.\n\n```ruby\nrequire \"fluvpay\"\n\nclient = FluvPay::Client.new(api_key: \"fluv_live_sua_chave_aqui\")\n```\n\nA base URL padrão é `https://api.fluvpay.com/api/v1` e pode ser trocada com `base_url:`.\n\n## Exemplo completo (copiável)\n\n```ruby\nrequire \"fluvpay\"\n\nclient = FluvPay::Client.new(api_key: \"fluv_test_sua_chave_de_teste\")\n\n# 1. Criar uma cobrança PIX.\n#    O valor vai em centavos. A Idempotency-Key é gerada automaticamente.\nbegin\n  charge = client.charges.create(\n    amount_cents: 5000,\n    description: \"Pedido 123\",\n    customer: { name: \"Maria\", email: \"maria@exemplo.com\" },\n    metadata: { pedido_id: \"123\" }\n  )\nrescue FluvPay::ValidationError =\u003e err\n  puts \"Dados inválidos: #{err.code} #{err.message}\"\n  err.details.each { |d| puts \" - #{d['field']} #{d['message']}\" }\n  raise\nend\n\nputs \"Cobrança criada: #{charge['id']} #{charge['status']}\"\nputs \"Copia e cola PIX: #{charge['pix_copy_paste']}\"\n\n# 2. Recuperar pela ID.\nmesma = client.charges.retrieve(charge[\"id\"])\nputs \"Status atual: #{mesma['status']}\"\n\n# 3. Listar cobranças (paginação page/per_page).\npagina = client.charges.list(page: 1, per_page: 20, status: \"paid\")\nputs \"Página #{pagina.page} de #{pagina.total} cobranças, há mais? #{pagina.has_next?}\"\npagina.each { |item| puts \" - #{item['id']} #{item['amount_cents']} #{item['status']}\" }\n\n# 4. Verificar a assinatura de um webhook recebido.\n#    Use o corpo CRU da requisição, nunca o JSON re-serializado.\ndef handle_webhook(raw_body, headers)\n  event = FluvPay::Webhooks.verify_signature(\n    raw_body,\n    headers[\"X-FluvPay-Signature\"],\n    headers[\"X-FluvPay-Timestamp\"],\n    \"whsec_seu_segredo_do_webhook\",\n    event_type: headers[\"X-FluvPay-Event\"],\n    delivery_id: headers[\"X-FluvPay-Delivery-Id\"],\n    tolerance_seconds: 300\n  )\n  puts \"Cobrança paga: #{event.data['id']}\" if event.type == \"charge.paid\"\nend\n```\n\n## Recursos e operações\n\nCharges (cobranças PIX):\n\n```ruby\nclient.charges.create(amount_cents:, idempotency_key: nil, **campos)  # POST /charges/\nclient.charges.retrieve(charge_id)                                    # GET  /charges/{id}\nclient.charges.list(page:, per_page:, sort:, status:)                 # GET  /charges/\n```\n\nTransactions (extrato):\n\n```ruby\nclient.transactions.list(page:, per_page:, sort:)   # GET /transactions/\nclient.transactions.retrieve(tx_id)                 # GET /transactions/{id}\n```\n\nWithdrawals (saques PIX, somente produção):\n\n```ruby\nclient.withdrawals.create(amount_cents:, pix_key:, pix_key_type:, idempotency_key: nil)  # POST /withdrawals/\nclient.withdrawals.list(limit:, offset:, status:)                                        # GET  /withdrawals/\nclient.withdrawals.retrieve(withdrawal_id)                                               # GET  /withdrawals/{id}\n```\n\nInternal Transfers (transferências FluvPay para FluvPay, somente produção):\n\n```ruby\nclient.internal_transfers.create(amount_cents:, recipient_email:, idempotency_key: nil)  # POST /internal-transfers/\nclient.internal_transfers.list(direction:, limit:, offset:)                              # GET  /internal-transfers/\nclient.internal_transfers.retrieve(transfer_id)                                          # GET  /internal-transfers/{id}\n```\n\nSandbox (apenas com chave `fluv_test_`):\n\n```ruby\nclient.sandbox.reset       # POST /test/reset\nclient.sandbox.scenarios   # GET  /test/scenarios\n```\n\n## Criar uma cobrança: campos aceitos\n\nO `charges.create` aceita exatamente os campos do contrato. Não envie `currency` nem `method`: a API rejeita com 422.\n\n| Campo | Tipo | Observação |\n|---|---|---|\n| `amount_cents` | Integer, obrigatório | 100 a 100000 (R$ 1,00 a R$ 1.000,00) |\n| `description` | String | até 500 caracteres |\n| `customer` | Hash | `{ name:, email:, document:, phone: }` |\n| `expires_in_seconds` | Integer | 60 a 604800 |\n| `affiliate_code` | String | 4 a 24 caracteres |\n| `split_rule_id` | String | 20 a 32 caracteres |\n| `pass_fee_to_payer` | Boolean | padrão `true` |\n| `metadata` | Hash | objeto livre |\n\nStatus de uma cobrança: `pending`, `paid`, `expired`, `cancelled`, `refunded`.\n\n## Paginação\n\nSão três envelopes distintos, expostos como objetos de página iteráveis:\n\n- `charges.list` e `transactions.list`: `page`, `per_page`, `total`, `has_next?`, `has_prev?`.\n- `withdrawals.list` e `internal_transfers.list`: `limit`, `offset`, `total`.\n\n```ruby\npage = client.withdrawals.list(limit: 10, offset: 0)\nputs [page.limit, page.offset, page.total].inspect\npage.each { |w| puts \"#{w['id']} #{w['status']} #{w['net_cents']}\" }\n```\n\n## Idempotência\n\nOs POSTs de escrita (`charges.create`, `withdrawals.create`, `internal_transfers.create`) usam o header `Idempotency-Key`. Se você não passar uma, o SDK gera um UUIDv4. Reenviar a mesma chave devolve a resposta original; reutilizar a chave com um payload diferente resulta em `FluvPay::ConflictError` (`IDEMPOTENCY_CONFLICT`).\n\n```ruby\nchave = FluvPay::Client.new_idempotency_key\nclient.charges.create(amount_cents: 5000, idempotency_key: chave)\n```\n\n## Erros\n\nTodos os erros herdam de `FluvPay::Error` e carregam `code`, `message`, `details`, `trace_id` e `status_code`.\n\n| Status | Exceção |\n|---|---|\n| 400 / 422 | `FluvPay::ValidationError` |\n| 401 | `FluvPay::AuthenticationError` |\n| 403 | `FluvPay::PermissionError` |\n| 404 | `FluvPay::NotFoundError` |\n| 409 | `FluvPay::ConflictError` |\n| 429 | `FluvPay::RateLimitError` (campo `retry_after`) |\n| 5xx | `FluvPay::ServerError` |\n| rede / timeout | `FluvPay::ConnectionError` |\n\n```ruby\nbegin\n  client.charges.list\nrescue FluvPay::RateLimitError =\u003e err\n  puts \"Rate limit. Tente de novo em #{err.retry_after} segundos.\"\nend\n```\n\n## Retries\n\nPor padrão o SDK tenta novamente 2 vezes (backoff exponencial com jitter) apenas em operações seguras: GET e POSTs que carregam Idempotency-Key, e somente para 429 e 5xx ou falha de conexão. Em 429 ele respeita o header `Retry-After`.\n\n```ruby\nclient = FluvPay::Client.new(api_key: \"fluv_live_...\", max_retries: 4)   # ajustar\nclient = FluvPay::Client.new(api_key: \"fluv_live_...\", max_retries: 0)   # desligar\n```\n\n## Webhooks\n\nA FluvPay assina cada entrega. O header `X-FluvPay-Signature` traz `v1=\u003chex\u003e`, onde:\n\n```\nhex = HMAC_SHA256(secret, \"{timestamp}.\" + corpo_cru)\n```\n\n`secret` é o `whsec_...` exibido na criação do webhook, `timestamp` vem de `X-FluvPay-Timestamp` e `corpo_cru` é o corpo da requisição exatamente como recebido. Use sempre o corpo cru, nunca o JSON re-serializado.\n\n```ruby\nbegin\n  event = FluvPay::Webhooks.verify_signature(\n    raw_body,\n    request.headers[\"X-FluvPay-Signature\"],\n    request.headers[\"X-FluvPay-Timestamp\"],\n    \"whsec_...\",\n    tolerance_seconds: 300\n  )\nrescue FluvPay::SignatureVerificationError\n  halt 400, \"assinatura inválida\"\nend\n```\n\nEventos disponíveis: `charge.created`, `charge.paid`, `charge.expired`, `charge.cancelled`, `charge.refunded`, `payout.created`, `payout.completed`, `payout.failed`.\n\n## Desenvolvimento\n\n```bash\nbundle install\nrake test\n```\n\nOs testes unitários rodam sem rede (`net/http` mockado com WebMock). O smoke no sandbox roda somente se a env `FLUVPAY_TEST_KEY` (prefixo `fluv_test_`) estiver presente; caso contrário, é pulado.\n\n## Licença\n\nMIT.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffluvpay%2Ffluvpay-ruby","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffluvpay%2Ffluvpay-ruby","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffluvpay%2Ffluvpay-ruby/lists"}