feat(payments): VK Votes payment rail
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 18s
CI / ui (pull_request) Successful in 1m10s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m45s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 18s
CI / ui (pull_request) Successful in 1m10s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m45s
Wire the VK Mini Apps ("голоса") rail end to end, reusing the intake engine. The
wallet.order endpoint branches by rail: a VK context opens a pending order
(provider vk) and returns its id, which the client passes to VKWebAppShowOrderBox.
VK's two-phase payment callback is verified at the gateway with the app protected
key (GATEWAY_VK_APP_SECRET) and proxied to a backend intake handler: get_item
returns the ordered pack's title and vote price; a chargeable order_status_change
credits the vk segment exactly once (the same Fund, idempotent on VK's own order
id) and records a succeeded event, so the dispatcher push refreshes the wallet.
Integration test for the VK order->credit path.
This commit is contained in:
@@ -182,6 +182,21 @@ export async function vkShowImages(url: string): Promise<boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* vkShowOrderBox opens VK's payment box for a "голоса" item purchase; item is the order id the
|
||||
* backend created (VK echoes it to our payment callback, which credits). Resolves true when the
|
||||
* payment completes, false on cancel/failure or outside VK. The chips are credited by the server
|
||||
* callback, so the wallet refreshes from the payment push regardless of this result.
|
||||
*/
|
||||
export async function vkShowOrderBox(item: string): Promise<boolean> {
|
||||
try {
|
||||
await (await bridge()).send('VKWebAppShowOrderBox', { type: 'item', item });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* vkDownloadFile downloads url as filename through the VK client (VKWebAppDownloadFile) —
|
||||
* the mobile in-app file delivery, where the webview ignores <a download>. Resolves false
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { executionContext, formatAmount, needsWebSpendWarning, type SpendContext } from '../lib/wallet';
|
||||
import { isGooglePlayBuild } from '../lib/distribution';
|
||||
import { openExternalUrl } from '../lib/links';
|
||||
import { vkShowOrderBox } from '../lib/vk';
|
||||
import type { Wallet, Catalog, CatalogProduct } from '../lib/model';
|
||||
|
||||
// The Wallet section: the context-visible chip balances, the active benefits, and the storefront.
|
||||
@@ -107,7 +108,12 @@
|
||||
busy = true;
|
||||
try {
|
||||
const order = await gateway.walletOrder(p.productId);
|
||||
openExternalUrl(order.redirectUrl);
|
||||
if (context === 'vk') {
|
||||
// VK settles the payment in-app via the bridge; the chips arrive on the server callback.
|
||||
await vkShowOrderBox(order.orderId);
|
||||
} else {
|
||||
openExternalUrl(order.redirectUrl);
|
||||
}
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user