mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
@@ -353,7 +353,7 @@ export class DMoneyProvider implements PaymentProvider {
|
||||
return this.config.get<string>("dmoney.returnUrl") ?? "";
|
||||
}
|
||||
private get timeoutExpress(): string {
|
||||
return this.config.get<string>("dmoney.timeoutExpress") ?? "120m";
|
||||
return this.config.get<string>("dmoney.timeoutExpress") ?? "5m";
|
||||
}
|
||||
private get language(): string {
|
||||
return this.config.get<string>("dmoney.language") ?? "en";
|
||||
|
||||
@@ -2,6 +2,8 @@ import { Injectable, Logger } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { HttpService } from "@nestjs/axios";
|
||||
import {
|
||||
ClientAction,
|
||||
PaymentPlatform,
|
||||
PaymentProvider,
|
||||
ProviderInitiationInput,
|
||||
ProviderInitiationResult,
|
||||
@@ -67,15 +69,7 @@ export class TelebirrProvider implements PaymentProvider {
|
||||
requestBody.biz_content.timeout_express,
|
||||
);
|
||||
const platform = input.platform ?? "web";
|
||||
const clientAction =
|
||||
platform === "mobile"
|
||||
? {
|
||||
type: "LAUNCH_APP" as const,
|
||||
appId: this.merchantAppId,
|
||||
receiveCode: response.biz_content?.receiveCode,
|
||||
shortCode: this.merchantCode,
|
||||
}
|
||||
: { type: "REDIRECT" as const, url: this.buildCheckoutUrl(prepayId) };
|
||||
const clientAction = this.buildClientAction(platform, prepayId, response);
|
||||
|
||||
return {
|
||||
providerOrderId: prepayId,
|
||||
@@ -199,6 +193,11 @@ export class TelebirrProvider implements PaymentProvider {
|
||||
input: ProviderInitiationInput,
|
||||
): CreateOrderRequest {
|
||||
const totalAmount = String(input.amountMinor);
|
||||
// In-app pays inside the SuperApp overlay and never navigates, so there is no browser
|
||||
// to send back — telebirr's own in-app integration omits redirect_url entirely. Keep it
|
||||
// absent rather than undefined: a signed-but-unsent field is what produced the earlier
|
||||
// "verify sign failed" (see docs/payment-service + telebirr.crypto skip-undefined).
|
||||
const wantsRedirect = input.platform !== "inapp" && !!input.redirectUrl;
|
||||
const req = {
|
||||
timestamp: createTimestamp(),
|
||||
nonce_str: createNonceStr(),
|
||||
@@ -214,7 +213,7 @@ export class TelebirrProvider implements PaymentProvider {
|
||||
total_amount: totalAmount,
|
||||
trans_currency: input.currency,
|
||||
timeout_express: this.timeoutExpress,
|
||||
...(input.redirectUrl ? { redirect_url: input.redirectUrl } : {}),
|
||||
...(wantsRedirect ? { redirect_url: input.redirectUrl! } : {}),
|
||||
},
|
||||
};
|
||||
const sign = signRequestObject(
|
||||
@@ -245,6 +244,68 @@ export class TelebirrProvider implements PaymentProvider {
|
||||
return { ...req, sign, sign_type: "SHA256WithRSA" };
|
||||
}
|
||||
|
||||
/**
|
||||
* Telebirr exposes the same pre-order three ways; only the launch payload differs.
|
||||
*
|
||||
* - `mobile` — native app hands off to the wallet app with a receiveCode.
|
||||
* - `inapp` — the portal is running inside the telebirr SuperApp mini-app WebView. The
|
||||
* H5 checkout page is unusable there: it deep-links to `kcbconsumer://…`,
|
||||
* which the WebView cannot resolve (`net::ERR_UNKNOWN_URL_SCHEME`). The
|
||||
* signed rawRequest goes to the host JS bridge instead — no navigation.
|
||||
* - `web` — ordinary browser; redirect to the H5 checkout page.
|
||||
*/
|
||||
private buildClientAction(
|
||||
platform: PaymentPlatform,
|
||||
prepayId: string,
|
||||
response: CreateOrderResponse,
|
||||
): ClientAction {
|
||||
switch (platform) {
|
||||
case "mobile":
|
||||
return {
|
||||
type: "LAUNCH_APP",
|
||||
appId: this.merchantAppId,
|
||||
receiveCode: response.biz_content?.receiveCode,
|
||||
shortCode: this.merchantCode,
|
||||
};
|
||||
case "inapp":
|
||||
return {
|
||||
type: "INVOKE_BRIDGE",
|
||||
bridge: "TELEBIRR",
|
||||
rawRequest: this.buildInAppRawRequest(prepayId),
|
||||
};
|
||||
default:
|
||||
return { type: "REDIRECT", url: this.buildCheckoutUrl(prepayId) };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Signed request handed verbatim to the SuperApp bridge (`js_fun_start_pay`).
|
||||
*
|
||||
* Emits `appid, merch_code, nonce_str, prepay_id, timestamp, sign_type, sign` in that
|
||||
* order — no `webBaseUrl` prefix and no `version`/`trade_type` tail, because the bridge
|
||||
* takes the bare query string rather than a URL.
|
||||
*
|
||||
* `sign_type` sits in the map purely so it lands in the output in the right position;
|
||||
* `buildCanonicalString` excludes it (as does telebirr's own reference implementation),
|
||||
* so the signature covers the same five fields as the web checkout URL.
|
||||
*
|
||||
* Kept separate from `buildCheckoutUrl` rather than sharing a builder: the two payloads
|
||||
* are consumed by different validators, and the web flow is live.
|
||||
*/
|
||||
private buildInAppRawRequest(prepayId: string): string {
|
||||
const map: Record<string, string> = {
|
||||
appid: this.merchantAppId,
|
||||
merch_code: this.merchantCode,
|
||||
nonce_str: createNonceStr(),
|
||||
prepay_id: prepayId,
|
||||
timestamp: createTimestamp(),
|
||||
sign_type: "SHA256WithRSA",
|
||||
};
|
||||
const sign = signRequestObject(map, this.privateKey);
|
||||
const fields = Object.entries(map).map(([k, v]) => `${k}=${v}`);
|
||||
return [...fields, `sign=${sign}`].join("&");
|
||||
}
|
||||
|
||||
private buildCheckoutUrl(prepayId: string): string {
|
||||
const map: Record<string, string> = {
|
||||
appid: this.merchantAppId,
|
||||
|
||||
@@ -32,7 +32,8 @@ export enum ProviderMethod {
|
||||
CBE_BILL = "CBE_BILL",
|
||||
}
|
||||
|
||||
export type PaymentPlatform = "web" | "mobile";
|
||||
|
||||
export type PaymentPlatform = "web" | "mobile" | "inapp";
|
||||
|
||||
export type ClientAction =
|
||||
| { type: "REDIRECT"; url: string }
|
||||
@@ -42,6 +43,16 @@ export type ClientAction =
|
||||
receiveCode?: string;
|
||||
shortCode: string;
|
||||
}
|
||||
| {
|
||||
type: "INVOKE_BRIDGE";
|
||||
/** Which SuperApp host bridge the payload targets. */
|
||||
bridge: "TELEBIRR";
|
||||
/**
|
||||
* Signed query string handed verbatim to the host bridge (`js_fun_start_pay`).
|
||||
* NOT a URL — it has no scheme or host and must never be navigated to.
|
||||
*/
|
||||
rawRequest: string;
|
||||
}
|
||||
| {
|
||||
type: "COLLECT_OTP";
|
||||
providerOrderId: string;
|
||||
|
||||
Reference in New Issue
Block a user