|
@@ -79,10 +79,22 @@ public class AlipayTransferService {
|
|
|
@org.springframework.context.annotation.Lazy
|
|
@org.springframework.context.annotation.Lazy
|
|
|
private com.payment.platform.module.payment.openapi.service.OpenapiService openapiService;
|
|
private com.payment.platform.module.payment.openapi.service.OpenapiService openapiService;
|
|
|
|
|
|
|
|
- // ---- 安全发常量 ----
|
|
|
|
|
|
|
+ // ---- 安全发常量(来自接入指南 https://opendocs.alipay.com/pre-open/01ruru) ----
|
|
|
|
|
+ /** 签约: 个人产品码(固定值) */
|
|
|
|
|
+ private static final String SIGN_PERSONAL_PRODUCT_CODE = "FUND_SAFT_SIGN_WITHHOLDING_P";
|
|
|
|
|
+ /** 签约: 商户产品码(固定值) */
|
|
|
|
|
+ private static final String SIGN_PRODUCT_CODE = "FUND_SAFT_SIGN_WITHHOLDING";
|
|
|
|
|
+ /** 签约: 场景码(固定值,不是下拉选的代扣行业码) */
|
|
|
|
|
+ private static final String SIGN_SCENE = "INDUSTRY|SATF_ACC";
|
|
|
|
|
+ /** 转账: 产品码(固定值) */
|
|
|
private static final String PRODUCT_CODE = "SINGLE_TRANSFER_NO_PWD";
|
|
private static final String PRODUCT_CODE = "SINGLE_TRANSFER_NO_PWD";
|
|
|
|
|
+ /** 转账: 服务商版转账 */
|
|
|
private static final String BIZ_SCENE_TRANSFER = "ENTRUST_TRANSFER";
|
|
private static final String BIZ_SCENE_TRANSFER = "ENTRUST_TRANSFER";
|
|
|
- private static final String BIZ_SCENE_ALLOCATION = "DIRECT_ALLOCATION";
|
|
|
|
|
|
|
+ /** 充值: 产品码(固定值) */
|
|
|
|
|
+ private static final String DEPOSIT_PRODUCT_CODE = "FUND_ACCOUNT_BOOK";
|
|
|
|
|
+ /** 充值: 场景码(固定值) */
|
|
|
|
|
+ private static final String BIZ_SCENE_DEPOSIT = "SATF_DEPOSIT";
|
|
|
|
|
+ /** 记账本场景码(固定值) */
|
|
|
private static final String SCENE_CODE_ACCOUNT_BOOK = "SATF_FUND_BOOK";
|
|
private static final String SCENE_CODE_ACCOUNT_BOOK = "SATF_FUND_BOOK";
|
|
|
|
|
|
|
|
private static final Map<String, String> TRANSFER_ERROR_HINTS = new LinkedHashMap<>();
|
|
private static final Map<String, String> TRANSFER_ERROR_HINTS = new LinkedHashMap<>();
|
|
@@ -107,19 +119,20 @@ public class AlipayTransferService {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* alipay.user.agreement.page.sign — 页面跳转接口
|
|
* alipay.user.agreement.page.sign — 页面跳转接口
|
|
|
- * 必填: personal_product_code, product_code, sign_scene, access_params
|
|
|
|
|
- * sign_scene 由前端选择(external_agreement_no 不为空时不能为 DEFAULT|DEFAULT)
|
|
|
|
|
|
|
+ * 参数全部为安全发固定值,来自接入指南 §1.1
|
|
|
*/
|
|
*/
|
|
|
- public Map<String, String> authorizeApply(String enterpriseId, String signScene) {
|
|
|
|
|
|
|
+ public Map<String, String> authorizeApply(String enterpriseId) {
|
|
|
String externalAgreementNo = enterpriseId + "_agreement";
|
|
String externalAgreementNo = enterpriseId + "_agreement";
|
|
|
try {
|
|
try {
|
|
|
AlipayUserAgreementPageSignModel model = new AlipayUserAgreementPageSignModel();
|
|
AlipayUserAgreementPageSignModel model = new AlipayUserAgreementPageSignModel();
|
|
|
- model.setPersonalProductCode("GENERAL_WITHHOLDING_P");
|
|
|
|
|
- model.setProductCode("GENERAL_WITHHOLDING");
|
|
|
|
|
- model.setSignScene(signScene != null && !signScene.isBlank() ? signScene : "DEFAULT|DEFAULT");
|
|
|
|
|
- model.setExternalAgreementNo(externalAgreementNo);
|
|
|
|
|
|
|
+ // 安全发固定值(接入指南 §1.1)
|
|
|
|
|
+ model.setPersonalProductCode(SIGN_PERSONAL_PRODUCT_CODE);
|
|
|
|
|
+ model.setProductCode(SIGN_PRODUCT_CODE);
|
|
|
|
|
+ model.setSignScene(SIGN_SCENE);
|
|
|
|
|
+ model.setThirdPartyType("PARTNER"); // 必选
|
|
|
|
|
+ model.setExternalAgreementNo(externalAgreementNo); // 必选
|
|
|
AccessParams accessParams = new AccessParams();
|
|
AccessParams accessParams = new AccessParams();
|
|
|
- accessParams.setChannel("ALIPAYAPP");
|
|
|
|
|
|
|
+ accessParams.setChannel("QRCODE"); // 扫码签约
|
|
|
model.setAccessParams(accessParams);
|
|
model.setAccessParams(accessParams);
|
|
|
|
|
|
|
|
AlipayUserAgreementPageSignRequest request = new AlipayUserAgreementPageSignRequest();
|
|
AlipayUserAgreementPageSignRequest request = new AlipayUserAgreementPageSignRequest();
|
|
@@ -145,12 +158,13 @@ public class AlipayTransferService {
|
|
|
* alipay.user.agreement.query — 通过 external_agreement_no 反查签约协议号
|
|
* alipay.user.agreement.query — 通过 external_agreement_no 反查签约协议号
|
|
|
* 用户完成签约后调用,获取支付宝侧的 agreement_no
|
|
* 用户完成签约后调用,获取支付宝侧的 agreement_no
|
|
|
*/
|
|
*/
|
|
|
- public Map<String, String> queryAgreement(String enterpriseId, String externalAgreementNo, String signScene) {
|
|
|
|
|
|
|
+ public Map<String, String> queryAgreement(String enterpriseId, String externalAgreementNo) {
|
|
|
try {
|
|
try {
|
|
|
AlipayUserAgreementQueryModel model = new AlipayUserAgreementQueryModel();
|
|
AlipayUserAgreementQueryModel model = new AlipayUserAgreementQueryModel();
|
|
|
model.setExternalAgreementNo(externalAgreementNo);
|
|
model.setExternalAgreementNo(externalAgreementNo);
|
|
|
- model.setPersonalProductCode("GENERAL_WITHHOLDING_P");
|
|
|
|
|
- model.setSignScene(signScene != null && !signScene.isBlank() ? signScene : "DEFAULT|DEFAULT");
|
|
|
|
|
|
|
+ // 安全发固定值(接入指南 §1.2)
|
|
|
|
|
+ model.setPersonalProductCode(SIGN_PERSONAL_PRODUCT_CODE);
|
|
|
|
|
+ model.setSignScene(SIGN_SCENE);
|
|
|
model.setThirdPartyType("PARTNER");
|
|
model.setThirdPartyType("PARTNER");
|
|
|
|
|
|
|
|
AlipayUserAgreementQueryRequest request = new AlipayUserAgreementQueryRequest();
|
|
AlipayUserAgreementQueryRequest request = new AlipayUserAgreementQueryRequest();
|
|
@@ -201,12 +215,11 @@ public class AlipayTransferService {
|
|
|
model.setMerchantUserType("BUSINESS_ORGANIZATION");
|
|
model.setMerchantUserType("BUSINESS_ORGANIZATION");
|
|
|
model.setSceneCode(SCENE_CODE_ACCOUNT_BOOK);
|
|
model.setSceneCode(SCENE_CODE_ACCOUNT_BOOK);
|
|
|
|
|
|
|
|
- if (agreementNo != null && !agreementNo.isBlank()) {
|
|
|
|
|
- Map<String, String> extInfo = new LinkedHashMap<>();
|
|
|
|
|
- extInfo.put("agreement_no", agreementNo);
|
|
|
|
|
- try { model.setExtInfo(objectMapper.writeValueAsString(extInfo)); }
|
|
|
|
|
- catch (Exception ignored) {}
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 接入指南 §2.1: ext_info 必选,agreement_no 必传
|
|
|
|
|
+ Map<String, String> extInfo = new LinkedHashMap<>();
|
|
|
|
|
+ extInfo.put("agreement_no", agreementNo != null ? agreementNo : "");
|
|
|
|
|
+ try { model.setExtInfo(objectMapper.writeValueAsString(extInfo)); }
|
|
|
|
|
+ catch (Exception e) { throw new BusinessException(400, "序列化 ext_info 失败"); }
|
|
|
|
|
|
|
|
AlipayFundAccountbookCreateRequest request = new AlipayFundAccountbookCreateRequest();
|
|
AlipayFundAccountbookCreateRequest request = new AlipayFundAccountbookCreateRequest();
|
|
|
request.setBizModel(model);
|
|
request.setBizModel(model);
|
|
@@ -237,21 +250,40 @@ public class AlipayTransferService {
|
|
|
// ==================== 3. 充值(页面跳转) ====================
|
|
// ==================== 3. 充值(页面跳转) ====================
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * alipay.fund.trans.page.pay — 页面跳转接口
|
|
|
|
|
- * 必填: out_biz_no, trans_amount, product_code, biz_scene, payee_info
|
|
|
|
|
|
|
+ * alipay.fund.trans.page.pay — 页面跳转接口(接入指南 §2.3)
|
|
|
|
|
+ * 必填: out_biz_no, trans_amount, product_code=FUND_ACCOUNT_BOOK, biz_scene=SATF_DEPOSIT,
|
|
|
|
|
+ * payee_info(identity_type=ACCOUNT_BOOK_ID, identity, ext_info含agreement_no), time_expire
|
|
|
*/
|
|
*/
|
|
|
@Transactional
|
|
@Transactional
|
|
|
public Map<String, String> deposit(String enterpriseId, String accountBookId, BigDecimal amount, String remark) {
|
|
public Map<String, String> deposit(String enterpriseId, String accountBookId, BigDecimal amount, String remark) {
|
|
|
|
|
+ // 查询记账本获取 agreement_no(接入指南要求payee_info.ext_info必传)
|
|
|
|
|
+ AccountEntity account = accountMapper.selectOne(
|
|
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<AccountEntity>()
|
|
|
|
|
+ .eq(AccountEntity::getAccountBookId, accountBookId));
|
|
|
|
|
+ String agreementNo = account != null ? account.getAgreementNo() : null;
|
|
|
|
|
+
|
|
|
try {
|
|
try {
|
|
|
AlipayFundTransPagePayModel model = new AlipayFundTransPagePayModel();
|
|
AlipayFundTransPagePayModel model = new AlipayFundTransPagePayModel();
|
|
|
model.setOutBizNo(SnowflakeIdGenerator.nextIdStr());
|
|
model.setOutBizNo(SnowflakeIdGenerator.nextIdStr());
|
|
|
model.setTransAmount(amount.toPlainString());
|
|
model.setTransAmount(amount.toPlainString());
|
|
|
- model.setProductCode(PRODUCT_CODE);
|
|
|
|
|
- model.setBizScene(BIZ_SCENE_ALLOCATION);
|
|
|
|
|
|
|
+ // 接入指南 §2.3: 充值专用产品码和场景码
|
|
|
|
|
+ model.setProductCode(DEPOSIT_PRODUCT_CODE);
|
|
|
|
|
+ model.setBizScene(BIZ_SCENE_DEPOSIT);
|
|
|
|
|
+ // 必选: 过期时间(格式 yyyy-MM-dd HH:mm)
|
|
|
|
|
+ model.setTimeExpire(java.time.LocalDateTime.now().plusHours(1)
|
|
|
|
|
+ .format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")));
|
|
|
|
|
|
|
|
Participant payee = new Participant();
|
|
Participant payee = new Participant();
|
|
|
payee.setIdentityType("ACCOUNT_BOOK_ID");
|
|
payee.setIdentityType("ACCOUNT_BOOK_ID");
|
|
|
payee.setIdentity(accountBookId);
|
|
payee.setIdentity(accountBookId);
|
|
|
|
|
+ // 接入指南 §2.3: payee_info.ext_info 必传 agreement_no
|
|
|
|
|
+ if (agreementNo != null && !agreementNo.isBlank()) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ Map<String, String> ext = new LinkedHashMap<>();
|
|
|
|
|
+ ext.put("agreement_no", agreementNo);
|
|
|
|
|
+ payee.setExtInfo(objectMapper.writeValueAsString(ext));
|
|
|
|
|
+ } catch (Exception ignored) {}
|
|
|
|
|
+ }
|
|
|
model.setPayeeInfo(payee);
|
|
model.setPayeeInfo(payee);
|
|
|
|
|
|
|
|
if (remark != null && !remark.isBlank()) model.setRemark(remark);
|
|
if (remark != null && !remark.isBlank()) model.setRemark(remark);
|
|
@@ -330,12 +362,11 @@ public class AlipayTransferService {
|
|
|
Participant payer = new Participant();
|
|
Participant payer = new Participant();
|
|
|
payer.setIdentityType("ACCOUNT_BOOK_ID");
|
|
payer.setIdentityType("ACCOUNT_BOOK_ID");
|
|
|
payer.setIdentity(account.getAccountBookId());
|
|
payer.setIdentity(account.getAccountBookId());
|
|
|
- if (account.getAgreementNo() != null && !account.getAgreementNo().isBlank()) {
|
|
|
|
|
- Map<String, String> payerExt = new LinkedHashMap<>();
|
|
|
|
|
- payerExt.put("agreement_no", account.getAgreementNo());
|
|
|
|
|
- try { payer.setExtInfo(objectMapper.writeValueAsString(payerExt)); }
|
|
|
|
|
- catch (Exception ignored) {}
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 接入指南 §3.2: payer_info.ext_info.agreement_no 必传
|
|
|
|
|
+ Map<String, String> payerExt = new LinkedHashMap<>();
|
|
|
|
|
+ payerExt.put("agreement_no", account.getAgreementNo() != null ? account.getAgreementNo() : "");
|
|
|
|
|
+ try { payer.setExtInfo(objectMapper.writeValueAsString(payerExt)); }
|
|
|
|
|
+ catch (Exception e) { throw new BusinessException(400, "序列化 payer ext_info 失败"); }
|
|
|
model.setPayerInfo(payer);
|
|
model.setPayerInfo(payer);
|
|
|
|
|
|
|
|
// 必填: 收款方
|
|
// 必填: 收款方
|
|
@@ -433,34 +464,47 @@ public class AlipayTransferService {
|
|
|
// ==================== 5. 查询记账本 ====================
|
|
// ==================== 5. 查询记账本 ====================
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * alipay.fund.accountbook.query
|
|
|
|
|
- * 必填: merchant_user_id, scene_code
|
|
|
|
|
|
|
+ * alipay.fund.accountbook.query(接入指南 §2.2)
|
|
|
|
|
+ * 必填: account_book_id, scene_code, ext_info(含agreement_no)
|
|
|
*/
|
|
*/
|
|
|
public List<Map<String, Object>> queryAccounts(String enterpriseId) {
|
|
public List<Map<String, Object>> queryAccounts(String enterpriseId) {
|
|
|
- try {
|
|
|
|
|
- AlipayFundAccountbookQueryModel model = new AlipayFundAccountbookQueryModel();
|
|
|
|
|
- model.setMerchantUserId(enterpriseId);
|
|
|
|
|
- model.setSceneCode(SCENE_CODE_ACCOUNT_BOOK);
|
|
|
|
|
-
|
|
|
|
|
- AlipayFundAccountbookQueryRequest request = new AlipayFundAccountbookQueryRequest();
|
|
|
|
|
- request.setBizModel(model);
|
|
|
|
|
-
|
|
|
|
|
- AlipayFundAccountbookQueryResponse response =
|
|
|
|
|
- alipayClientFactory.getClient(enterpriseId).execute(request);
|
|
|
|
|
|
|
+ // 先从本地DB查记账本ID和协议号
|
|
|
|
|
+ var accounts = accountMapper.selectList(
|
|
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<AccountEntity>()
|
|
|
|
|
+ .eq(AccountEntity::getEnterpriseId, enterpriseId));
|
|
|
|
|
+ if (accounts.isEmpty()) return List.of();
|
|
|
|
|
|
|
|
- if (!response.isSuccess())
|
|
|
|
|
- throw new BusinessException(400, "查询记账本失败: " + response.getMsg());
|
|
|
|
|
-
|
|
|
|
|
- List<Map<String, Object>> result = new ArrayList<>();
|
|
|
|
|
- Map<String, Object> m = new LinkedHashMap<>();
|
|
|
|
|
- m.put("account_book_id", response.getAccountBookId());
|
|
|
|
|
- m.put("available_amount", response.getAvailableAmount());
|
|
|
|
|
- m.put("scene", "B2B_TRANS");
|
|
|
|
|
- result.add(m);
|
|
|
|
|
- return result;
|
|
|
|
|
- } catch (AlipayApiException e) {
|
|
|
|
|
- throw new BusinessException(400, "查询记账本失败: " + e.getMessage());
|
|
|
|
|
|
|
+ List<Map<String, Object>> result = new ArrayList<>();
|
|
|
|
|
+ for (AccountEntity acc : accounts) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ AlipayFundAccountbookQueryModel model = new AlipayFundAccountbookQueryModel();
|
|
|
|
|
+ model.setAccountBookId(acc.getAccountBookId());
|
|
|
|
|
+ model.setSceneCode(SCENE_CODE_ACCOUNT_BOOK);
|
|
|
|
|
+ // 接入指南 §2.2: ext_info 必选,含 agreement_no
|
|
|
|
|
+ Map<String, String> ext = new LinkedHashMap<>();
|
|
|
|
|
+ ext.put("agreement_no", acc.getAgreementNo() != null ? acc.getAgreementNo() : "");
|
|
|
|
|
+ model.setExtInfo(objectMapper.writeValueAsString(ext));
|
|
|
|
|
+
|
|
|
|
|
+ AlipayFundAccountbookQueryRequest request = new AlipayFundAccountbookQueryRequest();
|
|
|
|
|
+ request.setBizModel(model);
|
|
|
|
|
+
|
|
|
|
|
+ AlipayFundAccountbookQueryResponse response =
|
|
|
|
|
+ alipayClientFactory.getClient(enterpriseId).execute(request);
|
|
|
|
|
+
|
|
|
|
|
+ if (response.isSuccess()) {
|
|
|
|
|
+ Map<String, Object> m = new LinkedHashMap<>();
|
|
|
|
|
+ m.put("account_book_id", response.getAccountBookId());
|
|
|
|
|
+ m.put("available_amount", response.getAvailableAmount());
|
|
|
|
|
+ m.put("scene", "B2B_TRANS");
|
|
|
|
|
+ result.add(m);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (AlipayApiException e) {
|
|
|
|
|
+ log.warn("查询记账本失败: account_book_id={}, err={}", acc.getAccountBookId(), e.getMessage());
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("序列化 ext_info 失败: {}", e.getMessage());
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
+ return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ==================== 6. 查询转账状态 ====================
|
|
// ==================== 6. 查询转账状态 ====================
|