diff --git a/weixin-java-pay/OVERSEAS_PAY.md b/weixin-java-pay/OVERSEAS_PAY.md new file mode 100644 index 0000000000..b9da9814f9 --- /dev/null +++ b/weixin-java-pay/OVERSEAS_PAY.md @@ -0,0 +1,120 @@ +# 境外微信支付(Overseas WeChat Pay)支持 + +本次更新添加了境外微信支付的支持,解决了 [Issue #3618](https://github.com/binarywang/WxJava/issues/3618) 中提到的问题。 + +## 问题背景 + +境外微信支付需要使用新的API接口地址和额外的参数: +- 使用不同的基础URL: `https://apihk.mch.weixin.qq.com` +- 需要额外的参数: `trade_type` 和 `merchant_category_code` +- 使用不同的API端点: `/global/v3/transactions/*` + +## 新增功能 + +### 1. GlobalTradeTypeEnum +新的枚举类,定义了境外支付的交易类型和对应的API端点: +- `APP`: `/global/v3/transactions/app` +- `JSAPI`: `/global/v3/transactions/jsapi` +- `NATIVE`: `/global/v3/transactions/native` +- `H5`: `/global/v3/transactions/h5` + +### 2. WxPayUnifiedOrderV3GlobalRequest +扩展的请求类,包含境外支付必需的额外字段: +- `trade_type`: 交易类型 (JSAPI, APP, NATIVE, H5) +- `merchant_category_code`: 商户类目代码(境外商户必填) + +### 3. 新的服务方法 +- `createOrderV3Global()`: 创建境外支付订单 +- `unifiedOrderV3Global()`: 境外统一下单接口 + +## 使用示例 + +### JSAPI支付示例 +```java +// 创建境外支付请求 +WxPayUnifiedOrderV3GlobalRequest request = new WxPayUnifiedOrderV3GlobalRequest(); +request.setOutTradeNo(RandomUtils.getRandomStr()); +request.setDescription("境外商品购买"); +request.setNotifyUrl("https://your-domain.com/notify"); + +// 设置金额 +WxPayUnifiedOrderV3GlobalRequest.Amount amount = new WxPayUnifiedOrderV3GlobalRequest.Amount(); +amount.setCurrency(WxPayConstants.CurrencyType.CNY); +amount.setTotal(100); // 1元,单位为分 +request.setAmount(amount); + +// 设置支付者 +WxPayUnifiedOrderV3GlobalRequest.Payer payer = new WxPayUnifiedOrderV3GlobalRequest.Payer(); +payer.setOpenid("用户的openid"); +request.setPayer(payer); + +// 设置境外支付必需的参数 +request.setTradeType("JSAPI"); +request.setMerchantCategoryCode("5812"); // 商户类目代码 + +// 调用境外支付接口 +WxPayUnifiedOrderV3Result.JsapiResult result = payService.createOrderV3Global( + GlobalTradeTypeEnum.JSAPI, + request +); +``` + +### APP支付示例 +```java +WxPayUnifiedOrderV3GlobalRequest request = new WxPayUnifiedOrderV3GlobalRequest(); +// ... 设置基础信息 ... + +request.setTradeType("APP"); +request.setMerchantCategoryCode("5812"); +request.setPayer(new WxPayUnifiedOrderV3GlobalRequest.Payer()); // APP支付不需要openid + +WxPayUnifiedOrderV3Result.AppResult result = payService.createOrderV3Global( + GlobalTradeTypeEnum.APP, + request +); +``` + +### NATIVE支付示例 +```java +WxPayUnifiedOrderV3GlobalRequest request = new WxPayUnifiedOrderV3GlobalRequest(); +// ... 设置基础信息 ... + +request.setTradeType("NATIVE"); +request.setMerchantCategoryCode("5812"); +request.setPayer(new WxPayUnifiedOrderV3GlobalRequest.Payer()); + +String codeUrl = payService.createOrderV3Global( + GlobalTradeTypeEnum.NATIVE, + request +); +``` + +## 配置说明 + +境外支付使用相同的 `WxPayConfig` 配置,无需特殊设置: + +```java +WxPayConfig config = new WxPayConfig(); +config.setAppId("你的AppId"); +config.setMchId("你的境外商户号"); +config.setMchKey("你的商户密钥"); +config.setNotifyUrl("https://your-domain.com/notify"); + +// V3相关配置 +config.setPrivateKeyPath("你的私钥文件路径"); +config.setCertSerialNo("你的商户证书序列号"); +config.setApiV3Key("你的APIv3密钥"); +``` + +**注意**: 境外支付会自动使用 `https://apihk.mch.weixin.qq.com` 作为基础URL,无需手动设置。 + +## 兼容性 + +- 完全向后兼容,不影响现有的国内支付功能 +- 使用相同的配置类和结果类 +- 遵循现有的代码风格和架构模式 + +## 参考文档 + +- [境外微信支付文档](https://pay.weixin.qq.com/doc/global/v3/zh/4013014223) +- [原始Issue #3618](https://github.com/binarywang/WxJava/issues/3618) \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyResult.java index ae86b8c854..8615a2e461 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyResult.java @@ -273,7 +273,7 @@ public String toString() { * */ @XStreamAlias("refund_recv_accout") - private String refundRecvAccout; + private String refundRecvAccount; /** *
@@ -324,7 +324,7 @@ public void loadXML(Document d) { settlementRefundFee = readXmlInteger(d, "settlement_refund_fee"); refundStatus = readXmlString(d, "refund_status"); successTime = readXmlString(d, "success_time"); - refundRecvAccout = readXmlString(d, "refund_recv_accout"); + refundRecvAccount = readXmlString(d, "refund_recv_accout"); refundAccount = readXmlString(d, "refund_account"); refundRequestSource = readXmlString(d, "refund_request_source"); } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayUnifiedOrderV3GlobalRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayUnifiedOrderV3GlobalRequest.java new file mode 100644 index 0000000000..296d3a8646 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayUnifiedOrderV3GlobalRequest.java @@ -0,0 +1,57 @@ +package com.github.binarywang.wxpay.bean.request; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +/** + *+ * 境外微信支付统一下单请求参数对象. + * 参考文档:https://pay.weixin.qq.com/doc/global/v3/zh/4013014223 + *+ * + * @author Binary Wang + */ +@Data +@NoArgsConstructor +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +public class WxPayUnifiedOrderV3GlobalRequest extends WxPayUnifiedOrderV3Request implements Serializable { + private static final long serialVersionUID = 1L; + + /** + *+ * 字段名:交易类型 + * 变量名:trade_type + * 是否必填:是 + * 类型:string[1,16] + * 描述: + * 交易类型,取值如下: + * JSAPI--JSAPI支付 + * NATIVE--Native支付 + * APP--APP支付 + * H5--H5支付 + * 示例值:JSAPI + *+ */ + @SerializedName(value = "trade_type") + private String tradeType; + + /** + *+ * 字段名:商户类目 + * 变量名:merchant_category_code + * 是否必填:是 + * 类型:string[1,32] + * 描述: + * 商户类目,境外商户必填 + * 示例值:5812 + *+ */ + @SerializedName(value = "merchant_category_code") + private String merchantCategoryCode; +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/enums/GlobalTradeTypeEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/enums/GlobalTradeTypeEnum.java new file mode 100644 index 0000000000..fd33b240f1 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/enums/GlobalTradeTypeEnum.java @@ -0,0 +1,36 @@ +package com.github.binarywang.wxpay.bean.result.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 境外微信支付方式 + * Overseas WeChat Pay trade types with global endpoints + * + * @author Binary Wang + */ +@Getter +@AllArgsConstructor +public enum GlobalTradeTypeEnum { + /** + * APP + */ + APP("/global/v3/transactions/app"), + /** + * JSAPI 或 小程序 + */ + JSAPI("/global/v3/transactions/jsapi"), + /** + * NATIVE + */ + NATIVE("/global/v3/transactions/native"), + /** + * H5 + */ + H5("/global/v3/transactions/h5"); + + /** + * 境外下单url + */ + private final String url; +} \ No newline at end of file diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java index 8ceac2b6ba..c73fb843e8 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java @@ -6,6 +6,7 @@ import com.github.binarywang.wxpay.bean.request.*; import com.github.binarywang.wxpay.bean.result.*; import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; +import com.github.binarywang.wxpay.bean.result.enums.GlobalTradeTypeEnum; import com.github.binarywang.wxpay.bean.transfer.TransferBillsNotifyResult; import com.github.binarywang.wxpay.config.WxPayConfig; import com.github.binarywang.wxpay.constant.WxPayConstants; @@ -640,6 +641,17 @@ public interface WxPayService { */T createPartnerOrderV3(TradeTypeEnum tradeType, WxPayPartnerUnifiedOrderV3Request request) throws WxPayException; + /** + * 境外微信支付调用统一下单接口,并组装生成支付所需参数对象. + * + * @param 请使用{@link WxPayUnifiedOrderV3Result}里的内部类或字段 + * @param tradeType the global trade type + * @param request 境外统一下单请求参数 + * @return 返回 {@link WxPayUnifiedOrderV3Result}里的内部类或字段 + * @throws WxPayException the wx pay exception + */ + T createOrderV3Global(GlobalTradeTypeEnum tradeType, WxPayUnifiedOrderV3GlobalRequest request) throws WxPayException; + /** * 在发起微信支付前,需要调用统一下单接口,获取"预支付交易会话标识" * @@ -660,6 +672,16 @@ public interface WxPayService { */ WxPayUnifiedOrderV3Result unifiedOrderV3(TradeTypeEnum tradeType, WxPayUnifiedOrderV3Request request) throws WxPayException; + /** + * 境外微信支付在发起支付前,需要调用统一下单接口,获取"预支付交易会话标识" + * + * @param tradeType the global trade type + * @param request 境外请求对象,注意一些参数如appid、mchid等不用设置,方法内会自动从配置对象中获取到(前提是对应配置中已经设置) + * @return the wx pay unified order result + * @throws WxPayException the wx pay exception + */ + WxPayUnifiedOrderV3Result unifiedOrderV3Global(GlobalTradeTypeEnum tradeType, WxPayUnifiedOrderV3GlobalRequest request) throws WxPayException; + /** * * 合单支付API(APP支付、JSAPI支付、H5支付、NATIVE支付). diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java index 5057ef2b6b..4177a54cd5 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java @@ -11,6 +11,7 @@ import com.github.binarywang.wxpay.bean.request.*; import com.github.binarywang.wxpay.bean.result.*; import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum; +import com.github.binarywang.wxpay.bean.result.enums.GlobalTradeTypeEnum; import com.github.binarywang.wxpay.bean.transfer.TransferBillsNotifyResult; import com.github.binarywang.wxpay.config.WxPayConfig; import com.github.binarywang.wxpay.config.WxPayConfigHolder; @@ -746,6 +747,14 @@ publicT createPartnerOrderV3(TradeTypeEnum tradeType, WxPayPartnerUnifiedOr return result.getPayInfo(tradeType, appId, request.getSubMchId(), this.getConfig().getPrivateKey()); } + @Override + public T createOrderV3Global(GlobalTradeTypeEnum tradeType, WxPayUnifiedOrderV3GlobalRequest request) throws WxPayException { + WxPayUnifiedOrderV3Result result = this.unifiedOrderV3Global(tradeType, request); + // Convert GlobalTradeTypeEnum to TradeTypeEnum for getPayInfo method + TradeTypeEnum domesticTradeType = TradeTypeEnum.valueOf(tradeType.name()); + return result.getPayInfo(domesticTradeType, request.getAppid(), request.getMchid(), this.getConfig().getPrivateKey()); + } + @Override public WxPayUnifiedOrderV3Result unifiedPartnerOrderV3(TradeTypeEnum tradeType, WxPayPartnerUnifiedOrderV3Request request) throws WxPayException { if (StringUtils.isBlank(request.getSpAppid())) { @@ -790,6 +799,28 @@ public WxPayUnifiedOrderV3Result unifiedOrderV3(TradeTypeEnum tradeType, WxPayUn return GSON.fromJson(response, WxPayUnifiedOrderV3Result.class); } + @Override + public WxPayUnifiedOrderV3Result unifiedOrderV3Global(GlobalTradeTypeEnum tradeType, WxPayUnifiedOrderV3GlobalRequest request) throws WxPayException { + if (StringUtils.isBlank(request.getAppid())) { + request.setAppid(this.getConfig().getAppId()); + } + if (StringUtils.isBlank(request.getMchid())) { + request.setMchid(this.getConfig().getMchId()); + } + if (StringUtils.isBlank(request.getNotifyUrl())) { + request.setNotifyUrl(this.getConfig().getNotifyUrl()); + } + if (StringUtils.isBlank(request.getTradeType())) { + request.setTradeType(tradeType.name()); + } + + // Use global WeChat Pay base URL for overseas payments + String globalBaseUrl = "https://apihk.mch.weixin.qq.com"; + String url = globalBaseUrl + tradeType.getUrl(); + String response = this.postV3WithWechatpaySerial(url, GSON.toJson(request)); + return GSON.fromJson(response, WxPayUnifiedOrderV3Result.class); + } + @Override public CombineTransactionsResult combine(TradeTypeEnum tradeType, CombineTransactionsRequest request) throws WxPayException { if (StringUtils.isBlank(request.getCombineAppid())) { diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyResultTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyResultTest.java index 963afb2618..e7a22ee6cd 100644 --- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyResultTest.java +++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyResultTest.java @@ -119,7 +119,7 @@ public void testFromXMLFastMode() throws WxPayException { refundNotifyResult.loadReqInfo(xmlDecryptedReqInfo); assertEquals(refundNotifyResult.getReqInfo().getRefundFee().intValue(), 15); assertEquals(refundNotifyResult.getReqInfo().getRefundStatus(), "SUCCESS"); - assertEquals(refundNotifyResult.getReqInfo().getRefundRecvAccout(), "用户零钱"); + assertEquals(refundNotifyResult.getReqInfo().getRefundRecvAccount(), "用户零钱"); System.out.println(refundNotifyResult); } finally { XmlConfig.fastMode = false; diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceGlobalImplTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceGlobalImplTest.java new file mode 100644 index 0000000000..c648c8a171 --- /dev/null +++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceGlobalImplTest.java @@ -0,0 +1,89 @@ +package com.github.binarywang.wxpay.service.impl; + +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3GlobalRequest; +import com.github.binarywang.wxpay.bean.result.enums.GlobalTradeTypeEnum; +import com.github.binarywang.wxpay.constant.WxPayConstants; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import me.chanjar.weixin.common.util.RandomUtils; +import org.testng.annotations.Test; + +import static org.testng.Assert.*; + +/** + * 境外微信支付测试类 + * + * @author Binary Wang + */ +public class BaseWxPayServiceGlobalImplTest { + + private static final Gson GSON = new GsonBuilder().create(); + + @Test + public void testWxPayUnifiedOrderV3GlobalRequest() { + // Test that the new request class has the required fields + WxPayUnifiedOrderV3GlobalRequest request = new WxPayUnifiedOrderV3GlobalRequest(); + + // Set basic order information + String outTradeNo = RandomUtils.getRandomStr(); + request.setOutTradeNo(outTradeNo); + request.setDescription("Test overseas payment"); + request.setNotifyUrl("https://api.example.com/notify"); + + // Set amount + WxPayUnifiedOrderV3GlobalRequest.Amount amount = new WxPayUnifiedOrderV3GlobalRequest.Amount(); + amount.setCurrency(WxPayConstants.CurrencyType.CNY); + amount.setTotal(100); // 1 yuan in cents + request.setAmount(amount); + + // Set payer + WxPayUnifiedOrderV3GlobalRequest.Payer payer = new WxPayUnifiedOrderV3GlobalRequest.Payer(); + payer.setOpenid("test_openid"); + request.setPayer(payer); + + // Set the new required fields for global payments + request.setTradeType("JSAPI"); + request.setMerchantCategoryCode("5812"); // Example category code + + // Assert that all fields are properly set + assertNotNull(request.getTradeType()); + assertNotNull(request.getMerchantCategoryCode()); + assertEquals("JSAPI", request.getTradeType()); + assertEquals("5812", request.getMerchantCategoryCode()); + assertEquals(outTradeNo, request.getOutTradeNo()); + assertEquals("Test overseas payment", request.getDescription()); + assertEquals(100, request.getAmount().getTotal()); + assertEquals("test_openid", request.getPayer().getOpenid()); + + // Test JSON serialization contains the new fields + String json = GSON.toJson(request); + assertTrue(json.contains("trade_type")); + assertTrue(json.contains("merchant_category_code")); + assertTrue(json.contains("JSAPI")); + assertTrue(json.contains("5812")); + } + + @Test + public void testGlobalTradeTypeEnum() { + // Test that all trade types have the correct global endpoints + assertEquals("/global/v3/transactions/app", GlobalTradeTypeEnum.APP.getUrl()); + assertEquals("/global/v3/transactions/jsapi", GlobalTradeTypeEnum.JSAPI.getUrl()); + assertEquals("/global/v3/transactions/native", GlobalTradeTypeEnum.NATIVE.getUrl()); + assertEquals("/global/v3/transactions/h5", GlobalTradeTypeEnum.H5.getUrl()); + } + + @Test + public void testGlobalTradeTypeEnumValues() { + // Test that we have all the main trade types + GlobalTradeTypeEnum[] tradeTypes = GlobalTradeTypeEnum.values(); + assertEquals(4, tradeTypes.length); + + // Test that we can convert between enum name and TradeTypeEnum + for (GlobalTradeTypeEnum globalType : tradeTypes) { + // This tests that the enum names match between Global and regular TradeTypeEnum + String name = globalType.name(); + assertNotNull(name); + assertTrue(name.equals("APP") || name.equals("JSAPI") || name.equals("NATIVE") || name.equals("H5")); + } + } +} \ No newline at end of file diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/OverseasWxPayExample.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/OverseasWxPayExample.java new file mode 100644 index 0000000000..ccccf9c803 --- /dev/null +++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/OverseasWxPayExample.java @@ -0,0 +1,153 @@ +package com.github.binarywang.wxpay.service.impl; + +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3GlobalRequest; +import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result; +import com.github.binarywang.wxpay.bean.result.enums.GlobalTradeTypeEnum; +import com.github.binarywang.wxpay.config.WxPayConfig; +import com.github.binarywang.wxpay.constant.WxPayConstants; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.WxPayService; +import me.chanjar.weixin.common.util.RandomUtils; + +/** + * 境外微信支付使用示例 + * Example usage for overseas WeChat Pay + * + * @author Binary Wang + */ +public class OverseasWxPayExample { + + /** + * 境外微信支付JSAPI下单示例 + * Example for overseas WeChat Pay JSAPI order creation + */ + public void createOverseasJsapiOrder(WxPayService payService) throws WxPayException { + // 创建境外支付请求对象 + WxPayUnifiedOrderV3GlobalRequest request = new WxPayUnifiedOrderV3GlobalRequest(); + + // 设置基础订单信息 + request.setOutTradeNo(RandomUtils.getRandomStr()); // 商户订单号 + request.setDescription("境外商品购买"); // 商品描述 + request.setNotifyUrl("https://your-domain.com/notify"); // 支付通知地址 + + // 设置金额信息 + WxPayUnifiedOrderV3GlobalRequest.Amount amount = new WxPayUnifiedOrderV3GlobalRequest.Amount(); + amount.setCurrency(WxPayConstants.CurrencyType.CNY); // 币种 + amount.setTotal(100); // 金额,单位为分 + request.setAmount(amount); + + // 设置支付者信息 + WxPayUnifiedOrderV3GlobalRequest.Payer payer = new WxPayUnifiedOrderV3GlobalRequest.Payer(); + payer.setOpenid("用户的openid"); // 用户openid + request.setPayer(payer); + + // 设置境外支付必需的参数 + request.setTradeType("JSAPI"); // 交易类型 + request.setMerchantCategoryCode("5812"); // 商户类目代码,境外商户必填 + + // 可选:设置场景信息 + WxPayUnifiedOrderV3GlobalRequest.SceneInfo sceneInfo = new WxPayUnifiedOrderV3GlobalRequest.SceneInfo(); + sceneInfo.setPayerClientIp("用户IP地址"); + request.setSceneInfo(sceneInfo); + + // 调用境外支付接口 + WxPayUnifiedOrderV3Result.JsapiResult result = payService.createOrderV3Global( + GlobalTradeTypeEnum.JSAPI, + request + ); + + // 返回的result包含前端需要的支付参数 + System.out.println("支付参数:" + result); + } + + /** + * 境外微信支付APP下单示例 + * Example for overseas WeChat Pay APP order creation + */ + public void createOverseasAppOrder(WxPayService payService) throws WxPayException { + WxPayUnifiedOrderV3GlobalRequest request = new WxPayUnifiedOrderV3GlobalRequest(); + + // 设置基础信息 + request.setOutTradeNo(RandomUtils.getRandomStr()); + request.setDescription("境外APP商品购买"); + request.setNotifyUrl("https://your-domain.com/notify"); + + // 设置金额 + WxPayUnifiedOrderV3GlobalRequest.Amount amount = new WxPayUnifiedOrderV3GlobalRequest.Amount(); + amount.setCurrency(WxPayConstants.CurrencyType.CNY); + amount.setTotal(200); // 2元 + request.setAmount(amount); + + // APP支付不需要设置payer.openid,但需要设置空的payer对象 + request.setPayer(new WxPayUnifiedOrderV3GlobalRequest.Payer()); + + // 境外支付必需参数 + request.setTradeType("APP"); + request.setMerchantCategoryCode("5812"); + + // 调用境外APP支付接口 + WxPayUnifiedOrderV3Result.AppResult result = payService.createOrderV3Global( + GlobalTradeTypeEnum.APP, + request + ); + + System.out.println("APP支付参数:" + result); + } + + /** + * 境外微信支付NATIVE下单示例 + * Example for overseas WeChat Pay NATIVE order creation + */ + public void createOverseasNativeOrder(WxPayService payService) throws WxPayException { + WxPayUnifiedOrderV3GlobalRequest request = new WxPayUnifiedOrderV3GlobalRequest(); + + request.setOutTradeNo(RandomUtils.getRandomStr()); + request.setDescription("境外扫码支付"); + request.setNotifyUrl("https://your-domain.com/notify"); + + // 设置金额 + WxPayUnifiedOrderV3GlobalRequest.Amount amount = new WxPayUnifiedOrderV3GlobalRequest.Amount(); + amount.setCurrency(WxPayConstants.CurrencyType.CNY); + amount.setTotal(300); // 3元 + request.setAmount(amount); + + // NATIVE支付不需要设置payer.openid + request.setPayer(new WxPayUnifiedOrderV3GlobalRequest.Payer()); + + // 境外支付必需参数 + request.setTradeType("NATIVE"); + request.setMerchantCategoryCode("5812"); + + // 调用境外NATIVE支付接口 + String result = payService.createOrderV3Global( + GlobalTradeTypeEnum.NATIVE, + request + ); + + System.out.println("NATIVE支付二维码链接:" + result); + } + + /** + * 配置示例 + * Configuration example + */ + public WxPayConfig createOverseasConfig() { + WxPayConfig config = new WxPayConfig(); + + // 基础配置 + config.setAppId("你的AppId"); + config.setMchId("你的境外商户号"); + config.setMchKey("你的商户密钥"); + config.setNotifyUrl("https://your-domain.com/notify"); + + // 境外支付使用的是全球API,在代码中会自动使用 https://apihk.mch.weixin.qq.com 作为基础URL + // 无需额外设置payBaseUrl,方法内部会自动处理 + + // V3相关配置(境外支付也使用V3接口) + config.setPrivateKeyPath("你的私钥文件路径"); + config.setCertSerialNo("你的商户证书序列号"); + config.setApiV3Key("你的APIv3密钥"); + + return config; + } +} \ No newline at end of file