d3ro-voice/server/supabase/functions/iap-verify/index.ts

87 lines
2.8 KiB
TypeScript

import { corsHeaders, handleCorsPreflightRequest } from '../_shared/cors.ts'
import { authErrorResponse, requireUser, type AuthError } from '../_shared/auth.ts'
import { createServiceRoleClient } from '../_shared/quota.ts'
import {
createGooglePlayPurchaseApi,
GooglePlayVerificationError,
} from '../_shared/google-play.ts'
import { applyGooglePlayPurchase } from '../_shared/google-play-apply.ts'
import {
createSupabaseGooglePlayPurchaseStore,
GooglePlayPurchasePersistenceError,
} from '../_shared/google-play-purchase-store.ts'
interface VerifyPurchaseRequest {
platform?: unknown
productId?: unknown
purchaseToken?: unknown
}
function jsonResponse(body: Record<string, unknown>, status = 200): Response {
return new Response(JSON.stringify(body), {
status,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
})
}
Deno.serve(async (req: Request) => {
const preflight = handleCorsPreflightRequest(req)
if (preflight) return preflight
if (req.method !== 'POST') {
return jsonResponse({ error: 'method_not_allowed' }, 405)
}
try {
const user = await requireUser(req)
const body = await req.json() as VerifyPurchaseRequest
if (
body.platform !== 'google_play'
|| typeof body.productId !== 'string'
|| typeof body.purchaseToken !== 'string'
) {
return jsonResponse({ error: 'invalid_request' }, 400)
}
const { purchase, stored } = await applyGooglePlayPurchase(
{
playApi: createGooglePlayPurchaseApi(fetch),
store: createSupabaseGooglePlayPurchaseStore(createServiceRoleClient()),
},
{
userId: user.id,
productId: body.productId,
purchaseToken: body.purchaseToken,
},
)
return jsonResponse({
purchase: stored,
verification: {
product_id: purchase.productId,
purchase_state: purchase.purchaseState,
entitled: purchase.entitled,
acknowledged: purchase.acknowledged,
},
finish_transaction: false,
server_acknowledged: purchase.acknowledged,
})
} catch (error) {
if (error && typeof error === 'object' && 'status' in error && 'message' in error) {
const candidate = error as { status: unknown; message: unknown }
if (
(candidate.status === 401 || candidate.status === 403)
&& typeof candidate.message === 'string'
) {
return authErrorResponse(error as AuthError, corsHeaders)
}
}
if (error instanceof GooglePlayPurchasePersistenceError && error.code !== 'purchase_persistence_failed') {
return jsonResponse({ error: error.code }, 409)
}
if (error instanceof GooglePlayVerificationError) {
return jsonResponse({ error: error.code }, error.status)
}
return jsonResponse({ error: 'purchase_verification_failed' }, 500)
}
})