Skip to content

Commit

Permalink
SumUp: Add Void and Refund calls (activemerchant#4891)
Browse files Browse the repository at this point in the history
Description
-------------------------
Add Void and Refund calls to SumUp Gateway adapter with the basic information needed
Add missing response statuses

This are the relevant links to review the initial implementation:

- [Deactivate a checkout](https://developer.sumup.com/docs/api/deactivate-a-checkout/)
- [Make a refund](https://developer.sumup.com/docs/online-payments/guides/refund/)

Tickets for Spreedly reference
SER-713

Unit test
-------------------------
Finished in 34.918843 seconds.
5614 tests, 78044 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications

Remote test
-------------------------
100% passed
160.77 tests/s, 2235.01 assertions/s

Rubocop
-------------------------
Running RuboCop...
Inspecting 769 files
769 files inspected, no offenses detected

Co-authored-by: Luis <[email protected]>
  • Loading branch information
sinourain and Luis authored Oct 16, 2023
1 parent af6cd39 commit f2e44d3
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
* Ogone: Add gateway specific 3ds option with default options mapping [jherreraa] #4894
* Rapyd: Add recurrence_type field [yunnydang] #4912
* Revert "Adyen: Update MIT flagging for NT" [almalee24] #4914
* SumUp: Void and partial refund calls [sinourain] #4891

== Version 1.135.0 (August 24, 2023)
* PaymentExpress: Correct endpoints [steveh] #4827
Expand Down
26 changes: 24 additions & 2 deletions lib/active_merchant/billing/gateways/sum_up.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ def purchase(money, payment, options = {})
end
end

def void(authorization, options = {})
checkout_id = authorization.split('#')[0]
commit('checkouts/' + checkout_id, {}, :delete)
end

def refund(money, authorization, options = {})
transaction_id = authorization.split('#')[-1]
payment_currency = options[:currency] || currency(money)
post = money ? { amount: localized_amount(money, payment_currency) } : {}
add_merchant_data(post, options)

commit('me/refund/' + transaction_id, post)
end

def supports_scrubbing?
true
end
Expand Down Expand Up @@ -143,7 +157,13 @@ def parse(body)
end

def success_from(response)
response[:status] == 'PENDING'
return false unless %w(PENDING EXPIRED PAID).include?(response[:status])

response[:transactions].each do |transaction|
return false unless %w(PENDING CANCELLED SUCCESSFUL).include?(transaction.symbolize_keys[:status])
end

true
end

def message_from(response)
Expand All @@ -153,7 +173,9 @@ def message_from(response)
end

def authorization_from(response)
response[:id]
return response[:id] unless response[:transaction_id]

[response[:id], response[:transaction_id]].join('#')
end

def auth_headers
Expand Down
44 changes: 44 additions & 0 deletions test/remote/gateways/remote_sum_up_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,50 @@ def test_failed_purchase_invalid_currency
assert_equal 'Given currency differs from merchant\'s country currency', response.message
end

def test_successful_void
purchase = @gateway.purchase(@amount, @credit_card, @options)
assert_success purchase
assert_equal 'PENDING', purchase.message
assert_equal @options[:order_id], purchase.params['checkout_reference']
refute_empty purchase.params['id']
refute_empty purchase.params['transactions']
refute_empty purchase.params['transactions'].first['id']
assert_equal 'PENDING', purchase.params['transactions'].first['status']

response = @gateway.void(purchase.params['id'])
assert_success response
refute_empty response.params['id']
assert_equal purchase.params['id'], response.params['id']
refute_empty response.params['transactions']
refute_empty response.params['transactions'].first['id']
assert_equal 'CANCELLED', response.params['transactions'].first['status']
end

def test_failed_void_invalid_checkout_id
response = @gateway.void('90858be3-23bb-4af5-9fba-ce3bc190fe5b22')
assert_failure response
assert_equal 'Resource not found', response.message
end

def test_failed_refund_for_pending_checkout
purchase = @gateway.purchase(@amount, @credit_card, @options)
assert_success purchase
assert_equal 'PENDING', purchase.message
assert_equal @options[:order_id], purchase.params['checkout_reference']
refute_empty purchase.params['id']
refute_empty purchase.params['transactions']

transaction_id = purchase.params['transactions'].first['id']

refute_empty transaction_id
assert_equal 'PENDING', purchase.params['transactions'].first['status']

response = @gateway.refund(nil, transaction_id)
assert_failure response
assert_equal 'CONFLICT', response.error_code
assert_equal 'The transaction is not refundable in its current state', response.message
end

def test_transcript_scrubbing
transcript = capture_transcript(@gateway) do
@gateway.purchase(@amount, @credit_card, @options)
Expand Down
80 changes: 80 additions & 0 deletions test/unit/gateways/sum_up_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,29 @@ def test_failed_purchase
assert_equal SumUpGateway::STANDARD_ERROR_CODE_MAPPING[:multiple_invalid_parameters], response.error_code
end

def test_successful_void
@gateway.expects(:ssl_request).returns(successful_void_response)
response = @gateway.void('c0887be5-9fd2-4018-a531-e573e0298fdd')
assert_success response
assert_equal 'EXPIRED', response.message
end

def test_failed_void
@gateway.expects(:ssl_request).returns(failed_void_response)
response = @gateway.void('c0887be5-9fd2-4018-a531-e573e0298fdd22')
assert_failure response
assert_equal 'Resource not found', response.message
assert_equal 'NOT_FOUND', response.error_code
end

def test_failed_refund
@gateway.expects(:ssl_request).returns(failed_refund_response)
response = @gateway.refund(nil, 'c0887be5-9fd2-4018-a531-e573e0298fdd22')
assert_failure response
assert_equal 'The transaction is not refundable in its current state', response.message
assert_equal 'CONFLICT', response.error_code
end

def test_scrub
assert @gateway.supports_scrubbing?
assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
Expand Down Expand Up @@ -404,6 +427,63 @@ def failed_complete_checkout_array_response
RESPONSE
end

def successful_void_response
<<-RESPONSE
{
"checkout_reference": "b5a47552-50e0-4c6e-af23-2495124b5091",
"id": "c0887be5-9fd2-4018-a531-e573e0298fdd",
"amount": 100.00,
"currency": "USD",
"pay_to_email": "[email protected]",
"merchant_code": "MTVU2XGK",
"description": "Sample one-time payment",
"purpose": "CHECKOUT",
"status": "EXPIRED",
"date": "2023-09-14T16:32:39.200+00:00",
"valid_until": "2023-09-14T18:08:49.977+00:00",
"merchant_name": "Spreedly",
"transactions": [{
"id": "fc805fc9-4864-4c6d-8e29-630c171fce54",
"transaction_code": "TDYEQ2RQ23",
"merchant_code": "MTVU2XGK",
"amount": 100.0,
"vat_amount": 0.0,
"tip_amount": 0.0,
"currency": "USD",
"timestamp": "2023-09-14T16:32:50.111+00:00",
"status": "CANCELLED",
"payment_type": "ECOM",
"entry_mode": "CUSTOMER_ENTRY",
"installments_count": 1,
"internal_id": 5165839144
}]
}
RESPONSE
end

def failed_void_response
<<-RESPONSE
{
"type": "https://developer.sumup.com/docs/problem/checkout-not-found/",
"title": "Not Found",
"status": 404,
"detail": "A checkout session with the id c0887be5-9fd2-4018-a531-e573e0298fdd22 does not exist",
"instance": "5e07254b2f25, 5e07254b2f25 a30463b627e3",
"error_code": "NOT_FOUND",
"message": "Resource not found"
}
RESPONSE
end

def failed_refund_response
<<-RESPONSE
{
"message": "The transaction is not refundable in its current state",
"error_code": "CONFLICT"
}
RESPONSE
end

def format_multiple_errors_response
{
error_code: 'MULTIPLE_INVALID_PARAMETERS',
Expand Down

0 comments on commit f2e44d3

Please sign in to comment.