juspay / juspay/grace

[Tech Spec] Ppro — CreateClientAuthenticationToken

Open
#152 0 comments 0 reactions 0 assignees View on GitHub
connector-integration
Dominant language
Python
Stars
4
Forks
6
PR merge metrics
No merged PRs in 30d

Description

# Ppro — CreateClientAuthenticationToken

**Complexity:** low
**Generated by:** Grace pipeline run `run-2026-05-13T09-40-37-723Z-6e4be6`

## Summary

Implement CreateClientAuthenticationToken payment method for Ppro connector. Technical Specification: Ppro — CreateClientAuthenticationToken

## Scope

# Technical Specification: Ppro — CreateClientAuthenticationToken

## 1. Connector Profile

| Field | Value |
|---|---|
| **Connector Name** | Ppro |
| **Primary Flow Scope** | Alternative Payment Methods (APMs) — redirect-based wallet, bank, and local payment flows |
| **API Family** | REST / JSON (camelCase request/response bodies) |
| **Production Host** | `https://api.eu.ppro.com` |
| **Sandbox Host** | `https://api.sandbox.eu.ppro.com` |
| **Currency Unit** | Minor (integer, e.g. 1000 = 10.00 EUR) |

---

## 2. Authentication

| Field | Value |
|---|---|
| **Scheme** | HTTP Bearer Token |
| **Authorization Header** | `Authorization: Bearer {api_key}` |
| **Merchant Identity Header** | `Merchant-Id: {merchant_id}` |
| **Idempotency Header** | `Request-Idempotency-Key: {connector_request_reference_id}` |

**Credentials required:**
- `api_key` (Secret string) — Bearer token issued by PPRO
- `merchant_id` (Secret string) — Merchant identifier for multi-tenant routing

**Implementation notes:**
- All three headers (`Authorization`, `Merchant-Id`, `Request-Idempotency-Key`) are required on every payment-charge request.
- The `Request-Idempotency-Key` must be unique per attempt; reusing the same key on a POST replays the original response.

---

## 3. Supported Flows

| Flow | HTTP | Path | Notes |
|---|---|---|---|
| **Authorize** | `POST` | `/v1/payment-charges` | Creates a payment charge; returns redirect URL via `authenticationMethods[].details.requestUrl` |
| **Capture** | `POST` | `/v1/payment-charges/{id}/captures` | Finalises authorized payment |
| **PSync** | `GET` | `/v1/payment-charges/{id}` | Retrieves current charge status |
| **Refund** | `POST` | `/v1/payment-charges/{id}/refunds` | Initiates partial or full refund |
| **RSync** | `GET` | `/v1/payment-charges/{id}` | Retrieves charge (used to sync refund status) |
| **Void** | `POST` | `/v1/payment-charges/{id}/voids` | Cancels an authorized charge |
| **SetupMandate** | `POST` | `/v1/payment-agreements

## Out of Scope

Not specified in techspec

## Technical Constraints

- Follow existing connector patterns in the codebase

---

## Full Tech Spec

# Technical Specification: Ppro — CreateClientAuthenticationToken

## 1. Connector Profile

| Field | Value |
|---|---|
| **Connector Name** | Ppro |
| **Primary Flow Scope** | Alternative Payment Methods (APMs) — redirect-based wallet, bank, and local payment flows |
| **API Family** | REST / JSON (camelCase request/response bodies) |
| **Production Host** | `https://api.eu.ppro.com` |
| **Sandbox Host** | `https://api.sandbox.eu.ppro.com` |
| **Currency Unit** | Minor (integer, e.g. 1000 = 10.00 EUR) |

---

## 2. Authentication

| Field | Value |
|---|---|
| **Scheme** | HTTP Bearer Token |
| **Authorization Header** | `Authorization: Bearer {api_key}` |
| **Merchant Identity Header** | `Merchant-Id: {merchant_id}` |
| **Idempotency Header** | `Request-Idempotency-Key: {connector_request_reference_id}` |

**Credentials required:**
- `api_key` (Secret string) — Bearer token issued by PPRO
- `merchant_id` (Secret string) — Merchant identifier for multi-tenant routing

**Implementation notes:**
- All three headers (`Authorization`, `Merchant-Id`, `Request-Idempotency-Key`) are required on every payment-charge request.
- The `Request-Idempotency-Key` must be unique per attempt; reusing the same key on a POST replays the original response.

---

## 3. Supported Flows

