top of page

Paya ACH integration plan for people.givehub.com
Goal: add donor-facing ACH payments to all GiveHub donation forms using Paya Checks by Web (SEC code WEB).

========================================
1. PRODUCT GOAL
========================================

In all donation forms:
- General Donation Form
- Custom Donation Forms created by orgs

the donor should eventually be able to choose:
- Credit / Debit Card
- ACH
- DAFpay (later)

Today’s focus is ACH through Paya.

This is a donor-facing internet flow, so the correct Paya product is:
- Checks by Web
- SEC code WEB

========================================
2. WHAT THE PAYA DOCS / TEMPLATE / XSD CONFIRMED
========================================

Use this flow:

Certification:
1. GetCertificationTerminalSettings
2. Build XML packet from the assigned WEB template/XSD
3. Submit via ProcessSingleCertificationCheck

Production:
1. GetTerminalSettings
2. Build XML packet from the assigned WEB template/XSD
3. Submit via ProcessSingleCheck

The packet is not JSON. It is XML.
That XML packet must match the terminal’s assigned schema.
That XML packet is then sent as a string inside the SOAP request.

The WEB 2310 template/XSD confirms this packet shape:

<AUTH_GATEWAY REQUEST_ID="">
  <TRANSACTION>
    <TRANSACTION_ID/>
    <TRANSACTION_AUTH_NUMBER/>
    <MERCHANT>
      <TERMINAL_ID/>
    </MERCHANT>
    <PACKET>
      <IDENTIFIER/>
      <ACCOUNT>
        <ROUTING_NUMBER/>
        <ACCOUNT_NUMBER/>
        <ACCOUNT_TYPE/>
      </ACCOUNT>
      <CONSUMER>
        <FIRST_NAME/>
        <LAST_NAME/>
        <ADDRESS1/>
        <ADDRESS2/>
        <CITY/>
        <STATE/>
        <ZIP/>
        <PHONE_NUMBER/>
        <DL_STATE/>
        <DL_NUMBER/>
        <COURTESY_CARD_ID/>
      </CONSUMER>
      <CHECK>
        <CHECK_AMOUNT/>
      </CHECK>
      <CUSTOM>
        <CUSTOM1/>
        <CUSTOM2/>
        <CUSTOM3/>
        <CUSTOM4/>
        <CUSTOM5/>
      </CUSTOM>
      <ECODE_OPTIONS/>
    </PACKET>
  </TRANSACTION>
</AUTH_GATEWAY>

Important schema notes confirmed:
- REQUEST_ID is optional
- TERMINAL_ID is required
- IDENTIFIER is required
- ACCOUNT is required
- CONSUMER is required
- CHECK is required
- CHECK_AMOUNT is required
- Account can be sent as either:
  - ROUTING_NUMBER + ACCOUNT_NUMBER + ACCOUNT_TYPE
  - OR TOKEN
- For initial GiveHub implementation, use routing/account/account_type
- DL fields are optional for terminal 2310
- ADDRESS2 optional
- STATE optional
- ZIP optional
- PHONE optional
- CUSTOM section is optional but useful for GiveHub metadata
- ACCOUNT_TYPE should be normalized to the exact enum expected by Paya
  likely CHECKING or SAVINGS
- CHECK_AMOUNT should always be formatted as decimal with 2 places

========================================
3. RECOMMENDED GIVEHUB ARCHITECTURE
========================================

Do NOT hardcode this directly into the existing donation form handler.

Create a separate ACH gateway provider, similar to how card gateways are separated.

Suggested new module:
- backend/gateways/paya_ach.ts

Suggested functions:
- getPayaCertificationTerminalSettings(config)
- getPayaProductionTerminalSettings(config)
- buildPayaWebAchXmlPacket(input, terminalSettings)
- sendPayaSoapRequest(config, methodName, xmlPacket)
- processPayaCertificationCheck(config, xmlPacket)
- processPayaSingleCheck(config, xmlPacket)
- parsePayaXmlResponse(responseXml)
- maskBankAccount(accountNumber)
- maskRoutingNumber(routingNumber)

Suggested normalized payment result object:
- success: boolean
- status: approved | declined | error | pending
- gateway: "paya"
- payment_method_type: "ach"
- transaction_id
- gateway_reference
- raw_response_xml
- message
- error_code
- settlement_status

========================================
4. DONATION FORM FRONTEND CHANGES
========================================

Add payment method selector to every donation form:

Payment Method
- Credit / Debit Card
- ACH
- DAFpay (coming later, hidden or disabled for now)

When ACH is selected, show:

