feat(aftersale): per-line refund/return flow with ledger-backed completion (add-aftersale-refunds)
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
import type {
|
||||
AccountSummary,
|
||||
Aftersale,
|
||||
AftersaleApplyBody,
|
||||
AftersaleArbitration,
|
||||
AftersaleDetail,
|
||||
AftersaleMessage,
|
||||
AftersaleMessageBody,
|
||||
AftersaleReturnTrackingBody,
|
||||
AftersaleStatus,
|
||||
Address,
|
||||
AddressBookEntry,
|
||||
AuthTokens,
|
||||
@@ -231,6 +239,16 @@ export interface ApiClient {
|
||||
listShopCouponTemplates(shopId: string): Promise<CouponTemplate[]>;
|
||||
listMyCoupons(): Promise<Coupon[]>;
|
||||
claimCoupon(templateId: string): Promise<Coupon>;
|
||||
applyForAftersale(body: AftersaleApplyBody): Promise<AftersaleDetail>;
|
||||
listMyAftersales(): Promise<Aftersale[]>;
|
||||
getAftersale(id: string): Promise<AftersaleDetail>;
|
||||
cancelAftersale(id: string): Promise<Aftersale>;
|
||||
reopenAftersale(id: string): Promise<Aftersale>;
|
||||
submitAftersaleReturnTracking(
|
||||
id: string,
|
||||
body: AftersaleReturnTrackingBody,
|
||||
): Promise<Aftersale>;
|
||||
addAftersaleMessage(id: string, body: AftersaleMessageBody): Promise<AftersaleMessage>;
|
||||
/** Public points catalog: published products only. */
|
||||
listPointsProducts(): Promise<IntegralProduct[]>;
|
||||
listMyRedemptions(): Promise<IntegralOrder[]>;
|
||||
@@ -280,6 +298,13 @@ export interface ApiClient {
|
||||
body: GroupBuyingActivityInput,
|
||||
): Promise<GroupBuyingActivity>;
|
||||
deleteGroupBuyingActivity(id: string): Promise<void>;
|
||||
listAftersales(status?: AftersaleStatus): Promise<Aftersale[]>;
|
||||
getAftersale(id: string): Promise<AftersaleDetail>;
|
||||
approveAftersale(id: string): Promise<Aftersale>;
|
||||
rejectAftersale(id: string): Promise<Aftersale>;
|
||||
confirmAftersaleReceipt(id: string): Promise<Aftersale>;
|
||||
refundAftersale(id: string): Promise<Aftersale>;
|
||||
addAftersaleMessage(id: string, body: AftersaleMessageBody): Promise<AftersaleMessage>;
|
||||
};
|
||||
admin: {
|
||||
listUsers(page?: number): Promise<Paged<User>>;
|
||||
@@ -298,6 +323,9 @@ export interface ApiClient {
|
||||
kind: K,
|
||||
items: ContentInputByKind[K],
|
||||
): Promise<HomeContent>;
|
||||
listAftersales(status?: AftersaleStatus): Promise<Aftersale[]>;
|
||||
getAftersale(id: string): Promise<AftersaleDetail>;
|
||||
arbitrateAftersale(id: string, outcome: AftersaleArbitration): Promise<Aftersale>;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -353,6 +381,14 @@ export function createApi(opts: ApiClientOptions): ApiClient {
|
||||
listShopCouponTemplates: (shopId) => r("GET", `/shops/${shopId}/coupon-templates`),
|
||||
listMyCoupons: () => r("GET", "/me/coupons"),
|
||||
claimCoupon: (templateId) => r("POST", "/me/coupons", { template_id: templateId }),
|
||||
applyForAftersale: (body) => r("POST", "/aftersales", body),
|
||||
listMyAftersales: () => r("GET", "/aftersales"),
|
||||
getAftersale: (id) => r("GET", `/aftersales/${id}`),
|
||||
cancelAftersale: (id) => r("POST", `/aftersales/${id}/cancel`),
|
||||
reopenAftersale: (id) => r("POST", `/aftersales/${id}/reopen`),
|
||||
submitAftersaleReturnTracking: (id, body) =>
|
||||
r("POST", `/aftersales/${id}/return-tracking`, body),
|
||||
addAftersaleMessage: (id, body) => r("POST", `/aftersales/${id}/messages`, body),
|
||||
listPointsProducts: () => r("GET", "/points/products"),
|
||||
listMyRedemptions: () => r("GET", "/points/redemptions"),
|
||||
redeemPoints: (body) => r("POST", "/points/redemptions", body),
|
||||
@@ -397,6 +433,13 @@ export function createApi(opts: ApiClientOptions): ApiClient {
|
||||
updateGroupBuyingActivity: (id, body) =>
|
||||
r("PUT", `/shop/group-buying-activities/${id}`, body),
|
||||
deleteGroupBuyingActivity: (id) => r("DELETE", `/shop/group-buying-activities/${id}`),
|
||||
listAftersales: (status) => r("GET", "/shop/aftersales", undefined, { status }),
|
||||
getAftersale: (id) => r("GET", `/shop/aftersales/${id}`),
|
||||
approveAftersale: (id) => r("POST", `/shop/aftersales/${id}/approve`),
|
||||
rejectAftersale: (id) => r("POST", `/shop/aftersales/${id}/reject`),
|
||||
confirmAftersaleReceipt: (id) => r("POST", `/shop/aftersales/${id}/confirm-receipt`),
|
||||
refundAftersale: (id) => r("POST", `/shop/aftersales/${id}/refund`),
|
||||
addAftersaleMessage: (id, body) => r("POST", `/shop/aftersales/${id}/messages`, body),
|
||||
},
|
||||
admin: {
|
||||
listUsers: (page = 1) => r("GET", "/admin/users", undefined, { page }),
|
||||
@@ -417,6 +460,10 @@ export function createApi(opts: ApiClientOptions): ApiClient {
|
||||
/** Replaces the whole ordered brand list. */
|
||||
getBrands: () => r("GET", "/brands"),
|
||||
replaceBrands: (items) => r("PUT", "/admin/brands", items),
|
||||
listAftersales: (status) => r("GET", "/admin/aftersales", undefined, { status }),
|
||||
getAftersale: (id) => r("GET", `/admin/aftersales/${id}`),
|
||||
arbitrateAftersale: (id, outcome) =>
|
||||
r("POST", `/admin/aftersales/${id}/arbitrate`, { outcome }),
|
||||
listPointsProducts: () => r("GET", "/admin/points/products"),
|
||||
createPointsProduct: (body) => r("POST", "/admin/points/products", body),
|
||||
updatePointsProduct: (id, body) => r("PUT", `/admin/points/products/${id}`, body),
|
||||
|
||||
@@ -159,6 +159,8 @@ export interface Order {
|
||||
total_minor: number;
|
||||
/** Server-calculated coupon discount, already applied to `total_minor`. */
|
||||
discount_minor: number;
|
||||
/** Authoritative sum of completed refunds, integer minor units. */
|
||||
refund_total_minor: number;
|
||||
/** The coupon this order redeemed, or null. */
|
||||
coupon_id: string | null;
|
||||
/** The group-buying activity this order joined, or null. */
|
||||
@@ -618,3 +620,83 @@ export type ShopProfileSelfInput = Omit<
|
||||
"score_rating" | "score_agreement" | "score_service" | "score_speed"
|
||||
>;
|
||||
|
||||
// ---- aftersale ----
|
||||
|
||||
export type AftersaleKind = "refund_only" | "return_refund";
|
||||
export type AftersaleStatus =
|
||||
| "pending"
|
||||
| "approved"
|
||||
| "rejected"
|
||||
| "buyer_shipping"
|
||||
| "merchant_confirmed"
|
||||
| "refunded"
|
||||
| "cancelled";
|
||||
|
||||
export interface Aftersale {
|
||||
id: string;
|
||||
order_id: string;
|
||||
order_item_id: string;
|
||||
shop_id: string;
|
||||
user_id: string;
|
||||
kind: AftersaleKind;
|
||||
status: AftersaleStatus;
|
||||
reason: LocalizedText;
|
||||
amount_minor: number;
|
||||
currency: string;
|
||||
evidence: string[];
|
||||
reopened: boolean;
|
||||
return_carrier: string | null;
|
||||
return_tracking_no: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export type AftersaleAuthorRole = "buyer" | "merchant" | "platform";
|
||||
|
||||
/** One append-only bilateral message. */
|
||||
export interface AftersaleMessage {
|
||||
id: string;
|
||||
aftersale_id: string;
|
||||
author_role: AftersaleAuthorRole;
|
||||
author_id: string;
|
||||
content: LocalizedText;
|
||||
evidence: string[];
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface AftersaleItemSummary {
|
||||
product_name: LocalizedText;
|
||||
sku_code: string;
|
||||
image: string | null;
|
||||
unit_price_minor: number;
|
||||
qty: number;
|
||||
}
|
||||
|
||||
/** Detail payload: the aftersale plus its item snapshot, messages, and the
|
||||
* remaining refundable minor-unit balance for the order line. */
|
||||
export interface AftersaleDetail extends Aftersale {
|
||||
item: AftersaleItemSummary;
|
||||
messages: AftersaleMessage[];
|
||||
remaining_refundable_minor: number;
|
||||
}
|
||||
|
||||
export interface AftersaleApplyBody {
|
||||
order_item_id: string;
|
||||
kind: AftersaleKind;
|
||||
reason: LocalizedText;
|
||||
amount_minor: number;
|
||||
evidence?: string[];
|
||||
}
|
||||
|
||||
export interface AftersaleMessageBody {
|
||||
content: LocalizedText;
|
||||
evidence?: string[];
|
||||
}
|
||||
|
||||
export interface AftersaleReturnTrackingBody {
|
||||
carrier: string;
|
||||
tracking_no: string;
|
||||
}
|
||||
|
||||
export type AftersaleArbitration = "refund" | "reject";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user