| Flow | HTTP | Path | Notes |
|---|---|---|---|
| **Authorize** | `POST` | `/v1/payment-charges` | Creates a payment charge; returns redirect URL via `authenticationMethods[].details.requestUrl` |
| **Capture** | `POST` | `/v1/payment-charges/{id}/captures` | Finalises authorized payment |
| **PSync** | `GET` | `/v1/payment-charges/{id}` | Retrieves current charge status |
| **Refund** | `POST` | `/v1/payment-charges/{id}/refunds` | Initiates partial or full refund |
| **RSync** | `GET` | `/v1/payment-charges/{id}` | Retrieves charge (used to sync refund status) |
| **Void** | `POST` | `/v1/payment-charges/{id}/voids` | Cancels an authorized charge |
| **SetupMandate** | `POST` | `/v1/payment-agreements` | Creates a recurring payment agreement |
| **RepeatPayment** | `POST` | `/v1/payment-agreements/{agr_id}/payment-charges` | Charges an existing agreement |
| **CreateClientAuthenticationToken** | `POST` | `/v1/payment-charges` | Creates a pre-initiated charge; returns `charge_id` and `redirect_url` for client-side authentication |
| **Webhooks** | — | Configured via `webhooksUrl` in request body | Delivers async event notifications |

---

## 4. Request Schema Highlights

### CreateClientAuthenticationToken — `POST /v1/payment-charges`

```json
{
"paymentMethod": "BANCONTACT",
"paymentMedium": "ECOMMERCE",
"merchantPaymentChargeReference": "ref_001",
"amount": {
"currency": "EUR",
"value": 1000
},
"consumer": {
"name": "John Doe",
"email": "john@example.com",
"country": "BE"
},
"authenticationSettings": [
{
"type": "REDIRECT",
"settings": {
"returnUrl": "https://merchant.com/return"
}
}
]
}
```

| Field | Type | Required | Notes |
|---|---|---|---|
| `paymentMethod` | String (enum) | Yes | One of: `BANCONTACT`, `IDEAL`, `TRUSTLY`, `BLIK`, `ALIPAY`, `WECHATPAY`, `MBWAY`, `SATISPAY`, `WERO`, `UPI` |
| `paymentMedium` | String (enum) | Yes | `ECOMMERCE` \| `MOTO` \| `POS` — defaults to `ECOMMERCE` for client auth flows |
| `merchantPaymentChargeReference` | String | Yes | Idempotency key; maps to `connector_request_reference_id` |
| `amount.currency` | String (ISO 4217) | Yes | Three-letter currency code |
| `amount.value` | Integer | Yes | Minor unit amount |
| `consumer.name` | String | Optional | Billing full name |
| `consumer.email` | String | Optional | Billing email |
| `consumer.country` | String | Optional | ISO 3166-1 alpha-2 country code from billing address |
| `consumer.merchantConsumerReference` | String | Optional | Required for Trustly; scopes agreement to consumer |
| `authenticationSettings[].type` | String (enum) | Optional | `REDIRECT` — instructs PPRO to return a redirect URL |
| `authenticationSettings[].settings.returnUrl` | String (URL) | Optional | Return URL after consumer completes redirect flow |

**Idempotency:** `Request-Idempotency-Key` header (equal to `merchantPaymentChargeReference`) provides request-level idempotency.

---

## 5. Response Schema Highlights

### Success Response (HTTP 200/201)

```json
{
"id": "chg_abc123",
"status": "AUTHENTICATION_PENDING",
"authenticationMethods": [
{
"type": "REDIRECT",
"details": {
"requestUrl": "https://redirect.ppro.com/pay/chg_abc123"
}
}
],
"failure": null
}
```

| Field | Type | Notes |
|---|---|---|
| `id` | String | Connector transaction ID (charge ID) — stored as `charge_id` in `PproClientAuthenticationResponse` |
| `status` | String (enum) | See status values below |
| `authenticationMethods[].type` | String | `REDIRECT` (or `SCAN_CODE`, `MULTI_FACTOR`, `APP_NOTIFICATION`, `APP_INTENT`) |
| `authenticationMethods[].details.requestUrl` | String | Redirect URL for client-side authentication — stored as `redirect_url` |
| `failure.failureType` | String | Category of failure (e.g., `FRAUD`, `INSUFFICIENT_FUNDS`) |
| `failure.failureCode` | String | Optional machine-readable failure code |
| `failure.failureMessage` | String | Human-readable failure description |

### Payment Status Values

| Status | Mapped AttemptStatus | Notes |
|---|---|---|
| `AUTHORIZATION_PROCESSING` | `Pending` | |
| `AUTHENTICATION_PENDING` | `AuthenticationPending` | Consumer must complete redirect |
| `AUTHORIZATION_ASYNC` | `Authorized` | |
| `CAPTURE_PENDING` | `Authorized` | |
| `CAPTURE_PROCESSING` | `Authorized` | |
| `CAPTURED` / `SUCCESS` | `Charged` | Terminal success |
| `FAILED` / `DISCARDED` / `REJECTED` / `DECLINED` | `Failure` | Terminal failure |
| `VOIDED` | `Voided` | |
| `REFUND_SETTLED` / `REFUNDED` | `Charged` | Post-refund, charge remains Charged |

