Conversation
Alfredpay moved to a new platform and decommissioned the Penny hosts (production answers 503, the sandboxes no longer resolve). Its adapter keeps the Penny paths but authenticates with a partner API key sent as a bearer token, so the api-key/api-secret pair and ALFREDPAY_API_SECRET go away. The base URL now defaults to the adapter for the environment, like the other providers, instead of the dead dev host.
Alfred derives the company from the API key, and its migration guide
says not to send a business id in the request body. Closing
AlfredpayQuoteMetadata to { customerId } makes the compiler reject one
if it comes back.
GET /alfredpayStatus sent any customer back to onboarding when Alfredpay answered 404 for their submission. During the platform migration a lookup can 404 for data Alfred has not moved yet, and the new platform does not serve US, so a status read would have forced approved customers through KYC again. Unapproved customers still reset.
Alfred's native API serializes amounts in minor units. The quote contract's decimal regex accepts both "500" and "50000", so a unit switch in the adapter would pass the live suite unnoticed. A fixed input must now come back unchanged and the output must move the plausible way against it.
✅ Deploy Preview for vortexfi ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for vrtx-dashboard ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for vortex-sandbox ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
The Penny adapter serves null minQuantity/maxQuantity on most pairs, meaning no limit on that side. The limits indexer passed null to Big, which threw and aborted every refresh, so provider bounds such as the ARS onramp minimum were never applied. A null bound now keeps our configured bound instead of reading as unlimited.
Alfred's Penny adapter answers POST .../onramp with the order fields flat and fiatPaymentInstructions beside them, where Penny nested the order under `transaction`. The mint lifecycle reads order.transaction.transactionId, so every onramp order would have failed right after creation. The client now returns the nested shape for both, in case Alfred restores Penny's format.
The new platform accepts only a CURP with a valid check digit as the Mexican dni and rejects Argentine individuals without a CUIT, both with a bare 110002 Invalid field(s). Our forms invited an INE number and marked CUIT optional, so those users would fail at submission. Validating both in the shared schema turns that into a field error.
The pinned customers belonged to the decommissioned Penny sandbox. The new AR/CO/MX customers were provisioned on api.sandbox.alfredpay.io. The KYC flow now uses a valid CURP, and every placeholder upload gets distinct bytes, because Alfred rejects a document identical to one already on the submission.
The pre-transfer read in ensureLiveProviderOrder had no catch, so a 404, 401, 5xx or timeout became an unrecoverable phase error and the ramp failed with the user's deposit and the subsidy on the ephemeral. The adapter switch makes such reads routine (orders it cannot find, a wrong key during rollout). Nothing has left the ephemeral at that point, so the read is now retried like the other provider reads.
createOnramp now parses the normalized order like createOfframp does, so a response without a transactionId fails inside the financial operation instead of showing the user instructions for an order we cannot track. A 409 limit body with a null maximum (the adapter's "no limit") now maps to the minimum breach. The multipart uploads get the same 30s timeout as the JSON calls.
The sandbox rejects an Argentine CUIT with a wrong check digit, like the CURP one, and accepts separators. The CURP and CUIT checks move to @vortexfi/shared so the API validator can use them too.
Integrators submit KYC without our forms, and the provider answers a bad CURP or a missing or wrong CUIT with an opaque 422 that reaches them as a 500. validateKycSubmission now returns a 400 for both, and the OpenAPI schema and corridor guide document the rules.
One absent or empty bound, an unknown customer type or an empty listing aborted or wiped the whole refresh, and a row without any bound could shadow a real one. Rows now count only with a decimal bound on the scale the quote path reads back.
GET /getKycStatus reset any customer to onboarding when the provider returned no submission, the case /alfredpayStatus already guards.
A fixed-input quote echoes its input. If the adapter ever switched to minor units like Alfred's native API, every onramp figure would be off by that factor; the offramp side already checks this.
The live half creates customers, accounts and orders, and the base URL now defaults to production outside sandbox deployments. Order tests use USDT like production, and the onramp test pins the paymentType partners receive.
Alfred's adapter reports UPDATE_REQUIRED for migrated customers its own API lists as ACTIVE. The status routes stored it, which demoted approved customers to started, and /retryKyc only reopens FAILED, so they could neither ramp nor re-verify. The provider-customer view, which every status write goes through, now ignores it for an approved customer, and /getKycStatus reports the stored status instead of the raw mapping.
Registration passed the caller's fiatAccountId straight to createOfframp, so only the provider checked that the account belongs to the customer, and nothing verified Alfred's new platform still does. Payout accounts also did not survive the migration, so a saved id now fails with a generic provider error. The preflight now requires the id in the customer's account list and answers 400 before an order exists. The nightly asserts the listing is scoped per customer.
Alfred's new platform requires KYB fields our form and API do not collect (business type, operating address, signer details and more), so every MX/CO business submission fails at the provider. One predicate marks the pause for discovery, the KYC machine and the API, and discovery stops advertising those flows until the KYB rework.
Business users in the widget and dashboard filled the whole KYB form and then failed at submission. The machine now fails a paused MX/CO business flow up front with a clear message, both for business input and for an individual who switches to business. The KYB tests lift the pause so the form flow stays covered for its rework.
A partner that skips discovery would otherwise create a business customer whose KYB submission can only fail at the provider. The check runs after the duplicate-customer check so an existing customer still gets its specific answer. The managed delegation test moves to the US corridor, which still onboards businesses.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Alfredpay moved to a new platform. The legacy Penny hosts are gone (the production host answers an nginx 503 on every path, the dev/staging sandboxes no longer resolve), so no Alfredpay call can succeed against them. Alfred serves the same Penny routes through an adapter on the new platform that authenticates with a partner API key.
What
AlfredpayApiService):Authorization: Bearer alfk_…on every JSON request and multipart upload; theapi-key/api-secretpair andALFREDPAY_API_SECRETare gone.ALFREDPAY_BASE_URLnow defaults to the adapter for the environment:https://api.sandbox.alfredpay.io/adapters/pennywhenSANDBOX_ENABLED, elsehttps://api.alfredpay.io/adapters/penny. The request paths stay the same.metadatano longer carriesbusinessId. Alfred derives the company from the API key, and the migration guide says not to send one.AlfredpayQuoteMetadatais now closed to{ customerId }, so it can't sneak back in.GET /alfredpayStatus: an upstream 404 no longer demotes an approved customer back into onboarding. During the migration a lookup can 404 for data Alfred hasn't moved yet, and the new platform doesn't serve US. Customers who aren't approved still reset, as before."500"from"50000".allConfigsreturnsnullminQuantity/maxQuantityon most pairs, meaning no limit on that side. The limits indexer turned that intonew Big(null), which threw and aborted every refresh. The schema now acceptsnull, and anullbound keeps our configured bound instead of reading as unlimited.fiatPaymentInstructionsbeside it, where Penny nested it undertransaction. The mint lifecycle readsorder.transaction.transactionId, so every onramp would have failed after creation. The client now returns the nested shape for both.dni(it rejected an INE number and a CURP with a wrong digit, and accepted a correct CURP that didn't match the customer's name), and it requires a CUIT for Argentine individuals. The shared KYC schema now validates both, so users get a field error instead of a failed submission.createOnrampvalidates the normalized order, so a response without atransactionIdfails inside the financial operation instead of showing payment instructions for an order we can't track. A 409 limit body with anullmaximum maps to the minimum breach. Uploads get the 30s timeout the JSON calls have.POST /v1/alfredpay/kyc(partners submit without our forms) now returns a 400 for an invalid CURP or a missing/invalid CUIT instead of the provider's opaque 422 → 500. The CUIT check digit is validated too (the sandbox enforces it). OpenAPI and the corridor guide document both.GET /getKycStatusno longer resets an approved customer when the provider reports no submission, matching/alfredpayStatus.fromAmountdoesn't echo the requested input (unit-switch guard, as the offramp side already has).ALFREDPAY_BASE_URLpoints at the sandbox, since the default is now production. Order tests use USDT like production.UPDATE_REQUIRED. The adapter reports it for customers Alfred lists as active, and/retryKyconly reopensFAILED, so they would have been locked out. Unapproved customers still move.fiatAccountIdto be in the customer's account list and answers 400 otherwise, before an order exists. Bank accounts did not survive the migration, and ownership was otherwise checked only by the provider. Verified on the sandbox that the listing is per customer; the nightly now asserts it.createBusinessCustomeranswers 503 for MX/CO, and onboarding discovery returns 404 for those combinations. The KYB flow tests lift the pause so the form stays covered; the dashboard's full KYB e2e is skipped until the rework.Schema check (what could be verified without credentials)
POST …/kyc/{submissionId}/retry, which only the US individual retry path uses.alfredpay.readme.io): the fields and enums we send match. That covers onramp/offramp/quote fields, fiat accounttype(SPEI,COELSA,ACH,BANK_USA) andaccountType(CLABE,CBU/CVU/ALIAS,CORRIENTE/AHORRO,CHECKING/SAVING). Their docs omit a few fields Penny accepted in production (KYB questionnaire, KYCphoneNumber, MX document types). The live KYC/KYB flows are the check for those.schemas.tsare the ones the nightly already validated against Penny's sandbox.Live verification (2026-09-30)
COMPLETEDthrough the real KYC calls. Uploads, submission and status responses all match our schemas.502 "Account validation service unavailable"and, as of 2026-10-01 16:29 UTC,502 112003 "Service temporarily unavailable", so offramps are not verified yet. An hourly probe re-runs the live suite when it recovers.businessActivities/walletAddressesmust be arrays, and 17 fields/documents our form doesn't collect are now required (business type, description, phone and incorporation date, an operating address, representative title/signer/control flags, terms acceptance, proof of business activity). New business onboarding needs a form rework, which is out of scope here. Existing business customers aren't migrated yet anyway.Before merging
UPDATE_REQUIRED: the adapter reports it for all five migrated customers that/v1/customerslists asACTIVE, andCOMPLETEDfor the one listed asNOT_STARTED. Our sync would downgrade them. Waiting on Alfred.e6b69e2c…,6f1032c5…,7b3d0856…);CONTRACT_ALFREDPAY_API_SECRETdeleted.UPDATE_REQUIREDguard, MX/CO business pause, payout-account check (see Migration guards).Deploy
alfk_key inALFREDPAY_API_KEYthere, removeALFREDPAY_API_SECRETand anyALFREDPAY_BASE_URLpointing at the legacy host. On the staging service, overrideALFREDPAY_API_KEY(sandbox key) andALFREDPAY_BASE_URL=https://api.sandbox.alfredpay.io/adapters/pennyunless staging runs withSANDBOX_ENABLED.USDinDISABLED_FIAT_CURRENCIES(US unsupported on the new platform). TakeMXN/COP/ARSout only after a read-only production smoke test.Tests
alfredpayApiService.test.ts: JSON requests and all three uploads send the bearer header and no legacy headers (fails on the old client).alfredpay-status-not-found.integration.test.ts: approved customer survives an upstream 404 (fails without the guard); unapproved customer still resets.mxn-offramp.scenario.test.ts: a 404/503/network error reading the order before the transfer is retried and the ramp completes with one submission (fails on the old executor).alfredpay-limits.service.test.ts: per-row robustness, empty listing keeps the cache, wrong-scale rows skipped (4 fail on the old indexer).identifiers.test.ts,schemas.test.ts,validators.test.ts: CURP/CUIT vectors at the form and the API boundary.alfredpayApiService.test.ts: malformed onramp orders rejected, 409 withnullmaximum.alfredpay-status-not-found.integration.test.ts:UPDATE_REQUIREDkeeps an approved customer approved on both status routes (fails without the guard).alfredpay-offramp.registration.test.ts: an unlisted account is rejected beforecreateOfframp; a failed listing aborts the preflight.machine.test.ts,alfredpay-business-kyb-paused.integration.test.ts,onboarding-requirements.route.test.ts, dashboardonboarding-alfredpay-mxn-kyb.spec.ts: the MX/CO business pause (unit and API tests fail without it).bun typecheck,bun lint:fix,wire-contract:check,docs:api:check, live sandbox suite 18/0 plus the new scoping check.