123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- 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);
- }
- }
|