Required:
- First Name
- Last Name
- Routing Number
- Account Number
- Account Type
- Address1
- City
- Donation Amount

Recommended / should collect:
- Address2
- State
- Zip
- Phone

For better donation operations, also keep:
- Email
- Fund / campaign selection
- Recurring frequency if donation is recurring
- Cover fees toggle if GiveHub supports that separately

UI suggestions:
- Label ACH as “Bank Account (ACH)”
- Add short note:
  “Bank payments may take several business days to process.”
- Use masked input formatting only in UI; strip formatting on backend
- Validate routing number length and digits before submit
- Validate account number presence and reasonable length
- Validate account type explicitly
- Require authorization checkbox before submit

========================================
5. REQUIRED ACH AUTHORIZATION / COMPLIANCE
========================================

For WEB ACH, show authorization text before submission and require donor consent.

Suggested text:

By providing your bank account information and submitting this donation, you authorize GiveHub and the organization receiving this donation to debit your bank account via ACH for the amount specified. You understand this authorization applies to the selected donation, and that bank processing may take several business days. For recurring donations, you authorize future ACH debits according to the selected schedule until cancelled.

Add a required checkbox:
- “I authorize this ACH debit.”

Store the following with every ACH donation:
- authorization_text_version
- authorization_text_rendered
- authorization_accepted = true
- authorization_timestamp
- donor_ip
- page_url
- user_agent
- donor_name
- amount
- recurring_frequency if applicable
- account_last4
- routing_last4
- masked_account
- masked_routing

Do NOT store or log full account/routing in plain logs after processing.
Limit full PAN-equivalent bank data exposure as much as possible.

========================================
6. FIELD MAPPING: GIVEHUB -> PAYA XML
========================================

Map GiveHub fields to XML like this:

GiveHub donation/payment data -> Paya XML

- donation.public_id or generated uuid -> REQUEST_ID
- donation.public_id or generated uuid -> TRANSACTION_ID
- optional blank -> TRANSACTION_AUTH_NUMBER
- org gateway config terminal id -> TERMINAL_ID
- "WEB" or terminal-specific identifier if required -> IDENTIFIER

ACCOUNT:
- bank.routing_number -> ROUTING_NUMBER
- bank.account_number -> ACCOUNT_NUMBER
- normalized account type -> ACCOUNT_TYPE

CONSUMER:
- donor.first_name -> FIRST_NAME
- donor.last_name -> LAST_NAME
- donor.address1 -> ADDRESS1
- donor.address2 -> ADDRESS2
- donor.city -> CITY
- donor.state -> STATE
- donor.zip -> ZIP
- donor.phone -> PHONE_NUMBER
- blank -> DL_STATE
- blank -> DL_NUMBER
- blank -> COURTESY_CARD_ID

CHECK:
- donation.amount -> CHECK_AMOUNT

CUSTOM:
Use these to help reconciliation:
- CUSTOM1 -> donation_id
- CUSTOM2 -> organization_id
- CUSTOM3 -> fund_id or campaign_id
- CUSTOM4 -> donor_id
- CUSTOM5 -> recurring_plan_id or form_id

========================================
7. SAMPLE XML PACKET
========================================

Example:

<AUTH_GATEWAY REQUEST_ID="don_12345">
  <TRANSACTION>
    <TRANSACTION_ID>don_12345</TRANSACTION_ID>
    <MERCHANT>
      <TERMINAL_ID>2310</TERMINAL_ID>
    </MERCHANT>
    <PACKET>
      <IDENTIFIER>WEB</IDENTIFIER>
      <ACCOUNT>
        <ROUTING_NUMBER>021000021</ROUTING_NUMBER>
        <ACCOUNT_NUMBER>123456789</ACCOUNT_NUMBER>
        <ACCOUNT_TYPE>CHECKING</ACCOUNT_TYPE>
      </ACCOUNT>
      <CONSUMER>
        <FIRST_NAME>Kyle</FIRST_NAME>
        <LAST_NAME>Salone</LAST_NAME>
        <ADDRESS1>123 Main St</ADDRESS1>
        <ADDRESS2></ADDRESS2>
        <CITY>Acworth</CITY>
        <STATE>GA</STATE>
        <ZIP>30101</ZIP>
        <PHONE_NUMBER>7706818170</PHONE_NUMBER>
        <DL_STATE></DL_STATE>
        <DL_NUMBER></DL_NUMBER>
        <COURTESY_CARD_ID></COURTESY_CARD_ID>
      </CONSUMER>
      <CHECK>
        <CHECK_AMOUNT>50.00</CHECK_AMOUNT>
      </CHECK>
      <CUSTOM>
        <CUSTOM1>don_12345</CUSTOM1>
        <CUSTOM2>org_789</CUSTOM2>
        <CUSTOM3>fund_building</CUSTOM3>
        <CUSTOM4>donor_456</CUSTOM4>
        <CUSTOM5>form_general</CUSTOM5>
      </CUSTOM>
    </PACKET>
  </TRANSACTION>
