hackclub / hackclub/theseus

billing: pass idempotency_key to HCB disbursements once HCB + hcbv4 support it

Open
#295 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Ruby
Stars
24
Forks
18
Avg merge
7h 11m
Merged PRs (30d)
7

Description

## context

The billing ledger rework (branch `ui2-fr`) creates an `HCB::Transfer` row with a unique `idempotency_key` *before* every disbursement call, and embeds it in the disbursement name and custom memo. But HCB's v4 `POST /organizations/:id/transfers` currently has no idempotency support, so a timeout / 5xx after HCB committed the disbursement leaves the transfer in `unknown` and `Billing::Reconciler` has to prove the outcome by matching amount + direction + counterparty against the HQ org's transactions.

That reconciler has one known gap: a human manually sending HQ the identical amount from the same org during the unknown window could be mis-matched. An idempotency key on HCB's side closes it.

## blocked on

- [ ] HCB PR: `idempotency_key` on `POST /organizations/:id/transfers` (unique per source org; replay returns the existing disbursement)
- [ ] hcbv4 PR: `create_disbursement(..., idempotency_key:)`

## when those land

- [ ] bump `hcbv4` in Gemfile
- [ ] `Billing::Executor`: pass `idempotency_key: transfer.idempotency_key` on both debit (`BillingProfile#create_disbursement!`) and credit (`BillingProfile.refund_to_organization!`) paths
- [ ] `Billing::Executor`: an `unknown` transfer becomes safely retryable — reclassify timeouts / 5xx as `failed` with backoff instead of `unknown` **only when** the key was sent
- [ ] keep `Billing::Reconciler` for the "HCB succeeded, we crashed before writing `remote_id`" case and for the historical backfill; it just stops being load-bearing
- [ ] update `spec/services/billing/executor_spec.rb` classification cases

Contributor guide

Open the contributing guide

Research direction

Start with the blocked HCB and hcbv4 prerequisites, then read Gemfile, Billing::Executor, BillingProfile#create_disbursement!, and BillingProfile.refund_to_organization!. Run spec/services/billing/executor_spec.rb, focusing on classification cases. Done means the dependency is bumped, both paths pass the idempotency key, retry classification is covered, and reconciler behavior remains covered.

Written by the indexing model from the issue text.

Assessment

Tech stack
ruby
Domain
backend, payments
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.