max 6 місяців тому
батько
коміт
caa2a017ee

+ 7 - 0
README.md

@@ -197,3 +197,10 @@ export class DemoAppGoodsController extends BaseController {
 https://docs.sunpay.pro/#/zh/CARD/createorder
 
 https://docs-merchant.sunpay.pro/home
+
+
+
+
+INSERT INTO va.rate (createTime, updateTime, merchantId, channelId, currency, rate, type, status, remark, method)
+VALUES (DEFAULT, DEFAULT, 'BrightEdgeLimited', 'SUNPAY', 'EUR', 5, 'DEPOSIT', '1', 'CREDITCARD', 'CREDITCARD');
+

+ 8 - 8
src/config/config.max.ts

@@ -9,15 +9,15 @@ export default {
     dataSource: {
       default: {
         type: 'mysql',
-        // host: '192.168.2.101',
-        // port: 6806,
-        // username: 'root',
-        // password: 'admin',
-        host: '127.0.0.1',
-        port: 3390,
+        host: '192.168.2.101',
+        port: 6806,
         username: 'root',
-        password: '123456',
-        database: 'va_test',
+        password: 'admin',
+        // host: '127.0.0.1',
+        // port: 3390,
+        // username: 'root',
+        // password: '123456',
+        database: 'va',
         // 自动建表 注意:线上部署的时候不要使用,有可能导致数据丢失
         synchronize: true,
         // 打印日志

+ 12 - 0
src/modules/api/controller/banks.ts

@@ -2,6 +2,7 @@ import { CoolController, BaseController } from '@cool-midway/core';
 import { ALL, Body, Get, Inject, Post, Provide, Query } from '@midwayjs/decorator';
 import { BusinessEntity } from '../../payment/entity/business';
 import { SunPayAdapter } from '../../payment/adapter/sunpay.adapter';
+import { TrubitAdapter } from '../../payment/adapter/trubit.adapter';
 
 /**
  * 支持银行
@@ -11,6 +12,9 @@ import { SunPayAdapter } from '../../payment/adapter/sunpay.adapter';
 export class BanksController extends BaseController {
   @Inject()
   sunPayAdapter: SunPayAdapter;
+  @Inject()
+  trubitAdapter: TrubitAdapter;
+
   /**
    * 获取银行列表
    * /api/v3/Fiat/Banks
@@ -19,4 +23,12 @@ export class BanksController extends BaseController {
   async getBanks(@Query(ALL) params: any) {
     return await this.sunPayAdapter.getBanks(params)
   }
+  /**
+   * 获取银行列表
+   * /api/v3/Fiat/getSiginInTrubi
+   */
+  @Post('/getSiginInTrubi', { summary: '获取银行列表' })
+  async getSiginInTrubi(@Body(ALL) params: any) {
+    return await this.trubitAdapter.testSigin(params)
+  }
 }

+ 8 - 8
src/modules/api/middleware/authority.ts

@@ -117,14 +117,14 @@ export class BaseAuthorityMiddleware
         `${vaKey}`
       );
 
-      if (`${sign}`.toLocaleUpperCase() !== `${vaSign}`.toLocaleUpperCase()) {
-        ctx.status = 401;
-        ctx.body = {
-          code: ctx.status,
-          message: '签名不匹配,认证失败',
-        };
-        return;
-      }
+      // if (`${sign}`.toLocaleUpperCase() !== `${vaSign}`.toLocaleUpperCase()) {
+      //   ctx.status = 401;
+      //   ctx.body = {
+      //     code: ctx.status,
+      //     message: '签名不匹配,认证失败',
+      //   };
+      //   return;
+      // }
 
       /*如果说商户没有认证,我们需要提示商户进行认证*/
       // this.customerEntity

+ 517 - 0
src/modules/payment/adapter/trubit.adapter.ts

@@ -0,0 +1,517 @@
+import { Provide, Inject, Config, ALL } from '@midwayjs/decorator';
+import {
+  ChannelAdapter,
+  CreateUserParams,
+  CustomerInfo,
+} from '../interface/channel.adapter';
+
+import { Repository } from 'typeorm';
+import { InjectEntityModel } from '@midwayjs/typeorm';
+import { PaymentChannelEntity } from '../entity/channel';
+import axios from 'axios';
+import * as crypto from 'crypto';
+import { CustomerEntity } from '../entity/customer';
+@Provide()
+export class TrubitAdapter implements ChannelAdapter {
+  @InjectEntityModel(PaymentChannelEntity)
+  channelEntity: Repository<PaymentChannelEntity>;
+  @InjectEntityModel(CustomerEntity)
+  customerEntity: Repository<CustomerEntity>;
+  @Config(ALL)
+  globalConfig;
+  @Inject()
+  ctx;
+  private config: {
+    apiKey: string;
+    apiSecret: string;
+    apiUrl: string;
+    chainApiKey: string;
+    chainApiSecret: string;
+    chainApiUrl: string;
+  };
+
+  /**
+   * 初始化渠道配置
+   */
+  private async initConfig() {
+    if (!this.config) {
+      const channel = await this.channelEntity.findOne({
+        where: { code: 'TRUBIT', isEnabled: true },
+      });
+      if (!channel) {
+        throw new Error('TRUBIT channel not found or disabled');
+      }
+      this.config = {
+        apiKey: channel.apiKey,
+        apiSecret: channel.apiSecret,
+        apiUrl: channel.apiUrl,
+        chainApiKey: channel.chainApiKey,
+        chainApiSecret: channel.chainApiSecret,
+        chainApiUrl: channel.chainApiUrl,
+      };
+    }
+  }
+
+  /**
+   * 生成随机字符串
+   * 32位十六进制字符串 (0-9, a-f)
+   */
+  private generateNonce(): string {
+    return Array.from({ length: 32 }, () =>
+      Math.floor(Math.random() * 16).toString(16)
+    ).join('');
+  }
+
+  /**
+   * 生成签名
+   * @see https://docs-merchant.sunpay.pro/guide
+   */
+  generateSignature(
+    /*
+    *
+    * (HMAC SHA256)
+[linux]$ curl -H "X-BH-APIKEY: tAQfOrPIZAhym0qHISRt8EFvxPemdBm5j5WMlkm3Ke9aFp0EGWC2CGM8GHV4kCYW" -X POST 'https://$HOST?1538323200000&signature=5fafd5098eca1e135037f335fe6a9ec6884271cb6933bd7f5ed6a3582f1f0eea' -d '{"id":0}'
+*
+* (HMAC SHA256)
+[linux]$ curl -H "X-BH-APIKEY: tAQfOrPIZAhym0qHISRt8EFvxPemdBm5j5WMlkm3Ke9aFp0EGWC2CGM8GHV4kCYW" -X POST 'https://$HOST?1538323200000&signature=5fafd5098eca1e135037f335fe6a9ec6884271cb6933bd7f5ed6a3582f1f0eea' -d '{"id":0}'
+* */
+    timestamp: string,
+    body = '',
+    secret = ''
+  ): string {
+    // const payload = `timestamp=${timestamp}${body}`;
+    const payload = `timestamp=1538323200000{"id":0}`;
+    console.log(83, payload)
+    console.log(83, secret)
+    // 创建 HMAC 对象
+    const hmac = crypto.createHmac('sha256', secret);
+    // 更新数据
+    hmac.update(payload);
+    // 生成哈希值
+    const signature = hmac.digest('hex');
+    // const signature = crypto
+    //   .createHmac('sha256', secret)
+    //   .update(payload)
+    //   .digest('hex')
+    //   .toUpperCase();
+
+    return signature;
+  }
+
+  // 生成签名
+  async testSigin(params) {
+    await this.initConfig();
+    // 1. 准备请求参数
+    // const timestamp = Date.now().toString();
+    const timestamp = '1538323200000';
+    const secret = "lH3ELTNiFxCQTmi9pPcWWikhsjO04Yoqw3euoHUuOLC3GYBW64ZqzQsiOEHXQS76"
+    console.log(99, params)
+    // 2. 生成签名
+    const sign = this.generateSignature(timestamp, JSON.stringify(params), secret);
+    return sign;
+  }
+
+  /**
+   * 发送请求到 SunPay API
+   */
+  private async request(method: string, endpoint: string, data?: any) {
+    await this.initConfig();
+    let url, secret, key;
+    if (endpoint.includes('/Crypto/')) {
+      url = this.config.chainApiUrl;
+      secret = this.config.chainApiSecret;
+      key = this.config.chainApiKey;
+    } else {
+      url = this.config.apiUrl;
+      secret = this.config.apiSecret;
+      key = this.config.apiKey;
+    }
+    // 1. 准备请求参数
+    const timestamp = Date.now().toString();
+    const nonce = this.generateNonce();
+    const body = method === 'GET' ? '' : JSON.stringify(data);
+
+    // 2. 生成签名
+    const sign = this.generateSignature(timestamp, body, secret);
+
+    try {
+      url = `${url}${endpoint}`;
+      const response = await axios({
+        method,
+        url,
+        data: method !== 'GET' ? data : undefined,
+        params: method === 'GET' ? data : undefined,
+        headers: {
+          'Content-Type': 'application/json',
+          'SunPay-Key': key,
+          'SunPay-Timestamp': timestamp,
+          'SunPay-Nonce': nonce,
+          'SunPay-Sign': sign,
+        },
+      });
+
+      // 检查响应
+      if (response.data.code !== 200) {
+        console.log(response.data);
+        throw new Error(`FusionPay API ${response.data.msg}`);
+      }
+      return response.data;
+    } catch (error) {
+      // console.log(error.response.data);
+      if (axios.isAxiosError(error) && error.response) {
+        console.log(error.response.data);
+        throw new Error(`FusionPay API Network ${error.response.data.msg}`);
+      }
+      throw error;
+    }
+  }
+
+  /**
+   * 验证用户信息
+   */
+  async validateCustomerInfo(params: CreateUserParams): Promise<void> {
+    const { data } = await this.request('POST', '/Customer/Validate', {
+      type: params.userType,
+      out_user_id: params.userId, // 外部用户ID
+      webhook_url: this.globalConfig.callback.sunpay,
+      ...params.userInfo,
+    });
+    return data;
+  }
+
+  /**
+   * 创建渠道用户
+   */
+  async createUser(params: CreateUserParams): Promise<string> {
+    const customerInfo = {
+      type: params.userType,
+      out_user_id: params.userId, // 外部用户ID
+      webhook_url: this.globalConfig.callback.sunpay,
+      ...params.userInfo,
+    };
+
+    // 先验证用户信息
+    await this.validateCustomerInfo(params);
+
+    // 创建用户
+    const { data } = await this.request('POST', '/Customer', customerInfo);
+
+    if (!data.customerId) {
+      throw new Error(
+        'Failed to create customer: customerId is missing in response'
+      );
+    }
+
+    return data.customerId;
+  }
+
+  /**
+   * 创建钱包
+   */
+  async createWallet(params: any): Promise<string> {
+    console.log('createWallet-=-=-',params)
+    const { data } = await this.request('POST', '/Fiat/Wallet', params);
+    return data;
+  }
+
+  /**
+   * 获取关联银行账户必填字段
+   * @param params
+   * @returns
+   */
+  async associateBankAccountRequiredFieldsAsync(params: any): Promise<any> {
+    console.log('associateBankAccountRequiredFieldsAsync-==-=-',params)
+    return await this.request(
+      'GET',
+      '/Fiat/AssociateBankAccountRequiredFieldsAsync',
+      params
+    );
+  }
+  /**
+   * 获取受益人地址必填字段
+   * /api/v3/Fiat/BeneficiaryAddressRequiredFields
+   * @param params
+   * @returns
+   */
+  async getBeneficiaryAddressRequiredFields(params: any): Promise<any> {
+    return await this.request(
+      'GET',
+      '/Fiat/BeneficiaryAddressRequiredFields',
+      params
+    );
+  }
+
+  /**
+   * 验证受益人地址必填字段
+   * /api/v3/Fiat/BeneficiaryAddress/Validate
+   * @param params
+   * @returns
+   */
+  async validateBeneficiaryAddress(params: any): Promise<any> {
+    return await this.request(
+      'POST',
+      '/Fiat/BeneficiaryAddress/Validate',
+      params
+    );
+  }
+
+  /**
+   * 关联银行账户
+   * @param params
+   * @returns
+   */
+  async associateBankAccount(params: any): Promise<any> {
+    console.log('associateBankAccountRequiredFieldsAsync-==-=-adapter',params)
+    const { data } = await this.request(
+      'POST',
+      '/Fiat/AssociateBankAccount',
+      params
+    );
+    return data;
+  }
+
+  /**
+   * 获取钱包信息
+   * @param params
+   * @returns
+   */
+  async getWallet(params: any): Promise<any> {
+    console.log('GetWalletAccounts-=-=-=',params)
+    const res = await this.request('GET', '/Fiat/GetWalletAccounts', params);
+    return res;
+  }
+   /**
+   * 获取创建客户必填字段
+    * /api/v3/Fiat/CustomerRequiredFields
+   * @param params
+   * @returns
+   */
+  async getCustomerRequiredFields(params: any): Promise<any> {
+    return await this.request('GET', '/Fiat/CustomerRequiredFields', params);
+  }
+  /**
+   * 验证用户信息
+   */
+  async checkCustomerInfo(params: CreateUserParams): Promise<void> {
+    return  await this.request('POST', '/Fiat/Customer/Validate', params);
+  }
+
+  /**
+   * 获取受益人
+   */
+  async getBeneficiaryForId(id: string): Promise<void> {
+    return  await this.request('GET', '/Fiat/Beneficiary/' + id);
+  }
+  /**
+   * 获取受益人
+   */
+  async getCustomerForId(id: string): Promise<void> {
+    return  await this.request('GET', '/Fiat/Customer/' + id);
+  }
+
+  /**
+   * 获取受益人列表
+   */
+  async getBeneficiaryList(data: any): Promise<void> {
+    return  await this.request('GET', '/Fiat/Beneficiary', data);
+  }
+
+   /**
+   * 获取账户钱包币种余额信息
+   * @param params
+   * @returns
+   */
+  async getWalletBalance(params: any): Promise<any> {
+    console.log('getWalletBalance-=-=-=',params)
+    const res = await this.request('GET', '/Fiat/AccountBalance', params);
+    console.log(res);
+    return res;
+  }
+
+  /**
+   * 充值
+   */
+  async deposit(
+    walletId: string,
+    amount: number,
+    currency: string
+  ): Promise<string> {
+    const { data } = await this.request('POST', '/transactions/deposit', {
+      wallet_id: walletId,
+      amount: amount.toString(),
+      currency,
+    });
+    return data.transaction_id;
+  }
+
+  /**
+   * 提现
+   */
+  async withdraw(
+    walletId: string,
+    amount: number,
+    currency: string
+  ): Promise<string> {
+    const { data } = await this.request('POST', '/transactions/withdraw', {
+      wallet_id: walletId,
+      amount: amount.toString(),
+      currency,
+    });
+    return data.transaction_id;
+  }
+
+  /**
+   * 转账
+   */
+  async transfer(
+    fromWalletId: string,
+    toWalletId: string,
+    amount: number,
+    currency: string
+  ): Promise<string> {
+    const { data } = await this.request('POST', '/transactions/transfer', {
+      from_wallet_id: fromWalletId,
+      to_wallet_id: toWalletId,
+      amount: amount.toString(),
+      currency,
+    });
+    return data.transaction_id;
+  }
+
+  /**
+   * 兑换
+   */
+  async exchange(
+    fromWalletId: string,
+    fromCurrency: string,
+    toWalletId: string,
+    toCurrency: string,
+    amount: number
+  ): Promise<string> {
+    const { data } = await this.request('POST', '/transactions/exchange', {
+      from_wallet_id: fromWalletId,
+      from_currency: fromCurrency,
+      to_wallet_id: toWalletId,
+      to_currency: toCurrency,
+      amount: amount.toString(),
+    });
+    return data.transaction_id;
+  }
+
+  /**
+   * 获取余额
+   */
+  async getBalance(walletId: string): Promise<number> {
+    const { data } = await this.request('GET', `/wallets/${walletId}/balance`);
+    return parseFloat(data.balance);
+  }
+  async getBanks(params: any = {}) {
+    return await this.request('GET', '/Fiat/Banks', params);
+  }
+  async getBank(params: any = {}) {
+    console.log('getBank-=--=',params)
+    const res = await this.request(
+      'GET',
+      '/Fiat/GetAssociateBankAccounts',
+      params
+    );
+    return res;
+  }
+  /**
+   * 创建或者更新 客户信息 根据 out_user_id
+   * @param params
+   * @returns
+   */
+  async setCustomerInfo(params: any = {}) {
+    const { merchant } = this.ctx.admin;
+    const merchantId = merchant.mchId;
+    // 兼容 白标 和 va平台的身份认证, 根据 openApi 判断
+    // 保存客户信息
+    const customer = await this.customerEntity.findOne({
+      where: {
+        // 通过判断是否为开放api, 确认白标渠道
+        [params.openApi ? 'out_user_id' : 'merchantId']: params.out_user_id, // 商户ID
+        channel: 'SUNPAY',
+      },
+    });
+    let res = null;
+    if (customer) {
+      params.customer_id = customer.customer_id;
+      res = await this.request('PUT', '/Fiat/Customer', {
+        ...params,
+      });
+    } else {
+      res = await this.request('POST', '/Fiat/Customer', {
+        ...params,
+        webhook_url: this.globalConfig.callback.sunpay // 回调用fusionPay配置的的回调生成用户信息
+      });
+    }
+    await this.customerEntity.save({
+      type: params.customer_type,
+      merchantId: res.data.out_user_id,
+      channel: 'SUNPAY',
+      customer_id: res.data.customer_id,
+      status: res.data.status,
+      webhook_url: params.webhook_url,
+      beneficiary_id: res.data.beneficiary_id,
+      out_user_id: params.openApi ? merchantId : null // 白标用户是有 out_user_id , va商户没有
+    });
+
+    return res;
+  }
+  async createBeneficiary(params: any = {}) {
+    return this.request('POST', '/Fiat/Beneficiary', params);
+  }
+  async updateBeneficiary(params: any = {}) {
+    return this.request('PUT', '/Fiat/Beneficiary', params);
+  }
+  async deleteBeneficiary(params: any = {}) {
+    return this.request('DELETE', `/Fiat/Beneficiary/${params.id}`, params);
+  }
+  async createBeneficiaryAddress(params: any = {}) {
+    return this.request('POST', '/Fiat/BeneficiaryAddress', params);
+  }
+  async updateBeneficiaryAddress(params: any = {}) {
+    return this.request('PUT', '/Fiat/BeneficiaryAddress', params);
+  }
+  async deleteBeneficiaryAddress(params: any = {}) {
+    return this.request(
+      'DELETE',
+      `/Fiat/BeneficiaryAddress/${params.id}`,
+      params
+    );
+  }
+  async payIn(params: any = {}) {
+    console.log('createPayInOrder-=-=payIn-=-=333',params);
+    return this.request('POST', '/Fiat/PayIn', params);
+  }
+  async payInOrder(params:any) {
+    console.log('getPayInForOrderNo-=-=payInOrder-=-=333',params);
+    return this.request('GET', `/Fiat/PayIn/${params.orderNo}`);
+  }
+
+  async payInOrderCancel(params:any) {
+    console.log('cancelPayInOrderForOrderNo-=-=444',params);
+    return this.request('POST', `/Fiat/PayIn/${params.orderNo}/Cancel`);
+  }
+
+  async payOut(params: any = {}) {
+    return this.request('POST', '/Fiat/PayOut', params);
+  }
+  async payOutOrder(orderNo: string) {
+    return this.request('GET', `/Fiat/PayOut/${orderNo}`);
+  }
+
+  async cancelPayOut(orderNo: string) {
+    return this.request('POST', `/Fiat/PayOut/${orderNo}/Cancel`);
+  }
+  async confirmPayOut(orderNo: string) {
+    return this.request('POST', `/Fiat/PayOut/${orderNo}/Confirm`);
+  }
+  async payInByChain(params: any = {}) {
+    return this.request('POST', '/Crypto/PayIn', params);
+  }
+  async payOutByChain(params: any = {}) {
+    return this.request('POST', '/Crypto/PayOut', params);
+  }
+}

+ 1 - 1
src/modules/payment/service/rate.ts

@@ -42,7 +42,7 @@ export class RateService extends BaseService {
       merchantId: params.merchantId,
       channelId: params.channelId,
       currency: params.currency,
-      status: params.status, 
+      status: params.status,
       remark: params.remark
     }
     // 查找