</AUTH_GATEWAY>

========================================
8. SOAP REQUEST HANDLING
========================================

Endpoint:
- https://demo.eftchecks.com/webservices/AuthGateway.asmx for demo/certification
Use production endpoint when Paya provides live credentials / live terminal setup.

Important:
- The XML packet is sent as a string inside the SOAP method request
- Do not send custom JSON to Paya
- Build packet first, then send it in the SOAP body

Engineering requirements:
- Escape XML correctly when embedding packet into SOAP
- Handle XML parsing robustly for both success and error responses
- Store masked request metadata for audit
- Optionally store raw XML response securely for support/debugging
- Never write full routing/account values into standard logs

========================================
9. TERMINAL SETTINGS STRATEGY
========================================

Do not hardcode terminal assumptions forever.

Implement terminal settings retrieval + caching.

Recommended:
- store terminal settings per org gateway config
- refresh on config update or on manual admin action
- cache response short term in app memory and long term in DB if useful

At minimum store:
- terminal id
- schema/template path if returned
- environment (certification vs production)
- retrieved_at timestamp

========================================
10. CERTIFICATION PLAN
========================================

Build and test in this order:

Phase 1:
- Implement GetCertificationTerminalSettings
- Implement buildPayaWebAchXmlPacket
- Implement ProcessSingleCertificationCheck
- Run Paya certification routing number scenarios
- Confirm approved / declined / error parsing

Phase 2:
- Add org admin ACH gateway settings UI if not already present
- Add secure credential storage
- Add internal test donation form support

Phase 3:
- After certification approval, switch to:
  - GetTerminalSettings
  - ProcessSingleCheck

========================================
11. DATABASE / PAYMENT MODEL RECOMMENDATION
========================================

Add or confirm fields in donation / payment tables for ACH:

- payment_method_type = 'ach'
- gateway = 'paya'
- gateway_environment
- gateway_transaction_id
- gateway_terminal_id
- payment_status
- settlement_status
- return_status
- return_code
- return_reason
- account_last4
- routing_last4
- authorization_timestamp
- authorization_text_version
- donor_ip
- raw_gateway_response_secure_ref

Important:
ACH is not the same as a card authorization.
Even after initial acceptance, settlement and returns may happen later.
So payment state model should support:
- submitted
- accepted
- pending_settlement
- settled
- returned
- failed
- voided / reversed if applicable

========================================
12. THE 2 BIG ARCHITECTURE DECISIONS THAT MATTER
========================================

ARCHITECTURE DECISION #1:
Treat ACH as asynchronous money movement, not instant-final success.

This is the most common mistake teams make.

Why this matters:
- A card auth can feel instant
- ACH can be accepted initially and still fail later due to:
  - NSF
  - invalid account
  - unauthorized debit
  - account closed
  - stop payment
  - other ACH return reasons

Therefore:
- Do not model ACH as permanently successful just because initial gateway response was positive
- Create a payment lifecycle that allows later return/reversal handling
- Donation receipts and admin screens should support statuses like:
  - Submitted
  - Processing
  - Settled
  - Returned
- Finance/reconciliation screens must be able to filter ACH separately from cards

Recommendation:
Initial donor-facing result can say:
- “Your bank donation has been submitted successfully.”
not necessarily:
- “Your payment has fully cleared.”

This will save GiveHub major headaches with reporting, refunds, and support.

ARCHITECTURE DECISION #2:
Separate bank account collection from reusable bank account storage/tokenization.

For phase 1:
- Process one-time ACH using direct routing/account entry
- Do not overbuild token vaulting immediately unless required by Paya flow or recurring design

Why this matters:
Recurring ACH and saved bank accounts create more complexity:
- tokenization
- stored payment method management
- revocation / cancellation workflows
- editing bank accounts
- retry rules
- stronger compliance controls

Recommendation:
Phase 1:
- one-time ACH donations
- optional recurring only if Paya + internal lifecycle is clearly supported

Safer rollout path:
1. one-time ACH first
2. returns/reconciliation/statuses second
3. recurring ACH third
4. saved / reusable bank account tokens later

This staged approach reduces support issues and lets GiveHub ship faster without painting itself into a corner.

========================================
13. RECURRING ACH RECOMMENDATION
========================================

Do not launch recurring ACH on day 1 unless the following are clearly designed:
- recurring authorization language
- stored payment method or token strategy
- cancellation / revocation workflow
- retry rules for failed drafts
- donor portal edit/cancel behavior
- org admin reporting for failed recurring ACH

Recommendation:
- Launch one-time ACH first
- Keep recurring ACH behind a feature flag until lifecycle is complete

========================================
14. RETURNS / NSF / RECONCILIATION DESIGN
========================================

Even if not fully implemented on day 1, build database support now.

Need eventual support for:
- ACH returns
- NSF
- unauthorized return
- account closed
- invalid account
- stop payment

Admin UI should eventually show:
- original donation amount
- initial acceptance date
- settlement status
- return code / reason
- net amount impact

Do not assume refunds work the same as cards.
ACH operations must be treated separately in finance logic.

========================================
15. SECURITY / LOGGING RULES
========================================

Required engineering rules:
- strip spaces/dashes before sending routing/account
- validate routing digits server-side
- mask routing/account in logs
- never store unmasked bank details in standard logs
- encrypt any sensitive persisted banking data if temporarily stored
- prefer immediate use + discard wherever possible
- add idempotency key to prevent accidental double submission
- do not auto-retry financial debit requests blindly
- timeouts must not create duplicate charges

Recommended idempotency approach:
- create internal payment attempt record before gateway call
- attach donation id + form submission id + amount hash
- if duplicate request appears, return existing result instead of resubmitting

========================================
16. ADMIN / ORG SETTINGS
========================================

Each org should have separate ACH gateway settings.

Suggested org gateway config:
- ach_enabled
- paya_environment
- paya_username / auth values as required
- paya_password / auth values as required
- paya_terminal_id
- certification_complete
- supports_one_time_ach
- supports_recurring_ach
- ach_receipt_messaging override optional

If possible, keep ACH and card gateway settings separate even if same org uses both.

========================================
17. DONOR RECEIPT / UI MESSAGING
========================================

For ACH success message:
- “Your bank donation has been submitted successfully.”
- “ACH payments may take several business days to process.”

For donor receipt email:
- show masked account only if needed
- mention bank processing timeline
- include org, amount, date, donation reference
- avoid implying final settlement if not yet settled

========================================
18. DAFPAY FUTURE-PROOFING
========================================

Design payment selection as a pluggable payment method strategy:

- card
- ach
- dafpay

Recommended architecture:
- donation intent / checkout data created first
- payment execution delegated by provider
- common normalized payment result returned to donation flow

That way DAFpay can be added later without rewriting the checkout architecture.

========================================
19. SUGGESTED IMPLEMENTATION PHASES
========================================

Phase A:
- Add ACH payment option UI
- Add ACH authorization UI
- Add backend Paya ACH gateway module
- Build certification XML packet
- Submit via certification endpoint
- Parse response
- Save ACH donation attempt and status

Phase B:
- Internal admin testing
- Validate org-specific credentials/settings
- Certification completion with Paya

Phase C:
- Enable one-time ACH for selected orgs
- Add donor receipts and admin reporting
- Monitor gateway responses

Phase D:
- Add ACH settlement / return lifecycle handling
- Expand finance reporting

Phase E:
- Add recurring ACH under feature flag if desired

========================================
20. WHAT TO BUILD RIGHT NOW
========================================

Build now:
- one-time ACH
- Paya certification flow
- XML packet builder
- SOAP request sender
- response parser
- authorization capture
- masked storage + audit trail
- ACH-specific payment statuses
- org-specific ACH gateway settings

Do not overbuild yet:
- saved bank accounts
- tokenized reusable bank vault
- recurring ACH
- refund automation
- donor self-service bank account management

========================================
21. SUCCESS CRITERIA
========================================

A successful first release means:
- donor can choose ACH on donation form
- donor can enter bank info and authorize debit
- backend builds valid WEB XML packet
- certification test cases pass
- org-specific gateway config works
- donation record shows ACH status separately from cards
- donor gets correct messaging
- masked audit/compliance data is stored
- architecture leaves room for DAFpay later

Givehub.com

GiveHub.com
Acworth, GA 30101

United States
Email: sales@givehub.com
Phone: 866-933-7048

 

MISSION STATEMENT

 

To offer a robust and a superior product suite to help non-profits and churches increase giving and operate more effectively and efficiently with cutting edge technology. 

 

Yours in Christ, 
GiveHub.com Team

  • Facebook Social Icon
  • LinkedIn Social Icon
  • Instagram Social Icon

© 2026 GiveHub.com - All rights reserved - Support - Book a Demo

bottom of page