@@ -149,6 +149,89 @@ describe("PaymentService", () => {
149149 expect ( result . requiresAction ) . toBeUndefined ( )
150150 } )
151151
152+ it ( "should surface a DECLINED capture as an explicit failure" , async ( ) => {
153+ const { getPayPalService } = await import ( "../paypal-service" )
154+ vi . mocked ( getPayPalService ) . mockReturnValue ( {
155+ captureOrder : vi . fn ( ) . mockResolvedValue ( {
156+ id : "ORDER_ABC" ,
157+ status : "COMPLETED" ,
158+ purchase_units : [ {
159+ payments : {
160+ captures : [ {
161+ id : "CAPTURE_DECLINED" ,
162+ status : "DECLINED" ,
163+ status_details : { reason : "INSTRUMENT_DECLINED" } ,
164+ } ] ,
165+ } ,
166+ } ] ,
167+ } ) ,
168+ createOrder : vi . fn ( ) ,
169+ refundCapture : vi . fn ( ) ,
170+ getOrder : vi . fn ( ) ,
171+ } as any )
172+
173+ const service = new PaymentService ( { provider : "paypal" } )
174+ const result = await service . processPayment ( 0 , "usd" , "order_ORDER_ABC" )
175+
176+ expect ( result . success ) . toBe ( false )
177+ expect ( result . error ) . toContain ( "declined" )
178+ expect ( result . error ) . toContain ( "INSTRUMENT_DECLINED" )
179+ // Falls back to the capture ID so callers can reconcile the attempt.
180+ expect ( result . transactionId ) . toBe ( "CAPTURE_DECLINED" )
181+ } )
182+
183+ it ( "should surface a FAILED capture as an explicit failure" , async ( ) => {
184+ const { getPayPalService } = await import ( "../paypal-service" )
185+ vi . mocked ( getPayPalService ) . mockReturnValue ( {
186+ captureOrder : vi . fn ( ) . mockResolvedValue ( {
187+ id : "ORDER_ABC" ,
188+ status : "COMPLETED" ,
189+ purchase_units : [ {
190+ payments : { captures : [ { id : "CAPTURE_FAILED" , status : "FAILED" } ] } ,
191+ } ] ,
192+ } ) ,
193+ createOrder : vi . fn ( ) ,
194+ refundCapture : vi . fn ( ) ,
195+ getOrder : vi . fn ( ) ,
196+ } as any )
197+
198+ const service = new PaymentService ( { provider : "paypal" } )
199+ const result = await service . processPayment ( 0 , "usd" , "order_ORDER_ABC" )
200+
201+ expect ( result . success ) . toBe ( false )
202+ expect ( result . error ) . toContain ( "failed" )
203+ } )
204+
205+ it ( "should treat a PENDING capture as pending (requiresAction), not success" , async ( ) => {
206+ const { getPayPalService } = await import ( "../paypal-service" )
207+ vi . mocked ( getPayPalService ) . mockReturnValue ( {
208+ captureOrder : vi . fn ( ) . mockResolvedValue ( {
209+ id : "ORDER_ABC" ,
210+ status : "COMPLETED" ,
211+ purchase_units : [ {
212+ payments : {
213+ captures : [ {
214+ id : "CAPTURE_PENDING" ,
215+ status : "PENDING" ,
216+ status_details : { reason : "PENDING_REVIEW" } ,
217+ } ] ,
218+ } ,
219+ } ] ,
220+ } ) ,
221+ createOrder : vi . fn ( ) ,
222+ refundCapture : vi . fn ( ) ,
223+ getOrder : vi . fn ( ) ,
224+ } as any )
225+
226+ const service = new PaymentService ( { provider : "paypal" } )
227+ const result = await service . processPayment ( 0 , "usd" , "order_ORDER_ABC" )
228+
229+ expect ( result . success ) . toBe ( false )
230+ expect ( result . requiresAction ) . toBe ( true )
231+ expect ( result . error ) . toContain ( "pending" )
232+ expect ( result . transactionId ) . toBe ( "CAPTURE_PENDING" )
233+ } )
234+
152235 it ( "should return error when PayPal is not configured" , async ( ) => {
153236 const { getPayPalService } = await import ( "../paypal-service" )
154237 vi . mocked ( getPayPalService ) . mockReturnValue ( null )
0 commit comments