Skip to content
33 changes: 33 additions & 0 deletions MODULAR PRODUCTION-GRADE SDK
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
STRUKTUR FINAL (PRODUCTION SDK)

pi-sdk-js-pro/
β”œβ”€ src/
β”‚ β”œβ”€ core/
β”‚ β”‚ β”œβ”€ config.ts
β”‚ β”‚ β”œβ”€ event-bus.ts
β”‚ β”‚ β”œβ”€ payment-engine.ts
β”‚ β”‚
β”‚ β”œβ”€ auth/
β”‚ β”‚ └─ connect.ts
β”‚ β”‚
β”‚ β”œβ”€ payment/
β”‚ β”‚ └─ create-payment.ts
β”‚ β”‚
β”‚ β”œβ”€ marketplace/
β”‚ β”‚ └─ merchant.ts
β”‚ β”‚
β”‚ β”œβ”€ plugins/
β”‚ β”‚ └─ plugin.ts
β”‚ β”‚
β”‚ β”œβ”€ audit/
β”‚ β”‚ └─ audit-log.ts
β”‚ β”‚
β”‚ β”œβ”€ types/
β”‚ β”‚ └─ index.ts
β”‚ β”‚
β”‚ β”œβ”€ sdk.ts
β”‚ └─ index.ts
β”‚
β”œβ”€ package.json
β”œβ”€ tsconfig.json
└─ README.md
38 changes: 38 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
export type PiEnv = 'production' | 'sandbox' | 'mock'

export interface PiUser {
uid: string
username: string
roles?: string[]
}

export type PaymentStatus =
| 'CREATED'
| 'APPROVED'
| 'SUBMITTED'
| 'COMPLETED'
| 'CANCELLED'
| 'FAILED'
| 'EXPIRED'

export interface PaymentData {
amount: number
memo: string
metadata?: Record<string, any>
}

export interface SplitRule {
merchantId: string
amount: number
}

export interface MarketplacePayment extends PaymentData {
splits?: SplitRule[]
escrow?: boolean
}

export interface Merchant {
merchantId: string
wallet: string
feePercent?: number
}
12 changes: 12 additions & 0 deletions src/types/src/core/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { PiEnv } from '../types'

export interface PiSdkConfig {
env?: PiEnv
appId?: string
debug?: boolean
}

export const defaultConfig: PiSdkConfig = {
env: 'production',
debug: false
}
15 changes: 15 additions & 0 deletions src/types/src/core/src/core/event-bus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export type EventHandler = (payload?: any) => void

export class EventBus {
private listeners = new Map<string, EventHandler[]>()

on(event: string, handler: EventHandler) {
const list = this.listeners.get(event) || []
list.push(handler)
this.listeners.set(event, list)
}

emit(event: string, payload?: any) {
this.listeners.get(event)?.forEach(h => h(payload))
}
}
15 changes: 15 additions & 0 deletions src/types/src/core/src/core/src/core/event-bus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export type EventHandler = (payload?: any) => void

export class EventBus {
private listeners = new Map<string, EventHandler[]>()

on(event: string, handler: EventHandler) {
const list = this.listeners.get(event) || []
list.push(handler)
this.listeners.set(event, list)
}

emit(event: string, payload?: any) {
this.listeners.get(event)?.forEach(h => h(payload))
}
}
8 changes: 8 additions & 0 deletions src/types/src/core/src/core/src/core/src/audit/audit-log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { PaymentStatus } from '../types'

export interface AuditLog {
paymentId: string
status: PaymentStatus
timestamp: number
payload?: any
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { PaymentStatus } from '../types'
import { AuditLog } from '../audit/audit-log'
import { EventBus } from './event-bus'

export class PaymentEngine {
private status: PaymentStatus = 'CREATED'
private audit: AuditLog[] = []

constructor(
private events: EventBus,
private debug = false
) {}

transition(status: PaymentStatus, payload?: any) {
this.status = status
this.audit.push({
paymentId: payload?.paymentId ?? 'unknown',
status,
timestamp: Date.now(),
payload
})

if (this.debug) {
console.log('[PiSDK][STATE]', status, payload)
}

this.events.emit(`payment:${status.toLowerCase()}`, payload)
}

getAuditTrail() {
return this.audit
}

getStatus() {
return this.status
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { PiSdk } from '../sdk'

export interface PiSdkPlugin {
name: string
onInit?(sdk: PiSdk): void
onPaymentEvent?(event: string, payload: any): void
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Merchant } from '../types'

export class MerchantRegistry {
private merchants = new Map<string, Merchant>()

register(merchant: Merchant) {
this.merchants.set(merchant.merchantId, merchant)
}

get(id: string) {
return this.merchants.get(id)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { PiUser } from '../types'

declare global {
interface Window {
Pi: any
}
}

export async function connectPi(env: string): Promise<PiUser> {
if (env === 'mock') {
return { uid: 'mock', username: 'MockUser' }
}

if (!window.Pi) {
throw new Error('Pi SDK not loaded')
}

const auth = await window.Pi.authenticate(
['payments', 'username'],
(err: any) => { if (err) throw err }
)

return auth.user
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { MarketplacePayment } from '../types'
import { PaymentEngine } from '../core/payment-engine'

declare global {
interface Window {
Pi: any
}
}

export function createPayment(
data: MarketplacePayment,
engine: PaymentEngine
) {
engine.transition('CREATED', data)

window.Pi.createPayment(
{
amount: data.amount,
memo: data.memo,
metadata: data.metadata
},
{
onReadyForServerApproval: (paymentId: string) =>
engine.transition('APPROVED', { paymentId }),

onReadyForServerCompletion: (paymentId: string) =>
engine.transition('SUBMITTED', { paymentId }),

onCancel: (paymentId: string) =>
engine.transition('CANCELLED', { paymentId }),

onError: (error: any, payment?: any) =>
engine.transition('FAILED', { error, payment })
}
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { PiSdkConfig, defaultConfig } from './core/config'
import { EventBus } from './core/event-bus'
import { PaymentEngine } from './core/payment-engine'
import { MerchantRegistry } from './marketplace/merchant'
import { PiSdkPlugin } from './plugins/plugin'
import { MarketplacePayment, PiUser } from './types'
import { connectPi } from './auth/connect'
import { createPayment } from './payment/create-payment'

export class PiSdk {
static user: PiUser | null = null

private events = new EventBus()
private engine: PaymentEngine
private merchants = new MerchantRegistry()
private plugins: PiSdkPlugin[] = []
private config: PiSdkConfig

constructor(config?: PiSdkConfig) {
this.config = { ...defaultConfig, ...config }
this.engine = new PaymentEngine(this.events, this.config.debug)
}

async connect() {
PiSdk.user = await connectPi(this.config.env!)
this.events.emit('user:connected', PiSdk.user)
return PiSdk.user
}

on(event: string, handler: any) {
this.events.on(event, handler)
}

use(plugin: PiSdkPlugin) {
plugin.onInit?.(this)
this.plugins.push(plugin)
}

registerMerchant(m: any) {
this.merchants.register(m)
}

pay(data: MarketplacePayment) {
createPayment(data, this.engine)
}

audit() {
return this.engine.getAuditTrail()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './sdk'
export * from './types'