Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Thumbs.db
# 临时文件
*.tmp
*.temp
*.pem

# 压缩包
*.zip
Expand Down
149 changes: 149 additions & 0 deletions alipay/alipay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
const { AlipaySdk, AlipayFormData } = require('alipay-sdk');
const fs = require('fs');
const path = require('path');

class AlipayClient {
constructor(config) {
this.appId = config.appId;

// Support both direct PEM string in config or external PEM files
try {
const privateKeyPath = path.join(__dirname, '../private_key.pem');
const alipayPublicKeyPath = path.join(__dirname, '../alipay_public_key.pem');

if (fs.existsSync(privateKeyPath)) {
this.privateKey = fs.readFileSync(privateKeyPath, 'ascii');
} else {
this.privateKey = this._formatKey(config.privateKey, 'PRIVATE KEY');
}

if (fs.existsSync(alipayPublicKeyPath)) {
this.alipayPublicKey = fs.readFileSync(alipayPublicKeyPath, 'ascii');
} else {
this.alipayPublicKey = this._formatKey(config.alipayPublicKey, 'PUBLIC KEY');
}
} catch (e) {
console.error('Error loading Alipay keys:', e.message);
this.privateKey = this._formatKey(config.privateKey, 'PRIVATE KEY');
this.alipayPublicKey = this._formatKey(config.alipayPublicKey, 'PUBLIC KEY');
}

this.alipaySdk = new AlipaySdk({
appId: this.appId,
privateKey: this.privateKey,
alipayPublicKey: this.alipayPublicKey,
gateway: 'https://openapi.alipay.com/gateway.do',
});
}

_formatKey(key, type) {
if (!key) return '';
if (key.includes('BEGIN')) return key;
const lines = key.match(/.{1,64}/g).join('\n');
return `-----BEGIN ${type}-----\n${lines}\n-----END ${type}-----`;
}

/**
* Unified Order - Generates Payment URL
*/
async unifiedOrder(orderParams) {
const formData = new AlipayFormData();
formData.setMethod('get');

const totalAmount = (orderParams.amount / 100).toFixed(2);

formData.addField('notifyUrl', orderParams.notifyUrl);
formData.addField('returnUrl', orderParams.returnUrl);

let passbackParams = orderParams.extParam;
if (passbackParams) {
passbackParams = encodeURIComponent(passbackParams);
}

formData.addField('bizContent', {
outTradeNo: orderParams.mchOrderNo,
productCode: 'FAST_INSTANT_TRADE_PAY',
totalAmount: totalAmount,
subject: orderParams.subject,
body: orderParams.body,
passback_params: passbackParams
});

const result = await this.alipaySdk.pageExec(
'alipay.trade.page.pay',
{ method: 'GET' },
{ formData: formData }
);

return {
payUrl: result,
payOrderId: orderParams.mchOrderNo,
};
}

/**
* Query Order
*/
async queryOrder(queryParams) {
const outTradeNo = queryParams.mchOrderNo;

try {
const result = await this.alipaySdk.exec('alipay.trade.query', {
bizContent: { out_trade_no: outTradeNo }
});

let state = '0';
const status = result.tradeStatus;

if (status === 'TRADE_SUCCESS' || status === 'TRADE_FINISHED') {
state = '2';
} else if (status === 'TRADE_CLOSED') {
state = '6';
} else if (status === 'WAIT_BUYER_PAY') {
state = '1';
}

return {
payOrderId: result.tradeNo,
mchOrderNo: result.outTradeNo,
amount: Math.round(parseFloat(result.totalAmount) * 100),
state: state,
successTime: result.sendPayDate
};
} catch (err) {
console.error('Alipay query error', err);
throw err;
}
}

verifyNotify(params) {
return this.alipaySdk.checkNotifySign(params);
}

async refundOrder(params) {
const result = await this.alipaySdk.exec('alipay.trade.refund', {
bizContent: {
out_trade_no: params.mchOrderNo,
refund_amount: (params.refundAmount / 100).toFixed(2),
refund_reason: params.refundReason,
out_request_no: params.mchRefundNo
}
});

if (result.code === '10000' && result.fundChange === 'Y') {
return {
refundOrderId: params.mchRefundNo,
payOrderId: params.payOrderId,
mchRefundNo: params.mchRefundNo,
refundAmount: params.refundAmount,
state: '2'
}
} else {
throw new Error(result.subMsg || result.msg || 'Refund failed');
}
}

async closeOrder() {}
}

module.exports = AlipayClient;
14 changes: 9 additions & 5 deletions config.example.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ module.exports = {
// ========== Jeepay 支付平台配置 ==========
jeepay: {
baseUrl: 'https://pay.jeepay.vip', // Jeepay API 基础地址
mchNo: '你的商户号', // 商户号
appId: '你的应用ID', // 应用ID
privateKey: '你的商户私钥' // 商户私钥(用于签名/验签)
mchNo: '你的商户号',
appId: '你的应用ID',
privateKey: '你的商户私钥'
},
// 直接接入支付宝配置 (可选,若配置则优先使用支付宝模式)
alipay: {
appId: '你的支付宝应用ID',
privateKey: '你的支付宝应用私钥',
alipayPublicKey: '支付宝公钥'
},

// ========== 易支付接口配置(适配器) ==========
epay: {
pid: '你的易支付商户ID',
key: '你的易支付密钥' // MD5 签名密钥
Expand Down
Loading