UserAPI.ts•11.4 kB
import { BunqContext } from "./BunqContext";
import { formatDateYYYYMMDD } from "../utils";
// Types moved from transaction-filter.ts for centralization
export interface LabelUser {
  uuid?: string | null;
  display_name?: string;
  country?: string;
  avatar?: any;
  public_nick_name?: string;
  type?: string | null;
  [key: string]: any;
}
export interface CounterpartyAlias {
  iban?: string | null;
  is_light?: boolean | null;
  display_name?: string;
  avatar?: any;
  label_user?: LabelUser;
  country?: string;
  merchant_category_code?: string;
  [key: string]: any;
}
export interface Payment {
  id: number;
  created: string;
  updated: string;
  monetary_account_id: number;
  amount: {
    value: string;
    currency: string;
  };
  payment_fee?: any;
  description: string;
  type: string;
  merchant_reference?: string | null;
  alias: any;
  counterparty_alias: CounterpartyAlias;
  attachment: any[];
  geolocation?: any;
  batch_id?: number | null;
  scheduled_id?: number | null;
  address_billing?: any;
  address_shipping?: any;
  sub_type?: string;
  payment_arrival_expected?: any;
  request_reference_split_the_bill?: any[];
  balance_after_mutation?: {
    value: string;
    currency: string;
  };
  payment_auto_allocate_instance?: any;
  payment_suspended_outgoing?: any;
}
export interface Counterparty {
  display_name?: string;
  public_nick_name?: string;
  iban?: string | null;
  type?: string;
  label_user?: LabelUser;
}
export interface UserProfile {
  id: number;
  displayName: string;
  firstName?: string;
  lastName?: string;
  email?: string;
}
export interface RequestInquiry {
  id: number;
  created: string;
  updated: string;
  time_responded: string;
  time_expiry: string;
  monetary_account_id: number;
  amount_inquired: { value: string; currency: string };
  amount_responded: { value: string; currency: string };
  user_alias_created: any;
  user_alias_revoked: any;
  counterparty_alias: any;
  description: string;
  merchant_reference: string;
  attachment: Array<{ id: number }>;
  status: string;
  batch_id: number;
  scheduled_id: number;
  minimum_age: number;
  require_address: string;
  bunqme_share_url: string;
  redirect_url: string;
  address_shipping: any;
  address_billing: any;
  geolocation: any;
  reference_split_the_bill: any;
}
export class UserAPI {
  #context: BunqContext;
  constructor(context: BunqContext) {
    this.#context = context;
  }
  async getInsights({
    userId,
    dateStart,
    dateEnd,
  }: {
    userId?: number;
    dateStart?: Date;
    dateEnd?: Date;
  }) {
    userId = userId ?? this.#context.token.userId;
    let endDate = dateEnd ? new Date(dateEnd) : new Date();
    // Set time to 00:00:00 for consistency
    endDate.setHours(0, 0, 0, 0);
    let startDate = dateStart ? new Date(dateStart) : new Date(endDate);
    if (!dateStart) {
      startDate.setDate(endDate.getDate() - 6); // 6 days before end, inclusive
    }
    const start = formatDateYYYYMMDD(startDate);
    const end = formatDateYYYYMMDD(endDate);
    const url = `/user/${userId}/insights?time_start=${start}&time_end=${end}`;
    const data = await this.#context.makeSignedRequest<unknown>(url);
    return data;
  }
  async getEvents(userId: number = this.#context.token.userId) {
    const data = await this.#context.makeSignedRequest<unknown>(`/user/${userId}/event`);
    return data;
  }
  async getMonetaryBankAccounts(userId: number = this.#context.token.userId) {
    type AccountResponse = {
      Response: Array<{
        MonetaryAccountBank: {
          id: number;
          description: string;
          balance: {
            value: string;
            currency: string;
          };
        };
      }>;
    };
    const data = await this.#context.makeSignedRequest<AccountResponse>(
      `/user/${userId}/monetary-account-bank`,
    );
    return data.Response.map((item) => ({
      id: item.MonetaryAccountBank.id,
      description: item.MonetaryAccountBank.description,
      balance: item.MonetaryAccountBank.balance.value,
      currency: item.MonetaryAccountBank.balance.currency,
    }));
  }
  async getUserSelf() {
    type UserSelfResponse = {
      Response: Array<{
        UserApiKey: {
          id: number;
          created: string;
          updated: string;
          requested_by_user: {
            UserPerson: {
              id: number;
              display_name: string;
              public_nick_name: string;
              avatar: {
                uuid: string;
                image: Array<{
                  attachment_public_uuid: string;
                  height: number;
                  width: number;
                  content_type: string;
                  urls: Array<{
                    type: string;
                    url: string;
                  }>;
                }>;
                anchor_uuid: string;
                style: string;
              };
              session_timeout: number;
            };
          };
          granted_by_user: {
            UserPerson: {
              id: number;
              display_name: string;
              public_nick_name: string;
              avatar: {
                uuid: string;
                image: Array<{
                  attachment_public_uuid: string;
                  height: number;
                  width: number;
                  content_type: string;
                  urls: Array<{
                    type: string;
                    url: string;
                  }>;
                }>;
                anchor_uuid: string;
                style: string;
              };
              session_timeout: number;
            };
          };
        };
      }>;
      Pagination: {
        future_url: string;
        newer_url: string | null;
        older_url: string | null;
      };
    };
    const data = await this.#context.makeSignedRequest<UserSelfResponse>(`/user`);
    return data;
  }
  /**
   * Get payments for a specific monetary account
   */
  async getPayments({
    userId = this.#context.token.userId,
    monetaryAccountId,
  }: {
    userId?: number;
    monetaryAccountId: number;
  }) {
    type PaymentResponse = {
      Response: Array<{
        Payment: Payment;
      }>;
    };
    const url = `/user/${userId}/monetary-account/${monetaryAccountId}/payment?count=200`;
    const data = await this.#context.makeSignedRequest<PaymentResponse>(url);
    return data.Response.map((item) => item.Payment);
  }
  /**
   * Get request inquiries for a specific monetary account
   */
  async getRequestInquiries({
    userId = this.#context.token.userId,
    monetaryAccountId,
  }: {
    userId?: number;
    monetaryAccountId: number;
  }) {
    type RequestInquiryResponse = {
      Response: Array<{
        RequestInquiry: RequestInquiry;
      }>;
    };
    const url = `/user/${userId}/monetary-account/${monetaryAccountId}/request-inquiry`;
    const data = await this.#context.makeSignedRequest<RequestInquiryResponse>(url);
    return data.Response.map((item) => item.RequestInquiry);
  }
  /**
   * Get payment auto-allocate objects for a specific monetary account (focused fields only)
   */
  async getPaymentAutoAllocates({
    userId = this.#context.token.userId,
    monetaryAccountId,
  }: {
    userId?: number;
    monetaryAccountId: number;
  }) {
    type PaymentAutoAllocateResponse = {
      Response: Array<{
        PaymentAutoAllocate: {
          id: number;
          created: string;
          updated: string;
          type: string;
          status: string;
          trigger_amount: { value: string; currency: string };
          payment: any;
        };
      }>;
    };
    const url = `/user/${userId}/monetary-account/${monetaryAccountId}/payment-auto-allocate`;
    const data = await this.#context.makeSignedRequest<PaymentAutoAllocateResponse>(url);
    // Only return the focused fields
    return data.Response.map((item) => ({
      id: item.PaymentAutoAllocate.id,
      created: item.PaymentAutoAllocate.created,
      updated: item.PaymentAutoAllocate.updated,
      type: item.PaymentAutoAllocate.type,
      status: item.PaymentAutoAllocate.status,
      trigger_amount: item.PaymentAutoAllocate.trigger_amount,
      payment: item.PaymentAutoAllocate.payment,
    }));
  }
  /**
   * Create a request inquiry for a specific monetary account
   * POST /v1/user/{userID}/monetary-account/{monetary-accountID}/request-inquiry
   * @param userId - User ID (optional, defaults to context)
   * @param monetaryAccountId - Monetary Account ID
   * @param body - Request body for the inquiry
   * @returns The created request inquiry ID
   */
  async createRequestInquiry({
    userId = this.#context.token.userId,
    monetaryAccountId,
    body,
  }: {
    userId?: number;
    monetaryAccountId: number;
    body: {
      amount_inquired: { value: string; currency: string };
      counterparty_alias:
        | { type: "EMAIL"; value: string; name?: string }
        | { type: "IBAN"; value: string; name?: string }
        | { type: "PHONE_NUMBER"; value: string; name?: string };
      description: string;
    };
  }): Promise<{ id: number }> {
    const url = `/user/${userId}/monetary-account/${monetaryAccountId}/request-inquiry`;
    // The Bunq API expects the body to be wrapped in an array with the key 'RequestInquiry'
    const requestBody = {
      ...body,
      counterparty_alias: {
        ...body.counterparty_alias,
      },
      allow_bunqme: false,
    };
    console.log("POSTING", url, requestBody);
    const data = await this.#context.makeSignedRequest<{ Response: [{ Id: { id: number } }] }>(
      url,
      "POST",
      requestBody,
    );
    console.log("RESPONSE", JSON.stringify(data, null, 2));
    // The response is { Id: { id: number } }
    return { id: data.Response[0].Id.id };
  }
  async getRequestInquiry({
    userId = this.#context.token.userId,
    monetaryAccountId,
    itemId,
  }: {
    userId?: number;
    monetaryAccountId: number;
    itemId: number;
  }): Promise<RequestInquiry> {
    const url = `/user/${userId}/monetary-account/${monetaryAccountId}/request-inquiry/${itemId}`;
    const data = await this.#context.makeSignedRequest<{
      Response: [{ RequestInquiry: RequestInquiry }];
    }>(url, "GET");
    return data.Response[0].RequestInquiry;
  }
  async listRequestInquiryResponse({
    userId = this.#context.token.userId,
    monetaryAccountId,
  }: {
    userId?: number;
    monetaryAccountId: number;
  }): Promise<any[]> {
    const url = `/user/${userId}/monetary-account/${monetaryAccountId}/request-response`;
    const data = await this.#context.makeSignedRequest<{
      Response: any[];
    }>(url, "GET");
    return data.Response;
  }
  async updateRequestInquiry({
    userId = this.#context.token.userId,
    monetaryAccountId,
    itemId,
  }: {
    userId?: number;
    monetaryAccountId: number;
    itemId: number;
  }): Promise<RequestInquiry> {
    const url = `/user/${userId}/monetary-account/${monetaryAccountId}/request-inquiry/${itemId}`;
    const requestBody = {
      status: "ACCEPTED",
    };
    const data = await this.#context.makeSignedRequest<{
      Response: [{ RequestInquiry: RequestInquiry }];
    }>(url, "PUT", requestBody);
    console.log("RESPONSE", JSON.stringify(data, null, 2));
    return data.Response[0].RequestInquiry;
  }
}