---

## 6. Error Handling

| HTTP | `status` field | Cause |
|---|---|---|
| `400` | 400 | Malformed request body — missing required fields or invalid enum values |
| `401` | 401 | Invalid or missing `Authorization` Bearer token |
| `403` | 403 | Valid token but insufficient permissions for this merchant |
| `409` | 409 | Duplicate `merchantPaymentChargeReference` — idempotency key collision |
| `422` | 422 | Unprocessable entity — e.g., unsupported `paymentMethod` for this merchant account |
| `429` | 429 | Rate limit exceeded |
| `500` | 500 | PPRO internal server error |

**Error Response Format:**
```json
{
"status": 400,
"failureMessage": "paymentMethod is required"
}
```
Mapped via `PproErrorResponse { status: u16, failure_message: String }`.

---

## 7. Webhooks / Async Notifications

### Subscription
Webhooks are subscribed per-payment by setting `webhooksUrl` in the payment charge request body.

### Delivery Format
CloudEvents 1.0 envelope (JSON):
```json
{
"specversion": "1.0",
"type": "PAYMENT_CHARGE_SUCCESS",
"source": "ppro",
"id": "event_001",
"time": "2024-01-01T00:00:00Z",
"data": {
"charge": {
"id": "chg_abc123",
"status": "SUCCEEDED",
"amount": 1000,
"currency": "EUR"
}
}
}
```

### Webhook Event Types

| Event Type | Notes |
|---|---|
| `PAYMENT_CHARGE_AUTHORIZATION_SUCCEEDED` | Authorization successful |
| `PAYMENT_CHARGE_SUCCESS` | Capture complete |
| `PAYMENT_CHARGE_AUTHORIZATION_FAILED` | Authorization failed |
| `PAYMENT_CHARGE_FAILED` | Capture/general failure |
| `PAYMENT_CHARGE_DISCARDED` | Charge discarded |
| `PAYMENT_CHARGE_CAPTURE_SUCCEEDED` | Capture confirmed |
| `PAYMENT_CHARGE_CAPTURE_FAILED` | Capture failed |
| `PAYMENT_CHARGE_VOID_SUCCEEDED` | Void successful |
| `PAYMENT_CHARGE_VOID_FAILED` | Void failed |
| `PAYMENT_CHARGE_REFUND_SUCCEEDED` | Refund settled |
| `PAYMENT_CHARGE_REFUND_FAILED` | Refund failed |
| `PAYMENT_AGREEMENT_ACTIVE` | Recurring agreement established |
| `PAYMENT_AGREEMENT_FAILED` | Recurring agreement failed |
| `PAYMENT_AGREEMENT_REVOKED_BY_CONSUMER` | Consumer revoked mandate |
| `PAYMENT_AGREEMENT_REVOKED_BY_MERCHANT` | Merchant revoked mandate |
| `PAYMENT_AGREEMENT_REVOKED_BY_PROVIDER` | PPRO revoked mandate |

### Signature Verification
- Header: `Webhook-Signature` (hex-encoded)
- Algorithm: SHA-256 HMAC over `body + "." + webhook_secret`
- Implementation: `crypto::Sha256.verify_signature(&[], &expected_signature, &message)`

### Retry Policy
Documented: PPRO retries failed webhook deliveries with exponential backoff. Exact retry count and backoff interval not publicly documented.

### Gaps
- No per-event subscription filtering — all events are delivered to `webhooksUrl`
- Webhook secret is configured out-of-band (not returned in API responses)

---

## 8. References

| Resource | URL |
|---|---|
| PPRO Sandbox API | `https://api.sandbox.eu.ppro.com` |
| PPRO Production API | `https://api.eu.ppro.com` |
| Ppro Connector Implementation | `crates/integrations/connector-integration/src/connectors/ppro.rs` |
| Ppro Transformers | `crates/integrations/connector-integration/src/connectors/ppro/transformers.rs` |
| Field Probe Data | `data/field_probe/ppro.json` |
| Connector Docs | `docs-generated/connectors/ppro.md` |

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with crates/integrations/connector-integration/src/connectors/ppro.rs and its transformers.rs, then compare existing connector patterns for CreateClientAuthenticationToken. Use data/field_probe/ppro.json and the request and response schemas to verify the Ppro charge flow, including the charge ID and redirect URL; done means the documented flow is implemented consistently with the Ppro connector.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
payments
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
65/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.