Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BYOC Example Adapter

A minimal Gr4vy Build-Your-Own-Connector adapter built with FastAPI. It implements every BYOC endpoint and uses a penny simulator so you can exercise every response path — success, decline, service error, pending / redirect — without connecting to a real payment provider.

Replace the simulator functions in app/simulator.py with calls to your actual provider API to turn this into a production connector.


File overview

app/
├── config.py      # Environment variables (SECRET_KEY, HOST, PORT)
├── auth.py        # HMAC-SHA256 signature verification (Gr4vy signs every request)
├── schemas.py     # Pydantic request models — one per BYOC endpoint
├── responses.py   # Pydantic response models — mirrors gr4vy/connector/custom/schemas.py
├── simulator.py   # Penny simulator — replace these functions with real provider logic
└── main.py        # FastAPI routes — wire requests to simulator / provider

Quick start

# 1. Install
pip install -e .

# 2. Set the shared secret (must match what you enter in the Gr4vy dashboard)
export SECRET_KEY=my-very-secret-key

# 3. Run
uvicorn app.main:app --reload --port 8000
# or:
serve

The adapter listens on http://0.0.0.0:8000 by default. You can override HOST and PORT with environment variables.

To expose a local server to Gr4vy's sandbox, use a tunnelling tool such as ngrok:

ngrok http 8000
# Copy the HTTPS URL, e.g. https://abc123.ngrok.io

Then configure the connection in the Gr4vy dashboard with:

  • URL: https://abc123.ngrok.io
  • Secret Key: my-very-secret-key

Gr4vy calls GET /verify-credentials. If it returns 200, the connector is active.


Penny simulator

The last two digits of amount control the response for all amount-based endpoints:

Penny Endpoint Outcome
01 all Decline: INSUFFICIENT_FUNDS / REFUND_DECLINED
02 all Decline: DO_NOT_HONOR
03 all Service error: SERVICE_ERROR (HTTP 502)
00 create-transaction Pending — buyer redirected to /simulator/approve
52 create-refund Pending — async settlement (refund delayed)
09 create-transaction Immediate capture regardless of intent
other all Succeed per intent (authorize or capture)

Storing a token alongside a transaction (store=true)

Pass "store": true in the POST /create-transaction request. When the transaction succeeds the response includes an embedded payment_method with a token, causing Gr4vy to emit both a TransactionAuthorizationSucceeded event and a TokenSucceeded event.

Testing the redirect flow (penny 00)

  1. Create a transaction with amount ending in 51 (e.g. 1051).
  2. The response contains status: "pending" and payment_method.approval_url pointing to GET /simulator/approve?redirect_url=...&xid=....
  3. /simulator/approve immediately redirects the buyer back to redirect_url.
  4. Gr4vy receives the callback and calls POST /approve-transaction.
  5. The transaction resolves to authorized or captured.

Token decline scenarios

Token endpoints (approve-token, delete-token) have no amount field. The simulator reads the test penny from the payment_service_transaction_id (xid), which was generated by POST /create-token.

To trigger a specific outcome, pass "external_identifier" in the POST /create-token request body:

{ "external_identifier": "01" }

The penny is encoded in the xid (format sim_{penny:02d}_{hex16}). When Gr4vy later calls approve-token or delete-token the adapter reads it back.

Penny approve-token delete-token
01 CANCELLED_BUYER_APPROVAL SERVICE_ERROR
02 DO_NOT_HONOR DO_NOT_HONOR
03 SERVICE_ERROR SERVICE_ERROR
other Succeed Succeed

Replacing the simulator with real provider logic

The adapter is deliberately structured so that the only file you need to touch is app/simulator.py. Each function maps one-to-one to a BYOC endpoint and returns a typed Pydantic model from app/responses.py.

Step-by-step

1. Set your provider credentials

Add them to app/config.py (or as environment variables):

# app/config.py
import os

SECRET_KEY: str = os.environ["SECRET_KEY"]
PROVIDER_API_KEY: str = os.environ["PROVIDER_API_KEY"]
PROVIDER_BASE_URL: str = os.environ.get("PROVIDER_BASE_URL", "https://api.myprovider.com")

2. Replace each simulator function

Each function in simulator.py has the same signature and return type. Swap out the body:

# Before (simulator):
def simulate_create_transaction(amount, intent, approval_url, *, store=False):
    _check_penny(amount)
    ...
    return TransactionResponse(status="succeeded", ...)

# After (real provider):
async def simulate_create_transaction(amount, intent, approval_url, *, store=False):
    response = await provider_client.create_charge(amount=amount, intent=intent)

    if response.status == "declined":
        raise DeclineError("INSUFFICIENT_FUNDS", response.decline_reason)

    return TransactionResponse(
        status="succeeded",
        payment_service_transaction_id=response.charge_id,
        authorized_amount=amount,
    )

Note: Change the function signatures from def to async def if your provider client is async. FastAPI route handlers already await the simulator functions once they are coroutines.

3. Update main.py to await the calls

If you make the simulator functions async, update each route handler:

# main.py — before
result = simulator.simulate_create_transaction(...)

# main.py — after
result = await simulator.simulate_create_transaction(...)

4. Handle webhooks

In main.py, the /webhook handler passes the raw provider body to your code:

@app.post("/webhook", dependencies=[_sig])
async def webhook(request: Request) -> WebhookResponse:
    raw_body = await request.body()
    events = parse_provider_webhook(raw_body, request.headers)
    return WebhookResponse(events=events)

Build each event using the typed classes in app/responses.py (e.g. TransactionCaptureSucceededEvent, RefundSucceededEvent). Gr4vy doesn't verify that the webhook came from the payment provider, use the payment provider recommended way to verify

Signature verification

app/auth.py verifies the X-BYOC-Timestamp / X-BYOC-Signature headers on every request. This should remain unchanged — it protects your adapter from spoofed requests.

Request schemas

app/schemas.py defines what Gr4vy sends to each endpoint. All models use extra="allow" so new fields added by Gr4vy in the future are accepted without breaking your adapter. You do not need to modify this file unless you want to add stricter validation.

Response schemas

app/responses.py defines every field Gr4vy reads from your responses — including all webhook event payload types. Fields you don't populate are simply None and are excluded from the JSON output. You only need to set the fields relevant to your provider.


Environment variables

Variable Default Description
SECRET_KEY development Shared secret set in the Gr4vy dashboard
HOST 0.0.0.0 Bind address for the dev server
PORT 8000 Port for the dev server

In production, always set SECRET_KEY to a long random string and serve over HTTPS.


Endpoints

Method Path Description
GET /verify-credentials Connectivity check — called on connection save
POST /create-transaction New transaction
POST /approve-transaction Complete transaction after buyer redirect
POST /capture-transaction Delayed or partial capture
POST /create-refund Refund a captured transaction
POST /create-token Store a payment method
POST /approve-token Complete tokenization after buyer redirect
POST /delete-token Delete a stored payment method
POST /sync Fetch current transaction status
POST /webhook Receive raw provider webhook forwarded by Gr4vy
GET /simulator/approve Test helper — simulates provider redirect page

Interactive API docs are available at http://localhost:8000/docs when the server is running.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages